Skip to content

Commit 6ae79b6

Browse files
authored
Fail closed when ILVerify crashes (#2755)
1 parent 9f9d237 commit 6ae79b6

6 files changed

Lines changed: 347 additions & 64 deletions

File tree

docs/adr/0115-csharp-to-gsharp-migration-tool.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,7 @@ Fields:
686686
- **`diagnostic.id` for stage 1.** A `translation-unsupported` failure has no G# diagnostic (no `.gs` is produced for the construct), so the pipeline emits a stable synthetic id: `CS2GS-UNSUPPORTED` for an unmapped construct, or `CS2GS-ROUNDTRIP` for an emitted `.gs` that fails to re-parse. Stage 2 uses the real `GSxxxx`.
687687
- **`sourceLocation` null rules.** The translator does not yet carry a per-line C#↔G# position map, so the two ends of `sourceLocation` are filled by *whichever* side the failure is anchored to and the other sub-fields are left `null` (the schema permits this): a stage-1 `translation-unsupported` fills `cs*` from the Roslyn node location and leaves `gs*` null; a stage-2 `compile-error` fills `gs*` from the `gsc` diagnostic and leaves `cs*` null. Correspondingly, `offendingCSharpConstruct` for a stage-1 failure is the C# construct (kind = Roslyn syntax-kind, snippet = the C# line), while for a stage-2 failure it is the *emitted G# construct* `gsc` flagged (kind classified from the G# line, snippet = the G# line). Wiring a real source map so stage-2/stage-3 artifacts also point back to the originating C# is deferred to a later step.
688688
- **Stage 3 (`ilverify`) artifacts and labels.** `IlVerifyStage` runs only after a green stage-2 compile (it reads the emitted assembly path the `CompileStage` publishes on the shared context). It invokes the repo-pinned `dotnet-ilverify` (`.config/dotnet-tools.json`, tool `dotnet-ilverify` 10.0.8, command `ilverify`) via `dotnet tool run ilverify <assembly> -s System.Private.CoreLib -r <eachReference>` with the process **working directory anchored at the repo root** (so the local tool manifest is discovered) and **all paths passed absolute**; `dotnet tool restore` is run once, lazily, only when the tool probe fails. The reference set is the host runtime BCL (`System.Private.CoreLib.dll`, `System.*.dll`, `mscorlib.dll`, `netstandard.dll` from the shared-framework dir) plus the corpus app's `ReferencedAssemblies`, mirroring `test/Compiler.Tests/IlVerifier.cs`. A non-zero exit with real verification errors yields category `ilverify-failure`, one artifact per distinct **error-code + failing-method skeleton**, with `diagnostic.id` = the ilverify error code (e.g. `StackUnexpected`), `severity` = `error`, `message` = the trimmed `[IL]: Error […]` line, and `offendingCSharpConstruct.kind` = the failing `Type::Method(sig)` parsed from the line (documented fallback `IlMethod` when ilverify reports no method). `sourceLocation.gsFile` points at the emitted `.gs`; all positions are `null` (no source map yet). Labels are **`Oats` + `cil-emit`** (per the §D label list). The fingerprint reuses the §D.2 hash over `category|stage|diagnostic.id|construct.kind|normalizedShape`.
689-
- **Stage 3 ilverify false-positive bundles (the only suppressions).** `dotnet-ilverify` 10.0.8 has two documented FALSE POSITIVES that are verifier limitations, **not** emitter bugs — the same minimal pattern emitted by `csc` fails identically and the JIT accepts the IL. The pipeline passes these as `ilverify -g <code>` ignore flags (and filters them defensively from parsed output) via the named constant `IlVerifyRunner.KnownIlVerifyFalsePositives`, so no spurious `ilverify-failure` gap is filed: (1) **`ReturnPtrToStack`** — by-value returns of a user-declared `ref struct` (track [dotnet/runtime#129030](https://github.com/dotnet/runtime/issues/129030)); (2) the static-virtual `constrained.` + `call` bundle **`CallAbstract`** + **`Constrained`** — ADR-0089 / issue #755 (track [dotnet/runtime#49558](https://github.com/dotnet/runtime/issues/49558)). These cite the same upstream issues as `IlVerifier.KnownIssues`. Any *other* ilverify error in a green-compiling app is a genuine `cil-emit` compiler gap and is captured (never suppressed). The whole stage no-ops to PASS when `GSHARP_SKIP_ILVERIFY=1` (for environments without the tool), matching `IlVerifier`.
689+
- **Stage 3 ilverify false-positive bundles (the only suppressions).** `dotnet-ilverify` 10.0.8 has two documented FALSE POSITIVES that are verifier limitations, **not** emitter bugs — the same minimal pattern emitted by `csc` fails identically and the JIT accepts the IL. The pipeline passes these as `ilverify -g <code>` ignore flags (and filters them defensively from parsed output) via the named constant `IlVerifyRunner.KnownIlVerifyFalsePositives`, so no spurious `ilverify-failure` gap is filed: (1) **`ReturnPtrToStack`** — by-value returns of a user-declared `ref struct` (track [dotnet/runtime#129030](https://github.com/dotnet/runtime/issues/129030)); (2) the static-virtual `constrained.` + `call` bundle **`CallAbstract`** + **`Constrained`** — ADR-0089 / issue #755 (track [dotnet/runtime#49558](https://github.com/dotnet/runtime/issues/49558)). These cite the same upstream issues as `IlVerifier.KnownIssues`. Any *other* ilverify error in a green-compiling app is a genuine `cil-emit` compiler gap and is captured (never suppressed). An abnormal tool exit is instead `verification-incomplete`: the runner excludes the member named by verbose output and retries to recover findings from the remaining members, while the stage stays failed and emits a separate `IlVerifyIncomplete` artifact. The whole stage no-ops to PASS when `GSHARP_SKIP_ILVERIFY=1` (for environments without the tool), matching `IlVerifier`.
690690
- **Stage 4 (`test-parity`) modes, artifacts, and labels.** `TestParityStage` runs only after a green stage-3 (it is appended last in `MigrationPipeline.DefaultStages()`, so it short-circuits with the rest — L2/L3, which stop at stage 1 today, never reach it). It proves the migrated program behaves identically to the original C# against the §E oracle, selecting one of two modes by the corpus app:
691691
- **Executable apps with a captured stdout golden (e.g. L1) → stdout parity.** The stage runs the stage-2/3 **emitted assembly** (the `EmittedAssemblyPath` the `CompileStage` publishes on the shared context) via `dotnet <emitted>.dll`, captures stdout, and compares it to `baseline.stdout.golden` with the L1 end-to-end normalization (CRLF→LF, single trailing newline). A divergence — or a non-zero exit — yields category `test-parity-failure`, one artifact with `diagnostic.id` = `STDOUT-MISMATCH`, `message` = the first differing line (expected-vs-actual), and `offendingCSharpConstruct.kind` = `ProgramStdout`. **L1 is the first corpus app green end-to-end across all four stages.**
692692
- **Library apps with a sibling `.Tests` oracle (L2/L3) → xUnit pass/fail-set parity.** The stage translates the C# `.Tests` project to a G# xUnit project (`@Fact`/`@Theory`/`@InlineData`/`Assert.*`), scaffolds an **isolated** test `.gsproj` consuming the **locally-built** `Gsharp.NET.Sdk` nupkg (copied into the repo `.nugs` feed and pinned), runs `dotnet test` producing a TRX, parses it into the `{name, outcome}` set, and compares it to `baseline.tests.json`. Any **missing**, **extra**, or **outcome-mismatch** test yields category `test-parity-failure`, **one artifact per differing test**, with `diagnostic.id` = `TESTPARITY-<Missing|Extra|OutcomeMismatch>`, `message` = expected-vs-actual outcome, and `offendingCSharpConstruct.kind` = the failing test name. The TRX parse mirrors `corpus/trx-to-baseline.py` (only `UnitTestResult` rows; namespace-agnostic by local name; sorted by name) so the comparison is apples-to-apples. Because C#-xUnit-test → G# translation is part of the not-yet-complete *map-advanced* step, the stage **skips the library path with an explicit, recorded reason** (`test-parity.log`: "test-translation pending map-advanced") when an unsupported construct, a round-trip-parse failure, or a G# build failure is hit — it never fabricates a pass and never emits an artifact except on a real outcome mismatch from a successful run. The live orchestration (`GsharpTestProjectRunner`) is proven on a minimal translated G# lib+test that builds and `dotnet test`s green against the local SDK.

0 commit comments

Comments
 (0)