From 66731cc221387afad66b38e53fb287f56a0c1eb8 Mon Sep 17 00:00:00 2001 From: PhysShell <45852143+PhysShell@users.noreply.github.com> Date: Tue, 20 Oct 2026 21:52:51 +0500 Subject: [PATCH 1/3] docs(evidence): N1-F0 pre-registration and oracle controls F0-A: CA2000 interprocedural kinds and every disposal-related diagnostic inside the buggy methods. F0-B: ownership oracle by repository-internal disposal of the hand-over target (field, collection, in-source wrapper), borrow table only suppresses; real R/B/X base+fix files and controls N1-N9, P-R/P-B/P-X. F0-C: can existing OwnIR facts carry factory acquisition, hand-over and throw edges. Fixed before any run. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TFkNAFFQz9KoTzpPws8vYG --- .../n1-f0-2026-10/00-preregistration.md | 117 +++++++++++++ .../controls/OwnershipControls.cs | 157 ++++++++++++++++++ 2 files changed, 274 insertions(+) create mode 100644 docs/evidence/n1-f0-2026-10/00-preregistration.md create mode 100644 docs/evidence/n1-f0-2026-10/controls/OwnershipControls.cs diff --git a/docs/evidence/n1-f0-2026-10/00-preregistration.md b/docs/evidence/n1-f0-2026-10/00-preregistration.md new file mode 100644 index 00000000..5cd951ca --- /dev/null +++ b/docs/evidence/n1-f0-2026-10/00-preregistration.md @@ -0,0 +1,117 @@ +# N1-F0 — ownership feasibility gate — pre-registration + +Written 2026-10-20, 21:51 +05, before any F0 run. Authority: owner decision +`ACCEPT-N1-COMPETITOR-GATE / GO-N1-OWNERSHIP-FEASIBILITY / NO-PRODUCTION`, on #417. + +- **Budget.** 0.25 agent-day for F0-A, F0-B and F0-C together. +- **Outcome.** One verdict, `PASS-F0` or `KILL-N1`. `PASS-F0` does not authorise production work or a PoC. +- **Rules of the work.** No production, OwnIR, CI or golden change. No third-party build and no package download (D13). + +## F0-A — finish the competitor check + +**Run.** The four reproductions of #417 (`n1-competitor-gate-2026-10/repro`, stubs as a separate assembly), under: + +| K | CA2000 settings | +|---|---| +| A1 | `AllPaths`, `interprocedural_analysis_kind = None` | +| A2 | `AllPaths`, `NonContextSensitive` | +| A3 | `AllPaths`, `ContextSensitive` | +| A4–A6 | A1–A3 plus `dispose_ownership_transfer_at_constructor = true` and `…_at_method_call = true` | + +plus K1–K5 of #417 again, so that every configuration uses the new collector. + +**Collector.** Every diagnostic whose location is inside the buggy **method** of each pair, from the first line of +its declaration to its closing brace, not only on the `ACQ` line. A diagnostic is **disposal-related** if its rule +is one of the following, or its message contains `dispos` (case-insensitive): + +- CA2000, CA1001, CA2213, CA2215, CA2216; +- IDISP001–IDISP026; +- S2930, S2931, S3966, S4487. + +**Verdict per pair.** + +- **`CAUGHT`:** at least one disposal-related diagnostic in the buggy method names the leaked resource (its + acquisition expression or its variable) in any configuration. +- **`ADJACENT`:** disposal-related diagnostics that name another object. They are listed and do not count. +- **`MISSED`:** otherwise. + +**F0-A kill.** Fewer than 2 pairs `MISSED` → `KILL-N1`. The other parts are then not scored. + +## F0-B — ownership oracle + +**Oracle (fixed now).** A call result `r` acquired in method `M` of type `T` is **owned by `M`** only when all of +the following hold: + +1. **Hand-over target.** On the success path, `M` hands `r` to one of: + - (a) an instance field `f` of `T`; + - (b) an element of a collection field `f` of `T` (indexer store, `Add`, `TryAdd`); + - (c) a constructor argument of an in-source type `W` whose constructor stores it in a field `g` of `W`, where + the `W` instance is returned or stored as in (a)/(b). +2. **The repository itself disposes that target.** The same type (`T`, or `W` for (c)) contains a + `Dispose`/`DisposeAsync`/`Close`/`CloseAsync` call whose receiver is: + - `f` or `g`; + - a local obtained from `f` by `TryRemove(…, out x)`, `Remove(…, out x)`, an indexer read, or `foreach` over + `f` / `f.Values`; + - or `using` / `await using` of one of those. +3. **Not a borrow.** The acquisition call is not in the borrow table: `IServiceProvider.GetService`, + `GetRequiredService`, `GetServices`, `GetKeyedService*`, `ActivatorUtilities.GetServiceOrCreateInstance`. The + table only suppresses; it never creates ownership. + +Ownership is **never** inferred from the result type being `IDisposable`/`IAsyncDisposable`, from a `Create*` name, +or from `await`. The result type matters only because rule 2 needs something to dispose. + +**Defect, given ownership.** A call that can throw (any invocation or `await`, other than `nameof`/`typeof`) lies +between the acquisition and the hand-over, and no enclosing `try` disposes `r` on the way out (`catch` or +`finally` that disposes `r`, or a `published`-style flag guarding a dispose). + +**F0-B inputs.** + +- **Real code**, as single files from GitHub at the pairs' SHAs, parsed with framework references only: + - R: rockbot `McpBridgeService.cs` base and fix; + - B: BareWire `RabbitMqTransportAdapter.cs` base and fix; + - X: NexusKit `PluginHostBuilder.cs` base and fix, plus `PluginHost.cs` at the same SHAs. +- **Negative controls** (`controls/OwnershipControls.cs`, written before the first run). Each must be **silent**: + +| # | Control | +|---|---| +| N1 | DI-resolved: `var s = sp.GetRequiredService(); await X(); _foo = s;` with `T` disposing `_foo` (borrow table) | +| N2 | borrowed reference: a parameter stored into a field `T` disposes (not acquired by a call) | +| N3 | shared instance stored in a field `T` never disposes: `var c = Cache.Get(k); await X(); _c = c;` | +| N4 | caching factory into a dictionary `T` never disposes from | +| N5 | factory result handed over **before** any throwing call | +| N6 | the throwing step inside `try`/`catch` that disposes `r` and rethrows (the fixes' shape) | +| N7 | `published` flag + `finally` disposal (the rockbot fix's shape) | +| N8 | factory result used and disposed locally with `using` (no hand-over) | +| N9 | factory result returned to the caller (rule 1 has no return case: silent by design, an inter-procedural case) | + +- **Positive samples**, which must be reported: the R, X and B shapes in synthetic form, P-R, P-X and P-B. + +**F0-B pass.** All three of: + +- the oracle reports the real R, X and B base files at the acquisition, with ownership established by rules 1–2, + each citing the disposing line it found; +- it is silent on their fix files; +- N1–N9 are silent and P-R, P-X and P-B are reported. + +Any failure → `KILL-N1`. The oracle must not be changed after the first run on the real files; control files may +be extended. + +The oracle is a throwaway research scan (Roslyn, the same build route as the #416 PoC). It is not an Own.NET +implementation. + +## F0-C — cost + +Read the extractor (`frontend/roslyn/OwnSharp.Extractor/Program.cs`) and OwnIR (`ownlang/ownir.py`) and answer +three questions with file and line references: + +1. Can the extractor emit an acquisition for a call result (rather than `new`) using an existing OwnIR fact kind? +2. Can it emit the hand-over (field, collection or wrapper store) as an existing release/move fact? +3. Are throw edges between acquisition and hand-over already expressible (the `--body-throw-edges` tier)? + +**F0-C kill.** Any of the three needs a new OwnIR fact kind, a version bump, or a new interprocedural pass in the +core → `KILL-N1`. Needing only extractor-side work (emitting existing facts from new syntax) is acceptable, and +the estimate is reported. + +## Verdict + +`PASS-F0` only if F0-A, F0-B and F0-C all pass. Otherwise `KILL-N1`, naming the first failing part. diff --git a/docs/evidence/n1-f0-2026-10/controls/OwnershipControls.cs b/docs/evidence/n1-f0-2026-10/controls/OwnershipControls.cs new file mode 100644 index 00000000..ee247aa1 --- /dev/null +++ b/docs/evidence/n1-f0-2026-10/controls/OwnershipControls.cs @@ -0,0 +1,157 @@ +// N1-F0 oracle controls (00-preregistration.md, F0-B). Written before the first run. +// Methods N_* must be SILENT; methods P_* must be REPORTED. +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; + +namespace OwnershipControls; + +public interface IFoo : IAsyncDisposable { Task PingAsync(); } +public interface IConn : IAsyncDisposable { Task OpenAsync(); } + +public static class Cache +{ + public static IFoo Get(string key) => throw new NotSupportedException(); +} + +public static class Remote +{ + public static Task CreateAsync(string url) => throw new NotSupportedException(); +} + +public sealed class Wrap : IAsyncDisposable +{ + private readonly IFoo _inner; + public Wrap(IFoo inner) { _inner = inner; } + public ValueTask DisposeAsync() => _inner.DisposeAsync(); +} + +public sealed class Holder : IAsyncDisposable +{ + private readonly IServiceProvider _sp; + private readonly IConn _conn; + private IFoo? _foo; + private IFoo? _shared; + private readonly ConcurrentDictionary _owned = new(); + private readonly Dictionary _cache = new(); + + public Holder(IServiceProvider sp, IConn conn) { _sp = sp; _conn = conn; } + + private static Task StepAsync() => Task.Delay(1); + + // ---- negative controls: must be silent ---- + + public async Task N1_DiResolved() + { + var s = _sp.GetRequiredService(); + await StepAsync(); + _foo = s; + } + + public async Task N2_Borrowed(IFoo given) + { + await StepAsync(); + _foo = given; + } + + public async Task N3_SharedNeverDisposed(string k) + { + var c = Cache.Get(k); + await StepAsync(); + _shared = c; + } + + public async Task N4_CachingFactoryNeverDisposed(string k) + { + var c = await Remote.CreateAsync(k); + await c.PingAsync(); + _cache[k] = c; + } + + public async Task N5_HandOverFirst(string k) + { + var c = await Remote.CreateAsync(k); + _owned[k] = c; + await c.PingAsync(); + } + + public async Task N6_TryCatchDispose(string k) + { + var c = await Remote.CreateAsync(k); + try + { + await c.PingAsync(); + } + catch + { + await c.DisposeAsync(); + throw; + } + _owned[k] = c; + } + + public async Task N7_PublishedFlag(string k) + { + IFoo? c = null; + var published = false; + try + { + c = await Remote.CreateAsync(k); + await c.PingAsync(); + _owned[k] = c; + published = true; + } + finally + { + if (!published && c is not null) await c.DisposeAsync(); + } + } + + public async Task N8_LocalUsing(string k) + { + await using var c = await Remote.CreateAsync(k); + await c.PingAsync(); + } + + public async Task N9_Returned(string k) + { + var c = await Remote.CreateAsync(k); + await c.PingAsync(); + return c; + } + + // ---- positive samples: must be reported ---- + + // P-R: awaited factory, throwing call on the result, then stored into a dictionary T disposes from + public async Task P_R(string k) + { + var c = await Remote.CreateAsync(k); + await c.PingAsync(); + _owned[k] = c; + } + + // P-B: awaited interface factory, throwing call, then dictionary store + public async Task P_B(string k) + { + IFoo ch = await _conn.OpenAsync(); + await ch.PingAsync(); + _owned[k] = ch; + } + + // P-X: factory result wrapped in an in-source type that disposes it, after a throwing call + public async Task P_X() + { + var c = await Remote.CreateAsync("x"); + await StepAsync(); + return new Wrap(c); + } + + public async ValueTask DisposeAsync() + { + if (_foo is not null) await _foo.DisposeAsync(); + foreach (var v in _owned.Values) await v.DisposeAsync(); + if (_owned.TryRemove("k", out var one)) await one.DisposeAsync(); + } +} From 081b68ee3ad1eedff8770ce678b7cd5b4c675528 Mon Sep 17 00:00:00 2001 From: PhysShell <45852143+PhysShell@users.noreply.github.com> Date: Tue, 20 Oct 2026 21:56:49 +0500 Subject: [PATCH 2/3] docs(evidence): N1-F0 oracle frozen before the real-file run; F0-A collector Oracle (rules 1-3, defect rule) with "disposes r" operationalised in the header; one control-driven fix (a dispose of r inside the catch is not a throwing site, control N6). Controls: N1-N9 silent, P-R/P-B/P-X reported. F0-A collector script (method spans, interprocedural kinds). Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TFkNAFFQz9KoTzpPws8vYG --- .../n1-f0-2026-10/data/f0b_controls.tsv | 4 + .../n1-f0-2026-10/oracle/F0Oracle.csproj | 15 + docs/evidence/n1-f0-2026-10/oracle/Program.cs | 280 ++++++++++++++++++ .../evidence/n1-f0-2026-10/scripts/run_f0a.py | 155 ++++++++++ 4 files changed, 454 insertions(+) create mode 100644 docs/evidence/n1-f0-2026-10/data/f0b_controls.tsv create mode 100644 docs/evidence/n1-f0-2026-10/oracle/F0Oracle.csproj create mode 100644 docs/evidence/n1-f0-2026-10/oracle/Program.cs create mode 100644 docs/evidence/n1-f0-2026-10/scripts/run_f0a.py diff --git a/docs/evidence/n1-f0-2026-10/data/f0b_controls.tsv b/docs/evidence/n1-f0-2026-10/data/f0b_controls.tsv new file mode 100644 index 00000000..d1710736 --- /dev/null +++ b/docs/evidence/n1-f0-2026-10/data/f0b_controls.tsv @@ -0,0 +1,4 @@ +group file type method acq_line acq handover_line kind dispose_evidence throw_line throw_site +controls OwnershipControls.cs Holder P_R 130 Remote.CreateAsync(k) 132 1b _owned[] 154: v.DisposeAsync() 131 await c.PingAsync() +controls OwnershipControls.cs Holder P_B 138 _conn.OpenAsync() 140 1b _owned[] 154: v.DisposeAsync() 139 await ch.PingAsync() +controls OwnershipControls.cs Holder P_X 146 Remote.CreateAsync("x") 148 1c Wrap Wrap._inner disposed at 28: _inner.DisposeAsync() 147 await StepAsync() diff --git a/docs/evidence/n1-f0-2026-10/oracle/F0Oracle.csproj b/docs/evidence/n1-f0-2026-10/oracle/F0Oracle.csproj new file mode 100644 index 00000000..def4cda1 --- /dev/null +++ b/docs/evidence/n1-f0-2026-10/oracle/F0Oracle.csproj @@ -0,0 +1,15 @@ + + + + Exe + net10.0 + enable + enable + f0oracle + + + $(RoslynDir)/Microsoft.CodeAnalysis.dll + $(RoslynDir)/Microsoft.CodeAnalysis.CSharp.dll + + diff --git a/docs/evidence/n1-f0-2026-10/oracle/Program.cs b/docs/evidence/n1-f0-2026-10/oracle/Program.cs new file mode 100644 index 00000000..f3d2acdf --- /dev/null +++ b/docs/evidence/n1-f0-2026-10/oracle/Program.cs @@ -0,0 +1,280 @@ +// N1-F0 part B: ownership oracle (00-preregistration.md, F0-B). Research only, throwaway. +// +// usage: f0oracle --refs [--refs ] --group =[;...] [--group ...] --out out.tsv +// +// A call result r acquired in method M of type T is OWNED when (rules 1-3 of the pre-registration): +// 1. M hands r over on the success path to (a) a field f of T, (b) an element of a collection field f +// (indexer store, Add, TryAdd), or (c) a constructor argument of an in-source type W whose ctor stores +// it in a field g of W, the W instance being returned or stored as in (a)/(b); +// 2. the same type (T, or W for (c)) disposes that target: Dispose/DisposeAsync/Close/CloseAsync on f/g, +// on a local taken from f (TryRemove/Remove out, indexer read, foreach over f or f.Values), or +// using/await using of one of those; +// 3. the acquisition is not a borrow (IServiceProvider.GetService and friends). +// DEFECT: a throwing site (any invocation or await, except nameof/typeof) lies between the acquisition and +// the hand-over, and no enclosing try disposes r in its catch or finally. +// "Disposes r" is operationalised (fixed before the first run on real files) as any of: +// - Dispose/DisposeAsync/Close/CloseAsync on r (also r?.X); +// - the same call on `new W(r)` where W is an owning wrapper by rules 1c+2; +// - r passed to a method of T whose body disposes the corresponding parameter (one hop). +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +var refDirs = new List(); +var groups = new List<(string Name, string[] Files)>(); +string? outPath = null; +for (var i = 0; i < args.Length; i++) +{ + switch (args[i]) + { + case "--refs": refDirs.Add(args[++i]); break; + case "--group": + var g = args[++i].Split('=', 2); + groups.Add((g[0], g[1].Split(';'))); + break; + case "--out": outPath = args[++i]; break; + default: throw new ArgumentException(args[i]); + } +} +var refs = refDirs.SelectMany(d => Directory.EnumerateFiles(d, "*.dll")) + .Select(p => (MetadataReference)MetadataReference.CreateFromFile(p)).ToList(); +var rows = new List { "group\tfile\ttype\tmethod\tacq_line\tacq\thandover_line\tkind\tdispose_evidence\tthrow_line\tthrow_site" }; +foreach (var (name, files) in groups) +{ + var trees = files.Select(f => CSharpSyntaxTree.ParseText(File.ReadAllText(f), + new CSharpParseOptions(LanguageVersion.Preview), Path.GetFileName(f))).ToList(); + var comp = CSharpCompilation.Create(name, trees, refs, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + var o = new Oracle(comp); + foreach (var t in trees) o.Run(t); + foreach (var f in o.Findings) rows.Add(name + "\t" + f); + Console.Error.WriteLine($"{name}: {o.Findings.Count} findings, {o.OwnedAcquisitions} owned acquisitions, {o.Acquisitions} call acquisitions"); +} +File.WriteAllLines(outPath ?? "f0b.tsv", rows); +foreach (var r in rows) Console.WriteLine(r); +return 0; + +sealed class Oracle(CSharpCompilation comp) +{ + public readonly List Findings = new(); + public int Acquisitions, OwnedAcquisitions; + + static readonly HashSet DisposeNames = new() { "Dispose", "DisposeAsync", "Close", "CloseAsync" }; + static readonly HashSet BorrowNames = new() + { "GetService", "GetRequiredService", "GetServices", "GetKeyedService", "GetRequiredKeyedService", "GetKeyedServices", "GetServiceOrCreateInstance" }; + static readonly HashSet StoreNames = new() { "Add", "TryAdd", "AddOrUpdate", "GetOrAdd", "Enqueue", "Push" }; + + static int Line(SyntaxNode n) => n.GetLocation().GetLineSpan().StartLinePosition.Line + 1; + static string Snip(SyntaxNode n) + { + var s = string.Join(' ', n.ToString().Split((char[])['\r', '\n', '\t'], StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim())); + return s.Length > 90 ? s[..90] + "…" : s; + } + + static ExpressionSyntax Strip(ExpressionSyntax e) + { + while (true) + { + switch (e) + { + case AwaitExpressionSyntax a: e = a.Expression; continue; + case ParenthesizedExpressionSyntax p: e = p.Expression; continue; + case PostfixUnaryExpressionSyntax { RawKind: (int)SyntaxKind.SuppressNullableWarningExpression } pu: e = pu.Operand; continue; + case InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax { Name.Identifier.Text: "ConfigureAwait" } ma }: e = ma.Expression; continue; + default: return e; + } + } + } + + static string? InvokedName(InvocationExpressionSyntax inv) => inv.Expression switch + { + MemberAccessExpressionSyntax ma => ma.Name.Identifier.Text, + SimpleNameSyntax s => s.Identifier.Text, + MemberBindingExpressionSyntax mb => mb.Name.Identifier.Text, + _ => null, + }; + + // the root identifier of a receiver / expression (x in x.a.b, this.x, x?.y) + static string? RootName(ExpressionSyntax e) + { + while (true) + { + switch (e) + { + case MemberAccessExpressionSyntax { Expression: ThisExpressionSyntax } m: return m.Name.Identifier.Text; + case MemberAccessExpressionSyntax m: e = m.Expression; continue; + case ConditionalAccessExpressionSyntax c: e = c.Expression; continue; + case ElementAccessExpressionSyntax ea: e = ea.Expression; continue; + case ParenthesizedExpressionSyntax p: e = p.Expression; continue; + case PostfixUnaryExpressionSyntax pu: e = pu.Operand; continue; + case IdentifierNameSyntax id: return id.Identifier.Text; + default: return null; + } + } + } + + // receiver of a dispose-family call: `x.Dispose()`, `x?.Dispose()`, `(expr).DisposeAsync()` + static ExpressionSyntax? DisposeReceiver(InvocationExpressionSyntax inv) + { + if (inv.Expression is MemberAccessExpressionSyntax ma && DisposeNames.Contains(ma.Name.Identifier.Text)) return ma.Expression; + if (inv.Expression is MemberBindingExpressionSyntax mb && DisposeNames.Contains(mb.Name.Identifier.Text) + && inv.Parent is ConditionalAccessExpressionSyntax ca) return ca.Expression; + return null; + } + + static IEnumerable DisposeCalls(SyntaxNode n) => + n.DescendantNodesAndSelf().OfType().Where(i => DisposeReceiver(i) is not null); + + // Rule 2: does type `decl` dispose field `field` (directly, via a local taken from it, or via using)? + string? DisposesField(TypeDeclarationSyntax decl, string field) + { + var fromField = new HashSet(); + foreach (var arg in decl.DescendantNodes().OfType()) + { + if (arg.Expression is DeclarationExpressionSyntax { Designation: SingleVariableDesignationSyntax sv } + && arg.Parent?.Parent is InvocationExpressionSyntax inv && InvokedName(inv) is "TryRemove" or "Remove" or "TryGetValue" or "TryDequeue" or "TryPop" or "TryTake" + && inv.Expression is MemberAccessExpressionSyntax ma && RootName(ma.Expression) == field) + fromField.Add(sv.Identifier.Text); + } + foreach (var v in decl.DescendantNodes().OfType()) + if (v.Initializer?.Value is ElementAccessExpressionSyntax ea && RootName(ea.Expression) == field) fromField.Add(v.Identifier.Text); + foreach (var fe in decl.DescendantNodes().OfType()) + if (RootName(fe.Expression) == field) fromField.Add(fe.Identifier.Text); + foreach (var call in DisposeCalls(decl)) + { + var root = RootName(DisposeReceiver(call)!); + if (root == field || (root is not null && fromField.Contains(root))) return $"{Line(call)}: {Snip(call)}"; + } + foreach (var u in decl.DescendantNodes().OfType().Where(l => l.UsingKeyword != default)) + foreach (var v in u.Declaration.Variables) + if (v.Initializer?.Value is { } init && (RootName(init) == field || (RootName(init) is { } r && fromField.Contains(r)))) + return $"{Line(u)}: {Snip(u)}"; + return null; + } + + bool IsField(TypeDeclarationSyntax decl, string name) => + decl.Members.OfType().Any(f => f.Declaration.Variables.Any(v => v.Identifier.Text == name)) + || decl.Members.OfType().Any(p => p.Identifier.Text == name); + + // Rule 1c: in-source wrapper type W whose ctor stores parameter #i into a field g that W disposes. + (TypeDeclarationSyntax W, string Evidence)? OwningWrapper(ObjectCreationExpressionSyntax oc, int argIndex) + { + var tn = oc.Type switch { IdentifierNameSyntax i => i.Identifier.Text, QualifiedNameSyntax q => q.Right.Identifier.Text, _ => null }; + if (tn is null) return null; + foreach (var tree in comp.SyntaxTrees) + foreach (var w in tree.GetRoot().DescendantNodes().OfType().Where(t => t.Identifier.Text == tn)) + foreach (var ctor in w.Members.OfType()) + { + if (argIndex >= ctor.ParameterList.Parameters.Count) continue; + var p = ctor.ParameterList.Parameters[argIndex].Identifier.Text; + foreach (var asg in ctor.DescendantNodes().OfType()) + if (asg.Right is IdentifierNameSyntax { Identifier.Text: var rp } && rp == p && RootName(asg.Left) is { } g && IsField(w, g) + && DisposesField(w, g) is { } ev) + return (w, $"{tn}.{g} disposed at {ev}"); + } + return null; + } + + public void Run(SyntaxTree tree) + { + foreach (var decl in tree.GetRoot().DescendantNodes().OfType()) + foreach (var m in decl.Members.OfType()) + if (m.Body is not null) Scan(decl, m); + } + + void Scan(TypeDeclarationSyntax T, MethodDeclarationSyntax M) + { + // acquisitions: `var r = ` or `r = `, the call possibly awaited + var acqs = new List<(string R, SyntaxNode Stmt, InvocationExpressionSyntax Call)>(); + foreach (var v in M.Body!.DescendantNodes().OfType()) + if (v.Initializer?.Value is { } init && Strip(init) is InvocationExpressionSyntax inv) + acqs.Add((v.Identifier.Text, v, inv)); + foreach (var a in M.Body.DescendantNodes().OfType()) + if (a.Left is IdentifierNameSyntax id && Strip(a.Right) is InvocationExpressionSyntax inv2 + && M.Body.DescendantNodes().OfType().Any(v => v.Identifier.Text == id.Identifier.Text)) + acqs.Add((id.Identifier.Text, a, inv2)); + foreach (var (r, stmt, call) in acqs) + { + if (InvokedName(call) is { } nm && (BorrowNames.Contains(nm) || nm is "nameof")) continue; // rule 3 + Acquisitions++; + var after = stmt.Span.End; + // rule 1: hand-over target on the success path, after the acquisition + SyntaxNode? hand = null; string? kind = null; string? ev = null; + foreach (var n in M.Body.DescendantNodes().Where(n => n.SpanStart >= after)) + { + if (n is AssignmentExpressionSyntax { Right: var rhs } asg && Strip(rhs) is IdentifierNameSyntax { Identifier.Text: var rr } && rr == r) + { + if (asg.Left is ElementAccessExpressionSyntax ea && RootName(ea.Expression) is { } cf && IsField(T, cf) && DisposesField(T, cf) is { } e1) + { hand = asg; kind = $"1b {cf}[]"; ev = e1; break; } + if (asg.Left is IdentifierNameSyntax or MemberAccessExpressionSyntax && RootName(asg.Left) is { } ff && ff != r && IsField(T, ff) && DisposesField(T, ff) is { } e2) + { hand = asg; kind = $"1a {ff}"; ev = e2; break; } + } + if (n is InvocationExpressionSyntax si && InvokedName(si) is { } sn && StoreNames.Contains(sn) + && si.Expression is MemberAccessExpressionSyntax sma && RootName(sma.Expression) is { } sf && IsField(T, sf) + && si.ArgumentList.Arguments.Any(x => Strip(x.Expression) is IdentifierNameSyntax { Identifier.Text: var ar } && ar == r) + && DisposesField(T, sf) is { } e3) + { hand = si; kind = $"1b {sf}.{sn}"; ev = e3; break; } + if (n is ObjectCreationExpressionSyntax oc && oc.ArgumentList is { } al) + { + var idx = al.Arguments.ToList().FindIndex(x => Strip(x.Expression) is IdentifierNameSyntax { Identifier.Text: var ar } && ar == r); + if (idx >= 0 && OwningWrapper(oc, idx) is { } w + && (oc.Parent is ReturnStatementSyntax || (oc.Parent is AssignmentExpressionSyntax pa && RootName(pa.Left) is { } wf && IsField(T, wf)))) + { hand = oc; kind = $"1c {w.W.Identifier.Text}"; ev = w.Evidence; break; } + } + } + if (hand is null) continue; + OwnedAcquisitions++; + // defect: a throwing site strictly between the acquisition and the hand-over, not covered by a disposing try + var throwing = M.Body.DescendantNodes() + .Where(n => n.SpanStart >= after && n.Span.End <= hand.SpanStart) + .Where(n => n is InvocationExpressionSyntax inv3 ? InvokedName(inv3) is not ("nameof") : n is AwaitExpressionSyntax) + .Where(n => !n.Ancestors().Any(a => a == hand)) + // disposing r cannot leak r (control N6: the dispose inside the catch) + .Where(n => !(Strip(n is AwaitExpressionSyntax aw ? aw.Expression : (ExpressionSyntax)n) is InvocationExpressionSyntax di + && DisposeReceiver(di) is { } dr && RootName(dr) == r)) + .FirstOrDefault(n => !CoveredByDisposingTry(T, n, r, M)); + if (throwing is null) continue; + Findings.Add(string.Join('\t', T.SyntaxTree.FilePath, T.Identifier.Text, M.Identifier.Text, Line(stmt), Snip(call), + Line(hand), kind, ev, Line(throwing), Snip(throwing))); + } + } + + bool CoveredByDisposingTry(TypeDeclarationSyntax T, SyntaxNode site, string r, MethodDeclarationSyntax M) + { + foreach (var tr in site.Ancestors().OfType()) + { + if (!tr.Block.Span.Contains(site.Span)) continue; + var cleanup = tr.Catches.Select(c => (SyntaxNode)c.Block).Concat(tr.Finally is { } f ? new SyntaxNode[] { f.Block } : []); + if (cleanup.Any(b => DisposesLocal(T, b, r))) return true; + } + return false; + } + + bool DisposesLocal(TypeDeclarationSyntax T, SyntaxNode block, string r) + { + foreach (var call in DisposeCalls(block)) + { + var recv = DisposeReceiver(call)!; + if (RootName(recv) == r) return true; + if (recv is ObjectCreationExpressionSyntax oc && oc.ArgumentList is { } al) + { + var idx = al.Arguments.ToList().FindIndex(x => Strip(x.Expression) is IdentifierNameSyntax { Identifier.Text: var ar } && ar == r); + if (idx >= 0 && OwningWrapper(oc, idx) is not null) return true; + } + } + // one hop: r passed to a method of T whose body disposes that parameter + foreach (var inv in block.DescendantNodes().OfType()) + { + if (inv.Expression is not IdentifierNameSyntax callee) continue; + var idx = inv.ArgumentList.Arguments.ToList().FindIndex(x => Strip(x.Expression) is IdentifierNameSyntax { Identifier.Text: var ar } && ar == r); + if (idx < 0) continue; + foreach (var md in T.Members.OfType().Where(x => x.Identifier.Text == callee.Identifier.Text)) + { + if (idx >= md.ParameterList.Parameters.Count) continue; + var p = md.ParameterList.Parameters[idx].Identifier.Text; + if (DisposeCalls(md).Any(c => RootName(DisposeReceiver(c)!) == p)) return true; + } + } + return false; + } +} diff --git a/docs/evidence/n1-f0-2026-10/scripts/run_f0a.py b/docs/evidence/n1-f0-2026-10/scripts/run_f0a.py new file mode 100644 index 00000000..a02ed1a7 --- /dev/null +++ b/docs/evidence/n1-f0-2026-10/scripts/run_f0a.py @@ -0,0 +1,155 @@ +"""N1-F0 part A: rebuild the #417 reproductions under more CA2000 settings and collect every +diagnostic inside each buggy method (not only on the ACQ line). + +usage: python -I run_f0a.py --repro <#417 repro dir> --work --nuget --out +""" + +from __future__ import annotations + +import argparse +import json +import os +import re +import shutil +import subprocess + +BASE = [ + "root = true", + "", + "[*.cs]", + "dotnet_analyzer_diagnostic.severity = warning", + "dotnet_diagnostic.CA2000.severity = warning", +] +ALL = "dotnet_code_quality.CA2000.dispose_analysis_kind = AllPaths" +IPA = "dotnet_code_quality.CA2000.interprocedural_analysis_kind = " +CTOR = "dotnet_code_quality.CA2000.dispose_ownership_transfer_at_constructor = true" +CALL = "dotnet_code_quality.CA2000.dispose_ownership_transfer_at_method_call = true" +CONFIGS = { + "A1": [ALL, IPA + "None"], + "A2": [ALL, IPA + "NonContextSensitive"], + "A3": [ALL, IPA + "ContextSensitive"], + "A4": [ALL, IPA + "None", CTOR, CALL], + "A5": [ALL, IPA + "NonContextSensitive", CTOR, CALL], + "A6": [ALL, IPA + "ContextSensitive", CTOR, CALL], + "K1": [], + "K2": [ALL], + "K3": [ALL, CTOR], + "K4": [ALL, CALL], + "K5": ["dotnet_code_quality.CA2000.dispose_analysis_kind = AllPathsOnlyNotDisposed"], +} +DISPOSAL_RULES = { + "CA2000", + "CA1001", + "CA2213", + "CA2215", + "CA2216", + "S2930", + "S2931", + "S3966", + "S4487", +} | {f"IDISP{n:03d}" for n in range(1, 27)} +CLASS = re.compile(r"^\s*public (?:sealed |static )?class (\w+)") +METHOD = re.compile(r"^\s*public async ") + + +def buggy_method_spans(repro: str) -> dict[tuple[str, str], tuple[int, int]]: + """(file, class) -> (first, last) line of the first `public async` method of a *Buggy class.""" + spans = {} + for f in sorted(os.listdir(repro)): + if not f.endswith(".cs"): + continue + lines = open(os.path.join(repro, f), encoding="utf-8").read().splitlines() + cls = None + i = 0 + while i < len(lines): + if m := CLASS.match(lines[i]): + cls = m.group(1) + if cls and cls.endswith("Buggy") and METHOD.match(lines[i]) and (f, cls) not in spans: + depth, j, opened = 0, i, False + while j < len(lines): + depth += lines[j].count("{") - lines[j].count("}") + opened = opened or "{" in lines[j] + if opened and depth == 0: + break + j += 1 + spans[(f, cls)] = (i + 1, j + 1) + i = j + i += 1 + return spans + + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument("--repro", required=True) + ap.add_argument("--work", required=True) + ap.add_argument("--nuget", required=True) + ap.add_argument("--out", required=True) + a = ap.parse_args() + spans = buggy_method_spans(a.repro) + os.makedirs(a.out, exist_ok=True) + stubs = os.path.join(os.path.dirname(os.path.abspath(a.repro)), "stubs") + shutil.rmtree(os.path.join(a.work, "stubs"), ignore_errors=True) + shutil.copytree(stubs, os.path.join(a.work, "stubs")) + rows = [] + for k, extra in CONFIGS.items(): + work = os.path.join(a.work, k) + shutil.rmtree(work, ignore_errors=True) + shutil.copytree(a.repro, work) + with open(os.path.join(work, ".editorconfig"), "w", encoding="utf-8", newline="\n") as f: + f.write("\n".join(BASE + extra) + "\n") + sarif = os.path.join(work, "out.sarif") + cmd = [ + "dotnet", + "build", + "-c", + "Release", + "-nologo", + "-v", + "q", + f"-p:NuGetRoot={a.nuget}", + f"-p:N1ErrorLog={sarif}", + "--configfile", + os.path.join(a.work, "nuget.config"), + ] + env = dict(os.environ, DOTNET_CLI_UI_LANGUAGE="en") + p = subprocess.run( + cmd, + cwd=work, + env=env, + capture_output=True, + text=True, + encoding="utf-8", + errors="replace", + ) + n_err = 0 + for run in json.load(open(sarif, encoding="utf-8")).get("runs", []): + for r in run.get("results", []): + n_err += r.get("level") == "error" + msg = r.get("message", "") + text = msg.get("text", "") if isinstance(msg, dict) else str(msg) + rule = r.get("ruleId", "") + if rule not in DISPOSAL_RULES and "dispos" not in text.lower(): + continue + for loc in r.get("locations", []): + pl = loc.get("physicalLocation") or loc.get("resultFile") or {} + art = pl.get("artifactLocation", {}).get("uri", "") or pl.get("uri", "") + uri, line = os.path.basename(art), pl.get("region", {}).get("startLine", 0) + for (f, cls), (lo, hi) in spans.items(): + if f == uri and lo <= line <= hi: + rows.append((k, cls, uri, line, rule, text.replace("\t", " "))) + rows.append((k, "_build", f"rc={p.returncode}", 0, f"errors={n_err}", "")) + with open( + os.path.join(a.out, "f0a_method_diagnostics.tsv"), "w", encoding="utf-8", newline="\n" + ) as f: + f.write("config\tclass\tfile\tline\trule\tmessage\n") + for r in sorted(set(rows)): + f.write("\t".join(map(str, r)) + "\n") + with open(os.path.join(a.out, "f0a_spans.tsv"), "w", encoding="utf-8", newline="\n") as f: + for (fn, cls), (lo, hi) in sorted(spans.items()): + f.write(f"{fn}\t{cls}\t{lo}\t{hi}\n") + print(open(os.path.join(a.out, "f0a_method_diagnostics.tsv"), encoding="utf-8").read()) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From ac094bd5cd35399356891e20966b5698204cdc1d Mon Sep 17 00:00:00 2001 From: PhysShell <45852143+PhysShell@users.noreply.github.com> Date: Tue, 20 Oct 2026 21:59:11 +0500 Subject: [PATCH 3/3] =?UTF-8?q?docs(evidence):=20N1-F0=20=E2=80=94=20KILL-?= =?UTF-8?q?N1=20at=20F0-B=20(may-throw=20model,=20not=20ownership)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit F0-A pass: R, X, B stay missed under every CA2000 interprocedural kind with all disposal diagnostics in the method collected. F0-B fail: the frozen oracle establishes ownership on all three real base files via repository-internal disposal (no IDisposable/Create*/await cue) and is silent on N1-N9, but flags the BareWire fix because the registered "any invocation may throw" rule counts Guid.NewGuid() before the early hand-over. F0-C: existing acquire/release/throw-edge facts suffice, but the existing StatementMayThrow is the same coarse model. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TFkNAFFQz9KoTzpPws8vYG --- docs/evidence/n1-f0-2026-10/REPORT.md | 107 ++++++++++++++++++ .../data/f0a_method_diagnostics.tsv | 31 +++++ .../evidence/n1-f0-2026-10/data/f0a_spans.tsv | 4 + .../n1-f0-2026-10/data/f0b_inputs.sha256 | 8 ++ docs/evidence/n1-f0-2026-10/data/f0b_real.tsv | 5 + 5 files changed, 155 insertions(+) create mode 100644 docs/evidence/n1-f0-2026-10/REPORT.md create mode 100644 docs/evidence/n1-f0-2026-10/data/f0a_method_diagnostics.tsv create mode 100644 docs/evidence/n1-f0-2026-10/data/f0a_spans.tsv create mode 100644 docs/evidence/n1-f0-2026-10/data/f0b_inputs.sha256 create mode 100644 docs/evidence/n1-f0-2026-10/data/f0b_real.tsv diff --git a/docs/evidence/n1-f0-2026-10/REPORT.md b/docs/evidence/n1-f0-2026-10/REPORT.md new file mode 100644 index 00000000..cfd9d56e --- /dev/null +++ b/docs/evidence/n1-f0-2026-10/REPORT.md @@ -0,0 +1,107 @@ +# N1-F0 — ownership feasibility gate — report + +**Result: `KILL-N1`. The first failing part is F0-B, and the failure is in the may-throw model, not in ownership.** + +| Part | Verdict | Short reason | +|---|---|---| +| F0-A competitor check | **pass** | R, X, B stay `MISSED` under every `interprocedural_analysis_kind` and ownership-transfer setting, with every disposal-related diagnostic inside the method collected | +| F0-B ownership oracle | **fail** | ownership is established on all three real base files without name/type/`await` heuristics, but the **BareWire fixed file is flagged**: `Guid.NewGuid().ToString("N")` counts as a may-throw call before the hand-over | +| F0-C cost | pass (not decisive) | acquisition, hand-over and throw edges fit existing OwnIR ops; the existing throw model is the same coarse one that failed F0-B | + +Rules: [`00-preregistration.md`](00-preregistration.md), committed in `66731cc2` before any F0 run. The oracle was +frozen in `081b68ee`, after its control-only debugging and before the one scored run on real files. Time used: +21:51–21:58 +05 on 2026-10-20 for the runs, inside the 0.25 agent-day budget. + +No production, OwnIR, CI or golden change. Nothing third-party was built or downloaded. + +## F0-A + +[`scripts/run_f0a.py`](scripts/run_f0a.py) rebuilt the #417 reproductions under 11 configurations: A1–A6 cover +`interprocedural_analysis_kind` `None` / `NonContextSensitive` / `ContextSensitive`, each with and without both +ownership-transfer switches; K1–K5 repeat the #417 set. The collector takes every disposal-related diagnostic +located inside the buggy method's line span (`data/f0a_spans.tsv`), not only on the `ACQ` line. + +Result (`data/f0a_method_diagnostics.tsv`): + +| Pair | Result | +|---|---| +| W | CA2000 on `new ServiceBusClient(…)` in A1–A6 and K2–K4 (`CAUGHT`) | +| R | only CA2000 on `out var oldClient`, line 28, in all 11 configurations. That is another object, `ADJACENT`, so R stays `MISSED` | +| X | nothing (`MISSED`) | +| B | nothing (`MISSED`) | + +3 pairs missed, against the required ≥ 2, so F0-A passes. No interprocedural setting changes anything. + +## F0-B + +**The oracle.** [`oracle/Program.cs`](oracle/Program.cs), a Roslyn scan, applies rules 1–3 as registered. +Ownership needs a hand-over target that the repository itself disposes, and the borrow table only suppresses. Two +details were fixed in the code header before the real run: + +- **"Disposes r"** means any of: `Dispose`/`DisposeAsync`/`Close`/`CloseAsync` on `r`; the same call on an owning + wrapper `new W(r)`; or `r` passed to a method of the same type that disposes its parameter. +- **One control-driven fix.** A dispose of `r` inside the `catch` is not a throwing site (control N6). + +**Controls** (`data/f0b_controls.tsv`). N1–N9 are silent: DI-resolved, borrowed parameter, shared and cached +instances never disposed, hand-over first, `try`/`catch` dispose, `published` flag, `using`, returned. P-R, P-B and +P-X are reported. + +**Real files** (`data/f0b_real.tsv`). Single files at the pairs' SHAs (hashes in `data/f0b_inputs.sha256`), parsed +with framework references only. + +| File | Oracle | Ownership evidence it found | +|---|---|---| +| R base `McpBridgeService.cs` | reported at `McpClient.CreateAsync` (l. 561); throw at `await newClient.ListToolsAsync` (l. 564); hand-over `_clients[name]` (l. 577) | `_clients` element disposed: `oldClient.DisposeAsync()` (l. 573), with `oldClient` taken by `_clients.Remove(name, out var oldClient)` | +| R fix | silent | **no owned acquisition**: the fix hands the client over through `new ConnectionResources(newClient)` assigned to a local and an object-initializer member, which rules 1a–1c do not cover. Silent, but not because the cleanup was recognised | +| X base `PluginHostBuilder.cs` + `PluginHost.cs` | reported at `BuildServiceProvider` (l. 90); first throw at `provider.GetService<…>()` (l. 92); hand-over `return new PluginHost(provider)` (l. 136) | wrapper rule 1c: `PluginHost.mProvider` disposed at `PluginHost.cs:27` | +| X fix | silent | the `catch` disposes `new PluginHost(provider)`, an owning wrapper | +| B base `RabbitMqTransportAdapter.cs` | reported at `CreateChannelAsync` (l. 285); first throw listed at `Math.Min(…)` (l. 294), and `BasicQosAsync` lies in the same window; hand-over `_activeConsumerChannels[id]` (l. 321) | `channel.CloseAsync(…)` (l. 249) on a channel taken from `_activeConsumerChannels` | +| **B fix** | **reported** (FP) | ownership is right, but the only calls between `CreateChannelAsync` (l. 286) and the early hand-over (l. 300) are `Guid.NewGuid().ToString("N")` (l. 299). The registered rule "any invocation can throw" counts them | + +By the registered pass rule, which requires silence on every fix file, F0-B fails. The oracle was not changed after +this run. + +**What the failure means.** +- **Confirmed.** The ownership half works on these inputs: + - it found the disposing line for all three real targets; + - it stayed silent on all nine negative controls without any `IDisposable`, `Create*` or `await` cue; + - in X and B it established ownership across a wrapper and across a collection. +- **Confirmed.** The registered may-throw rule is too coarse. One of three fixes is flagged only because of + non-throwing BCL calls. +- **Inference, not scored.** A may-throw model that exempts such calls would clear B fix while keeping B base, + because `BasicQosAsync` lies inside its window. Nothing was run to show it. +- **Limit.** The R fix shows a recall gap: a hand-over through a local wrapper or an object initializer establishes + no ownership. A bug in that shape would be missed. + +## F0-C + +Read with line references in `main` (`38fb952d`): + +1. **Acquisition from a call.** Already expressible: the flow-locals pass emits `acquire` for `new`, pool rents and a + curated owning-factory table (`IsOwningFactory`, `frontend/roslyn/OwnSharp.Extractor/Program.cs:3832-3837`). + For first-party factories the core can mint the acquire itself from a `call` op (P-005 D5.2, + `Program.cs:3849`). An oracle-based acquire would be extractor-side only. +2. **Hand-over.** The existing `release` op can discharge the obligation (`ownlang/ownir.py:343-347`, + `_FLOW_OPS`). Today a local stored to a field "escapes" and is not flow-tracked at all (`--flow-locals` covers + non-escaping locals only, `Program.cs:84-87`). Changing that is extractor-side work. +3. **Throw edges.** These exist (`InjectThrowEdge`, `Program.cs:3569-3587`, `--body-throw-edges`). They use + `StatementMayThrow` (`Program.cs:3590-3599`): any non-dispose call or `new` may throw. That is the same coarse + model that failed F0-B, and the extractor itself calls this tier "the CA2000 firehose" (`Program.cs:100-103`). + +None of the three needs a new OwnIR op, a version bump or a core interprocedural pass, so F0-C passes by its rule. +It also shows that a precise may-throw model does not exist yet; it would be new extractor work. + +Estimate, an inference: the oracle port plus escape tracking is roughly several days of extractor work. A may-throw +model precise enough to clear cases like B fix has no estimate, because nothing like it exists in the repository. + +## Files + +- [`00-preregistration.md`](00-preregistration.md) +- [`controls/OwnershipControls.cs`](controls/OwnershipControls.cs) +- [`oracle/`](oracle/): the oracle, a Roslyn console, built like the #416 PoC. +- [`scripts/run_f0a.py`](scripts/run_f0a.py) +- `data/`: + - `f0a_method_diagnostics.tsv`, `f0a_spans.tsv`: F0-A; + - `f0b_controls.tsv`, `f0b_real.tsv`, `f0b_inputs.sha256`: F0-B. + +The third-party source files are not committed; they are named by SHA and hash. diff --git a/docs/evidence/n1-f0-2026-10/data/f0a_method_diagnostics.tsv b/docs/evidence/n1-f0-2026-10/data/f0a_method_diagnostics.tsv new file mode 100644 index 00000000..c6040b77 --- /dev/null +++ b/docs/evidence/n1-f0-2026-10/data/f0a_method_diagnostics.tsv @@ -0,0 +1,31 @@ +config class file line rule message +A1 RBuggy R.cs 28 CA2000 Use recommended dispose pattern to ensure that object created by 'out var oldClient' is disposed on all paths. If possible, wrap the creation within a 'using' statement or a 'using' declaration. Otherwise, use a try-finally pattern, with a dedicated local variable declared before the try region and an unconditional Dispose invocation on non-null value in the 'finally' region, say 'x?.Dispose()'. If the object is explicitly disposed within the try region or the dispose ownership is transfered to another object or method, assign 'null' to the local variable just after such an operation to prevent double dispose in 'finally'. +A1 WBuggy W.cs 23 CA2000 Use recommended dispose pattern to ensure that object created by 'new ServiceBusClient(connectionString)' is disposed on all exception paths. If possible, wrap the creation within a 'using' statement or a 'using' declaration. Otherwise, use a try-finally pattern, with a dedicated local variable declared before the try region and an unconditional Dispose invocation on non-null value in the 'finally' region, say 'x?.Dispose()'. If the object is explicitly disposed within the try region or the dispose ownership is transfered to another object or method, assign 'null' to the local variable just after such an operation to prevent double dispose in 'finally'. +A1 _build rc=0 0 errors=0 +A2 RBuggy R.cs 28 CA2000 Use recommended dispose pattern to ensure that object created by 'out var oldClient' is disposed on all paths. If possible, wrap the creation within a 'using' statement or a 'using' declaration. Otherwise, use a try-finally pattern, with a dedicated local variable declared before the try region and an unconditional Dispose invocation on non-null value in the 'finally' region, say 'x?.Dispose()'. If the object is explicitly disposed within the try region or the dispose ownership is transfered to another object or method, assign 'null' to the local variable just after such an operation to prevent double dispose in 'finally'. +A2 WBuggy W.cs 23 CA2000 Use recommended dispose pattern to ensure that object created by 'new ServiceBusClient(connectionString)' is disposed on all exception paths. If possible, wrap the creation within a 'using' statement or a 'using' declaration. Otherwise, use a try-finally pattern, with a dedicated local variable declared before the try region and an unconditional Dispose invocation on non-null value in the 'finally' region, say 'x?.Dispose()'. If the object is explicitly disposed within the try region or the dispose ownership is transfered to another object or method, assign 'null' to the local variable just after such an operation to prevent double dispose in 'finally'. +A2 _build rc=0 0 errors=0 +A3 RBuggy R.cs 28 CA2000 Use recommended dispose pattern to ensure that object created by 'out var oldClient' is disposed on all paths. If possible, wrap the creation within a 'using' statement or a 'using' declaration. Otherwise, use a try-finally pattern, with a dedicated local variable declared before the try region and an unconditional Dispose invocation on non-null value in the 'finally' region, say 'x?.Dispose()'. If the object is explicitly disposed within the try region or the dispose ownership is transfered to another object or method, assign 'null' to the local variable just after such an operation to prevent double dispose in 'finally'. +A3 WBuggy W.cs 23 CA2000 Use recommended dispose pattern to ensure that object created by 'new ServiceBusClient(connectionString)' is disposed on all exception paths. If possible, wrap the creation within a 'using' statement or a 'using' declaration. Otherwise, use a try-finally pattern, with a dedicated local variable declared before the try region and an unconditional Dispose invocation on non-null value in the 'finally' region, say 'x?.Dispose()'. If the object is explicitly disposed within the try region or the dispose ownership is transfered to another object or method, assign 'null' to the local variable just after such an operation to prevent double dispose in 'finally'. +A3 _build rc=0 0 errors=0 +A4 RBuggy R.cs 28 CA2000 Use recommended dispose pattern to ensure that object created by 'out var oldClient' is disposed on all paths. If possible, wrap the creation within a 'using' statement or a 'using' declaration. Otherwise, use a try-finally pattern, with a dedicated local variable declared before the try region and an unconditional Dispose invocation on non-null value in the 'finally' region, say 'x?.Dispose()'. If the object is explicitly disposed within the try region or the dispose ownership is transfered to another object or method, assign 'null' to the local variable just after such an operation to prevent double dispose in 'finally'. +A4 WBuggy W.cs 23 CA2000 Use recommended dispose pattern to ensure that object created by 'new ServiceBusClient(connectionString)' is disposed on all exception paths. If possible, wrap the creation within a 'using' statement or a 'using' declaration. Otherwise, use a try-finally pattern, with a dedicated local variable declared before the try region and an unconditional Dispose invocation on non-null value in the 'finally' region, say 'x?.Dispose()'. If the object is explicitly disposed within the try region or the dispose ownership is transfered to another object or method, assign 'null' to the local variable just after such an operation to prevent double dispose in 'finally'. +A4 _build rc=0 0 errors=0 +A5 RBuggy R.cs 28 CA2000 Use recommended dispose pattern to ensure that object created by 'out var oldClient' is disposed on all paths. If possible, wrap the creation within a 'using' statement or a 'using' declaration. Otherwise, use a try-finally pattern, with a dedicated local variable declared before the try region and an unconditional Dispose invocation on non-null value in the 'finally' region, say 'x?.Dispose()'. If the object is explicitly disposed within the try region or the dispose ownership is transfered to another object or method, assign 'null' to the local variable just after such an operation to prevent double dispose in 'finally'. +A5 WBuggy W.cs 23 CA2000 Use recommended dispose pattern to ensure that object created by 'new ServiceBusClient(connectionString)' is disposed on all exception paths. If possible, wrap the creation within a 'using' statement or a 'using' declaration. Otherwise, use a try-finally pattern, with a dedicated local variable declared before the try region and an unconditional Dispose invocation on non-null value in the 'finally' region, say 'x?.Dispose()'. If the object is explicitly disposed within the try region or the dispose ownership is transfered to another object or method, assign 'null' to the local variable just after such an operation to prevent double dispose in 'finally'. +A5 _build rc=0 0 errors=0 +A6 RBuggy R.cs 28 CA2000 Use recommended dispose pattern to ensure that object created by 'out var oldClient' is disposed on all paths. If possible, wrap the creation within a 'using' statement or a 'using' declaration. Otherwise, use a try-finally pattern, with a dedicated local variable declared before the try region and an unconditional Dispose invocation on non-null value in the 'finally' region, say 'x?.Dispose()'. If the object is explicitly disposed within the try region or the dispose ownership is transfered to another object or method, assign 'null' to the local variable just after such an operation to prevent double dispose in 'finally'. +A6 WBuggy W.cs 23 CA2000 Use recommended dispose pattern to ensure that object created by 'new ServiceBusClient(connectionString)' is disposed on all exception paths. If possible, wrap the creation within a 'using' statement or a 'using' declaration. Otherwise, use a try-finally pattern, with a dedicated local variable declared before the try region and an unconditional Dispose invocation on non-null value in the 'finally' region, say 'x?.Dispose()'. If the object is explicitly disposed within the try region or the dispose ownership is transfered to another object or method, assign 'null' to the local variable just after such an operation to prevent double dispose in 'finally'. +A6 _build rc=0 0 errors=0 +K1 RBuggy R.cs 28 CA2000 Use recommended dispose pattern to ensure that object created by 'out var oldClient' is disposed on all paths. If possible, wrap the creation within a 'using' statement or a 'using' declaration. Otherwise, use a try-finally pattern, with a dedicated local variable declared before the try region and an unconditional Dispose invocation on non-null value in the 'finally' region, say 'x?.Dispose()'. If the object is explicitly disposed within the try region or the dispose ownership is transfered to another object or method, assign 'null' to the local variable just after such an operation to prevent double dispose in 'finally'. +K1 _build rc=0 0 errors=0 +K2 RBuggy R.cs 28 CA2000 Use recommended dispose pattern to ensure that object created by 'out var oldClient' is disposed on all paths. If possible, wrap the creation within a 'using' statement or a 'using' declaration. Otherwise, use a try-finally pattern, with a dedicated local variable declared before the try region and an unconditional Dispose invocation on non-null value in the 'finally' region, say 'x?.Dispose()'. If the object is explicitly disposed within the try region or the dispose ownership is transfered to another object or method, assign 'null' to the local variable just after such an operation to prevent double dispose in 'finally'. +K2 WBuggy W.cs 23 CA2000 Use recommended dispose pattern to ensure that object created by 'new ServiceBusClient(connectionString)' is disposed on all exception paths. If possible, wrap the creation within a 'using' statement or a 'using' declaration. Otherwise, use a try-finally pattern, with a dedicated local variable declared before the try region and an unconditional Dispose invocation on non-null value in the 'finally' region, say 'x?.Dispose()'. If the object is explicitly disposed within the try region or the dispose ownership is transfered to another object or method, assign 'null' to the local variable just after such an operation to prevent double dispose in 'finally'. +K2 _build rc=0 0 errors=0 +K3 RBuggy R.cs 28 CA2000 Use recommended dispose pattern to ensure that object created by 'out var oldClient' is disposed on all paths. If possible, wrap the creation within a 'using' statement or a 'using' declaration. Otherwise, use a try-finally pattern, with a dedicated local variable declared before the try region and an unconditional Dispose invocation on non-null value in the 'finally' region, say 'x?.Dispose()'. If the object is explicitly disposed within the try region or the dispose ownership is transfered to another object or method, assign 'null' to the local variable just after such an operation to prevent double dispose in 'finally'. +K3 WBuggy W.cs 23 CA2000 Use recommended dispose pattern to ensure that object created by 'new ServiceBusClient(connectionString)' is disposed on all exception paths. If possible, wrap the creation within a 'using' statement or a 'using' declaration. Otherwise, use a try-finally pattern, with a dedicated local variable declared before the try region and an unconditional Dispose invocation on non-null value in the 'finally' region, say 'x?.Dispose()'. If the object is explicitly disposed within the try region or the dispose ownership is transfered to another object or method, assign 'null' to the local variable just after such an operation to prevent double dispose in 'finally'. +K3 _build rc=0 0 errors=0 +K4 RBuggy R.cs 28 CA2000 Use recommended dispose pattern to ensure that object created by 'out var oldClient' is disposed on all paths. If possible, wrap the creation within a 'using' statement or a 'using' declaration. Otherwise, use a try-finally pattern, with a dedicated local variable declared before the try region and an unconditional Dispose invocation on non-null value in the 'finally' region, say 'x?.Dispose()'. If the object is explicitly disposed within the try region or the dispose ownership is transfered to another object or method, assign 'null' to the local variable just after such an operation to prevent double dispose in 'finally'. +K4 WBuggy W.cs 23 CA2000 Use recommended dispose pattern to ensure that object created by 'new ServiceBusClient(connectionString)' is disposed on all exception paths. If possible, wrap the creation within a 'using' statement or a 'using' declaration. Otherwise, use a try-finally pattern, with a dedicated local variable declared before the try region and an unconditional Dispose invocation on non-null value in the 'finally' region, say 'x?.Dispose()'. If the object is explicitly disposed within the try region or the dispose ownership is transfered to another object or method, assign 'null' to the local variable just after such an operation to prevent double dispose in 'finally'. +K4 _build rc=0 0 errors=0 +K5 _build rc=0 0 errors=0 diff --git a/docs/evidence/n1-f0-2026-10/data/f0a_spans.tsv b/docs/evidence/n1-f0-2026-10/data/f0a_spans.tsv new file mode 100644 index 00000000..65031f85 --- /dev/null +++ b/docs/evidence/n1-f0-2026-10/data/f0a_spans.tsv @@ -0,0 +1,4 @@ +B.cs BBuggy 17 41 +R.cs RBuggy 16 48 +W.cs WBuggy 13 33 +X.cs XBuggy 23 42 diff --git a/docs/evidence/n1-f0-2026-10/data/f0b_inputs.sha256 b/docs/evidence/n1-f0-2026-10/data/f0b_inputs.sha256 new file mode 100644 index 00000000..dc4cc956 --- /dev/null +++ b/docs/evidence/n1-f0-2026-10/data/f0b_inputs.sha256 @@ -0,0 +1,8 @@ +35a4ded5b111f95e4e236b87f9e05f552a17257f8aa7448da032160a1c02cca0 *rockbot.base.cs +b1ff628215df8cebeae4e6b9e33155ba7a0bf3d48ddc1453a0705ad98c1111ed *rockbot.fix.cs +3315ce0344471803113de639268f52db3dd6f1f1d9b7e8e275eb1ac520d87f4e *barewire.RabbitMqTransportAdapter.base.cs +d660fc1b2e9cb258605d6d1d339df94287045a137277436975770b6e70843c82 *barewire.RabbitMqTransportAdapter.fix.cs +447fdd3f6dfa571d444ab1601b9fd89ac41167d205d6c4fb31d8162a12325d58 *nexuskit.PluginHost.base.cs +447fdd3f6dfa571d444ab1601b9fd89ac41167d205d6c4fb31d8162a12325d58 *nexuskit.PluginHost.fix.cs +cc098126cb1a9611d4f8e7ae6758abc16fc6e4b9bc7d8322ff4fb73a32f2e10a *nexuskit.base.cs +f93b0e736d627ccea98437ee28b8bac6d00bb36fef7f60b7ef52b69cdef0993b *nexuskit.fix.cs diff --git a/docs/evidence/n1-f0-2026-10/data/f0b_real.tsv b/docs/evidence/n1-f0-2026-10/data/f0b_real.tsv new file mode 100644 index 00000000..06ff8316 --- /dev/null +++ b/docs/evidence/n1-f0-2026-10/data/f0b_real.tsv @@ -0,0 +1,5 @@ +group file type method acq_line acq handover_line kind dispose_evidence throw_line throw_site +R.base rockbot.base.cs McpBridgeService ConnectServerAsync 561 McpClient.CreateAsync(transport, clientOptions, cancellationToken: ct) 577 1b _clients[] 573: oldClient.DisposeAsync() 564 await newClient.ListToolsAsync(cancellationToken: ct) +B.base barewire.RabbitMqTransportAdapter.base.cs RabbitMqTransportAdapter ConsumeAsync 285 _connection! .CreateChannelAsync( new CreateChannelOptions( publisherConfirmationsEnabled:… 321 1b _activeConsumerChannels[] 249: channel.CloseAsync(cancellationToken) 294 Math.Min(flowControl.MaxInFlightMessages, ushort.MaxValue) +B.fix barewire.RabbitMqTransportAdapter.fix.cs RabbitMqTransportAdapter ConsumeAsync 286 _connection! .CreateChannelAsync( new CreateChannelOptions( publisherConfirmationsEnabled:… 300 1b _activeConsumerChannels[] 250: channel.CloseAsync(cancellationToken) 299 Guid.NewGuid().ToString("N") +X.base nexuskit.base.cs PluginHostBuilder BuildAsync 90 services.BuildServiceProvider(validateScopes: false) 136 1c PluginHost PluginHost.mProvider disposed at 27: mProvider.DisposeAsync() 92 provider.GetService>()