Skip to content

Commit 1d05e8d

Browse files
authored
feat: Add overflow detection during parsing integer literals (#2365)
1 parent 7e1ff30 commit 1d05e8d

15 files changed

+309
-33
lines changed

src/compiler.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8095,14 +8095,13 @@ export class Compiler extends DiagnosticEmitter {
80958095
return module.f64(floatValue);
80968096
}
80978097
case LiteralKind.INTEGER: {
8098-
let intValue = (<IntegerLiteralExpression>expression).value;
8099-
if (implicitlyNegate) {
8100-
intValue = i64_sub(
8101-
i64_new(0),
8102-
intValue
8103-
);
8104-
}
8105-
let type = this.resolver.determineIntegerLiteralType(intValue, contextualType);
8098+
let expr = <IntegerLiteralExpression>expression;
8099+
let type = this.resolver.determineIntegerLiteralType(expr, implicitlyNegate, contextualType);
8100+
8101+
let intValue = implicitlyNegate
8102+
? i64_neg(expr.value)
8103+
: expr.value;
8104+
81068105
this.currentType = type;
81078106
switch (type.kind) {
81088107
case TypeKind.ISIZE: if (!this.options.isWasm64) return module.i32(i64_low(intValue));

src/diagnosticMessages.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"Property '{0}' is always assigned before being used.": 233,
4747
"Expression does not compile to a value at runtime.": 234,
4848
"Only variables, functions and enums become WebAssembly module exports.": 235,
49+
"Literal '{0}' does not fit into 'i64' or 'u64' types.": 236,
4950

5051
"Importing the table disables some indirect call optimizations.": 901,
5152
"Exporting the table disables some indirect call optimizations.": 902,

src/extra/ast.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,9 @@ export class ASTBuilder {
678678
}
679679

680680
visitIntegerLiteralExpression(node: IntegerLiteralExpression): void {
681-
this.sb.push(i64_to_string(node.value));
681+
var range = node.range;
682+
var hasExplicitSign = range.source.text.startsWith("-", range.start);
683+
this.sb.push(i64_to_string(node.value, !hasExplicitSign));
682684
}
683685

684686
visitStringLiteral(str: string): void {

src/extra/tsconfig.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
{
22
"extends": "../../std/portable.json",
33
"include": [
4-
"./**/*.ts"
4+
"../**/*.ts"
5+
],
6+
"exclude": [
7+
"../**/node_modules/",
8+
"../tests/**",
9+
"../lib/**",
10+
"./glue/wasm/**"
511
]
612
}

src/glue/js/i64.d.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@ declare type i64 = { __Long__: true }; // opaque
77

88
declare const i64_zero: i64;
99
declare const i64_one: i64;
10+
declare const i64_neg_one: i64;
11+
declare const i64_minimum: i64;
12+
declare const i64_maximum: i64;
1013

1114
declare function i64_is(value: unknown): value is i64;
1215
declare function i64_new(lo: i32, hi?: i32): i64;
1316
declare function i64_low(value: i64): i32;
1417
declare function i64_high(value: i64): i32;
1518

1619
declare function i64_not(value: i64): i64;
20+
declare function i64_neg(value: i64): i64;
1721
declare function i64_clz(value: i64): i32;
1822
declare function i64_ctz(value: i64): i32;
1923

@@ -31,11 +35,20 @@ declare function i64_xor(left: i64, right: i64): i64;
3135
declare function i64_shl(left: i64, right: i64): i64;
3236
declare function i64_shr(left: i64, right: i64): i64;
3337
declare function i64_shr_u(left: i64, right: i64): i64;
38+
3439
declare function i64_eq(left: i64, right: i64): boolean;
3540
declare function i64_ne(left: i64, right: i64): boolean;
41+
declare function i64_ge(left: i64, right: i64): boolean;
42+
declare function i64_ge_u(left: i64, right: i64): boolean;
3643
declare function i64_gt(left: i64, right: i64): boolean;
44+
declare function i64_gt_u(left: i64, right: i64): boolean;
45+
declare function i64_le(left: i64, right: i64): boolean;
46+
declare function i64_le_u(left: i64, right: i64): boolean;
47+
declare function i64_lt(left: i64, right: i64): boolean;
48+
declare function i64_lt_u(left: i64, right: i64): boolean;
3749

3850
declare function i64_align(value: i64, alignment: i32): i64;
51+
declare function i64_signbit(value): boolean;
3952

4053
declare function i64_is_i8(value: i64): boolean;
4154
declare function i64_is_i16(value: i64): boolean;
@@ -50,3 +63,4 @@ declare function i64_is_f64(value: i64): boolean;
5063
declare function i64_to_f32(value: i64): f64;
5164
declare function i64_to_f64(value: i64): f64;
5265
declare function i64_to_string(value: i64, unsigned?: boolean): string;
66+
declare function i64_clone(value: i64): i64;

src/glue/js/i64.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import Long from "long";
1010
globalThis.i64_zero = Long.ZERO;
1111
globalThis.i64_one = Long.ONE;
1212
globalThis.i64_neg_one = Long.fromInt(-1);
13+
globalThis.i64_minimum = Long.MIN_VALUE;
14+
globalThis.i64_maximum = Long.MAX_VALUE;
1315

1416
globalThis.i64_is = function i64_is(value) {
1517
return Long.isLong(value);
@@ -31,6 +33,10 @@ globalThis.i64_not = function i64_not(value) {
3133
return value.not();
3234
};
3335

36+
globalThis.i64_neg = function i64_neg(value) {
37+
return value.neg();
38+
};
39+
3440
globalThis.i64_clz = function i64_clz(value) {
3541
return value.clz();
3642
};
@@ -124,16 +130,48 @@ globalThis.i64_ne = function i64_ne(left, right) {
124130
return left.ne(right);
125131
};
126132

133+
globalThis.i64_ge = function i64_ge(left, right) {
134+
return left.ge(right);
135+
};
136+
137+
globalThis.i64_ge_u = function i64_ge_u(left, right) {
138+
return left.toUnsigned().ge(right.toUnsigned());
139+
};
140+
127141
globalThis.i64_gt = function i64_gt(left, right) {
128142
return left.gt(right);
129143
};
130144

145+
globalThis.i64_gt_u = function i64_gt_u(left, right) {
146+
return left.toUnsigned().gt(right.toUnsigned());
147+
};
148+
149+
globalThis.i64_le = function i64_le(left, right) {
150+
return left.le(right);
151+
};
152+
153+
globalThis.i64_le_u = function i64_le_u(left, right) {
154+
return left.toUnsigned().le(right.toUnsigned());
155+
};
156+
157+
globalThis.i64_lt = function i64_lt(left, right) {
158+
return left.lt(right);
159+
};
160+
161+
globalThis.i64_lt_u = function i64_lt_u(left, right) {
162+
return left.toUnsigned().lt(right.toUnsigned());
163+
};
164+
131165
globalThis.i64_align = function i64_align(value, alignment) {
132166
assert(alignment && (alignment & (alignment - 1)) == 0);
133167
var mask = Long.fromInt(alignment - 1);
134168
return value.add(mask).and(mask.not());
135169
};
136170

171+
globalThis.i64_signbit = function i64_signbit(value) {
172+
return Boolean(value.high >>> 31);
173+
};
174+
137175
globalThis.i64_is_i8 = function i64_is_i8(value) {
138176
return value.high === 0 && (value.low >= 0 && value.low <= i8.MAX_VALUE)
139177
|| value.high === -1 && (value.low >= i8.MIN_VALUE && value.low < 0);
@@ -190,3 +228,7 @@ globalThis.i64_to_f64 = function i64_to_f64(value) {
190228
globalThis.i64_to_string = function i64_to_string(value, unsigned) {
191229
return unsigned ? value.toUnsigned().toString() : value.toString();
192230
};
231+
232+
globalThis.i64_clone = function i64_clone(value) {
233+
return Long.fromBits(value.low, value.high, value.unsigned);
234+
};

src/glue/wasm/i64.ts

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414
// @ts-ignore: decorator
1515
@global const i64_neg_one: i64 = -1;
1616

17+
// @ts-ignore: decorator
18+
@global const i64_minimum: i64 = i64.MIN_VALUE;
19+
20+
// @ts-ignore: decorator
21+
@global const i64_maximum: i64 = i64.MAX_VALUE;
22+
1723
// @ts-ignore: decorator
1824
@global @inline
1925
function i64_is<T>(value: T): bool {
@@ -38,6 +44,12 @@ function i64_not(value: i64): i64 {
3844
return ~value;
3945
}
4046

47+
// @ts-ignore: decorator
48+
@global @inline
49+
function i64_neg(value: i64): i64 {
50+
return -value;
51+
}
52+
4153
// @ts-ignore: decorator
4254
@global @inline
4355
function i64_clz(value: i64): i32 {
@@ -164,12 +176,54 @@ function i64_ne(left: i64, right: i64): bool {
164176
return left != right;
165177
}
166178

179+
// @ts-ignore: decorator
180+
@global @inline
181+
function i64_ge(left: i64, right: i64): bool {
182+
return left >= right;
183+
}
184+
185+
// @ts-ignore: decorator
186+
@global @inline
187+
function i64_ge_u(left: i64, right: i64): bool {
188+
return <u64>left >= <u64>right;
189+
}
190+
167191
// @ts-ignore: decorator
168192
@global @inline
169193
function i64_gt(left: i64, right: i64): bool {
170194
return left > right;
171195
}
172196

197+
// @ts-ignore: decorator
198+
@global @inline
199+
function i64_gt_u(left: i64, right: i64): bool {
200+
return <u64>left > <u64>right;
201+
}
202+
203+
// @ts-ignore: decorator
204+
@global @inline
205+
function i64_le(left: i64, right: i64): bool {
206+
return left <= right;
207+
}
208+
209+
// @ts-ignore: decorator
210+
@global @inline
211+
function i64_le_u(left: i64, right: i64): bool {
212+
return <u64>left <= <u64>right;
213+
}
214+
215+
// @ts-ignore: decorator
216+
@global @inline
217+
function i64_lt(left: i64, right: i64): bool {
218+
return left < right;
219+
}
220+
221+
// @ts-ignore: decorator
222+
@global @inline
223+
function i64_lt_u(left: i64, right: i64): bool {
224+
return <u64>left < <u64>right;
225+
}
226+
173227
// @ts-ignore: decorator
174228
@global @inline
175229
function i64_align(value: i64, alignment: i64): i64 {
@@ -178,22 +232,28 @@ function i64_align(value: i64, alignment: i64): i64 {
178232
return (value + mask) & ~mask;
179233
}
180234

235+
// @ts-ignore: decorator
236+
@global @inline
237+
function i64_signbit(value: i64): bool {
238+
return <bool>(value >>> 63);
239+
}
240+
181241
// @ts-ignore: decorator
182242
@global @inline
183243
function i64_is_i8(value: i64): bool {
184-
return value >= i8.MIN_VALUE && value <= <i64>i8.MAX_VALUE;
244+
return value >= i8.MIN_VALUE && value <= i8.MAX_VALUE;
185245
}
186246

187247
// @ts-ignore: decorator
188248
@global @inline
189249
function i64_is_i16(value: i64): bool {
190-
return value >= i16.MIN_VALUE && value <= <i64>i16.MAX_VALUE;
250+
return value >= i16.MIN_VALUE && value <= i16.MAX_VALUE;
191251
}
192252

193253
// @ts-ignore: decorator
194254
@global @inline
195255
function i64_is_i32(value: i64): bool {
196-
return value >= i32.MIN_VALUE && value <= <i64>i32.MAX_VALUE;
256+
return value >= i32.MIN_VALUE && value <= i32.MAX_VALUE;
197257
}
198258

199259
// @ts-ignore: decorator
@@ -249,3 +309,9 @@ function i64_to_f64(value: i64): f64 {
249309
function i64_to_string(value: i64, unsigned: bool = false): string {
250310
return unsigned ? u64(value).toString() : value.toString();
251311
}
312+
313+
// @ts-ignore: decorator
314+
@global @inline
315+
function i64_clone(value: i64): i64 {
316+
return value;
317+
}

src/resolver.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,10 +1512,24 @@ export class Resolver extends DiagnosticEmitter {
15121512
/** Determines the final type of an integer literal given the specified contextual type. */
15131513
determineIntegerLiteralType(
15141514
/** Integer literal value. */
1515-
intValue: i64,
1515+
expr: IntegerLiteralExpression,
1516+
/** Has unary minus before literal. */
1517+
negate: bool,
15161518
/** Contextual type. */
15171519
ctxType: Type
15181520
): Type {
1521+
let intValue = expr.value;
1522+
if (negate) {
1523+
// x + i64.min > 0 -> underflow
1524+
if (i64_gt(i64_add(intValue, i64_minimum), i64_zero)) {
1525+
let range = expr.range;
1526+
this.error(
1527+
DiagnosticCode.Literal_0_does_not_fit_into_i64_or_u64_types,
1528+
range, range.source.text.substring(range.start - 1, range.end)
1529+
);
1530+
}
1531+
intValue = i64_neg(intValue);
1532+
}
15191533
if (ctxType.isValue) {
15201534
// compile to contextual type if matching
15211535
switch (ctxType.kind) {
@@ -1715,7 +1729,11 @@ export class Resolver extends DiagnosticEmitter {
17151729
case Token.MINUS: {
17161730
// implicitly negate if an integer literal to distinguish between i32/u32/i64
17171731
if (operand.isLiteralKind(LiteralKind.INTEGER)) {
1718-
return this.determineIntegerLiteralType(i64_sub(i64_zero, (<IntegerLiteralExpression>operand).value), ctxType);
1732+
return this.determineIntegerLiteralType(
1733+
<IntegerLiteralExpression>operand,
1734+
true,
1735+
ctxType
1736+
);
17191737
}
17201738
// fall-through
17211739
}
@@ -2181,7 +2199,8 @@ export class Resolver extends DiagnosticEmitter {
21812199
switch (node.literalKind) {
21822200
case LiteralKind.INTEGER: {
21832201
let intType = this.determineIntegerLiteralType(
2184-
(<IntegerLiteralExpression>node).value,
2202+
<IntegerLiteralExpression>node,
2203+
false,
21852204
ctxType
21862205
);
21872206
return assert(intType.getClassOrWrapper(this.program));

0 commit comments

Comments
 (0)