Skip to content

Commit

Permalink
feat: Add overflow detection during parsing integer literals (Assembl…
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxGraey authored Jul 20, 2022
1 parent 7e1ff30 commit 1d05e8d
Show file tree
Hide file tree
Showing 15 changed files with 309 additions and 33 deletions.
15 changes: 7 additions & 8 deletions src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8095,14 +8095,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 @@ -2181,7 +2199,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

0 comments on commit 1d05e8d

Please sign in to comment.