Skip to content

Commit e64f2ff

Browse files
authored
chore(compiler): Add warning for print and toString with unsafe values (#2018)
1 parent 1626a1f commit e64f2ff

File tree

6 files changed

+142
-34
lines changed

6 files changed

+142
-34
lines changed

compiler/src/typed/typed_well_formedness.re

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@ let rec resolve_unsafe_type = ({exp_type}) => {
4242
switch (t.desc) {
4343
| TTyConstr(path, _, _) =>
4444
switch (path) {
45-
| t when t == Builtin_types.path_wasmi32 => "WasmI32"
46-
| t when t == Builtin_types.path_wasmi64 => "WasmI64"
47-
| t when t == Builtin_types.path_wasmf32 => "WasmF32"
48-
| t when t == Builtin_types.path_wasmf64 => "WasmI64"
45+
| t when t == Builtin_types.path_wasmi32 => "I32"
46+
| t when t == Builtin_types.path_wasmi64 => "I64"
47+
| t when t == Builtin_types.path_wasmf32 => "F32"
48+
| t when t == Builtin_types.path_wasmf64 => "I64"
4949
| _ => failwith("Impossible: type cannot be a wasm unsafe value")
5050
}
5151
| TTyLink(t) => type_is_wasm_unsafe(t)
@@ -263,7 +263,8 @@ module WellFormednessArg: TypedtreeIter.IteratorArgument = {
263263
if (List.exists(((_, arg)) => exp_is_wasm_unsafe(arg), args)) {
264264
let typeName =
265265
switch (args) {
266-
| [(_, arg), _] => resolve_unsafe_type(arg)
266+
| [(_, arg), _] when exp_is_wasm_unsafe(arg) =>
267+
"Wasm" ++ resolve_unsafe_type(arg)
267268
| _ => "(WasmI32 | WasmI64 | WasmF32 | WasmF64)"
268269
};
269270
let warning =
@@ -276,6 +277,53 @@ module WellFormednessArg: TypedtreeIter.IteratorArgument = {
276277
Grain_parsing.Location.prerr_warning(exp_loc, warning);
277278
};
278279
}
280+
// Check: Warn if using Pervasives print on WasmXX types
281+
| TExpApp(
282+
{
283+
exp_desc:
284+
TExpIdent(
285+
Path.PExternal(Path.PIdent({name: "Pervasives"}), "print"),
286+
_,
287+
_,
288+
),
289+
},
290+
_,
291+
args,
292+
) =>
293+
switch (List.find_opt(((_, arg)) => exp_is_wasm_unsafe(arg), args)) {
294+
| Some((_, arg)) =>
295+
let typeName = resolve_unsafe_type(arg);
296+
let warning = Grain_utils.Warnings.PrintUnsafe(typeName);
297+
if (Grain_utils.Warnings.is_active(warning)) {
298+
Grain_parsing.Location.prerr_warning(exp_loc, warning);
299+
};
300+
| _ => ()
301+
}
302+
// Check: Warn if using Pervasives toString on WasmXX types
303+
| TExpApp(
304+
{
305+
exp_desc:
306+
TExpIdent(
307+
Path.PExternal(
308+
Path.PIdent({name: "Pervasives"}),
309+
"toString",
310+
),
311+
_,
312+
_,
313+
),
314+
},
315+
_,
316+
args,
317+
) =>
318+
switch (List.find_opt(((_, arg)) => exp_is_wasm_unsafe(arg), args)) {
319+
| Some((_, arg)) =>
320+
let typeName = resolve_unsafe_type(arg);
321+
let warning = Grain_utils.Warnings.ToStringUnsafe(typeName);
322+
if (Grain_utils.Warnings.is_active(warning)) {
323+
Grain_parsing.Location.prerr_warning(exp_loc, warning);
324+
};
325+
| _ => ()
326+
}
279327
// Check: Warn if using Int32.fromNumber(<literal>)
280328
| TExpApp(
281329
{

compiler/src/utils/warnings.re

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@ type t =
3030
| FromNumberLiteralU64(string)
3131
| FromNumberLiteralF32(string)
3232
| FromNumberLiteralF64(string)
33-
| UselessRecordSpread;
33+
| UselessRecordSpread
34+
| PrintUnsafe(string)
35+
| ToStringUnsafe(string);
3436

35-
let last_warning_number = 24;
37+
let last_warning_number = 26;
3638

3739
let number =
3840
fun
@@ -59,7 +61,9 @@ let number =
5961
| FromNumberLiteralU64(_) => 21
6062
| FromNumberLiteralF32(_) => 22
6163
| FromNumberLiteralF64(_) => 23
62-
| UselessRecordSpread => last_warning_number;
64+
| UselessRecordSpread => 24
65+
| PrintUnsafe(_) => 25
66+
| ToStringUnsafe(_) => last_warning_number;
6367

6468
let message =
6569
fun
@@ -157,7 +161,15 @@ let message =
157161
"it looks like you are calling Float64.fromNumber() with a constant number. Try using the literal syntax (e.g. `%sd`) instead.",
158162
String.contains(n, '.') ? n : n ++ ".",
159163
)
160-
| UselessRecordSpread => "this record spread is useless as all of the record's fields are overridden.";
164+
| UselessRecordSpread => "this record spread is useless as all of the record's fields are overridden."
165+
| PrintUnsafe(typ) =>
166+
"it looks like you are using `print` on an unsafe Wasm value here.\nThis is generally unsafe and will cause errors. Use `DebugPrint.print`"
167+
++ typ
168+
++ " from the `runtime/debugPrint` module instead."
169+
| ToStringUnsafe(typ) =>
170+
"it looks like you are using `toString` on an unsafe Wasm value here.\nThis is generally unsafe and will cause errors. Use `DebugPrint.toString`"
171+
++ typ
172+
++ " from the `runtime/debugPrint` module instead.";
161173

162174
let sub_locs =
163175
fun
@@ -207,6 +219,8 @@ let defaults = [
207219
FromNumberLiteralF32(""),
208220
FromNumberLiteralF64(""),
209221
UselessRecordSpread,
222+
PrintUnsafe(""),
223+
ToStringUnsafe(""),
210224
];
211225

212226
let _ = List.iter(x => current^.active[number(x)] = true, defaults);

compiler/src/utils/warnings.rei

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ type t =
4444
| FromNumberLiteralU64(string)
4545
| FromNumberLiteralF32(string)
4646
| FromNumberLiteralF64(string)
47-
| UselessRecordSpread;
47+
| UselessRecordSpread
48+
| PrintUnsafe(string)
49+
| ToStringUnsafe(string);
4850

4951
let is_active: t => bool;
5052
let is_error: t => bool;

compiler/test/__snapshots__/basic_functionality.1bf5759c.0.snapshot

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,18 @@ basic functionality › unsafe_wasm_globals
1212
(import \"_genv\" \"runtimeHeapStart\" (global $runtimeHeapStart_0 i32))
1313
(import \"_genv\" \"runtimeHeapNextPtr\" (global $runtimeHeapNextPtr_0 (mut i32)))
1414
(import \"_genv\" \"metadataPtr\" (global $metadataPtr_0 i32))
15-
(import \"GRAIN$MODULE$unsafeWasmGlobalsExports.gr\" \"GRAIN$EXPORT$_F64_VAL\" (global $_F64_VAL_1147 (mut f64)))
16-
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"GRAIN$EXPORT$printF64\" (global $printF64_1146 (mut i32)))
17-
(import \"GRAIN$MODULE$unsafeWasmGlobalsExports.gr\" \"GRAIN$EXPORT$_F32_VAL\" (global $_F32_VAL_1145 (mut f32)))
18-
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"GRAIN$EXPORT$printF32\" (global $printF32_1144 (mut i32)))
19-
(import \"GRAIN$MODULE$unsafeWasmGlobalsExports.gr\" \"GRAIN$EXPORT$_I64_VAL\" (global $_I64_VAL_1143 (mut i64)))
20-
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"GRAIN$EXPORT$printI64\" (global $printI64_1142 (mut i32)))
21-
(import \"GRAIN$MODULE$unsafeWasmGlobalsExports.gr\" \"GRAIN$EXPORT$_I32_VAL\" (global $_I32_VAL_1141 (mut i32)))
22-
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"GRAIN$EXPORT$printI32\" (global $printI32_1140 (mut i32)))
23-
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"printF64\" (func $printF64_1146 (param i32 f64) (result i32)))
24-
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"printF32\" (func $printF32_1144 (param i32 f32) (result i32)))
25-
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"printI64\" (func $printI64_1142 (param i32 i64) (result i32)))
26-
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"printI32\" (func $printI32_1140 (param i32 i32) (result i32)))
15+
(import \"GRAIN$MODULE$unsafeWasmGlobalsExports.gr\" \"GRAIN$EXPORT$_F64_VAL\" (global $_F64_VAL_1159 (mut f64)))
16+
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"GRAIN$EXPORT$printF64\" (global $printF64_1158 (mut i32)))
17+
(import \"GRAIN$MODULE$unsafeWasmGlobalsExports.gr\" \"GRAIN$EXPORT$_F32_VAL\" (global $_F32_VAL_1157 (mut f32)))
18+
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"GRAIN$EXPORT$printF32\" (global $printF32_1156 (mut i32)))
19+
(import \"GRAIN$MODULE$unsafeWasmGlobalsExports.gr\" \"GRAIN$EXPORT$_I64_VAL\" (global $_I64_VAL_1155 (mut i64)))
20+
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"GRAIN$EXPORT$printI64\" (global $printI64_1154 (mut i32)))
21+
(import \"GRAIN$MODULE$unsafeWasmGlobalsExports.gr\" \"GRAIN$EXPORT$_I32_VAL\" (global $_I32_VAL_1153 (mut i32)))
22+
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"GRAIN$EXPORT$printI32\" (global $printI32_1152 (mut i32)))
23+
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"printF64\" (func $printF64_1158 (param i32 f64) (result i32)))
24+
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"printF32\" (func $printF32_1156 (param i32 f32) (result i32)))
25+
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"printI64\" (func $printI64_1154 (param i32 i64) (result i32)))
26+
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"printI32\" (func $printI32_1152 (param i32 i32) (result i32)))
2727
(global $GRAIN$TABLE_SIZE i32 (i32.const 0))
2828
(memory $0 0)
2929
(elem $elem (global.get $relocBase_0))
@@ -40,26 +40,26 @@ basic functionality › unsafe_wasm_globals
4040
(local $5 f64)
4141
(block $compile_block.1
4242
(drop
43-
(call $printI32_1140
44-
(global.get $printI32_1140)
45-
(global.get $_I32_VAL_1141)
43+
(call $printI32_1152
44+
(global.get $printI32_1152)
45+
(global.get $_I32_VAL_1153)
4646
)
4747
)
4848
(drop
49-
(call $printI64_1142
50-
(global.get $printI64_1142)
51-
(global.get $_I64_VAL_1143)
49+
(call $printI64_1154
50+
(global.get $printI64_1154)
51+
(global.get $_I64_VAL_1155)
5252
)
5353
)
5454
(drop
55-
(call $printF32_1144
56-
(global.get $printF32_1144)
57-
(global.get $_F32_VAL_1145)
55+
(call $printF32_1156
56+
(global.get $printF32_1156)
57+
(global.get $_F32_VAL_1157)
5858
)
5959
)
60-
(return_call $printF64_1146
61-
(global.get $printF64_1146)
62-
(global.get $_F64_VAL_1147)
60+
(return_call $printF64_1158
61+
(global.get $printF64_1158)
62+
(global.get $_F64_VAL_1159)
6363
)
6464
)
6565
)

stdlib/runtime/debugPrint.gr

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,23 @@ provide let printF32 = val => {
4848
provide let printF64 = val => {
4949
print(Utils.dtoa(val))
5050
}
51+
52+
@unsafe
53+
provide let toStringI32 = val => {
54+
Utils.itoa32(val, 10n)
55+
}
56+
57+
@unsafe
58+
provide let toStringI64 = val => {
59+
Utils.itoa64(val, 10n)
60+
}
61+
62+
@unsafe
63+
provide let toStringF32 = val => {
64+
Utils.dtoa(WasmF64.promoteF32(val))
65+
}
66+
67+
@unsafe
68+
provide let toStringF64 = val => {
69+
Utils.dtoa(val)
70+
}

stdlib/runtime/debugPrint.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,27 @@ printF32 : (val: WasmF32) => Void
3636
printF64 : (val: WasmF64) => Void
3737
```
3838

39+
### DebugPrint.**toStringI32**
40+
41+
```grain
42+
toStringI32 : (val: WasmI32) => String
43+
```
44+
45+
### DebugPrint.**toStringI64**
46+
47+
```grain
48+
toStringI64 : (val: WasmI64) => String
49+
```
50+
51+
### DebugPrint.**toStringF32**
52+
53+
```grain
54+
toStringF32 : (val: WasmF32) => String
55+
```
56+
57+
### DebugPrint.**toStringF64**
58+
59+
```grain
60+
toStringF64 : (val: WasmF64) => String
61+
```
62+

0 commit comments

Comments
 (0)