Skip to content

Commit fea328e

Browse files
authored
Implement --noUnsafe compiler option (AssemblyScript#710)
1 parent 3b227d4 commit fea328e

23 files changed

+293
-428
lines changed

cli/asc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,7 @@ exports.main = function main(argv, options, callback) {
550550
assemblyscript.setMemoryBase(compilerOptions, args.memoryBase >>> 0);
551551
assemblyscript.setSourceMap(compilerOptions, args.sourceMap != null);
552552
assemblyscript.setOptimizeLevelHints(compilerOptions, optimizeLevel, shrinkLevel);
553+
assemblyscript.setNoUnsafe(compilerOptions, args.noUnsafe);
553554

554555
// Initialize default aliases
555556
assemblyscript.setGlobalAlias(compilerOptions, "Math", "NativeMath");

cli/asc.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,14 @@
9494
"type": "s",
9595
"default": "full"
9696
},
97+
"noUnsafe": {
98+
"description": [
99+
"Disallows the use of unsafe features in user code.",
100+
"Does not affect library files and external modules."
101+
],
102+
"type": "b",
103+
"default": false
104+
},
97105
"debug": {
98106
"description": "Enables debug information in emitted binaries.",
99107
"type": "b",

src/ast.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1585,12 +1585,14 @@ export abstract class Statement extends Node { }
15851585

15861586
/** Indicates the specific kind of a source. */
15871587
export enum SourceKind {
1588-
/** Default source. Usually imported from an entry file. */
1589-
DEFAULT,
1590-
/** Entry file. */
1591-
ENTRY,
1592-
/** Library file. */
1593-
LIBRARY
1588+
/** User-provided file. */
1589+
USER = 0,
1590+
/** User-provided entry file. */
1591+
USER_ENTRY = 1,
1592+
/** Library-provided file. */
1593+
LIBRARY = 2,
1594+
/** Library-provided entry file. */
1595+
LIBRARY_ENTRY = 3
15941596
}
15951597

15961598
/** A top-level source node. */
@@ -1631,10 +1633,10 @@ export class Source extends Node {
16311633
this.text = text;
16321634
}
16331635

1634-
/** Tests if this source is an entry file. */
1635-
get isEntry(): bool { return this.sourceKind == SourceKind.ENTRY; }
1636-
/** Tests if this source is a stdlib file. */
1637-
get isLibrary(): bool { return this.sourceKind == SourceKind.LIBRARY; }
1636+
get isLibrary(): bool {
1637+
var kind = this.sourceKind;
1638+
return kind == SourceKind.LIBRARY || kind == SourceKind.LIBRARY_ENTRY;
1639+
}
16381640
}
16391641

16401642
/** Base class of all declaration statements. */

src/compiler.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,8 @@ import {
157157
nodeIsConstantValue,
158158
findDecorator,
159159
isTypeOmitted,
160-
ExportDefaultStatement
160+
ExportDefaultStatement,
161+
SourceKind
161162
} from "./ast";
162163

163164
import {
@@ -201,6 +202,8 @@ export class Options {
201202
globalAliases: Map<string,string> | null = null;
202203
/** Additional features to activate. */
203204
features: Feature = Feature.NONE;
205+
/** If true, disallows unsafe features in user code. */
206+
noUnsafe: bool = false;
204207

205208
/** Hinted optimize level. Not applied by the compiler itself. */
206209
optimizeLevelHint: i32 = 0;
@@ -359,7 +362,7 @@ export class Compiler extends DiagnosticEmitter {
359362
// compile entry file(s) while traversing reachable elements
360363
var files = program.filesByName;
361364
for (let file of files.values()) {
362-
if (file.source.isEntry) {
365+
if (file.source.sourceKind == SourceKind.USER_ENTRY) {
363366
this.compileFile(file);
364367
this.compileExports(file);
365368
}
@@ -450,7 +453,7 @@ export class Compiler extends DiagnosticEmitter {
450453

451454
// set up module exports
452455
for (let file of this.program.filesByName.values()) {
453-
if (file.source.isEntry) this.ensureModuleExports(file);
456+
if (file.source.sourceKind == SourceKind.USER_ENTRY) this.ensureModuleExports(file);
454457
}
455458
return module;
456459
}
@@ -5146,12 +5149,10 @@ export class Compiler extends DiagnosticEmitter {
51465149
if (!this.compileGlobal(<Global>target)) return this.module.unreachable(); // reports
51475150
// fall-through
51485151
}
5152+
case ElementKind.LOCAL:
51495153
case ElementKind.FIELD: {
51505154
targetType = (<VariableLikeElement>target).type;
5151-
break;
5152-
}
5153-
case ElementKind.LOCAL: {
5154-
targetType = (<VariableLikeElement>target).type;
5155+
if (target.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(expression);
51555156
break;
51565157
}
51575158
case ElementKind.PROPERTY_PROTOTYPE: { // static property
@@ -5167,6 +5168,7 @@ export class Compiler extends DiagnosticEmitter {
51675168
if (!setterInstance) return this.module.unreachable();
51685169
assert(setterInstance.signature.parameterTypes.length == 1); // parser must guarantee this
51695170
targetType = setterInstance.signature.parameterTypes[0];
5171+
if (setterPrototype.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(expression);
51705172
break;
51715173
}
51725174
case ElementKind.PROPERTY: { // instance property
@@ -5180,6 +5182,7 @@ export class Compiler extends DiagnosticEmitter {
51805182
}
51815183
assert(setterInstance.signature.parameterTypes.length == 1); // parser must guarantee this
51825184
targetType = setterInstance.signature.parameterTypes[0];
5185+
if (setterInstance.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(expression);
51835186
break;
51845187
}
51855188
case ElementKind.CLASS: {
@@ -5216,6 +5219,7 @@ export class Compiler extends DiagnosticEmitter {
52165219
}
52175220
assert(indexedSet.signature.parameterTypes.length == 2); // parser must guarantee this
52185221
targetType = indexedSet.signature.parameterTypes[1]; // 2nd parameter is the element
5222+
if (indexedSet.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(expression);
52195223
break;
52205224
}
52215225
// fall-through
@@ -5873,6 +5877,7 @@ export class Compiler extends DiagnosticEmitter {
58735877
makeMap<string,Type>(flow.contextualTypeArguments)
58745878
);
58755879
if (!instance) return this.module.unreachable();
5880+
if (prototype.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(expression);
58765881
return this.makeCallDirect(instance, argumentExprs, expression, contextualType == Type.void);
58775882
// TODO: this skips inlining because inlining requires compiling its temporary locals in
58785883
// the scope of the inlined flow. might need another mechanism to lock temp. locals early,
@@ -6010,6 +6015,8 @@ export class Compiler extends DiagnosticEmitter {
60106015
expression: CallExpression,
60116016
contextualType: Type
60126017
): ExpressionRef {
6018+
if (prototype.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(expression);
6019+
60136020
var typeArguments: Type[] | null = null;
60146021

60156022
// builtins handle omitted type arguments on their own. if present, however, resolve them here
@@ -6108,6 +6115,17 @@ export class Compiler extends DiagnosticEmitter {
61086115
return true;
61096116
}
61106117

6118+
/** Checks that an unsafe expression is allowed. */
6119+
private checkUnsafe(reportNode: Node): void {
6120+
// Library files may always use unsafe features
6121+
if (this.options.noUnsafe && !reportNode.range.source.isLibrary) {
6122+
this.error(
6123+
DiagnosticCode.Expression_is_unsafe,
6124+
reportNode.range
6125+
);
6126+
}
6127+
}
6128+
61116129
/** Compiles a direct call to a concrete function. */
61126130
compileCallDirect(
61136131
instance: Function,
@@ -6127,6 +6145,7 @@ export class Compiler extends DiagnosticEmitter {
61276145
this.currentType = signature.returnType;
61286146
return this.module.unreachable();
61296147
}
6148+
if (instance.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(reportNode);
61306149

61316150
// Inline if explicitly requested
61326151
if (instance.hasDecorator(DecoratorFlags.INLINE)) {
@@ -7688,6 +7707,7 @@ export class Compiler extends DiagnosticEmitter {
76887707
);
76897708
return module.unreachable();
76907709
}
7710+
if (ctor.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(expression);
76917711
}
76927712

76937713
// check and compile field values
@@ -7905,6 +7925,7 @@ export class Compiler extends DiagnosticEmitter {
79057925
reportNode: Node
79067926
): ExpressionRef {
79077927
var ctor = this.ensureConstructor(classInstance, reportNode);
7928+
if (ctor.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(reportNode);
79087929
var expr = this.compileCallDirect( // no need for another autoreleased local
79097930
ctor,
79107931
argumentExpressions,
@@ -7935,6 +7956,7 @@ export class Compiler extends DiagnosticEmitter {
79357956

79367957
var target = this.resolver.resolvePropertyAccessExpression(propertyAccess, flow, contextualType); // reports
79377958
if (!target) return module.unreachable();
7959+
if (target.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(propertyAccess);
79387960

79397961
switch (target.kind) {
79407962
case ElementKind.GLOBAL: { // static field

src/definitions.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ import {
3232
TypeKind
3333
} from "./types";
3434

35+
import {
36+
SourceKind
37+
} from "./ast";
38+
3539
import {
3640
indent
3741
} from "./util";
@@ -55,7 +59,7 @@ abstract class ExportsWalker {
5559
/** Walks all elements and calls the respective handlers. */
5660
walk(): void {
5761
for (let file of this.program.filesByName.values()) {
58-
if (file.source.isEntry) this.visitFile(file);
62+
if (file.source.sourceKind == SourceKind.USER_ENTRY) this.visitFile(file);
5963
}
6064
}
6165

src/diagnosticMessages.generated.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export enum DiagnosticCode {
3434
Module_cannot_have_multiple_start_functions = 221,
3535
_0_must_be_a_value_between_1_and_2_inclusive = 222,
3636
_0_must_be_a_power_of_two = 223,
37-
TODO_Cannot_inline_inferred_calls_and_specific_internals_yet = 224,
37+
Expression_is_unsafe = 224,
3838
Expression_is_never_null = 225,
3939
Unterminated_string_literal = 1002,
4040
Identifier_expected = 1003,
@@ -173,7 +173,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
173173
case 221: return "Module cannot have multiple start functions.";
174174
case 222: return "'{0}' must be a value between '{1}' and '{2}' inclusive.";
175175
case 223: return "'{0}' must be a power of two.";
176-
case 224: return "TODO: Cannot inline inferred calls and specific internals yet.";
176+
case 224: return "Expression is unsafe.";
177177
case 225: return "Expression is never 'null'.";
178178
case 1002: return "Unterminated string literal.";
179179
case 1003: return "Identifier expected.";

src/diagnosticMessages.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"Module cannot have multiple start functions.": 221,
2727
"'{0}' must be a value between '{1}' and '{2}' inclusive.": 222,
2828
"'{0}' must be a power of two.": 223,
29-
"TODO: Cannot inline inferred calls and specific internals yet.": 224,
29+
"Expression is unsafe.": 224,
3030
"Expression is never 'null'.": 225,
3131

3232
"Unterminated string literal.": 1002,

src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ export function setExplicitStart(options: Options, explicitStart: bool): void {
109109
options.explicitStart = explicitStart;
110110
}
111111

112+
/** Sets the `noUnsafe` option. */
113+
export function setNoUnsafe(options: Options, noUnsafe: bool): void {
114+
options.noUnsafe = noUnsafe;
115+
}
116+
112117
/** Sign extension operations. */
113118
export const FEATURE_SIGN_EXTENSION = Feature.SIGN_EXTENSION;
114119
/** Mutable global imports and exports. */

src/parser.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,12 @@ export class Parser extends DiagnosticEmitter {
131131
normalizedPath,
132132
text,
133133
isEntry
134-
? SourceKind.ENTRY
135-
: path.startsWith(LIBRARY_PREFIX) && path.indexOf(PATH_DELIMITER, LIBRARY_PREFIX.length) < 0
136-
? SourceKind.LIBRARY
137-
: SourceKind.DEFAULT
134+
? SourceKind.USER_ENTRY
135+
: path.startsWith(LIBRARY_PREFIX)
136+
? path.indexOf(PATH_DELIMITER, LIBRARY_PREFIX.length) < 0
137+
? SourceKind.LIBRARY_ENTRY
138+
: SourceKind.LIBRARY
139+
: SourceKind.USER
138140
);
139141
var program = this.program;
140142
program.sources.push(source);

src/program.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ export class Program extends DiagnosticEmitter {
410410
diagnostics: DiagnosticMessage[] | null = null
411411
) {
412412
super(diagnostics);
413-
var nativeSource = new Source(LIBRARY_SUBST, "[native code]", SourceKind.LIBRARY);
413+
var nativeSource = new Source(LIBRARY_SUBST, "[native code]", SourceKind.LIBRARY_ENTRY);
414414
this.nativeSource = nativeSource;
415415
var nativeFile = new File(this, nativeSource);
416416
this.nativeFile = nativeFile;
@@ -876,8 +876,9 @@ export class Program extends DiagnosticEmitter {
876876
// mark module exports, i.e. to apply proper wrapping behavior on the boundaries
877877
for (let file of this.filesByName.values()) {
878878
let exports = file.exports;
879-
if (!(file.source.isEntry && exports)) continue;
880-
for (let element of exports.values()) this.markModuleExport(element);
879+
if (exports !== null && file.source.sourceKind == SourceKind.USER_ENTRY) {
880+
for (let element of exports.values()) this.markModuleExport(element);
881+
}
881882
}
882883
}
883884

@@ -2188,7 +2189,7 @@ export class File extends Element {
21882189
var exports = this.exports;
21892190
if (!exports) this.exports = exports = new Map();
21902191
exports.set(name, element);
2191-
if (this.source.isLibrary) this.program.ensureGlobal(name, element);
2192+
if (this.source.sourceKind == SourceKind.LIBRARY_ENTRY) this.program.ensureGlobal(name, element);
21922193
}
21932194

21942195
/** Ensures that another file is a re-export of this file. */
@@ -2876,6 +2877,7 @@ export class Field extends VariableLikeElement {
28762877
);
28772878
this.prototype = prototype;
28782879
this.flags = prototype.flags;
2880+
this.decoratorFlags = prototype.decoratorFlags;
28792881
assert(type != Type.void);
28802882
this.setType(type);
28812883
registerConcreteElement(this.program, this);
@@ -2945,6 +2947,8 @@ export class Property extends VariableLikeElement {
29452947
)
29462948
);
29472949
this.prototype = prototype;
2950+
this.flags = prototype.flags;
2951+
this.decoratorFlags = prototype.decoratorFlags;
29482952
registerConcreteElement(this.program, this);
29492953
}
29502954

src/resolver.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1505,10 +1505,12 @@ export class Resolver extends DiagnosticEmitter {
15051505
}
15061506
let typeNode = parameterDeclaration.type;
15071507
if (isTypeOmitted(typeNode)) {
1508-
this.error(
1509-
DiagnosticCode.Type_expected,
1510-
typeNode.range
1511-
);
1508+
if (reportMode == ReportMode.REPORT) {
1509+
this.error(
1510+
DiagnosticCode.Type_expected,
1511+
typeNode.range
1512+
);
1513+
}
15121514
return null;
15131515
}
15141516
let parameterType = this.resolveType(
@@ -1531,10 +1533,12 @@ export class Resolver extends DiagnosticEmitter {
15311533
} else {
15321534
let typeNode = signatureNode.returnType;
15331535
if (isTypeOmitted(typeNode)) {
1534-
this.error(
1535-
DiagnosticCode.Type_expected,
1536-
typeNode.range
1537-
);
1536+
if (reportMode == ReportMode.REPORT) {
1537+
this.error(
1538+
DiagnosticCode.Type_expected,
1539+
typeNode.range
1540+
);
1541+
}
15381542
return null;
15391543
}
15401544
let type = this.resolveType(

0 commit comments

Comments
 (0)