Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Compilation/CompilationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ public class CompilationContext
public TypeMapper TypeMapper { get; }
public LocalsManager Locals { get; }

/// <summary>
/// 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.
/// </summary>
public ValidatedILBuilder ILBuilder { get; private set; }

/// <summary>
/// Type provider for resolving .NET types (runtime or reference assembly mode).
/// Use this instead of typeof() for type resolution to support --ref-asm compilation.
Expand Down Expand Up @@ -416,6 +422,16 @@ public CompilationContext(
Classes = classes;
Types = types ?? TypeProvider.Runtime;
Locals = new LocalsManager(il);
ILBuilder = new ValidatedILBuilder(il, Types);
}

/// <summary>
/// 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.
/// </summary>
public void UpdateILBuilder(ILGenerator newIL)
{
ILBuilder = new ValidatedILBuilder(newIL, Types);
}

public void DefineParameter(string name, int argIndex, Type? paramType = null)
Expand Down
21 changes: 11 additions & 10 deletions Compilation/ILCompiler.Classes.Methods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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);
}
Expand Down
21 changes: 11 additions & 10 deletions Compilation/ILCompiler.Classes.Static.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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);
}
Expand Down
19 changes: 10 additions & 9 deletions Compilation/ILEmitter.Calls.cs
Original file line number Diff line number Diff line change
Expand Up @@ -731,19 +731,20 @@ private void EmitAmbiguousMethodCall(Expr obj, string methodName, List<Expr> 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);

Expand Down Expand Up @@ -811,10 +812,10 @@ private void EmitAmbiguousMethodCall(Expr obj, string methodName, List<Expr> 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);

Expand Down Expand Up @@ -877,7 +878,7 @@ private void EmitAmbiguousMethodCall(Expr obj, string methodName, List<Expr> arg
break;
}

IL.MarkLabel(doneLabel);
builder.MarkLabel(doneLabel);
}

/// <summary>
Expand Down
20 changes: 11 additions & 9 deletions Compilation/ILEmitter.Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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();
}
Expand Down
31 changes: 17 additions & 14 deletions Compilation/ILEmitter.Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -33,6 +34,7 @@ public void FinalizeReturns()
public void EmitDefaultParameters(List<Stmt.Parameter> parameters, bool isInstanceMethod)
{
int argOffset = isInstanceMethod ? 1 : 0;
var builder = _ctx.ILBuilder;

for (int i = 0; i < parameters.Count; i++)
{
Expand All @@ -42,18 +44,18 @@ public void EmitDefaultParameters(List<Stmt.Parameter> parameters, bool isInstan
int argIndex = i + argOffset;

// if (arg == null) { arg = <default>; }
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);
}
}

Expand Down Expand Up @@ -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) =>
Expand Down
9 changes: 5 additions & 4 deletions Compilation/ILEmitter.Operators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
}

Expand Down
13 changes: 7 additions & 6 deletions Compilation/ILEmitter.Properties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
Loading