diff --git a/Compilation/CompilationContext.cs b/Compilation/CompilationContext.cs index db747eb..948215f 100644 --- a/Compilation/CompilationContext.cs +++ b/Compilation/CompilationContext.cs @@ -46,6 +46,12 @@ public class CompilationContext public TypeMapper TypeMapper { get; } public LocalsManager Locals { get; } + /// + /// Validated IL builder that wraps the ILGenerator with compile-time checks. + /// Use this for new code to catch label, stack, and exception block errors early. + /// + public ValidatedILBuilder ILBuilder { get; private set; } + /// /// Type provider for resolving .NET types (runtime or reference assembly mode). /// Use this instead of typeof() for type resolution to support --ref-asm compilation. @@ -416,6 +422,16 @@ public CompilationContext( Classes = classes; Types = types ?? TypeProvider.Runtime; Locals = new LocalsManager(il); + ILBuilder = new ValidatedILBuilder(il, Types); + } + + /// + /// Updates the validated IL builder for a new ILGenerator (when switching methods). + /// Call this when the IL property changes to a new method's generator. + /// + public void UpdateILBuilder(ILGenerator newIL) + { + ILBuilder = new ValidatedILBuilder(newIL, Types); } public void DefineParameter(string name, int argIndex, Type? paramType = null) diff --git a/Compilation/ILCompiler.Classes.Methods.cs b/Compilation/ILCompiler.Classes.Methods.cs index c4e4e85..1211bb4 100644 --- a/Compilation/ILCompiler.Classes.Methods.cs +++ b/Compilation/ILCompiler.Classes.Methods.cs @@ -683,8 +683,9 @@ private void EmitMethod(TypeBuilder typeBuilder, Stmt.Function method, FieldInfo lockTakenLocal = il.DeclareLocal(typeof(bool)); // bool __lockTaken // Set up deferred return handling for the lock's exception block + // Use the builder to define the label so it's tracked for validation ctx.ReturnValueLocal = il.DeclareLocal(typeof(object)); - ctx.ReturnLabel = il.DefineLabel(); + ctx.ReturnLabel = ctx.ILBuilder.DefineLabel("lock_deferred_return"); ctx.ExceptionBlockDepth++; // int __prevReentrancy = _lockReentrancy.Value; @@ -719,8 +720,8 @@ private void EmitMethod(TypeBuilder typeBuilder, Stmt.Function method, FieldInfo il.MarkLabel(skipEnterLabel); - // Begin try block - il.BeginExceptionBlock(); + // Begin try block - use builder to keep exception depth in sync + ctx.ILBuilder.BeginExceptionBlock(); } // Abstract methods have no body to emit @@ -740,10 +741,10 @@ private void EmitMethod(TypeBuilder typeBuilder, Stmt.Function method, FieldInfo // ReturnValueLocal is guaranteed non-null here (set up earlier in hasLock block) il.Emit(OpCodes.Ldnull); il.Emit(OpCodes.Stloc, ctx.ReturnValueLocal!); - il.Emit(OpCodes.Leave, ctx.ReturnLabel); + ctx.ILBuilder.Emit_Leave(ctx.ReturnLabel); - // Begin finally block - il.BeginFinallyBlock(); + // Begin finally block - use builder for exception block tracking + ctx.ILBuilder.BeginFinallyBlock(); // _lockReentrancy.Value = __prevReentrancy; il.Emit(OpCodes.Ldarg_0); // this @@ -763,13 +764,13 @@ private void EmitMethod(TypeBuilder typeBuilder, Stmt.Function method, FieldInfo il.MarkLabel(skipExitLabel); - // End try/finally block - il.EndExceptionBlock(); + // End try/finally block - use builder for exception block tracking + ctx.ILBuilder.EndExceptionBlock(); ctx.ExceptionBlockDepth--; - // Mark return label and emit actual return - il.MarkLabel(ctx.ReturnLabel); + // Mark return label and emit actual return - use builder since label was defined with builder + ctx.ILBuilder.MarkLabel(ctx.ReturnLabel); il.Emit(OpCodes.Ldloc, ctx.ReturnValueLocal!); // Non-null in hasLock path il.Emit(OpCodes.Ret); } diff --git a/Compilation/ILCompiler.Classes.Static.cs b/Compilation/ILCompiler.Classes.Static.cs index c9e2f5c..7699b79 100644 --- a/Compilation/ILCompiler.Classes.Static.cs +++ b/Compilation/ILCompiler.Classes.Static.cs @@ -221,8 +221,9 @@ private void EmitStaticMethodBody(string className, Stmt.Function method) lockTakenLocal = il.DeclareLocal(typeof(bool)); // bool __lockTaken // Set up deferred return handling for the lock's exception block + // Use the builder to define the label so it's tracked for validation ctx.ReturnValueLocal = il.DeclareLocal(typeof(object)); - ctx.ReturnLabel = il.DefineLabel(); + ctx.ReturnLabel = ctx.ILBuilder.DefineLabel("static_lock_deferred_return"); ctx.ExceptionBlockDepth++; // int __prevReentrancy = _staticLockReentrancy.Value; @@ -254,8 +255,8 @@ private void EmitStaticMethodBody(string className, Stmt.Function method) il.MarkLabel(skipEnterLabel); - // Begin try block - il.BeginExceptionBlock(); + // Begin try block - use builder to keep exception depth in sync + ctx.ILBuilder.BeginExceptionBlock(); } // Abstract methods have no body to emit @@ -275,10 +276,10 @@ private void EmitStaticMethodBody(string className, Stmt.Function method) // ReturnValueLocal is guaranteed non-null here (set up earlier in hasLock block) il.Emit(OpCodes.Ldnull); il.Emit(OpCodes.Stloc, ctx.ReturnValueLocal!); - il.Emit(OpCodes.Leave, ctx.ReturnLabel); + ctx.ILBuilder.Emit_Leave(ctx.ReturnLabel); - // Begin finally block - il.BeginFinallyBlock(); + // Begin finally block - use builder for exception block tracking + ctx.ILBuilder.BeginFinallyBlock(); // _staticLockReentrancy.Value = __prevReentrancy; il.Emit(OpCodes.Ldsfld, staticReentrancyField); // _staticLockReentrancy @@ -296,13 +297,13 @@ private void EmitStaticMethodBody(string className, Stmt.Function method) il.MarkLabel(skipExitLabel); - // End try/finally block - il.EndExceptionBlock(); + // End try/finally block - use builder for exception block tracking + ctx.ILBuilder.EndExceptionBlock(); ctx.ExceptionBlockDepth--; - // Mark return label and emit actual return - il.MarkLabel(ctx.ReturnLabel); + // Mark return label and emit actual return - use builder since label was defined with builder + ctx.ILBuilder.MarkLabel(ctx.ReturnLabel); il.Emit(OpCodes.Ldloc, ctx.ReturnValueLocal!); // Non-null in hasLock path il.Emit(OpCodes.Ret); } diff --git a/Compilation/ILEmitter.Calls.cs b/Compilation/ILEmitter.Calls.cs index e0f69fa..1b3432d 100644 --- a/Compilation/ILEmitter.Calls.cs +++ b/Compilation/ILEmitter.Calls.cs @@ -731,19 +731,20 @@ private void EmitAmbiguousMethodCall(Expr obj, string methodName, List arg IL.Emit(OpCodes.Stloc, objLocal); // Check if it's a string - var isStringLabel = IL.DefineLabel(); - var isListLabel = IL.DefineLabel(); - var doneLabel = IL.DefineLabel(); + var builder = _ctx.ILBuilder; + var isStringLabel = builder.DefineLabel("ambiguous_string"); + var isListLabel = builder.DefineLabel("ambiguous_list"); + var doneLabel = builder.DefineLabel("ambiguous_done"); IL.Emit(OpCodes.Ldloc, objLocal); IL.Emit(OpCodes.Isinst, _ctx.Types.String); - IL.Emit(OpCodes.Brtrue, isStringLabel); + builder.Emit_Brtrue(isStringLabel); // Assume it's a list if not a string - IL.Emit(OpCodes.Br, isListLabel); + builder.Emit_Br(isListLabel); // String path - IL.MarkLabel(isStringLabel); + builder.MarkLabel(isStringLabel); IL.Emit(OpCodes.Ldloc, objLocal); IL.Emit(OpCodes.Castclass, _ctx.Types.String); @@ -811,10 +812,10 @@ private void EmitAmbiguousMethodCall(Expr obj, string methodName, List arg IL.Emit(OpCodes.Call, _ctx.Runtime!.StringConcat); break; } - IL.Emit(OpCodes.Br, doneLabel); + builder.Emit_Br(doneLabel); // List path - IL.MarkLabel(isListLabel); + builder.MarkLabel(isListLabel); IL.Emit(OpCodes.Ldloc, objLocal); IL.Emit(OpCodes.Castclass, _ctx.Types.ListOfObject); @@ -877,7 +878,7 @@ private void EmitAmbiguousMethodCall(Expr obj, string methodName, List arg break; } - IL.MarkLabel(doneLabel); + builder.MarkLabel(doneLabel); } /// diff --git a/Compilation/ILEmitter.Expressions.cs b/Compilation/ILEmitter.Expressions.cs index 99045af..a985bea 100644 --- a/Compilation/ILEmitter.Expressions.cs +++ b/Compilation/ILEmitter.Expressions.cs @@ -163,8 +163,9 @@ protected override void EmitSuper(Expr.Super s) protected override void EmitTernary(Expr.Ternary t) { - var elseLabel = IL.DefineLabel(); - var endLabel = IL.DefineLabel(); + var builder = _ctx.ILBuilder; + var elseLabel = builder.DefineLabel("ternary_else"); + var endLabel = builder.DefineLabel("ternary_end"); EmitExpression(t.Condition); // Handle condition based on what's actually on the stack @@ -187,35 +188,36 @@ protected override void EmitTernary(Expr.Ternary t) EnsureBoxed(); // Ensure it's boxed first EmitTruthyCheck(); } - IL.Emit(OpCodes.Brfalse, elseLabel); + builder.Emit_Brfalse(elseLabel); EmitExpression(t.ThenBranch); EmitBoxIfNeeded(t.ThenBranch); - IL.Emit(OpCodes.Br, endLabel); + builder.Emit_Br(endLabel); - IL.MarkLabel(elseLabel); + builder.MarkLabel(elseLabel); EmitExpression(t.ElseBranch); EmitBoxIfNeeded(t.ElseBranch); - IL.MarkLabel(endLabel); + builder.MarkLabel(endLabel); // Both branches box, so result is Unknown (boxed object) SetStackUnknown(); } protected override void EmitNullishCoalescing(Expr.NullishCoalescing nc) { - var endLabel = IL.DefineLabel(); + var builder = _ctx.ILBuilder; + var endLabel = builder.DefineLabel("nullish_end"); EmitExpression(nc.Left); EmitBoxIfNeeded(nc.Left); IL.Emit(OpCodes.Dup); - IL.Emit(OpCodes.Brtrue, endLabel); + builder.Emit_Brtrue(endLabel); IL.Emit(OpCodes.Pop); EmitExpression(nc.Right); EmitBoxIfNeeded(nc.Right); - IL.MarkLabel(endLabel); + builder.MarkLabel(endLabel); // Both branches box, so result is Unknown (boxed object) SetStackUnknown(); } diff --git a/Compilation/ILEmitter.Helpers.cs b/Compilation/ILEmitter.Helpers.cs index 1e7f938..2fef7e9 100644 --- a/Compilation/ILEmitter.Helpers.cs +++ b/Compilation/ILEmitter.Helpers.cs @@ -15,7 +15,8 @@ public void FinalizeReturns() if (_ctx.ReturnValueLocal != null) { // Mark the return label and emit the actual return - IL.MarkLabel(_ctx.ReturnLabel); + // Use builder's MarkLabel since ReturnLabel was defined with builder + _ctx.ILBuilder.MarkLabel(_ctx.ReturnLabel); IL.Emit(OpCodes.Ldloc, _ctx.ReturnValueLocal); IL.Emit(OpCodes.Ret); } @@ -33,6 +34,7 @@ public void FinalizeReturns() public void EmitDefaultParameters(List parameters, bool isInstanceMethod) { int argOffset = isInstanceMethod ? 1 : 0; + var builder = _ctx.ILBuilder; for (int i = 0; i < parameters.Count; i++) { @@ -42,18 +44,18 @@ public void EmitDefaultParameters(List parameters, bool isInstan int argIndex = i + argOffset; // if (arg == null) { arg = ; } - var skipDefault = IL.DefineLabel(); + var skipDefault = builder.DefineLabel($"skip_default_{i}"); // Load argument and check if null IL.Emit(OpCodes.Ldarg, argIndex); - IL.Emit(OpCodes.Brtrue, skipDefault); + builder.Emit_Brtrue(skipDefault); // Argument is null, emit default value and store EmitExpression(param.DefaultValue); EmitBoxIfNeeded(param.DefaultValue); IL.Emit(OpCodes.Starg, argIndex); - IL.MarkLabel(skipDefault); + builder.MarkLabel(skipDefault); } } @@ -296,35 +298,36 @@ protected override void EmitTruthyCheck() // - null => false // - boxed false => false // - everything else => true - var checkBoolLabel = IL.DefineLabel(); - var falseLabel = IL.DefineLabel(); - var endLabel = IL.DefineLabel(); + var builder = _ctx.ILBuilder; + var checkBoolLabel = builder.DefineLabel("truthy_checkbool"); + var falseLabel = builder.DefineLabel("truthy_false"); + var endLabel = builder.DefineLabel("truthy_end"); // Check for null IL.Emit(OpCodes.Dup); - IL.Emit(OpCodes.Brfalse, falseLabel); + builder.Emit_Brfalse(falseLabel); // Check if it's a boolean IL.Emit(OpCodes.Dup); IL.Emit(OpCodes.Isinst, _ctx.Types.Boolean); - IL.Emit(OpCodes.Brfalse, checkBoolLabel); + builder.Emit_Brfalse(checkBoolLabel); // It's a boxed bool - unbox and use the value IL.Emit(OpCodes.Unbox_Any, _ctx.Types.Boolean); - IL.Emit(OpCodes.Br, endLabel); + builder.Emit_Br(endLabel); - IL.MarkLabel(checkBoolLabel); + builder.MarkLabel(checkBoolLabel); // Not null and not bool - always truthy IL.Emit(OpCodes.Pop); IL.Emit(OpCodes.Ldc_I4_1); - IL.Emit(OpCodes.Br, endLabel); + builder.Emit_Br(endLabel); - IL.MarkLabel(falseLabel); + builder.MarkLabel(falseLabel); // Null - false IL.Emit(OpCodes.Pop); IL.Emit(OpCodes.Ldc_I4_0); - IL.MarkLabel(endLabel); + builder.MarkLabel(endLabel); } private static bool IsComparisonOp(TokenType op) => diff --git a/Compilation/ILEmitter.Operators.cs b/Compilation/ILEmitter.Operators.cs index f0331f4..5567da5 100644 --- a/Compilation/ILEmitter.Operators.cs +++ b/Compilation/ILEmitter.Operators.cs @@ -156,7 +156,8 @@ private bool IsComparisonExpr(Expr expr) protected override void EmitLogical(Expr.Logical l) { - var endLabel = IL.DefineLabel(); + var builder = _ctx.ILBuilder; + var endLabel = builder.DefineLabel("logical_end"); EmitExpression(l.Left); EmitBoxIfNeeded(l.Left); @@ -166,19 +167,19 @@ protected override void EmitLogical(Expr.Logical l) if (l.Operator.Type == TokenType.AND_AND) { // Short-circuit: if left is falsy, return left - IL.Emit(OpCodes.Brfalse, endLabel); + builder.Emit_Brfalse(endLabel); } else // OR { // Short-circuit: if left is truthy, return left - IL.Emit(OpCodes.Brtrue, endLabel); + builder.Emit_Brtrue(endLabel); } IL.Emit(OpCodes.Pop); // Pop the duplicate left value EmitExpression(l.Right); EmitBoxIfNeeded(l.Right); - IL.MarkLabel(endLabel); + builder.MarkLabel(endLabel); SetStackUnknown(); // Logical operators return boxed object } diff --git a/Compilation/ILEmitter.Properties.cs b/Compilation/ILEmitter.Properties.cs index 7978c60..2c43d98 100644 --- a/Compilation/ILEmitter.Properties.cs +++ b/Compilation/ILEmitter.Properties.cs @@ -162,21 +162,22 @@ protected override void EmitGet(Expr.Get g) if (g.Optional) { - var nullLabel = IL.DefineLabel(); - var endLabel = IL.DefineLabel(); + var builder = _ctx.ILBuilder; + var nullLabel = builder.DefineLabel("optional_null"); + var endLabel = builder.DefineLabel("optional_end"); IL.Emit(OpCodes.Dup); - IL.Emit(OpCodes.Brfalse, nullLabel); + builder.Emit_Brfalse(nullLabel); IL.Emit(OpCodes.Ldstr, g.Name.Lexeme); IL.Emit(OpCodes.Call, _ctx.Runtime!.GetProperty); - IL.Emit(OpCodes.Br, endLabel); + builder.Emit_Br(endLabel); - IL.MarkLabel(nullLabel); + builder.MarkLabel(nullLabel); IL.Emit(OpCodes.Pop); IL.Emit(OpCodes.Ldnull); - IL.MarkLabel(endLabel); + builder.MarkLabel(endLabel); } else { diff --git a/Compilation/ILEmitter.Statements.cs b/Compilation/ILEmitter.Statements.cs index 3d5a179..628bc36 100644 --- a/Compilation/ILEmitter.Statements.cs +++ b/Compilation/ILEmitter.Statements.cs @@ -106,8 +106,9 @@ protected override void EmitIf(Stmt.If i) } // BothReachable: emit both branches with condition check - var elseLabel = IL.DefineLabel(); - var endLabel = IL.DefineLabel(); + var builder = _ctx.ILBuilder; + var elseLabel = builder.DefineLabel("if_else"); + var endLabel = builder.DefineLabel("if_end"); EmitExpression(i.Condition); // Handle condition based on what's actually on the stack @@ -130,28 +131,29 @@ protected override void EmitIf(Stmt.If i) EnsureBoxed(); EmitTruthyCheck(); } - IL.Emit(OpCodes.Brfalse, elseLabel); + builder.Emit_Brfalse(elseLabel); EmitStatement(i.ThenBranch); - IL.Emit(OpCodes.Br, endLabel); + builder.Emit_Br(endLabel); - IL.MarkLabel(elseLabel); + builder.MarkLabel(elseLabel); if (i.ElseBranch != null) { EmitStatement(i.ElseBranch); } - IL.MarkLabel(endLabel); + builder.MarkLabel(endLabel); } protected override void EmitWhile(Stmt.While w) { - var startLabel = IL.DefineLabel(); - var endLabel = IL.DefineLabel(); + var builder = _ctx.ILBuilder; + var startLabel = builder.DefineLabel("while_start"); + var endLabel = builder.DefineLabel("while_end"); _ctx.EnterLoop(endLabel, startLabel); - IL.MarkLabel(startLabel); + builder.MarkLabel(startLabel); EmitExpression(w.Condition); // Handle condition based on what's actually on the stack if (_stackType == StackType.Boolean) @@ -173,29 +175,30 @@ protected override void EmitWhile(Stmt.While w) EnsureBoxed(); EmitTruthyCheck(); } - IL.Emit(OpCodes.Brfalse, endLabel); + builder.Emit_Brfalse(endLabel); EmitStatement(w.Body); - IL.Emit(OpCodes.Br, startLabel); + builder.Emit_Br(startLabel); - IL.MarkLabel(endLabel); + builder.MarkLabel(endLabel); _ctx.ExitLoop(); } protected override void EmitDoWhile(Stmt.DoWhile dw) { - var startLabel = IL.DefineLabel(); - var endLabel = IL.DefineLabel(); - var continueLabel = IL.DefineLabel(); + var builder = _ctx.ILBuilder; + var startLabel = builder.DefineLabel("dowhile_start"); + var endLabel = builder.DefineLabel("dowhile_end"); + var continueLabel = builder.DefineLabel("dowhile_continue"); _ctx.EnterLoop(endLabel, continueLabel); // Body executes at least once - IL.MarkLabel(startLabel); + builder.MarkLabel(startLabel); EmitStatement(dw.Body); // Continue target is after the body, before condition check - IL.MarkLabel(continueLabel); + builder.MarkLabel(continueLabel); // Evaluate condition EmitExpression(dw.Condition); @@ -219,15 +222,16 @@ protected override void EmitDoWhile(Stmt.DoWhile dw) EnsureBoxed(); EmitTruthyCheck(); } - IL.Emit(OpCodes.Brtrue, startLabel); + builder.Emit_Brtrue(startLabel); - IL.MarkLabel(endLabel); + builder.MarkLabel(endLabel); _ctx.ExitLoop(); } protected override void EmitForOf(Stmt.ForOf f) { _ctx.Locals.EnterScope(); + var builder = _ctx.ILBuilder; // Evaluate iterable TypeInfo? iterableType = _ctx.TypeMap?.Get(f.Iterable); @@ -248,9 +252,9 @@ protected override void EmitForOf(Stmt.ForOf f) // For generators, use enumerator-based iteration (with its own labels) if (iterableType is TypeInfo.Generator) { - var genStartLabel = IL.DefineLabel(); - var genEndLabel = IL.DefineLabel(); - var genContinueLabel = IL.DefineLabel(); + var genStartLabel = builder.DefineLabel("forof_gen_start"); + var genEndLabel = builder.DefineLabel("forof_gen_end"); + var genContinueLabel = builder.DefineLabel("forof_gen_continue"); _ctx.EnterLoop(genEndLabel, genContinueLabel); EmitForOfEnumerator(f, genStartLabel, genEndLabel, genContinueLabel); return; @@ -262,8 +266,8 @@ protected override void EmitForOf(Stmt.ForOf f) // Try iterator protocol first: GetIteratorFunction(iterable, Symbol.iterator) var iteratorFnLocal = IL.DeclareLocal(_ctx.Types.Object); - var indexBasedLabel = IL.DefineLabel(); - var afterLoopLabel = IL.DefineLabel(); + var indexBasedLabel = builder.DefineLabel("forof_index_based"); + var afterLoopLabel = builder.DefineLabel("forof_after"); IL.Emit(OpCodes.Ldloc, iterableLocal); IL.Emit(OpCodes.Ldsfld, _ctx.Runtime!.SymbolIterator); @@ -272,13 +276,13 @@ protected override void EmitForOf(Stmt.ForOf f) // If iterator function is null, fall back to index-based iteration IL.Emit(OpCodes.Ldloc, iteratorFnLocal); - IL.Emit(OpCodes.Brfalse, indexBasedLabel); + builder.Emit_Brfalse(indexBasedLabel); // ===== Iterator protocol path ===== { - var iterStartLabel = IL.DefineLabel(); - var iterEndLabel = IL.DefineLabel(); - var iterContinueLabel = IL.DefineLabel(); + var iterStartLabel = builder.DefineLabel("forof_iter_start"); + var iterEndLabel = builder.DefineLabel("forof_iter_end"); + var iterContinueLabel = builder.DefineLabel("forof_iter_continue"); _ctx.EnterLoop(iterEndLabel, iterContinueLabel); // Call the iterator function to get the iterator object @@ -311,12 +315,12 @@ protected override void EmitForOf(Stmt.ForOf f) var moveNext = _ctx.Types.GetMethod(_ctx.Types.IEnumerator, "MoveNext"); var current = _ctx.Types.IEnumerator.GetProperty("Current")!.GetGetMethod()!; - IL.MarkLabel(iterStartLabel); + builder.MarkLabel(iterStartLabel); // Call MoveNext IL.Emit(OpCodes.Ldloc, enumLocal); IL.Emit(OpCodes.Callvirt, moveNext); - IL.Emit(OpCodes.Brfalse, iterEndLabel); + builder.Emit_Brfalse(iterEndLabel); // Get Current IL.Emit(OpCodes.Ldloc, enumLocal); @@ -326,20 +330,20 @@ protected override void EmitForOf(Stmt.ForOf f) // Emit body EmitStatement(f.Body); - IL.MarkLabel(iterContinueLabel); - IL.Emit(OpCodes.Br, iterStartLabel); + builder.MarkLabel(iterContinueLabel); + builder.Emit_Br(iterStartLabel); - IL.MarkLabel(iterEndLabel); + builder.MarkLabel(iterEndLabel); _ctx.ExitLoop(); - IL.Emit(OpCodes.Br, afterLoopLabel); // Skip the index-based path + builder.Emit_Br(afterLoopLabel); // Skip the index-based path } // ===== Index-based fallback (for arrays, strings, etc.) ===== - IL.MarkLabel(indexBasedLabel); + builder.MarkLabel(indexBasedLabel); { - var startLabel = IL.DefineLabel(); - var endLabel = IL.DefineLabel(); - var continueLabel = IL.DefineLabel(); + var startLabel = builder.DefineLabel("forof_idx_start"); + var endLabel = builder.DefineLabel("forof_idx_end"); + var continueLabel = builder.DefineLabel("forof_idx_continue"); _ctx.EnterLoop(endLabel, continueLabel); // Create index variable @@ -350,14 +354,14 @@ protected override void EmitForOf(Stmt.ForOf f) // Loop variable var indexLoopVar = _ctx.Locals.DeclareLocal(f.Variable.Lexeme, _ctx.Types.Object); - IL.MarkLabel(startLabel); + builder.MarkLabel(startLabel); // Check if index < length IL.Emit(OpCodes.Ldloc, indexLocal); IL.Emit(OpCodes.Ldloc, iterableLocal); IL.Emit(OpCodes.Call, _ctx.Runtime!.GetLength); IL.Emit(OpCodes.Clt); - IL.Emit(OpCodes.Brfalse, endLabel); + builder.Emit_Brfalse(endLabel); // Get current element IL.Emit(OpCodes.Ldloc, iterableLocal); @@ -368,7 +372,7 @@ protected override void EmitForOf(Stmt.ForOf f) // Emit body EmitStatement(f.Body); - IL.MarkLabel(continueLabel); + builder.MarkLabel(continueLabel); // Increment index IL.Emit(OpCodes.Ldloc, indexLocal); @@ -376,19 +380,21 @@ protected override void EmitForOf(Stmt.ForOf f) IL.Emit(OpCodes.Add); IL.Emit(OpCodes.Stloc, indexLocal); - IL.Emit(OpCodes.Br, startLabel); + builder.Emit_Br(startLabel); - IL.MarkLabel(endLabel); + builder.MarkLabel(endLabel); _ctx.ExitLoop(); } // Common exit point for both paths - IL.MarkLabel(afterLoopLabel); + builder.MarkLabel(afterLoopLabel); _ctx.Locals.ExitScope(); } private void EmitForOfEnumerator(Stmt.ForOf f, Label startLabel, Label endLabel, Label continueLabel) { + var builder = _ctx.ILBuilder; + // Use IEnumerable.GetEnumerator()/MoveNext()/Current pattern for generators var getEnumerator = _ctx.Types.GetMethod(_ctx.Types.IEnumerable, "GetEnumerator"); var moveNext = _ctx.Types.GetMethod(_ctx.Types.IEnumerator, "MoveNext"); @@ -404,12 +410,12 @@ private void EmitForOfEnumerator(Stmt.ForOf f, Label startLabel, Label endLabel, // Loop variable var loopVar = _ctx.Locals.DeclareLocal(f.Variable.Lexeme, _ctx.Types.Object); - IL.MarkLabel(startLabel); + builder.MarkLabel(startLabel); // Call MoveNext IL.Emit(OpCodes.Ldloc, enumLocal); IL.Emit(OpCodes.Callvirt, moveNext); - IL.Emit(OpCodes.Brfalse, endLabel); + builder.Emit_Brfalse(endLabel); // Get Current IL.Emit(OpCodes.Ldloc, enumLocal); @@ -419,19 +425,20 @@ private void EmitForOfEnumerator(Stmt.ForOf f, Label startLabel, Label endLabel, // Emit body EmitStatement(f.Body); - IL.MarkLabel(continueLabel); - IL.Emit(OpCodes.Br, startLabel); + builder.MarkLabel(continueLabel); + builder.Emit_Br(startLabel); - IL.MarkLabel(endLabel); + builder.MarkLabel(endLabel); _ctx.Locals.ExitScope(); _ctx.ExitLoop(); } protected override void EmitForIn(Stmt.ForIn f) { - var startLabel = IL.DefineLabel(); - var endLabel = IL.DefineLabel(); - var continueLabel = IL.DefineLabel(); + var builder = _ctx.ILBuilder; + var startLabel = builder.DefineLabel("forin_start"); + var endLabel = builder.DefineLabel("forin_end"); + var continueLabel = builder.DefineLabel("forin_continue"); _ctx.EnterLoop(endLabel, continueLabel); _ctx.Locals.EnterScope(); @@ -450,14 +457,14 @@ protected override void EmitForIn(Stmt.ForIn f) // Loop variable (holds current key) var loopVar = _ctx.Locals.DeclareLocal(f.Variable.Lexeme, _ctx.Types.Object); - IL.MarkLabel(startLabel); + builder.MarkLabel(startLabel); // Check if index < keys.Count IL.Emit(OpCodes.Ldloc, indexLocal); IL.Emit(OpCodes.Ldloc, keysLocal); IL.Emit(OpCodes.Call, _ctx.Runtime!.GetLength); IL.Emit(OpCodes.Clt); - IL.Emit(OpCodes.Brfalse, endLabel); + builder.Emit_Brfalse(endLabel); // Get current key: keys[index] IL.Emit(OpCodes.Ldloc, keysLocal); @@ -468,7 +475,7 @@ protected override void EmitForIn(Stmt.ForIn f) // Emit body EmitStatement(f.Body); - IL.MarkLabel(continueLabel); + builder.MarkLabel(continueLabel); // Increment index IL.Emit(OpCodes.Ldloc, indexLocal); @@ -476,9 +483,9 @@ protected override void EmitForIn(Stmt.ForIn f) IL.Emit(OpCodes.Add); IL.Emit(OpCodes.Stloc, indexLocal); - IL.Emit(OpCodes.Br, startLabel); + builder.Emit_Br(startLabel); - IL.MarkLabel(endLabel); + builder.MarkLabel(endLabel); _ctx.Locals.ExitScope(); _ctx.ExitLoop(); } @@ -508,13 +515,15 @@ protected override void EmitReturn(Stmt.Return r) if (_ctx.ExceptionBlockDepth > 0) { // Inside exception block: store value and leave + // Use builder for Leave validation (ensures we're inside exception block) + var builder = _ctx.ILBuilder; if (_ctx.ReturnValueLocal == null) { _ctx.ReturnValueLocal = IL.DeclareLocal(_ctx.Types.Object); - _ctx.ReturnLabel = IL.DefineLabel(); + _ctx.ReturnLabel = builder.DefineLabel("deferred_return"); } IL.Emit(OpCodes.Stloc, _ctx.ReturnValueLocal); - IL.Emit(OpCodes.Leave, _ctx.ReturnLabel); + builder.Emit_Leave(_ctx.ReturnLabel); } else { @@ -545,15 +554,16 @@ protected override void EmitContinue(Stmt.Continue c) protected override void EmitLabeledStatement(Stmt.LabeledStatement labeledStmt) { string labelName = labeledStmt.Label.Lexeme; - var breakLabel = IL.DefineLabel(); - var continueLabel = IL.DefineLabel(); + var builder = _ctx.ILBuilder; + var breakLabel = builder.DefineLabel($"labeled_{labelName}_break"); + var continueLabel = builder.DefineLabel($"labeled_{labelName}_continue"); // For labeled statements, we need to handle both loops and non-loop statements. // For loops, the inner loop will use its own labels for unlabeled break/continue, // but labeled break/continue should use the labels registered here. // Mark continue label at the start (for labeled continue, restart from here) - IL.MarkLabel(continueLabel); + builder.MarkLabel(continueLabel); _ctx.EnterLoop(breakLabel, continueLabel, labelName); try @@ -568,7 +578,7 @@ protected override void EmitLabeledStatement(Stmt.LabeledStatement labeledStmt) } // Mark the break label (after the statement, for labeled break) - IL.MarkLabel(breakLabel); + builder.MarkLabel(breakLabel); } protected override void EmitSwitch(Stmt.Switch s) @@ -577,9 +587,10 @@ protected override void EmitSwitch(Stmt.Switch s) var switchAnalysis = _ctx.DeadCode?.GetSwitchResult(s); bool skipDefault = switchAnalysis?.DefaultIsUnreachable == true; - var endLabel = IL.DefineLabel(); - var defaultLabel = IL.DefineLabel(); - var caseLabels = s.Cases.Select(_ => IL.DefineLabel()).ToList(); + var builder = _ctx.ILBuilder; + var endLabel = builder.DefineLabel("switch_end"); + var defaultLabel = builder.DefineLabel("switch_default"); + var caseLabels = s.Cases.Select((_, i) => builder.DefineLabel($"switch_case_{i}")).ToList(); // Evaluate subject once EmitExpression(s.Subject); @@ -594,23 +605,23 @@ protected override void EmitSwitch(Stmt.Switch s) EmitExpression(s.Cases[i].Value); EmitBoxIfNeeded(s.Cases[i].Value); IL.Emit(OpCodes.Call, _ctx.Runtime!.Equals); - IL.Emit(OpCodes.Brtrue, caseLabels[i]); + builder.Emit_Brtrue(caseLabels[i]); } // Jump to default or end (skip default if unreachable) if (skipDefault || s.DefaultBody == null) { - IL.Emit(OpCodes.Br, endLabel); + builder.Emit_Br(endLabel); } else { - IL.Emit(OpCodes.Br, defaultLabel); + builder.Emit_Br(defaultLabel); } // Emit case bodies for (int i = 0; i < s.Cases.Count; i++) { - IL.MarkLabel(caseLabels[i]); + builder.MarkLabel(caseLabels[i]); foreach (var stmt in s.Cases[i].Body) { if (stmt is Stmt.Break breakStmt) @@ -623,7 +634,7 @@ protected override void EmitSwitch(Stmt.Switch s) else { // Unlabeled break - exits switch only - IL.Emit(OpCodes.Br, endLabel); + builder.Emit_Br(endLabel); } } else @@ -637,7 +648,7 @@ protected override void EmitSwitch(Stmt.Switch s) // Default case (skip if unreachable) if (s.DefaultBody != null && !skipDefault) { - IL.MarkLabel(defaultLabel); + builder.MarkLabel(defaultLabel); foreach (var stmt in s.DefaultBody) { if (stmt is Stmt.Break breakStmt) @@ -650,7 +661,7 @@ protected override void EmitSwitch(Stmt.Switch s) else { // Unlabeled break - exits switch only - IL.Emit(OpCodes.Br, endLabel); + builder.Emit_Br(endLabel); } } else @@ -660,14 +671,17 @@ protected override void EmitSwitch(Stmt.Switch s) } } - IL.MarkLabel(endLabel); + builder.MarkLabel(endLabel); } protected override void EmitTryCatch(Stmt.TryCatch t) { - _ctx.ExceptionBlockDepth++; + // Use ValidatedILBuilder for exception block operations - it tracks depth automatically + // and validates proper Begin/End pairing + var builder = _ctx.ILBuilder; - IL.BeginExceptionBlock(); + _ctx.ExceptionBlockDepth++; + builder.BeginExceptionBlock(); foreach (var stmt in t.TryBlock) { @@ -676,7 +690,7 @@ protected override void EmitTryCatch(Stmt.TryCatch t) if (t.CatchBlock != null) { - IL.BeginCatchBlock(_ctx.Types.Exception); + builder.BeginCatchBlock(_ctx.Types.Exception); if (t.CatchParam != null) { @@ -698,15 +712,14 @@ protected override void EmitTryCatch(Stmt.TryCatch t) if (t.FinallyBlock != null) { - IL.BeginFinallyBlock(); + builder.BeginFinallyBlock(); foreach (var stmt in t.FinallyBlock) { EmitStatement(stmt); } } - IL.EndExceptionBlock(); - + builder.EndExceptionBlock(); _ctx.ExceptionBlockDepth--; } diff --git a/Compilation/ILEmitter.cs b/Compilation/ILEmitter.cs index 5b8e1df..7e46358 100644 --- a/Compilation/ILEmitter.cs +++ b/Compilation/ILEmitter.cs @@ -57,7 +57,7 @@ private StackType _stackType } public ILEmitter(CompilationContext ctx) - : base(new StateMachineEmitHelpers(ctx.IL, ctx.Types)) + : base(new StateMachineEmitHelpers(ctx.IL, ctx.Types, ctx.ILBuilder)) { _ctx = ctx; _resolver = new LocalVariableResolver(ctx.IL, ctx, ctx.Types); @@ -86,11 +86,12 @@ protected override bool IsDead(Stmt stmt) protected override void EmitBranchToLabel(Label target) { - // Use Leave instead of Br when inside exception blocks + // Use builder for branch validation - it enforces Leave vs Br rules + var builder = _ctx.ILBuilder; if (_ctx.ExceptionBlockDepth > 0) - IL.Emit(OpCodes.Leave, target); + builder.Emit_Leave(target); else - IL.Emit(OpCodes.Br, target); + builder.Emit_Br(target); } protected override LocalBuilder? DeclareLoopVariable(string name) diff --git a/Compilation/ILValidationException.cs b/Compilation/ILValidationException.cs new file mode 100644 index 0000000..3e2fa1f --- /dev/null +++ b/Compilation/ILValidationException.cs @@ -0,0 +1,42 @@ +namespace SharpTS.Compilation; + +/// +/// Exception thrown when IL validation fails during emit. +/// +/// +/// This exception is thrown by when it detects +/// an invalid IL emission pattern at compile time. The error is caught early, +/// before the generated assembly would fail PEVerify or crash at runtime. +/// +/// Common causes: +/// +/// A label was defined but never marked (forgotten MarkLabel call) +/// Stack depth mismatch at a branch target (inconsistent code paths) +/// Using Br inside an exception block (should use Leave) +/// Using Leave outside an exception block +/// Attempting to Box a reference type +/// Attempting to Unbox a value type +/// Insufficient values on the stack for an operation +/// +/// +public class ILValidationException : Exception +{ + /// + /// Creates a new IL validation exception. + /// + /// Description of the validation failure. + public ILValidationException(string message) + : base($"IL Validation Error: {message}") + { + } + + /// + /// Creates a new IL validation exception with an inner exception. + /// + /// Description of the validation failure. + /// The underlying exception. + public ILValidationException(string message, Exception innerException) + : base($"IL Validation Error: {message}", innerException) + { + } +} diff --git a/Compilation/StateMachineEmitHelpers.cs b/Compilation/StateMachineEmitHelpers.cs index a30c8eb..e8be171 100644 --- a/Compilation/StateMachineEmitHelpers.cs +++ b/Compilation/StateMachineEmitHelpers.cs @@ -11,6 +11,7 @@ public class StateMachineEmitHelpers { private readonly ILGenerator _il; private readonly TypeProvider _types; + private readonly ValidatedILBuilder? _builder; private StackType _stackType = StackType.Unknown; /// @@ -32,12 +33,52 @@ public StackType StackType /// public TypeProvider Types => _types; + /// + /// The validated IL builder, if available. Use this for label and branch operations + /// to get compile-time validation of IL correctness. + /// + public ValidatedILBuilder? Builder => _builder; + public StateMachineEmitHelpers(ILGenerator il, TypeProvider types) { _il = il; _types = types; + _builder = null; + } + + public StateMachineEmitHelpers(ILGenerator il, TypeProvider types, ValidatedILBuilder builder) + { + _il = il; + _types = types; + _builder = builder; + } + + #region Label Operations + + /// + /// Defines a new label. Uses validated builder if available. + /// + /// Optional debug name for error messages. + public Label DefineLabel(string? debugName = null) + { + return _builder != null + ? _builder.DefineLabel(debugName) + : _il.DefineLabel(); } + /// + /// Marks a label at the current position. Uses validated builder if available. + /// + public void MarkLabel(Label label) + { + if (_builder != null) + _builder.MarkLabel(label); + else + _il.MarkLabel(label); + } + + #endregion + #region Boxing and Type Conversion /// @@ -438,7 +479,7 @@ public void EmitTruthyCheck(MethodInfo isTruthyMethod) /// Runtime.IsTruthy method public void EmitLogical(bool isAnd, Action emitLeft, Action emitRight, MethodInfo isTruthyMethod) { - var endLabel = _il.DefineLabel(); + var endLabel = DefineLabel("logical_end"); emitLeft(); EnsureBoxed(); @@ -462,7 +503,7 @@ public void EmitLogical(bool isAnd, Action emitLeft, Action emitRight, MethodInf EnsureBoxed(); } - _il.MarkLabel(endLabel); + MarkLabel(endLabel); SetStackUnknown(); } @@ -471,8 +512,8 @@ public void EmitLogical(bool isAnd, Action emitLeft, Action emitRight, MethodInf /// public void EmitTernary(Action emitCondition, Action emitThen, Action emitElse, MethodInfo isTruthyMethod) { - var elseLabel = _il.DefineLabel(); - var endLabel = _il.DefineLabel(); + var elseLabel = DefineLabel("ternary_else"); + var endLabel = DefineLabel("ternary_end"); emitCondition(); EnsureBoxed(); @@ -483,11 +524,11 @@ public void EmitTernary(Action emitCondition, Action emitThen, Action emitElse, EnsureBoxed(); _il.Emit(OpCodes.Br, endLabel); - _il.MarkLabel(elseLabel); + MarkLabel(elseLabel); emitElse(); EnsureBoxed(); - _il.MarkLabel(endLabel); + MarkLabel(endLabel); SetStackUnknown(); } @@ -496,8 +537,8 @@ public void EmitTernary(Action emitCondition, Action emitThen, Action emitElse, /// public void EmitNullishCoalescing(Action emitLeft, Action emitRight) { - var rightLabel = _il.DefineLabel(); - var endLabel = _il.DefineLabel(); + var rightLabel = DefineLabel("nullish_right"); + var endLabel = DefineLabel("nullish_end"); emitLeft(); EnsureBoxed(); @@ -505,12 +546,12 @@ public void EmitNullishCoalescing(Action emitLeft, Action emitRight) _il.Emit(OpCodes.Brfalse, rightLabel); _il.Emit(OpCodes.Br, endLabel); - _il.MarkLabel(rightLabel); + MarkLabel(rightLabel); _il.Emit(OpCodes.Pop); emitRight(); EnsureBoxed(); - _il.MarkLabel(endLabel); + MarkLabel(endLabel); SetStackUnknown(); } diff --git a/Compilation/ValidatedILBuilder.cs b/Compilation/ValidatedILBuilder.cs new file mode 100644 index 0000000..dd52427 --- /dev/null +++ b/Compilation/ValidatedILBuilder.cs @@ -0,0 +1,1345 @@ +using System.Reflection; +using System.Reflection.Emit; + +namespace SharpTS.Compilation; + +/// +/// Type of value on the IL evaluation stack. +/// +public enum StackEntryType +{ + /// Unknown or untracked type. + Unknown, + /// 32-bit integer (int32). + Int32, + /// 64-bit integer (int64). + Int64, + /// 64-bit floating point (float64). + Double, + /// 32-bit floating point (float32). + Float, + /// Boolean (represented as int32 0/1). + Boolean, + /// String reference. + String, + /// Object reference (any reference type). + Reference, + /// Null reference. + Null, + /// Value type (struct). + ValueType, + /// Native integer (IntPtr). + NativeInt +} + +/// +/// Represents a single entry on the IL evaluation stack. +/// +/// The category of the stack entry. +/// Optional CLR type for more precise tracking. +public readonly record struct StackEntry(StackEntryType Type, Type? ClrType = null) +{ + /// + /// Returns true if this entry is a value type that would need boxing. + /// + public bool IsValueType => Type is StackEntryType.Int32 or StackEntryType.Int64 + or StackEntryType.Double or StackEntryType.Float or StackEntryType.Boolean + or StackEntryType.ValueType or StackEntryType.NativeInt; +} + +/// +/// Snapshot of the stack state at a particular point, used for branch validation. +/// +/// Stack depth at this point. +/// Types on the stack (top of stack is last element). +public readonly record struct StackSnapshot(int Depth, StackEntry[] Types); + +/// +/// Information about a defined label. +/// +/// Optional debug name for error messages. +/// Exception block depth when label was defined. +public record LabelInfo(string? DebugName, int DefinedInExceptionDepth); + +/// +/// Information about an active exception block. +/// +public class ExceptionBlockInfo +{ + /// Current phase of the exception block. + public ExceptionBlockPhase Phase { get; set; } + + /// Stack depth when the block was entered. + public int EntryStackDepth { get; init; } + + /// The label returned by BeginExceptionBlock. + public Label EndLabel { get; init; } + + public ExceptionBlockInfo(ExceptionBlockPhase phase, int entryStackDepth, Label endLabel) + { + Phase = phase; + EntryStackDepth = entryStackDepth; + EndLabel = endLabel; + } +} + +/// +/// Phase of an exception block. +/// +public enum ExceptionBlockPhase { Try, Catch, Finally } + +/// +/// Validation mode for the IL builder. +/// +public enum ValidationMode +{ + /// Throw immediately on validation error. + FailFast, + /// Collect all errors and report at method end. + CollectAll +} + +/// +/// IL emission wrapper that validates label usage, stack balance, and exception blocks +/// at emit time to catch errors before runtime PEVerify failures. +/// +/// +/// This class wraps and intercepts critical operations to add +/// compile-time validation. It does not modify the emitted IL; it only checks that +/// the emission sequence is valid. +/// +/// Validated operations: +/// +/// Labels: All defined labels must be marked before method ends +/// Stack: Branch targets must have consistent stack depth +/// Exception blocks: Br not allowed inside try/catch; use Leave instead +/// Boxing: Box requires value type on stack +/// +/// +public sealed class ValidatedILBuilder +{ + private readonly ILGenerator _il; + private readonly TypeProvider _types; + private readonly ValidationMode _mode; + private readonly List _collectedErrors = []; + + // Label tracking + private readonly Dictionary _labels = []; + private readonly HashSet