Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
bfd263a
Fix console restoration verification for synchronized writers (#1930)
Oct 4, 2026
6beda53
Preserve compiled computed tagged-template receivers (#1931)
Oct 4, 2026
c965854
Honor boxed Number exotic hooks before string unwrapping (#1932)
Oct 4, 2026
b9c80a5
Share exotic numeric coercion for explicit Number conversion (#1933)
Oct 4, 2026
b73d101
Strip hexadecimal prefixes with explicit parseInt radix 16 (#1934)
Oct 4, 2026
c849177
Resolve globalThis.Math to the shared singleton (#1935)
Oct 4, 2026
2b31a02
Materialize numeric arrays before precise summation (#1936)
Oct 4, 2026
cecbc04
Preserve negative zero in Math.sign function values (#1937)
Oct 4, 2026
27c4bed
Apply signed-zero tie rules to Math min/max adapters (#1938)
Oct 4, 2026
284fb4d
Emit bigint literals consistently in state-machine bodies (#1940)
Oct 4, 2026
ba26177
Resolve BigInt static methods across async state transitions (#1939)
Oct 4, 2026
2e34d1c
Keep primitive string length non-enumerable (#1954)
Oct 4, 2026
229e1e5
Reject inherited non-writable static field shadows (#1958)
Oct 4, 2026
83f1a98
Preserve static values in partial descriptor definitions (#1959)
Oct 4, 2026
96bd8bb
Verify the existing ARM64 numeric capture repair (#1956)
Oct 4, 2026
b373485
Propagate strictness through property read-modify-write setters (#1960)
Oct 4, 2026
0a51e77
Align legacy standalone assertions with repaired runtime behavior
Oct 5, 2026
cde8437
Respect nearer writable static fields during inherited writes
Oct 5, 2026
a7237a9
Shard historical deployment probes outside the core CI budget
Oct 5, 2026
18384cf
Verify console writer restoration in fresh initialized test processes
Oct 5, 2026
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
34 changes: 34 additions & 0 deletions docs/plans/archive/issue-1956-resolution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Issue #1956: escaped numeric closure verification

Verified on Windows ARM64, .NET SDK 10.0.401/runtime 10.0.12, Release, on
2026-10-04. The existing `EscapedNumericClosureKeepsLiveUpdates` source and its
`13 14\ntrue Infinity\n` expectation remain enabled and unchanged.

An independently restored/built archive of reported baseline
`78931b4add5a6937ab6e63452f2e0ad97fe12adb` reproduces the test-host access violation
in `CastHelpers.IsInstanceOfClass`, called from `$Program.create(Double)`.
Compiling that same source with the baseline CLI and `--standalone --verify`
reports:

```text
[IL Error] $Program.create: StackObjRef {Offset=98, Found=Double} - Expected an ObjRef on the stack.
```

The emitted method loads the captured `Double current` field at IL offset
`0x005C`, then applies `isinst $LexicalUninitialized` at `0x0062` without boxing.
The verifier therefore identifies an invalid object-reference operation,
independently of the execution exception.

An independently restored/built archive of current main
`987a8b58d5fe32ddac4d8edd25384c923665e301` passes both original API modes, and its
standalone assembly passes IL verification. Its generated method omits that
sentinel check on the promoted numeric slot. The responsible existing repair is
`1331f8f06` (PR #1969), in `ILEmitter.EmitLexicalAssignmentTdzGuard`: value-type
capture fields cannot hold the object-valued TDZ sentinel, so the guard returns
before loading and inspecting them.

This consolidation adds deployment coverage for the original source and live
update/TDZ controls, including both declaration paths, verified standalone
execution and actual hosted initialization. It credits the existing product
repair; it does not claim a new compiler repair or infer behavior on other
architectures from the local exception.
7 changes: 7 additions & 0 deletions src/SharpTS/Compilation/Emitters/GlobalThisStaticEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ public bool TryEmitStaticPropertyGet(IEmitterContext emitter, string propertyNam
if (TryEmitBuiltInClassType(il, ctx, propertyName))
return true;

if (propertyName == "Math")
{
il.Emit(OpCodes.Ldstr, propertyName);
il.Emit(OpCodes.Call, ctx.Runtime!.GlobalObject.GetProperty);
return true;
}

// Check if this is a known built-in that has its own static emitter
var staticEmitter = _registry.GetStaticStrategy(propertyName);
if (staticEmitter != null)
Expand Down
37 changes: 25 additions & 12 deletions src/SharpTS/Compilation/ExpressionEmitterBase.Operators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ namespace SharpTS.Compilation;
/// </summary>
public abstract partial class ExpressionEmitterBase
{
// PutValue uses the strictness of the expression's enclosing code, including
// state-machine bodies. Call only after the read and any short-circuit check.
protected void EmitPropertyWrite(bool indexed)
{
if (Ctx.IsStrictMode)
{
IL.Emit(OpCodes.Ldc_I4_1);
IL.Emit(OpCodes.Call, indexed ? Ctx.Runtime!.ObjectWrite.IndexStrict : Ctx.Runtime!.ObjectWrite.PropertyStrict);
}
else
IL.Emit(OpCodes.Call, indexed ? Ctx.Runtime!.ObjectWrite.Index : Ctx.Runtime!.ObjectWrite.Property);
}

/// <summary>
/// Emits the logical condition check for &&=, ||=, ??= operators.
/// After this, the stack has the current value and control flows to either
Expand Down Expand Up @@ -253,7 +266,7 @@ protected virtual void EmitPrefixIncrement(Expr.PrefixIncrement pi)
EmitMemberAccessIncrement(
isPrefix: true, pi.Operator.Type, objLocal,
emitKey: () => IL.Emit(OpCodes.Ldstr, get.Name.Lexeme),
Ctx.Runtime!.ObjectRead.Property, Ctx.Runtime!.ObjectWrite.Property);
Ctx.Runtime!.ObjectRead.Property, indexed: false);
return;
}

Expand All @@ -265,7 +278,7 @@ protected virtual void EmitPrefixIncrement(Expr.PrefixIncrement pi)
EmitMemberAccessIncrement(
isPrefix: true, pi.Operator.Type, objLocal,
emitKey: () => IL.Emit(OpCodes.Ldloc, indexLocal),
Ctx.Runtime!.ObjectRead.Index, Ctx.Runtime!.ObjectWrite.Index);
Ctx.Runtime!.ObjectRead.Index, indexed: true);
return;
}

Expand Down Expand Up @@ -314,7 +327,7 @@ protected virtual void EmitPostfixIncrement(Expr.PostfixIncrement poi)
EmitMemberAccessIncrement(
isPrefix: false, poi.Operator.Type, objLocal,
emitKey: () => IL.Emit(OpCodes.Ldstr, get.Name.Lexeme),
Ctx.Runtime!.ObjectRead.Property, Ctx.Runtime!.ObjectWrite.Property);
Ctx.Runtime!.ObjectRead.Property, indexed: false);
return;
}

Expand All @@ -326,7 +339,7 @@ protected virtual void EmitPostfixIncrement(Expr.PostfixIncrement poi)
EmitMemberAccessIncrement(
isPrefix: false, poi.Operator.Type, objLocal,
emitKey: () => IL.Emit(OpCodes.Ldloc, indexLocal),
Ctx.Runtime!.ObjectRead.Index, Ctx.Runtime!.ObjectWrite.Index);
Ctx.Runtime!.ObjectRead.Index, indexed: true);
return;
}

Expand Down Expand Up @@ -400,7 +413,7 @@ private bool TryEmitNativeNumericLocalIncrement(
/// </summary>
private void EmitMemberAccessIncrement(
bool isPrefix, TokenType op, LocalBuilder objLocal, Action emitKey,
MethodBuilder getMethod, MethodBuilder setMethod)
MethodBuilder getMethod, bool indexed)
{
double delta = op == TokenType.PLUS_PLUS ? 1.0 : -1.0;

Expand All @@ -418,7 +431,7 @@ private void EmitMemberAccessIncrement(
IL.Emit(OpCodes.Ldloc, objLocal);
emitKey();
IL.Emit(OpCodes.Ldloc, newValue);
IL.Emit(OpCodes.Call, setMethod);
EmitPropertyWrite(indexed);

IL.Emit(OpCodes.Ldloc, resultValue);
SetStackUnknown();
Expand Down Expand Up @@ -503,7 +516,7 @@ private bool TryEmitStaticFieldIncrement(Expr.Get get, bool isPrefix, TokenType
IL.Emit(OpCodes.Call, Types.TypeGetTypeFromHandle);
IL.Emit(OpCodes.Ldstr, get.Name.Lexeme);
IL.Emit(OpCodes.Ldloc, newValue);
IL.Emit(OpCodes.Call, Ctx.Runtime!.ObjectWrite.Property);
EmitPropertyWrite(indexed: false);

IL.Emit(OpCodes.Ldloc, resultValue);
SetStackUnknown();
Expand Down Expand Up @@ -543,7 +556,7 @@ protected virtual void EmitLogicalSet(Expr.LogicalSet ls)
IL.Emit(OpCodes.Ldloc, objLocal);
IL.Emit(OpCodes.Ldstr, ls.Name.Lexeme);
IL.Emit(OpCodes.Ldloc, resultLocal);
IL.Emit(OpCodes.Call, Ctx.Runtime!.ObjectWrite.Property);
EmitPropertyWrite(indexed: false);
IL.Emit(OpCodes.Ldloc, resultLocal);
IL.Emit(OpCodes.Br, endLabel);

Expand Down Expand Up @@ -583,7 +596,7 @@ protected virtual void EmitLogicalSetIndex(Expr.LogicalSetIndex lsi)
IL.Emit(OpCodes.Ldloc, objLocal);
IL.Emit(OpCodes.Ldloc, indexLocal);
IL.Emit(OpCodes.Ldloc, resultLocal);
IL.Emit(OpCodes.Call, Ctx.Runtime!.ObjectWrite.Index);
EmitPropertyWrite(indexed: true);
IL.Emit(OpCodes.Ldloc, resultLocal);
IL.Emit(OpCodes.Br, endLabel);

Expand Down Expand Up @@ -641,7 +654,7 @@ protected virtual void EmitCompoundSet(Expr.CompoundSet cs)
IL.Emit(OpCodes.Call, Types.TypeGetTypeFromHandle);
IL.Emit(OpCodes.Ldstr, cs.Name.Lexeme);
IL.Emit(OpCodes.Ldloc, inheritedResultTemp);
IL.Emit(OpCodes.Call, Ctx.Runtime!.ObjectWrite.Property);
EmitPropertyWrite(indexed: false);

IL.Emit(OpCodes.Ldloc, inheritedResultTemp);
SetStackUnknown();
Expand Down Expand Up @@ -677,7 +690,7 @@ protected virtual void EmitCompoundSet(Expr.CompoundSet cs)
IL.Emit(OpCodes.Ldloc, objTemp);
IL.Emit(OpCodes.Ldstr, cs.Name.Lexeme);
IL.Emit(OpCodes.Ldloc, resultLocal);
IL.Emit(OpCodes.Call, Ctx.Runtime!.ObjectWrite.Property);
EmitPropertyWrite(indexed: false);

IL.Emit(OpCodes.Ldloc, resultLocal);
SetStackUnknown();
Expand Down Expand Up @@ -717,7 +730,7 @@ protected virtual void EmitCompoundSetIndex(Expr.CompoundSetIndex csi)
IL.Emit(OpCodes.Ldloc, objTemp);
IL.Emit(OpCodes.Ldloc, indexTemp);
IL.Emit(OpCodes.Ldloc, resultLocal);
IL.Emit(OpCodes.Call, Ctx.Runtime!.ObjectWrite.Index);
EmitPropertyWrite(indexed: true);

IL.Emit(OpCodes.Ldloc, resultLocal);
SetStackUnknown();
Expand Down
27 changes: 25 additions & 2 deletions src/SharpTS/Compilation/ExpressionEmitterBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -369,8 +369,7 @@ public virtual void EmitExpression(Expr expr)

/// <summary>
/// Emits a literal value. Default uses helper methods (EmitNullConstant, EmitDoubleConstant, etc.).
/// ILEmitter overrides for BigInteger/SharpTSUndefined support.
/// AsyncArrowMoveNextEmitter overrides for eager boxing.
/// BigInteger literals are boxed consistently in synchronous and state-machine bodies.
/// </summary>
protected virtual void EmitLiteral(Expr.Literal lit)
{
Expand All @@ -380,6 +379,7 @@ protected virtual void EmitLiteral(Expr.Literal lit)
case double d: EmitDoubleConstant(d); break;
case bool b: EmitBoolConstant(b); break;
case string s: EmitStringConstant(s); break;
case System.Numerics.BigInteger integer: EmitBigIntConstant(integer); break;
// The `undefined` keyword parses to a Literal holding the $Undefined sentinel
// (Parser.Expressions.cs), as do array holes. State-machine emitters use this base
// method, so without an explicit arm `undefined` would fall to `default` and emit
Expand All @@ -391,6 +391,22 @@ protected virtual void EmitLiteral(Expr.Literal lit)
}
}

protected void EmitBigIntConstant(System.Numerics.BigInteger value)
{
if (value >= long.MinValue && value <= long.MaxValue)
{
IL.Emit(OpCodes.Ldc_I8, (long)value);
IL.Emit(OpCodes.Newobj, Types.GetConstructor(Types.BigInteger, Types.Int64));
}
else
{
IL.Emit(OpCodes.Ldstr, value.ToString(System.Globalization.CultureInfo.InvariantCulture));
IL.Emit(OpCodes.Call, Types.GetMethod(Types.BigInteger, "Parse", Types.String));
}
IL.Emit(OpCodes.Box, Types.BigInteger);
SetStackUnknown();
}

/// <summary>
/// Emits an expression boxed and spills it into a fresh object local.
/// State-machine emitters (the only users of these base implementations) can suspend
Expand Down Expand Up @@ -2284,6 +2300,7 @@ protected bool TryEmitBuiltInClassType(string name)
// bare Symbol in its own pseudo-variable arm; this entry covers the
// state-machine emitters that resolve through this base path.
"Symbol" => Ctx.Runtime!.Symbols.Type,
"BigInt" => Types.BigInteger,
// Omitted Web stream constructors fall through to ThrowUndefinedVariable.
"ReadableStream" => Ctx.Runtime!.WebStreams?.ReadableType,
"WritableStream" => Ctx.Runtime!.WebStreams?.WritableType,
Expand Down Expand Up @@ -2623,6 +2640,12 @@ protected virtual void EmitLogical(Expr.Logical l)
/// </summary>
protected virtual void EmitUnary(Expr.Unary u)
{
if (u.Operator.Type == TokenType.MINUS && u.Right is Expr.Literal { Value: System.Numerics.BigInteger value })
{
EmitBigIntConstant(-value);
return;
}

switch (u.Operator.Type)
{
case TokenType.MINUS:
Expand Down
35 changes: 16 additions & 19 deletions src/SharpTS/Compilation/ILEmitter.Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,7 @@ protected override void EmitLiteral(Expr.Literal lit)
EmitBoolConstant(b);
break;
case System.Numerics.BigInteger bi:
if (bi >= long.MinValue && bi <= long.MaxValue)
{
// Optimization: Use BigInteger(long) constructor for small values
IL.Emit(OpCodes.Ldc_I8, (long)bi);
IL.Emit(OpCodes.Newobj, _ctx.Types.GetConstructor(_ctx.Types.BigInteger, _ctx.Types.Int64));
}
else
{
// Fallback: Parse from string for large values
IL.Emit(OpCodes.Ldstr, bi.ToString());
IL.Emit(OpCodes.Call, _ctx.Types.GetMethod(_ctx.Types.BigInteger, "Parse", _ctx.Types.String));
}
IL.Emit(OpCodes.Box, _ctx.Types.BigInteger);
SetStackUnknown();
EmitBigIntConstant(bi);
break;
case Runtime.Types.SharpTSUndefined:
EmitUndefinedConstant();
Expand Down Expand Up @@ -1001,22 +988,32 @@ protected override void EmitTaggedTemplateLiteral(Expr.TaggedTemplateLiteral ttl
}

// Detect property access tag (obj.method`...`) for this binding
bool hasThisBinding = ttl.Tag is Expr.Get;
bool hasThisBinding = ttl.Tag is Expr.Get or Expr.GetIndex;
LocalBuilder? receiverLocal = null;

// 1. Emit the tag function reference (and receiver for property access tags)
if (hasThisBinding)
{
var g = (Expr.Get)ttl.Tag;
var receiver = ttl.Tag is Expr.Get g ? g.Object : ((Expr.GetIndex)ttl.Tag).Object;
// Emit and save the receiver object
EmitExpression(g.Object);
EmitExpression(receiver);
EnsureBoxed();
receiverLocal = _ctx.ILBuilder.DeclareLocal(_ctx.Types.Object);
IL.Emit(OpCodes.Stloc, receiverLocal);
// Get the method: GetProperty(obj, name) — handles all object types including dictionaries
IL.Emit(OpCodes.Ldloc, receiverLocal);
IL.Emit(OpCodes.Ldstr, g.Name.Lexeme);
IL.Emit(OpCodes.Call, _ctx.Runtime!.ObjectRead.Property);
if (ttl.Tag is Expr.Get property)
{
IL.Emit(OpCodes.Ldstr, property.Name.Lexeme);
IL.Emit(OpCodes.Call, _ctx.Runtime!.ObjectRead.Property);
}
else
{
var index = ((Expr.GetIndex)ttl.Tag).Index;
EmitExpression(index);
EmitBoxIfNeeded(index);
IL.Emit(OpCodes.Call, _ctx.Runtime!.ObjectRead.Index);
}
// Push thisArg (receiver) for WithThis call
IL.Emit(OpCodes.Ldloc, receiverLocal);
}
Expand Down
16 changes: 8 additions & 8 deletions src/SharpTS/Compilation/ILEmitter.Operators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1107,7 +1107,7 @@ protected override void EmitLogicalSet(Expr.LogicalSet ls)
var resultLocal = IL.DeclareLocal(_ctx.Types.Object);
IL.Emit(OpCodes.Dup);
IL.Emit(OpCodes.Stloc, resultLocal);
IL.Emit(OpCodes.Call, _ctx.Runtime!.ObjectWrite.Property);
EmitPropertyWrite(indexed: false);
IL.Emit(OpCodes.Ldloc, resultLocal);
builder.Emit_Br(endLabel);

Expand Down Expand Up @@ -1181,7 +1181,7 @@ protected override void EmitLogicalSetIndex(Expr.LogicalSetIndex lsi)
var resultLocal = IL.DeclareLocal(_ctx.Types.Object);
IL.Emit(OpCodes.Dup);
IL.Emit(OpCodes.Stloc, resultLocal);
IL.Emit(OpCodes.Call, _ctx.Runtime!.ObjectWrite.Index);
EmitPropertyWrite(indexed: true);
IL.Emit(OpCodes.Ldloc, resultLocal);
builder.Emit_Br(endLabel);

Expand Down Expand Up @@ -2252,7 +2252,7 @@ private void EmitIncrementProperty(Expr.Get get, bool isIncrement, bool isPrefix
IL.Emit(OpCodes.Ldloc, receiver);
IL.Emit(OpCodes.Ldstr, get.Name.Lexeme);
IL.Emit(OpCodes.Ldloc, newValue);
IL.Emit(OpCodes.Call, _ctx.Runtime!.ObjectWrite.Property);
EmitPropertyWrite(indexed: false);
IL.Emit(OpCodes.Ldloc, isPrefix ? newValue : oldValue);
SetStackUnknown();
}
Expand All @@ -2276,7 +2276,7 @@ private void EmitIncrementIndex(Expr.GetIndex gi, bool isIncrement, bool isPrefi
IL.Emit(OpCodes.Ldloc, receiver);
IL.Emit(OpCodes.Ldloc, index);
IL.Emit(OpCodes.Ldloc, newValue);
IL.Emit(OpCodes.Call, _ctx.Runtime!.ObjectWrite.Index);
EmitPropertyWrite(indexed: true);
IL.Emit(OpCodes.Ldloc, isPrefix ? newValue : oldValue);
SetStackUnknown();
}
Expand Down Expand Up @@ -2896,7 +2896,7 @@ private bool TryEmitStaticFieldIncrement(Expr.Get get, bool isIncrement, bool is
IL.Emit(OpCodes.Call, _ctx.Types.TypeGetTypeFromHandle);
IL.Emit(OpCodes.Ldstr, get.Name.Lexeme);
IL.Emit(OpCodes.Ldloc, newValue);
IL.Emit(OpCodes.Call, _ctx.Runtime!.ObjectWrite.Property);
EmitPropertyWrite(indexed: false);
}

if (isPrefix)
Expand Down Expand Up @@ -2965,7 +2965,7 @@ protected override void EmitCompoundSet(Expr.CompoundSet cs)
IL.Emit(OpCodes.Call, _ctx.Types.TypeGetTypeFromHandle);
IL.Emit(OpCodes.Ldstr, cs.Name.Lexeme);
IL.Emit(OpCodes.Ldloc, inheritedResultCS);
IL.Emit(OpCodes.Call, _ctx.Runtime!.ObjectWrite.Property);
EmitPropertyWrite(indexed: false);

IL.Emit(OpCodes.Ldloc, inheritedResultCS);
SetStackUnknown();
Expand Down Expand Up @@ -2998,7 +2998,7 @@ protected override void EmitCompoundSet(Expr.CompoundSet cs)
IL.Emit(OpCodes.Ldloc, objectLocal);
IL.Emit(OpCodes.Ldstr, cs.Name.Lexeme);
IL.Emit(OpCodes.Ldloc, resultLocal);
IL.Emit(OpCodes.Call, _ctx.Runtime!.ObjectWrite.Property);
EmitPropertyWrite(indexed: false);

// Leave result on stack
IL.Emit(OpCodes.Ldloc, resultLocal);
Expand Down Expand Up @@ -3125,7 +3125,7 @@ protected override void EmitCompoundSetIndex(Expr.CompoundSetIndex csi)
IL.Emit(OpCodes.Ldloc, objLocal);
IL.Emit(OpCodes.Ldloc, indexLocal);
IL.Emit(OpCodes.Ldloc, resultLocal);
IL.Emit(OpCodes.Call, _ctx.Runtime!.ObjectWrite.Index);
EmitPropertyWrite(indexed: true);

// Leave result on stack
IL.Emit(OpCodes.Ldloc, resultLocal);
Expand Down
Loading
Loading