Skip to content

Commit 5cbbf84

Browse files
authored
Check global uses more strictly (#2632)
BREAKING CHANGE: Use of global variables (in the Wasm sense) is now checked more strictly to prevent undesirable execution order. If the compiler detects that it is possible that a variable might not have been initialized when accessed, a diagnostic is produced. It cannot be ruled out that some amount of existing code will be affected, since such checks are performed at runtime in JS but are proven at compile time in AS. If encountered, the fix is to move the variable's declaration up, say before the first invocation of a function (that might call another function) accessing the variable, so it is guaranteed that it is initialized before its first use.
1 parent 941b0e1 commit 5cbbf84

12 files changed

+4267
-137
lines changed

Diff for: src/builtins.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,9 @@ export const builtinVariables_onAccess = new Map<string, (ctx: BuiltinVariableCo
787787

788788
// === Static type evaluation =================================================================
789789

790+
// helper global used by checkConstantType
791+
let checkConstantType_expr: ExpressionRef = 0;
792+
790793
// isBoolean<T!>() / isBoolean<T?>(value: T) -> bool
791794
function builtin_isBoolean(ctx: BuiltinFunctionContext): ExpressionRef {
792795
let compiler = ctx.compiler;
@@ -10620,8 +10623,6 @@ export function compileRTTI(compiler: Compiler): void {
1062010623

1062110624
// Helpers
1062210625

10623-
let checkConstantType_expr: ExpressionRef = 0;
10624-
1062510626
/** Checks the constant type of a type argument *or* expression. */
1062610627
function checkConstantType(ctx: BuiltinFunctionContext): Type | null {
1062710628
let compiler = ctx.compiler;

Diff for: src/compiler.ts

+30-9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
* @license Apache-2.0
44
*/
55

6+
// helper globals used by mangleImportName
7+
let mangleImportName_moduleName: string = "";
8+
let mangleImportName_elementName: string = "";
9+
610
import {
711
BuiltinNames,
812
BuiltinFunctionContext,
@@ -1097,6 +1101,20 @@ export class Compiler extends DiagnosticEmitter {
10971101

10981102
// === Globals ==================================================================================
10991103

1104+
/** Tries to compile a global variable lazily. */
1105+
compileGlobalLazy(global: Global, reportNode: Node): bool {
1106+
if (global.is(CommonFlags.Compiled)) return !global.is(CommonFlags.Errored);
1107+
if (global.hasAnyDecorator(DecoratorFlags.Lazy | DecoratorFlags.Builtin) || global.is(CommonFlags.Ambient)) {
1108+
return this.compileGlobal(global); // compile now
1109+
}
1110+
// Otherwise the global is used before its initializer executes
1111+
this.errorRelated(
1112+
DiagnosticCode.Variable_0_used_before_its_declaration,
1113+
reportNode.range, global.identifierNode.range, global.internalName
1114+
);
1115+
return false;
1116+
}
1117+
11001118
/** Compiles a global variable. */
11011119
compileGlobal(global: Global): bool {
11021120
if (global.is(CommonFlags.Compiled)) return !global.is(CommonFlags.Errored);
@@ -5548,8 +5566,9 @@ export class Compiler extends DiagnosticEmitter {
55485566
let targetType: Type;
55495567
switch (target.kind) {
55505568
case ElementKind.Global: {
5551-
// not yet compiled if a static field compiled as a global
5552-
if (!this.compileGlobal(<Global>target)) return this.module.unreachable(); // reports
5569+
if (!this.compileGlobalLazy(<Global>target, expression)) {
5570+
return this.module.unreachable();
5571+
}
55535572
// fall-through
55545573
}
55555574
case ElementKind.Local: {
@@ -5691,7 +5710,9 @@ export class Compiler extends DiagnosticEmitter {
56915710
}
56925711
case ElementKind.Global: {
56935712
let global = <Global>target;
5694-
if (!this.compileGlobal(global)) return module.unreachable();
5713+
if (!this.compileGlobalLazy(global, valueExpression)) {
5714+
return module.unreachable();
5715+
}
56955716
if (target.isAny(CommonFlags.Const | CommonFlags.Readonly)) {
56965717
this.error(
56975718
DiagnosticCode.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property,
@@ -6766,7 +6787,7 @@ export class Compiler extends DiagnosticEmitter {
67666787
let resolved = this.resolver.lookupExpression(initializer, instance.flow, parameterTypes[i], ReportMode.Swallow);
67676788
if (resolved && resolved.kind == ElementKind.Global) {
67686789
let global = <Global>resolved;
6769-
if (this.compileGlobal(global) && global.is(CommonFlags.Inlined)) {
6790+
if (this.compileGlobalLazy(global, initializer) && global.is(CommonFlags.Inlined)) {
67706791
operands.push(
67716792
this.compileInlineConstant(global, parameterTypes[i], Constraints.ConvImplicit)
67726793
);
@@ -7329,7 +7350,7 @@ export class Compiler extends DiagnosticEmitter {
73297350
}
73307351
case ElementKind.Global: {
73317352
let global = <Global>target;
7332-
if (!this.compileGlobal(global)) { // reports; not yet compiled if a static field
7353+
if (!this.compileGlobalLazy(global, expression)) {
73337354
return module.unreachable();
73347355
}
73357356
let globalType = global.type;
@@ -8895,7 +8916,9 @@ export class Compiler extends DiagnosticEmitter {
88958916
switch (target.kind) {
88968917
case ElementKind.Global: { // static field
88978918
let global = <Global>target;
8898-
if (!this.compileGlobal(global)) return module.unreachable(); // reports
8919+
if (!this.compileGlobalLazy(global, expression)) {
8920+
return module.unreachable();
8921+
}
88998922
let globalType = global.type;
89008923
assert(globalType != Type.void);
89018924
if (this.pendingElements.has(global)) {
@@ -10388,6 +10411,7 @@ export class Compiler extends DiagnosticEmitter {
1038810411
}
1038910412

1039010413
// helpers
10414+
1039110415
function mangleImportName(
1039210416
element: Element,
1039310417
declaration: DeclarationStatement
@@ -10444,6 +10468,3 @@ function mangleImportName(
1044410468
);
1044510469
}
1044610470
}
10447-
10448-
let mangleImportName_moduleName: string = "";
10449-
let mangleImportName_elementName: string = "";

Diff for: src/program.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1782,7 +1782,7 @@ export class Program extends DiagnosticEmitter {
17821782
let global = new Global(
17831783
name,
17841784
this.nativeFile,
1785-
DecoratorFlags.None,
1785+
DecoratorFlags.Lazy,
17861786
this.makeNativeVariableDeclaration(name, CommonFlags.Const | CommonFlags.Export)
17871787
);
17881788
global.setConstantIntegerValue(value, type);
@@ -1795,7 +1795,7 @@ export class Program extends DiagnosticEmitter {
17951795
let global = new Global(
17961796
name,
17971797
this.nativeFile,
1798-
DecoratorFlags.None,
1798+
DecoratorFlags.Lazy,
17991799
this.makeNativeVariableDeclaration(name, CommonFlags.Const | CommonFlags.Export)
18001800
);
18011801
global.setConstantFloatValue(value, type);

0 commit comments

Comments
 (0)