Skip to content

feat: Add overflow detection during parsing integer literals #2365

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 26 commits into from
Jul 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8090,14 +8090,13 @@ export class Compiler extends DiagnosticEmitter {
return module.f64(floatValue);
}
case LiteralKind.INTEGER: {
let intValue = (<IntegerLiteralExpression>expression).value;
if (implicitlyNegate) {
intValue = i64_sub(
i64_new(0),
intValue
);
}
let type = this.resolver.determineIntegerLiteralType(intValue, contextualType);
let expr = <IntegerLiteralExpression>expression;
let type = this.resolver.determineIntegerLiteralType(expr, implicitlyNegate, contextualType);

let intValue = implicitlyNegate
? i64_neg(expr.value)
: expr.value;

this.currentType = type;
switch (type.kind) {
case TypeKind.ISIZE: if (!this.options.isWasm64) return module.i32(i64_low(intValue));
Expand Down
1 change: 1 addition & 0 deletions src/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"Property '{0}' is always assigned before being used.": 233,
"Expression does not compile to a value at runtime.": 234,
"Only variables, functions and enums become WebAssembly module exports.": 235,
"Literal '{0}' does not fit into 'i64' or 'u64' types.": 236,

"Importing the table disables some indirect call optimizations.": 901,
"Exporting the table disables some indirect call optimizations.": 902,
Expand Down
4 changes: 3 additions & 1 deletion src/extra/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -678,7 +678,9 @@ export class ASTBuilder {
}

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

visitStringLiteral(str: string): void {
Expand Down
8 changes: 7 additions & 1 deletion src/extra/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
{
"extends": "../../std/portable.json",
"include": [
"./**/*.ts"
"../**/*.ts"
],
"exclude": [
"../**/node_modules/",
"../tests/**",
"../lib/**",
"./glue/wasm/**"
]
}
14 changes: 14 additions & 0 deletions src/glue/js/i64.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ declare type i64 = { __Long__: true }; // opaque

declare const i64_zero: i64;
declare const i64_one: i64;
declare const i64_neg_one: i64;
declare const i64_minimum: i64;
declare const i64_maximum: i64;

declare function i64_is(value: unknown): value is i64;
declare function i64_new(lo: i32, hi?: i32): i64;
declare function i64_low(value: i64): i32;
declare function i64_high(value: i64): i32;

declare function i64_not(value: i64): i64;
declare function i64_neg(value: i64): i64;
declare function i64_clz(value: i64): i32;
declare function i64_ctz(value: i64): i32;

Expand All @@ -31,11 +35,20 @@ declare function i64_xor(left: i64, right: i64): i64;
declare function i64_shl(left: i64, right: i64): i64;
declare function i64_shr(left: i64, right: i64): i64;
declare function i64_shr_u(left: i64, right: i64): i64;

declare function i64_eq(left: i64, right: i64): boolean;
declare function i64_ne(left: i64, right: i64): boolean;
declare function i64_ge(left: i64, right: i64): boolean;
declare function i64_ge_u(left: i64, right: i64): boolean;
declare function i64_gt(left: i64, right: i64): boolean;
declare function i64_gt_u(left: i64, right: i64): boolean;
declare function i64_le(left: i64, right: i64): boolean;
declare function i64_le_u(left: i64, right: i64): boolean;
declare function i64_lt(left: i64, right: i64): boolean;
declare function i64_lt_u(left: i64, right: i64): boolean;

declare function i64_align(value: i64, alignment: i32): i64;
declare function i64_signbit(value): boolean;

declare function i64_is_i8(value: i64): boolean;
declare function i64_is_i16(value: i64): boolean;
Expand All @@ -50,3 +63,4 @@ declare function i64_is_f64(value: i64): boolean;
declare function i64_to_f32(value: i64): f64;
declare function i64_to_f64(value: i64): f64;
declare function i64_to_string(value: i64, unsigned?: boolean): string;
declare function i64_clone(value: i64): i64;
42 changes: 42 additions & 0 deletions src/glue/js/i64.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import Long from "long";
globalThis.i64_zero = Long.ZERO;
globalThis.i64_one = Long.ONE;
globalThis.i64_neg_one = Long.fromInt(-1);
globalThis.i64_minimum = Long.MIN_VALUE;
globalThis.i64_maximum = Long.MAX_VALUE;

globalThis.i64_is = function i64_is(value) {
return Long.isLong(value);
Expand All @@ -31,6 +33,10 @@ globalThis.i64_not = function i64_not(value) {
return value.not();
};

globalThis.i64_neg = function i64_neg(value) {
return value.neg();
};

globalThis.i64_clz = function i64_clz(value) {
return value.clz();
};
Expand Down Expand Up @@ -124,16 +130,48 @@ globalThis.i64_ne = function i64_ne(left, right) {
return left.ne(right);
};

globalThis.i64_ge = function i64_ge(left, right) {
return left.ge(right);
};

globalThis.i64_ge_u = function i64_ge_u(left, right) {
return left.toUnsigned().ge(right.toUnsigned());
};

globalThis.i64_gt = function i64_gt(left, right) {
return left.gt(right);
};

globalThis.i64_gt_u = function i64_gt_u(left, right) {
return left.toUnsigned().gt(right.toUnsigned());
};

globalThis.i64_le = function i64_le(left, right) {
return left.le(right);
};

globalThis.i64_le_u = function i64_le_u(left, right) {
return left.toUnsigned().le(right.toUnsigned());
};

globalThis.i64_lt = function i64_lt(left, right) {
return left.lt(right);
};

globalThis.i64_lt_u = function i64_lt_u(left, right) {
return left.toUnsigned().lt(right.toUnsigned());
};

globalThis.i64_align = function i64_align(value, alignment) {
assert(alignment && (alignment & (alignment - 1)) == 0);
var mask = Long.fromInt(alignment - 1);
return value.add(mask).and(mask.not());
};

globalThis.i64_signbit = function i64_signbit(value) {
return Boolean(value.high >>> 31);
};

globalThis.i64_is_i8 = function i64_is_i8(value) {
return value.high === 0 && (value.low >= 0 && value.low <= i8.MAX_VALUE)
|| value.high === -1 && (value.low >= i8.MIN_VALUE && value.low < 0);
Expand Down Expand Up @@ -190,3 +228,7 @@ globalThis.i64_to_f64 = function i64_to_f64(value) {
globalThis.i64_to_string = function i64_to_string(value, unsigned) {
return unsigned ? value.toUnsigned().toString() : value.toString();
};

globalThis.i64_clone = function i64_clone(value) {
return Long.fromBits(value.low, value.high, value.unsigned);
};
72 changes: 69 additions & 3 deletions src/glue/wasm/i64.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
// @ts-ignore: decorator
@global const i64_neg_one: i64 = -1;

// @ts-ignore: decorator
@global const i64_minimum: i64 = i64.MIN_VALUE;

// @ts-ignore: decorator
@global const i64_maximum: i64 = i64.MAX_VALUE;

// @ts-ignore: decorator
@global @inline
function i64_is<T>(value: T): bool {
Expand All @@ -38,6 +44,12 @@ function i64_not(value: i64): i64 {
return ~value;
}

// @ts-ignore: decorator
@global @inline
function i64_neg(value: i64): i64 {
return -value;
}

// @ts-ignore: decorator
@global @inline
function i64_clz(value: i64): i32 {
Expand Down Expand Up @@ -164,12 +176,54 @@ function i64_ne(left: i64, right: i64): bool {
return left != right;
}

// @ts-ignore: decorator
@global @inline
function i64_ge(left: i64, right: i64): bool {
return left >= right;
}

// @ts-ignore: decorator
@global @inline
function i64_ge_u(left: i64, right: i64): bool {
return <u64>left >= <u64>right;
}

// @ts-ignore: decorator
@global @inline
function i64_gt(left: i64, right: i64): bool {
return left > right;
}

// @ts-ignore: decorator
@global @inline
function i64_gt_u(left: i64, right: i64): bool {
return <u64>left > <u64>right;
}

// @ts-ignore: decorator
@global @inline
function i64_le(left: i64, right: i64): bool {
return left <= right;
}

// @ts-ignore: decorator
@global @inline
function i64_le_u(left: i64, right: i64): bool {
return <u64>left <= <u64>right;
}

// @ts-ignore: decorator
@global @inline
function i64_lt(left: i64, right: i64): bool {
return left < right;
}

// @ts-ignore: decorator
@global @inline
function i64_lt_u(left: i64, right: i64): bool {
return <u64>left < <u64>right;
}

// @ts-ignore: decorator
@global @inline
function i64_align(value: i64, alignment: i64): i64 {
Expand All @@ -178,22 +232,28 @@ function i64_align(value: i64, alignment: i64): i64 {
return (value + mask) & ~mask;
}

// @ts-ignore: decorator
@global @inline
function i64_signbit(value: i64): bool {
return <bool>(value >>> 63);
}

// @ts-ignore: decorator
@global @inline
function i64_is_i8(value: i64): bool {
return value >= i8.MIN_VALUE && value <= <i64>i8.MAX_VALUE;
return value >= i8.MIN_VALUE && value <= i8.MAX_VALUE;
}

// @ts-ignore: decorator
@global @inline
function i64_is_i16(value: i64): bool {
return value >= i16.MIN_VALUE && value <= <i64>i16.MAX_VALUE;
return value >= i16.MIN_VALUE && value <= i16.MAX_VALUE;
}

// @ts-ignore: decorator
@global @inline
function i64_is_i32(value: i64): bool {
return value >= i32.MIN_VALUE && value <= <i64>i32.MAX_VALUE;
return value >= i32.MIN_VALUE && value <= i32.MAX_VALUE;
}

// @ts-ignore: decorator
Expand Down Expand Up @@ -249,3 +309,9 @@ function i64_to_f64(value: i64): f64 {
function i64_to_string(value: i64, unsigned: bool = false): string {
return unsigned ? u64(value).toString() : value.toString();
}

// @ts-ignore: decorator
@global @inline
function i64_clone(value: i64): i64 {
return value;
}
25 changes: 22 additions & 3 deletions src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1512,10 +1512,24 @@ export class Resolver extends DiagnosticEmitter {
/** Determines the final type of an integer literal given the specified contextual type. */
determineIntegerLiteralType(
/** Integer literal value. */
intValue: i64,
expr: IntegerLiteralExpression,
/** Has unary minus before literal. */
negate: bool,
/** Contextual type. */
ctxType: Type
): Type {
let intValue = expr.value;
if (negate) {
// x + i64.min > 0 -> underflow
if (i64_gt(i64_add(intValue, i64_minimum), i64_zero)) {
let range = expr.range;
this.error(
DiagnosticCode.Literal_0_does_not_fit_into_i64_or_u64_types,
range, range.source.text.substring(range.start - 1, range.end)
);
}
intValue = i64_neg(intValue);
}
if (ctxType.isValue) {
// compile to contextual type if matching
switch (ctxType.kind) {
Expand Down Expand Up @@ -1715,7 +1729,11 @@ export class Resolver extends DiagnosticEmitter {
case Token.MINUS: {
// implicitly negate if an integer literal to distinguish between i32/u32/i64
if (operand.isLiteralKind(LiteralKind.INTEGER)) {
return this.determineIntegerLiteralType(i64_sub(i64_zero, (<IntegerLiteralExpression>operand).value), ctxType);
return this.determineIntegerLiteralType(
<IntegerLiteralExpression>operand,
true,
ctxType
);
}
// fall-through
}
Expand Down Expand Up @@ -2178,7 +2196,8 @@ export class Resolver extends DiagnosticEmitter {
switch (node.literalKind) {
case LiteralKind.INTEGER: {
let intType = this.determineIntegerLiteralType(
(<IntegerLiteralExpression>node).value,
<IntegerLiteralExpression>node,
false,
ctxType
);
return assert(intType.getClassOrWrapper(this.program));
Expand Down
Loading