diff --git a/cli/asc.js b/cli/asc.js index 72e1019e43..88a27214bd 100644 --- a/cli/asc.js +++ b/cli/asc.js @@ -445,6 +445,7 @@ exports.main = function main(argv, options, callback) { assemblyscript.setMemoryBase(compilerOptions, args.memoryBase >>> 0); assemblyscript.setSourceMap(compilerOptions, args.sourceMap != null); assemblyscript.setOptimizeLevelHints(compilerOptions, optimizeLevel, shrinkLevel); + assemblyscript.setNoUnsafe(compilerOptions, args.noUnsafe); // Initialize default aliases assemblyscript.setGlobalAlias(compilerOptions, "Math", "NativeMath"); diff --git a/cli/asc.json b/cli/asc.json index 0272886a26..ac9a50c1d8 100644 --- a/cli/asc.json +++ b/cli/asc.json @@ -94,6 +94,14 @@ "type": "s", "default": "full" }, + "noUnsafe": { + "description": [ + "Disallows the use of unsafe features in user code.", + "Does not affect library files and external modules." + ], + "type": "b", + "default": false + }, "debug": { "description": "Enables debug information in emitted binaries.", "type": "b", diff --git a/src/ast.ts b/src/ast.ts index 68a373d3f9..d4f091f06b 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -1585,12 +1585,14 @@ export abstract class Statement extends Node { } /** Indicates the specific kind of a source. */ export enum SourceKind { - /** Default source. Usually imported from an entry file. */ - DEFAULT, - /** Entry file. */ - ENTRY, - /** Library file. */ - LIBRARY + /** User-provided file. */ + USER = 0, + /** User-provided entry file. */ + USER_ENTRY = 1, + /** Library-provided file. */ + LIBRARY = 2, + /** Library-provided entry file. */ + LIBRARY_ENTRY = 3 } /** A top-level source node. */ @@ -1631,10 +1633,10 @@ export class Source extends Node { this.text = text; } - /** Tests if this source is an entry file. */ - get isEntry(): bool { return this.sourceKind == SourceKind.ENTRY; } - /** Tests if this source is a stdlib file. */ - get isLibrary(): bool { return this.sourceKind == SourceKind.LIBRARY; } + get isLibrary(): bool { + var kind = this.sourceKind; + return kind == SourceKind.LIBRARY || kind == SourceKind.LIBRARY_ENTRY; + } } /** Base class of all declaration statements. */ diff --git a/src/compiler.ts b/src/compiler.ts index 5fdfa4b9cd..826408c9f4 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -157,7 +157,8 @@ import { nodeIsConstantValue, findDecorator, isTypeOmitted, - ExportDefaultStatement + ExportDefaultStatement, + SourceKind } from "./ast"; import { @@ -201,6 +202,8 @@ export class Options { globalAliases: Map | null = null; /** Additional features to activate. */ features: Feature = Feature.NONE; + /** If true, disallows unsafe features in user code. */ + noUnsafe: bool = false; /** Hinted optimize level. Not applied by the compiler itself. */ optimizeLevelHint: i32 = 0; @@ -360,7 +363,7 @@ export class Compiler extends DiagnosticEmitter { // compile entry file(s) while traversing reachable elements var files = program.filesByName; for (let file of files.values()) { - if (file.source.isEntry) { + if (file.source.sourceKind == SourceKind.USER_ENTRY) { this.compileFile(file); this.compileExports(file); } @@ -451,7 +454,7 @@ export class Compiler extends DiagnosticEmitter { // set up module exports for (let file of this.program.filesByName.values()) { - if (file.source.isEntry) this.ensureModuleExports(file); + if (file.source.sourceKind == SourceKind.USER_ENTRY) this.ensureModuleExports(file); } return module; } @@ -5145,12 +5148,10 @@ export class Compiler extends DiagnosticEmitter { if (!this.compileGlobal(target)) return this.module.unreachable(); // reports // fall-through } + case ElementKind.LOCAL: case ElementKind.FIELD: { targetType = (target).type; - break; - } - case ElementKind.LOCAL: { - targetType = (target).type; + if (target.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(expression); break; } case ElementKind.PROPERTY_PROTOTYPE: { // static property @@ -5166,6 +5167,7 @@ export class Compiler extends DiagnosticEmitter { if (!setterInstance) return this.module.unreachable(); assert(setterInstance.signature.parameterTypes.length == 1); // parser must guarantee this targetType = setterInstance.signature.parameterTypes[0]; + if (setterPrototype.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(expression); break; } case ElementKind.PROPERTY: { // instance property @@ -5179,6 +5181,7 @@ export class Compiler extends DiagnosticEmitter { } assert(setterInstance.signature.parameterTypes.length == 1); // parser must guarantee this targetType = setterInstance.signature.parameterTypes[0]; + if (setterInstance.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(expression); break; } case ElementKind.CLASS: { @@ -5215,6 +5218,7 @@ export class Compiler extends DiagnosticEmitter { } assert(indexedSet.signature.parameterTypes.length == 2); // parser must guarantee this targetType = indexedSet.signature.parameterTypes[1]; // 2nd parameter is the element + if (indexedSet.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(expression); break; } // fall-through @@ -5872,6 +5876,7 @@ export class Compiler extends DiagnosticEmitter { makeMap(flow.contextualTypeArguments) ); if (!instance) return this.module.unreachable(); + if (prototype.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(expression); return this.makeCallDirect(instance, argumentExprs, expression, contextualType == Type.void); // TODO: this skips inlining because inlining requires compiling its temporary locals in // the scope of the inlined flow. might need another mechanism to lock temp. locals early, @@ -6009,6 +6014,8 @@ export class Compiler extends DiagnosticEmitter { expression: CallExpression, contextualType: Type ): ExpressionRef { + if (prototype.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(expression); + var typeArguments: Type[] | null = null; // builtins handle omitted type arguments on their own. if present, however, resolve them here @@ -6107,6 +6114,17 @@ export class Compiler extends DiagnosticEmitter { return true; } + /** Checks that an unsafe expression is allowed. */ + private checkUnsafe(reportNode: Node): void { + // Library files may always use unsafe features + if (this.options.noUnsafe && !reportNode.range.source.isLibrary) { + this.error( + DiagnosticCode.Expression_is_unsafe, + reportNode.range + ); + } + } + /** Compiles a direct call to a concrete function. */ compileCallDirect( instance: Function, @@ -6126,6 +6144,7 @@ export class Compiler extends DiagnosticEmitter { this.currentType = signature.returnType; return this.module.unreachable(); } + if (instance.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(reportNode); // Inline if explicitly requested if (instance.hasDecorator(DecoratorFlags.INLINE)) { @@ -7687,6 +7706,7 @@ export class Compiler extends DiagnosticEmitter { ); return module.unreachable(); } + if (ctor.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(expression); } // check and compile field values @@ -7904,6 +7924,7 @@ export class Compiler extends DiagnosticEmitter { reportNode: Node ): ExpressionRef { var ctor = this.ensureConstructor(classInstance, reportNode); + if (ctor.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(reportNode); var expr = this.compileCallDirect( // no need for another autoreleased local ctor, argumentExpressions, @@ -7934,6 +7955,7 @@ export class Compiler extends DiagnosticEmitter { var target = this.resolver.resolvePropertyAccessExpression(propertyAccess, flow, contextualType); // reports if (!target) return module.unreachable(); + if (target.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(propertyAccess); switch (target.kind) { case ElementKind.GLOBAL: { // static field diff --git a/src/definitions.ts b/src/definitions.ts index 990a079b41..5bdae4912d 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -32,6 +32,10 @@ import { TypeKind } from "./types"; +import { + SourceKind + } from "./ast"; + import { indent } from "./util"; @@ -55,7 +59,7 @@ abstract class ExportsWalker { /** Walks all elements and calls the respective handlers. */ walk(): void { for (let file of this.program.filesByName.values()) { - if (file.source.isEntry) this.visitFile(file); + if (file.source.sourceKind == SourceKind.USER_ENTRY) this.visitFile(file); } } diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index 4542f564f1..56d0eaceaf 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -34,7 +34,7 @@ export enum DiagnosticCode { Module_cannot_have_multiple_start_functions = 221, _0_must_be_a_value_between_1_and_2_inclusive = 222, _0_must_be_a_power_of_two = 223, - TODO_Cannot_inline_inferred_calls_and_specific_internals_yet = 224, + Expression_is_unsafe = 224, Expression_is_never_null = 225, Unterminated_string_literal = 1002, Identifier_expected = 1003, @@ -173,7 +173,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 221: return "Module cannot have multiple start functions."; case 222: return "'{0}' must be a value between '{1}' and '{2}' inclusive."; case 223: return "'{0}' must be a power of two."; - case 224: return "TODO: Cannot inline inferred calls and specific internals yet."; + case 224: return "Expression is unsafe."; case 225: return "Expression is never 'null'."; case 1002: return "Unterminated string literal."; case 1003: return "Identifier expected."; diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index 673ebe2e1e..6b0eb5740f 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -26,7 +26,7 @@ "Module cannot have multiple start functions.": 221, "'{0}' must be a value between '{1}' and '{2}' inclusive.": 222, "'{0}' must be a power of two.": 223, - "TODO: Cannot inline inferred calls and specific internals yet.": 224, + "Expression is unsafe.": 224, "Expression is never 'null'.": 225, "Unterminated string literal.": 1002, diff --git a/src/index.ts b/src/index.ts index 58411c1895..139cd939e1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -104,6 +104,11 @@ export function setExplicitStart(options: Options, explicitStart: bool): void { options.explicitStart = explicitStart; } +/** Sets the `noUnsafe` option. */ +export function setNoUnsafe(options: Options, noUnsafe: bool): void { + options.noUnsafe = noUnsafe; +} + /** Sign extension operations. */ export const FEATURE_SIGN_EXTENSION = Feature.SIGN_EXTENSION; /** Mutable global imports and exports. */ diff --git a/src/parser.ts b/src/parser.ts index 4e94c9a181..5e4dffcd57 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -128,10 +128,12 @@ export class Parser extends DiagnosticEmitter { normalizedPath, text, isEntry - ? SourceKind.ENTRY - : path.startsWith(LIBRARY_PREFIX) && path.indexOf(PATH_DELIMITER, LIBRARY_PREFIX.length) < 0 - ? SourceKind.LIBRARY - : SourceKind.DEFAULT + ? SourceKind.USER_ENTRY + : path.startsWith(LIBRARY_PREFIX) + ? path.indexOf(PATH_DELIMITER, LIBRARY_PREFIX.length) < 0 + ? SourceKind.LIBRARY_ENTRY + : SourceKind.LIBRARY + : SourceKind.USER ); var program = this.program; program.sources.push(source); diff --git a/src/program.ts b/src/program.ts index ad5b382525..a2822af3f6 100644 --- a/src/program.ts +++ b/src/program.ts @@ -410,7 +410,7 @@ export class Program extends DiagnosticEmitter { diagnostics: DiagnosticMessage[] | null = null ) { super(diagnostics); - var nativeSource = new Source(LIBRARY_SUBST, "[native code]", SourceKind.LIBRARY); + var nativeSource = new Source(LIBRARY_SUBST, "[native code]", SourceKind.LIBRARY_ENTRY); this.nativeSource = nativeSource; var nativeFile = new File(this, nativeSource); this.nativeFile = nativeFile; @@ -876,8 +876,9 @@ export class Program extends DiagnosticEmitter { // mark module exports, i.e. to apply proper wrapping behavior on the boundaries for (let file of this.filesByName.values()) { let exports = file.exports; - if (!(file.source.isEntry && exports)) continue; - for (let element of exports.values()) this.markModuleExport(element); + if (exports !== null && file.source.sourceKind == SourceKind.USER_ENTRY) { + for (let element of exports.values()) this.markModuleExport(element); + } } } @@ -2188,7 +2189,7 @@ export class File extends Element { var exports = this.exports; if (!exports) this.exports = exports = new Map(); exports.set(name, element); - if (this.source.isLibrary) this.program.ensureGlobal(name, element); + if (this.source.sourceKind == SourceKind.LIBRARY_ENTRY) this.program.ensureGlobal(name, element); } /** Ensures that another file is a re-export of this file. */ @@ -2876,6 +2877,7 @@ export class Field extends VariableLikeElement { ); this.prototype = prototype; this.flags = prototype.flags; + this.decoratorFlags = prototype.decoratorFlags; assert(type != Type.void); this.setType(type); registerConcreteElement(this.program, this); @@ -2945,6 +2947,8 @@ export class Property extends VariableLikeElement { ) ); this.prototype = prototype; + this.flags = prototype.flags; + this.decoratorFlags = prototype.decoratorFlags; registerConcreteElement(this.program, this); } diff --git a/src/resolver.ts b/src/resolver.ts index 2f52dc88d2..3314003d06 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -1492,10 +1492,12 @@ export class Resolver extends DiagnosticEmitter { } let typeNode = parameterDeclaration.type; if (isTypeOmitted(typeNode)) { - this.error( - DiagnosticCode.Type_expected, - typeNode.range - ); + if (reportMode == ReportMode.REPORT) { + this.error( + DiagnosticCode.Type_expected, + typeNode.range + ); + } return null; } let parameterType = this.resolveType( @@ -1518,10 +1520,12 @@ export class Resolver extends DiagnosticEmitter { } else { let typeNode = signatureNode.returnType; if (isTypeOmitted(typeNode)) { - this.error( - DiagnosticCode.Type_expected, - typeNode.range - ); + if (reportMode == ReportMode.REPORT) { + this.error( + DiagnosticCode.Type_expected, + typeNode.range + ); + } return null; } let type = this.resolveType( diff --git a/std/assembly/builtins.ts b/std/assembly/builtins.ts index d12e40acf5..96bbcd2fc5 100644 --- a/std/assembly/builtins.ts +++ b/std/assembly/builtins.ts @@ -111,11 +111,11 @@ export declare function sqrt(value: T): T; export declare function trunc(value: T): T; // @ts-ignore: decorator -@builtin +@unsafe @builtin export declare function load(offset: usize, immOffset?: usize, immAlign?: usize): T; // @ts-ignore: decorator -@builtin +@unsafe @builtin export declare function store(offset: usize, value: void, immOffset?: usize, immAlign?: usize): void; // @ts-ignore: decorator @@ -151,15 +151,15 @@ export declare function changetype(value: void): T; export declare function assert(isTrueish: T, message?: string): T; // @ts-ignore: decorator -@builtin +@unsafe @builtin export declare function unchecked(expr: T): T; // @ts-ignore: decorator -@builtin +@unsafe @builtin export declare function call_indirect(target: void, ...args: void[]): T; // @ts-ignore: decorator -@builtin +@unsafe @builtin export declare function call_direct(target: void, ...args: void[]): T; // @ts-ignore: decorator @@ -168,11 +168,11 @@ export declare function instantiate(...args: void[]): T; export namespace atomic { // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function load(offset: usize, immOffset?: usize): T; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function store(offset: usize, value: T, immOffset?: usize): void; // @ts-ignore: decorator @@ -196,11 +196,11 @@ export namespace atomic { export declare function xor(ptr: usize, value: T, immOffset?: usize): T; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function xchg(ptr: usize, value: T, immOffset?: usize): T; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function cmpxchg(ptr: usize, expected: T, replacement: T, immOffset?: usize): T; // @ts-ignore: decorator @@ -309,15 +309,15 @@ export namespace i32 { export declare function load(offset: usize, immOffset?: usize, immAlign?: usize): i32; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function store8(offset: usize, value: i32, immOffset?: usize, immAlign?: usize): void; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function store16(offset: usize, value: i32, immOffset?: usize, immAlign?: usize): void; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function store(offset: usize, value: i32, immOffset?: usize, immAlign?: usize): void; export namespace atomic { @@ -335,15 +335,15 @@ export namespace i32 { export declare function load(offset: usize, immOffset?: usize): i32; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function store8(offset: usize, value: i32, immOffset?: usize): void; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function store16(offset: usize, value: i32, immOffset?: usize): void; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function store(offset: usize, value: i32, immOffset?: usize): void; // @ts-ignore: decorator @@ -373,11 +373,11 @@ export namespace i32 { export declare function xor_u(offset: usize, value: i32, immOffset?: usize): i32; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function xchg_u(offset: usize, value: i32, immOffset?: usize): i32; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function cmpxchg_u(offset: usize, expected: i32, replacement: i32, immOffset?: usize): i32; } @@ -404,11 +404,11 @@ export namespace i32 { export declare function xor_u(offset: usize, value: i32, immOffset?: usize): i32; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function xchg_u(offset: usize, value: i32, immOffset?: usize): i32; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function cmpxchg_u(offset: usize, expected: i32, replacement: i32, immOffset?: usize): i32; } @@ -435,11 +435,11 @@ export namespace i32 { export declare function xor(offset: usize, value: i32, immOffset?: usize): i32; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function xchg(offset: usize, value: i32, immOffset?: usize): i32; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function cmpxchg(offset: usize, expected: i32, replacement: i32, immOffset?: usize): i32; } } @@ -512,19 +512,19 @@ export namespace i64 { export declare function reinterpret_f64(value: f64): i64; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function store8(offset: usize, value: i64, immOffset?: usize, immAlign?: usize): void; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function store16(offset: usize, value: i64, immOffset?: usize, immAlign?: usize): void; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function store32(offset: usize, value: i64, immOffset?: usize, immAlign?: usize): void; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function store(offset: usize, value: i64, immOffset?: usize, immAlign?: usize): void; export namespace atomic { @@ -546,19 +546,19 @@ export namespace i64 { export declare function load(offset: usize, immOffset?: usize): i64; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function store8(offset: usize, value: i64, immOffset?: usize): void; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function store16(offset: usize, value: i64, immOffset?: usize): void; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function store32(offset: usize, value: i64, immOffset?: usize): void; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function store(offset: usize, value: i64, immOffset?: usize): void; // @ts-ignore: decorator @@ -588,11 +588,11 @@ export namespace i64 { export declare function xor_u(offset: usize, value: i64, immOffset?: usize): i64; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function xchg_u(offset: usize, value: i64, immOffset?: usize): i64; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function cmpxchg_u(offset: usize, expected: i64, replacement: i64, immOffset?: usize): i64; } @@ -619,11 +619,11 @@ export namespace i64 { export declare function xor_u(offset: usize, value: i64, immOffset?: usize): i64; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function xchg_u(offset: usize, value: i64, immOffset?: usize): i64; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function cmpxchg_u(offset: usize, expected: i64, replacement: i64, immOffset?: usize): i64; } @@ -650,11 +650,11 @@ export namespace i64 { export declare function xor_u(offset: usize, value: i64, immOffset?: usize): i64; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function xchg_u(offset: usize, value: i64, immOffset?: usize): i64; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function cmpxchg_u(offset: usize, expected: i64, replacement: i64, immOffset?: usize): i64; } @@ -681,11 +681,11 @@ export namespace i64 { export declare function xor(offset: usize, value: i64, immOffset?: usize): i64; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function xchg(offset: usize, value: i64, immOffset?: usize): i64; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function cmpxchg(offset: usize, expected: i64, replacement: i64, immOffset?: usize): i64; } } @@ -873,7 +873,7 @@ export namespace f32 { export declare function sqrt(value: f32): f32; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function store(offset: usize, value: f32, immOffset?: usize, immAlign?: usize): void; // @ts-ignore: decorator @@ -952,7 +952,7 @@ export namespace f64 { export declare function sqrt(value: f64): f64; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function store(offset: usize, value: f64, immOffset?: usize, immAlign?: usize): void; // @ts-ignore: decorator @@ -986,11 +986,11 @@ export namespace v128 { export declare function shuffle(a: v128, b: v128, ...lanes: u8[]): v128; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function load(offset: usize, immOffset?: usize, immAlign?: usize): v128; // @ts-ignore: decorator - @builtin + @unsafe @builtin export declare function store(offset: usize, value: v128, immOffset?: usize, immAlign?: usize): void; // @ts-ignore: decorator diff --git a/tests/README.md b/tests/README.md index 68958f0b0f..1695952af3 100644 --- a/tests/README.md +++ b/tests/README.md @@ -71,6 +71,10 @@ and it might be necessary to export entry points. Additional fixtures for the optimized module etc. are generated as well but are used for visual confirmation only. +If present, error checks are performed by expecting the exact sequence of substrings provided within +the respective `.json` file. Using the `stderr` config option will skip instantiating and running +the module. + Running all tests: ``` diff --git a/tests/compiler.js b/tests/compiler.js index 7db6ee9124..8d68d202dc 100644 --- a/tests/compiler.js +++ b/tests/compiler.js @@ -77,34 +77,20 @@ if (argv.length) { } } -const EXPECT_ERROR_PREFIX = '// Expect error:'; - -// Returns an array of error strings to expect, or null if compilation should succeed. -function getExpectedErrors(filePath) { - const lines = fs.readFileSync(filePath).toString().split('\n'); - const expectErrorLines = lines.filter(line => line.startsWith(EXPECT_ERROR_PREFIX)); - if (expectErrorLines.length === 0) { - return null; - } - return expectErrorLines.map(line => line.slice(EXPECT_ERROR_PREFIX.length).trim()); -} - // TODO: asc's callback is synchronous here. This might change. tests.forEach(filename => { console.log(colorsUtil.white("Testing compiler/" + filename) + "\n"); - const expectedErrors = getExpectedErrors(path.join(basedir, filename)); const basename = filename.replace(/\.ts$/, ""); - - const stdout = asc.createMemoryStream(); - const stderr = asc.createMemoryStream(chunk => process.stderr.write(chunk.toString().replace(/^(?!$)/mg, " "))); - stderr.isTTY = true; - const configPath = path.join(basedir, basename + ".json"); const config = fs.existsSync(configPath) ? require(configPath) : {}; + const stdout = asc.createMemoryStream(); + const stderr = asc.createMemoryStream(chunk => process.stderr.write(chunk.toString().replace(/^(?!$)/mg, " "))); + stderr.isTTY = true; + var asc_flags = []; var v8_flags = ""; var v8_no_flags = ""; @@ -166,19 +152,30 @@ tests.forEach(filename => { }, err => { console.log(); - if (expectedErrors) { + // check expected stderr patterns in order + let expectStderr = config.stderr; + if (expectStderr) { const stderrString = stderr.toString(); - for (const expectedError of expectedErrors) { - if (!stderrString.includes(expectedError)) { - console.log(`Expected error "${expectedError}" was not in the error output.`); - console.log("- " + colorsUtil.red("error check ERROR")); + if (typeof expectStderr === "string") expectStderr = [ expectStderr ]; + let lastIndex = 0; + let failed = false; + expectStderr.forEach((substr, i) => { + var index = stderrString.indexOf(substr, lastIndex); + if (index < 0) { + console.log("Missing pattern #" + (i + 1) + " '" + substr + "' in stderr at " + lastIndex + "+."); failedTests.add(basename); - console.log(); - return; + failed = true; + } else { + lastIndex = index + substr.length; } + }); + if (failed) { + failedTests.add(basename); + failedMessages.set(basename, "stderr mismatch"); + console.log("\n- " + colorsUtil.red("stderr MISMATCH") + "\n"); + } else { + console.log("- " + colorsUtil.green("stderr MATCH") + "\n"); } - console.log("- " + colorsUtil.green("error check OK")); - console.log(); return; } diff --git a/tests/compiler/basic-nullable.json b/tests/compiler/basic-nullable.json index b1da366ff4..0d7a7ffafd 100644 --- a/tests/compiler/basic-nullable.json +++ b/tests/compiler/basic-nullable.json @@ -1,5 +1,9 @@ { "asc_flags": [ "--runtime none" + ], + "stderr": [ + "AS204: Basic type 'i32' cannot be nullable.", + "EOF" ] } \ No newline at end of file diff --git a/tests/compiler/basic-nullable.ts b/tests/compiler/basic-nullable.ts index e3c9f36f01..72f9d9e481 100644 --- a/tests/compiler/basic-nullable.ts +++ b/tests/compiler/basic-nullable.ts @@ -1,2 +1,3 @@ var a: i32 | null; -// Expect error: AS204 + +ERROR("EOF"); // mark end and ensure fail diff --git a/tests/compiler/basic-nullable.untouched.wat b/tests/compiler/basic-nullable.untouched.wat deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/compiler/constant-assign.json b/tests/compiler/constant-assign.json index b1da366ff4..76d94db1ce 100644 --- a/tests/compiler/constant-assign.json +++ b/tests/compiler/constant-assign.json @@ -1,5 +1,13 @@ { "asc_flags": [ "--runtime none" + ], + "stderr": [ + "TS2540: Cannot assign to", "b = 3;", + "TS2540: Cannot assign to", "a = 2;", + "TS2540: Cannot assign to", "b = [ 2 ];", + "TS2540: Cannot assign to", "a = [ 2 ];", + "TS2540: Cannot assign to", "a = 2;", + "EOF" ] } \ No newline at end of file diff --git a/tests/compiler/constant-assign.optimized.wat b/tests/compiler/constant-assign.optimized.wat deleted file mode 100644 index 342a301dbb..0000000000 --- a/tests/compiler/constant-assign.optimized.wat +++ /dev/null @@ -1,319 +0,0 @@ -(module - (type $FUNCSIG$iii (func (param i32 i32) (result i32))) - (type $FUNCSIG$v (func)) - (type $FUNCSIG$vii (func (param i32 i32))) - (type $FUNCSIG$ii (func (param i32) (result i32))) - (memory $0 1) - (data (i32.const 8) "\04\00\00\00\01\00\00\00\00\00\00\00\04\00\00\00\02") - (data (i32.const 32) "\04\00\00\00\01\00\00\00\00\00\00\00\04\00\00\00\01") - (data (i32.const 56) "\04\00\00\00\01\00\00\00\00\00\00\00\04\00\00\00\02") - (global $~lib/rt/stub/startOffset (mut i32) (i32.const 0)) - (global $~lib/rt/stub/offset (mut i32) (i32.const 0)) - (export "memory" (memory $0)) - (start $start) - (func $~lib/rt/stub/__alloc (; 0 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32) - (local $2 i32) - (local $3 i32) - (local $4 i32) - (local $5 i32) - local.get $0 - i32.const 1073741808 - i32.gt_u - if - unreachable - end - global.get $~lib/rt/stub/offset - i32.const 16 - i32.add - local.tee $3 - local.get $0 - i32.const 1 - local.get $0 - i32.const 1 - i32.gt_u - select - i32.add - i32.const 15 - i32.add - i32.const -16 - i32.and - local.tee $2 - memory.size - local.tee $4 - i32.const 16 - i32.shl - i32.gt_u - if - local.get $4 - local.get $2 - local.get $3 - i32.sub - i32.const 65535 - i32.add - i32.const -65536 - i32.and - i32.const 16 - i32.shr_u - local.tee $5 - local.get $4 - local.get $5 - i32.gt_s - select - memory.grow - i32.const 0 - i32.lt_s - if - local.get $5 - memory.grow - i32.const 0 - i32.lt_s - if - unreachable - end - end - end - local.get $2 - global.set $~lib/rt/stub/offset - local.get $3 - i32.const 16 - i32.sub - local.tee $2 - local.get $1 - i32.store offset=8 - local.get $2 - local.get $0 - i32.store offset=12 - local.get $3 - ) - (func $~lib/memory/memory.copy (; 1 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32) - (local $2 i32) - (local $3 i32) - (local $4 i32) - block $~lib/util/memory/memmove|inlined.0 - i32.const 4 - local.set $2 - local.get $0 - local.get $1 - i32.eq - br_if $~lib/util/memory/memmove|inlined.0 - local.get $0 - local.get $1 - i32.lt_u - if - local.get $1 - i32.const 7 - i32.and - local.get $0 - i32.const 7 - i32.and - i32.eq - if - loop $continue|0 - local.get $0 - i32.const 7 - i32.and - if - local.get $2 - i32.eqz - br_if $~lib/util/memory/memmove|inlined.0 - local.get $2 - i32.const 1 - i32.sub - local.set $2 - local.get $0 - local.tee $3 - i32.const 1 - i32.add - local.set $0 - local.get $1 - local.tee $4 - i32.const 1 - i32.add - local.set $1 - local.get $3 - local.get $4 - i32.load8_u - i32.store8 - br $continue|0 - end - end - loop $continue|1 - local.get $2 - i32.const 8 - i32.ge_u - if - local.get $0 - local.get $1 - i64.load - i64.store - local.get $2 - i32.const 8 - i32.sub - local.set $2 - local.get $0 - i32.const 8 - i32.add - local.set $0 - local.get $1 - i32.const 8 - i32.add - local.set $1 - br $continue|1 - end - end - end - loop $continue|2 - local.get $2 - if - local.get $0 - local.tee $3 - i32.const 1 - i32.add - local.set $0 - local.get $1 - local.tee $4 - i32.const 1 - i32.add - local.set $1 - local.get $3 - local.get $4 - i32.load8_u - i32.store8 - local.get $2 - i32.const 1 - i32.sub - local.set $2 - br $continue|2 - end - end - else - local.get $1 - i32.const 7 - i32.and - local.get $0 - i32.const 7 - i32.and - i32.eq - if - loop $continue|3 - local.get $0 - local.get $2 - i32.add - i32.const 7 - i32.and - if - local.get $2 - i32.eqz - br_if $~lib/util/memory/memmove|inlined.0 - local.get $2 - i32.const 1 - i32.sub - local.tee $2 - local.get $0 - i32.add - local.get $1 - local.get $2 - i32.add - i32.load8_u - i32.store8 - br $continue|3 - end - end - loop $continue|4 - local.get $2 - i32.const 8 - i32.ge_u - if - local.get $2 - i32.const 8 - i32.sub - local.tee $2 - local.get $0 - i32.add - local.get $1 - local.get $2 - i32.add - i64.load - i64.store - br $continue|4 - end - end - end - loop $continue|5 - local.get $2 - if - local.get $2 - i32.const 1 - i32.sub - local.tee $2 - local.get $0 - i32.add - local.get $1 - local.get $2 - i32.add - i32.load8_u - i32.store8 - br $continue|5 - end - end - end - end - ) - (func $~lib/rt/__allocArray (; 2 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) - (local $1 i32) - (local $2 i32) - i32.const 16 - i32.const 3 - call $~lib/rt/stub/__alloc - local.tee $1 - i32.const 4 - i32.const 0 - call $~lib/rt/stub/__alloc - local.tee $2 - i32.store - local.get $1 - local.get $2 - i32.store offset=4 - local.get $1 - i32.const 4 - i32.store offset=8 - local.get $1 - i32.const 1 - i32.store offset=12 - local.get $0 - if - local.get $2 - local.get $0 - call $~lib/memory/memory.copy - end - local.get $1 - ) - (func $start:constant-assign (; 3 ;) (type $FUNCSIG$v) - i32.const 80 - global.set $~lib/rt/stub/startOffset - global.get $~lib/rt/stub/startOffset - global.set $~lib/rt/stub/offset - i32.const 0 - call $~lib/rt/__allocArray - i32.load offset=4 - i32.const 1 - i32.store - i32.const 24 - call $~lib/rt/__allocArray - drop - i32.const 48 - call $~lib/rt/__allocArray - drop - i32.const 72 - call $~lib/rt/__allocArray - drop - ) - (func $start (; 4 ;) (type $FUNCSIG$v) - block - call $start:constant-assign - end - ) - (func $null (; 5 ;) (type $FUNCSIG$v) - nop - ) -) diff --git a/tests/compiler/constant-assign.ts b/tests/compiler/constant-assign.ts index af26b295e5..b3ea0ad514 100644 --- a/tests/compiler/constant-assign.ts +++ b/tests/compiler/constant-assign.ts @@ -1,6 +1,3 @@ -// Expect error: TS2540 -// ^ TODO: Properly handle multiple - function localConst(a: i32): void { const b = a + 1; b = 3; @@ -28,3 +25,5 @@ localConstArrayInline(); // globalConst const a = 1; a = 2; + +ERROR("EOF"); // mark end and ensure fail diff --git a/tests/compiler/constant-assign.untouched.wat b/tests/compiler/constant-assign.untouched.wat deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/compiler/unsafe.json b/tests/compiler/unsafe.json new file mode 100644 index 0000000000..64cda034fb --- /dev/null +++ b/tests/compiler/unsafe.json @@ -0,0 +1,34 @@ +{ + "asc_flags": [ + "--runtime none", + "--noUnsafe" + ], + "stderr": [ + "AS212: Decorator '@unsafe' is not valid here.", + "AS224: Expression is unsafe.", "f1();", + "AS224: Expression is unsafe.", "f2();", + "AS224: Expression is unsafe.", "= new Foo();", + "AS224: Expression is unsafe.", "super();", + "AS224: Expression is unsafe.", ": Foo = {};", + "AS224: Expression is unsafe.", "= instantiate()", + "AS224: Expression is unsafe.", "Foo.bar();", + "AS224: Expression is unsafe.", "foo.bar();", + "AS224: Expression is unsafe.", " = Foo.foo;", + "AS224: Expression is unsafe.", "Foo.foo = 1;", + "AS224: Expression is unsafe.", "Foo.foo++;", + "AS224: Expression is unsafe.", "Foo.foo += 1;", + "AS224: Expression is unsafe.", " = foo.foo;", + "AS224: Expression is unsafe.", "foo.foo = 1;", + "AS224: Expression is unsafe.", "foo.foo++;", + "AS224: Expression is unsafe.", "foo.foo += 1;", + "AS224: Expression is unsafe.", "= Foo.baz;", + "AS224: Expression is unsafe.", "Foo.baz = 1;", + "AS224: Expression is unsafe.", "Foo.baz++;", + "AS224: Expression is unsafe.", "Foo.baz += 1;", + "AS224: Expression is unsafe.", "= foo.baz;", + "AS224: Expression is unsafe.", "foo.baz = 1;", + "AS224: Expression is unsafe.", "foo.baz++;", + "AS224: Expression is unsafe.", "foo.baz += 1;", + "EOF" + ] +} \ No newline at end of file diff --git a/tests/compiler/unsafe.ts b/tests/compiler/unsafe.ts new file mode 100644 index 0000000000..80f0735908 --- /dev/null +++ b/tests/compiler/unsafe.ts @@ -0,0 +1,85 @@ +// Global + +@unsafe var g = 0; // not valid here + +// Function + +@unsafe function f1(): void {} +f1(); + +// Inline function + +@unsafe @inline function f2(): void {} +f2(); + +// === Class members === + +class Foo { + @unsafe constructor() {} + @unsafe static foo: i32 = 0; + @unsafe static bar(): void {} + @unsafe foo: i32 = 0; + @unsafe bar(): void {} + @unsafe static get baz(): i32 { return 0; } + @unsafe static set baz(i: i32) { } + @unsafe get baz(): i32 { return 0; } + @unsafe set baz(i: i32) { } +} + +// Constructor + +var foo = new Foo(); + +// Constructor via super + +class Bar extends Foo { + constructor() { super(); } +} + +var bar = new Bar(); + +// Constructor via object literal + +var foo2: Foo = {}; + +// Constructor via instantiate + +var foo3 = instantiate(); + +// Static method + +Foo.bar(); + +// Instance method + +foo.bar(); + +// Static field + +var n1 = Foo.foo; +Foo.foo = 1; +Foo.foo++; +Foo.foo += 1; + +// Instance field + +var n2 = foo.foo; +foo.foo = 1; +foo.foo++; +foo.foo += 1; + +// Static property + +var n3 = Foo.baz; +Foo.baz = 1; +Foo.baz++; +Foo.baz += 1; + +// Instance property + +var n4 = foo.baz; +foo.baz = 1; +foo.baz++; +foo.baz += 1; + +ERROR("EOF"); // mark end and ensure fail