Context
Investigating the 9 failing tests fixed in 2e13fe2c surfaced a problem with the IL verification test harness: a coarse substring filter is silently masking real bugs.
The filter
SharpTS.Tests/Infrastructure/TestHarness.cs:1203 drops any verifier error whose message contains the substring "Failed to load assembly":
var allErrors = VerifyIL(dllPath);
var errors = allErrors.Where(e => !e.Contains("Failed to load assembly")).ToList();
This exists because SharpTS.Compilation.ILVerifier.ResolveAssembly falls back through SDK reference assemblies → runtime directory, and SharpTS.dll itself isn't reachable from either. When the verifier can't resolve a dependency it reports a wrapped exception containing that string, which is genuinely not actionable. The filter exists to suppress that noise.
Why it's a problem
When the verifier can resolve enough of a method to reach an actual StackUnexpected, those real errors slip through. When it can't, both the Failed to load assembly cascade error AND any earlier real errors get suppressed together because they share that substring in some methods. Concretely:
-
Stringify, GetProperty, SetProperty, ToNumber, ToJsString, ToObject, IsTruthy, CheckCancellation, _ObjectPrototypePopulate, and several other $Runtime helpers all produce real StackUnexpected errors when verified directly via the ilverify CLI. They are not reported by the test because the filter masks them.
-
PropertyIsEnumerableHelper had a real correctness bug (commit 89b75f7e passed a Symbol object as the string name parameter to PDSGetPropertyDescriptor). Whether the test caught it was effectively a coin flip — for ~3 months it was masked by the filter, then a downstream simplification made the method verifiable enough that the StackUnexpected surfaced. Could just as easily have shipped.
-
The systemic Newobj $TypeError → Call $Runtime.CreateException(object) → Throw pattern (used in ~25 emitter sites for spec-compliant TypeError throws) produces an ILVerify StackUnexpected because of cross-assembly System.Object identity (TypeBuilder types resolving against [System.Runtime] vs [System.Private.CoreLib]). The runtime IL is correct; this is verifier strictness. But because every site has this same false positive, the filter is currently the only thing keeping the IL verification tests green.
Proposed fix
Two parts:
1. Make the resolver actually resolve SharpTS.dll
ILVerifier.ResolveAssembly should accept an explicit list of assemblies to search beyond the SDK refs. TestHarness.VerifyIL should pass typeof(RuntimeTypes).Assembly.Location (and its dependency directory) so SharpTS.dll, ZstdSharp.dll, etc. are resolvable. Once that's done, the "Failed to load assembly" cascade goes away for every method.
2. Replace the substring filter with a scoped allowlist by (method, error code)
The KnownRuntimeErrors set in SharpTS.Tests/CompilerTests/ILVerificationTests.cs:16 is already the right shape for this — it's keyed by method prefix and explicitly documents why each entry is allowed. Extend it to also key on the verifier's error code (StackUnexpected, ThrowOrCatchOnlyExceptionType, etc.) and bind each entry to the specific cross-assembly false positive it suppresses:
// Cross-assembly System.Object identity false positive from ILVerify
// (TypeBuilder types resolving against [System.Runtime] vs [S.P.CoreLib]).
// All TypeError throw sites trip this; the IL runs correctly.
(\"$Runtime\", \"StackUnexpected\", \"$TypeError\"),
(\"$Runtime\", \"StackUnexpected\", \"[S.P.CoreLib]System.Object\"),
Then enforce that every entry in the allowlist actually matches at least one verifier error — a dead allowlist entry (e.g. after a method rename) should fail the test, not silently pass.
3. Bonus: factor the throw pattern
The 25+ sites that emit Newobj $TypeError → Call CreateException → Throw could be collapsed into a single $Runtime.ThrowTypeError(string) helper. Two opcodes per call site instead of four, one place for the cross-assembly verifier complaint, easier to add stack-trace context later. Not required for this issue but reduces the allowlist surface.
Acceptance
Source
Discovered while fixing 9 failing tests in 2e13fe2. See conversation summary in commit body.
Context
Investigating the 9 failing tests fixed in 2e13fe2c surfaced a problem with the IL verification test harness: a coarse substring filter is silently masking real bugs.
The filter
SharpTS.Tests/Infrastructure/TestHarness.cs:1203drops any verifier error whose message contains the substring"Failed to load assembly":This exists because
SharpTS.Compilation.ILVerifier.ResolveAssemblyfalls back through SDK reference assemblies → runtime directory, and SharpTS.dll itself isn't reachable from either. When the verifier can't resolve a dependency it reports a wrapped exception containing that string, which is genuinely not actionable. The filter exists to suppress that noise.Why it's a problem
When the verifier can resolve enough of a method to reach an actual
StackUnexpected, those real errors slip through. When it can't, both theFailed to load assemblycascade error AND any earlier real errors get suppressed together because they share that substring in some methods. Concretely:Stringify,GetProperty,SetProperty,ToNumber,ToJsString,ToObject,IsTruthy,CheckCancellation,_ObjectPrototypePopulate, and several other$Runtimehelpers all produce realStackUnexpectederrors when verified directly via theilverifyCLI. They are not reported by the test because the filter masks them.PropertyIsEnumerableHelperhad a real correctness bug (commit89b75f7epassed a Symbol object as thestring nameparameter toPDSGetPropertyDescriptor). Whether the test caught it was effectively a coin flip — for ~3 months it was masked by the filter, then a downstream simplification made the method verifiable enough that the StackUnexpected surfaced. Could just as easily have shipped.The systemic
Newobj $TypeError → Call $Runtime.CreateException(object) → Throwpattern (used in ~25 emitter sites for spec-compliant TypeError throws) produces an ILVerifyStackUnexpectedbecause of cross-assembly System.Object identity (TypeBuilder types resolving against[System.Runtime]vs[System.Private.CoreLib]). The runtime IL is correct; this is verifier strictness. But because every site has this same false positive, the filter is currently the only thing keeping the IL verification tests green.Proposed fix
Two parts:
1. Make the resolver actually resolve SharpTS.dll
ILVerifier.ResolveAssemblyshould accept an explicit list of assemblies to search beyond the SDK refs.TestHarness.VerifyILshould passtypeof(RuntimeTypes).Assembly.Location(and its dependency directory) so SharpTS.dll, ZstdSharp.dll, etc. are resolvable. Once that's done, the "Failed to load assembly" cascade goes away for every method.2. Replace the substring filter with a scoped allowlist by (method, error code)
The
KnownRuntimeErrorsset inSharpTS.Tests/CompilerTests/ILVerificationTests.cs:16is already the right shape for this — it's keyed by method prefix and explicitly documents why each entry is allowed. Extend it to also key on the verifier's error code (StackUnexpected,ThrowOrCatchOnlyExceptionType, etc.) and bind each entry to the specific cross-assembly false positive it suppresses:Then enforce that every entry in the allowlist actually matches at least one verifier error — a dead allowlist entry (e.g. after a method rename) should fail the test, not silently pass.
3. Bonus: factor the throw pattern
The 25+ sites that emit
Newobj $TypeError → Call CreateException → Throwcould be collapsed into a single$Runtime.ThrowTypeError(string)helper. Two opcodes per call site instead of four, one place for the cross-assembly verifier complaint, easier to add stack-trace context later. Not required for this issue but reduces the allowlist surface.Acceptance
TestHarness.VerifyILinvokesILVerifierwith SharpTS.dll resolvable;"Failed to load assembly"errors stop appearingTestHarness.cs:1203removed; replaced with scoped (method-pattern, error-code) allowlist inILVerificationTests.csILVerificationTests.*_PassesILVerificationtests still passSource
Discovered while fixing 9 failing tests in 2e13fe2. See conversation summary in commit body.