From e524972d5a5489034333fd163c6afea60e8623b4 Mon Sep 17 00:00:00 2001 From: PhysShell <45852143+PhysShell@users.noreply.github.com> Date: Sat, 10 Oct 2026 10:02:47 +0000 Subject: [PATCH 1/7] =?UTF-8?q?docs(h28):=20resolve=20the=20DbCommand=20re?= =?UTF-8?q?sidual=20=E2=80=94=20H28-PROVIDER-SPECIFIC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit H-28 (research/ownership-semantics-lab-v1 @ 298b305, not on main) settled DataTable.Load(reader) with a runtime falsifier: the reader IS closed, so the presumed BORROW was refuted and the demanding instance (victor-wiki/DatabaseManager DbInterpreter.GetDataTableAsync:699) is not a protocol leak. The same record then wrote one sentence and never classified it: "what remains is the never-disposed DbCommand `cmd` (an object leak without a protocol consequence)" That sentence is a description, not a verdict: it was never falsified, never entered the witnessed-callee table, never reached a gate. This closes it. VERDICT: H28-PROVIDER-SPECIFIC. The absence of DbCommand.Dispose() in this family is not one fact. * Npgsql 10.0.3 (and main, identical): NpgsqlCommand has no finalizer, both ctors GC.SuppressFinalize, and Dispose(bool) only nulls _transaction, sets a managed state flag and optionally recycles the command into the connection's one-slot CachedCommand. Reset() is managed-only. Server-side prepared statements belong to NpgsqlConnector.PreparedStatementManager and are never DEALLOCATEd by the command. => releases nothing. * Microsoft.Data.SqlClient, MySqlConnector: managed-only Dispose, no finalizer. => releases nothing. * Microsoft.Data.Sqlite: SqliteCommand.Dispose -> DisposePreparedStatements finalizes native sqlite3_stmt SafeHandles (ReleaseHandle -> sqlite3_finalize) AND disposes its reader. A plain ExecuteReader() runs PrepareAndEnumerateStatements(), which puts those handles on the COMMAND; SqliteDataReader.Close()/Dispose() does not finalize them. => the command is the only deterministic releaser. REAL defect. So the old residual is true for Npgsql and false for Microsoft.Data.Sqlite; it was written from a two-provider falsifier whose SQLite arm never looked at the command. Sharper witness found on the way (pivot recorded in the report): the ownership relation connection -> command -> reader is not what either side assumes. NpgsqlCommand.CurrentActivity (System.Diagnostics.Activity IS IDisposable in .NET 8 and 10) is stopped by exactly two call sites repo-wide, both in NpgsqlDataReader.Dispose/DisposeAsync -> Command.TraceCommandStop(). Neither NpgsqlDataReader.Close() nor NpgsqlCommand.Dispose() calls it — and DataTable.Load calls Close(). With a tracing listener attached the span never ends. Meanwhile reader Cleanup sets Command.State = Idle, which makes the command reusable: that is not ownership. CURRENT OWN.NET (extractor @ e889f8b, source-derived; engine executed): IsOwningFactory mints an owned local for IDbConnection.CreateCommand() (L4880-4882 -> L7035), IsDisposeOptional does not cover DbCommand (L2593), and the argument-escape rule untracks the reader at table.Load(reader) (L7155-7182). Predicted: OWN001 (error) on `command`, silence on `reader`. The engine half reproduces exactly that (evidence/engine-CommandDispose.txt: 2x command in B/E + 1x reader in E, A/D clean). On Npgsql with tracing on the verdict is inverted — error on the object whose Dispose frees nothing, silence on the one whose disposal is load-bearing. The escape drop is also only accidentally right: DataTable.Load closes the reader IFF !reader.NextResult(), so a multi-result-set reader is left OPEN and the drop is a false negative there. NO PRODUCTION CHANGE. Nothing here touches the extractor, the core, the vocabulary or a diagnostic. corpus/ownership-lab/ is not globbed by tests/test_corpus.py (still 33/33). The model gap is shown to be an instance of #382 — which already names this lowering and this exact loss — with proven_call (OwnIR v2, H1) as the landed transport precedent; section 10 proposes the smallest experiment and explicitly not a lattice, a new code, or provider special-casing. Deliverables: docs/notes/h28-npgsql-command-resolution.md plus corpus/ownership-lab/h28-command/ — fx/CommandDispose.cs (9 variants, abstract ADO.NET types on purpose: that is what IsOwningFactory matches), a runnable falsifier pinned to net8.0/Npgsql 10.0.3/MS.Data.Sqlite 10.0.12 with probes P1-P6 each chosen to separate two hypotheses, and 32 source citations auto-derived from pinned refs (blob sha + sha256 + line range) by scripts/derive_source_evidence.py, which exits non-zero when the note goes stale. Honest limits, stated in the report rather than papered over: there is no .NET SDK and no reachable NuGet feed in the producing sandbox, so the Roslyn extractor and the runtime falsifier were NOT executed. fx/CommandDispose.cs expectations and probes P2/P4/P6 are marked PREDICTED; run.sh runtime prints SKIP-WHY instead of faking it. What WAS executed: the engine half, all 32 source derivations, and a live PostgreSQL 16.2 (pgserver) used to pin that prepared statements and cursors are session-scoped — independently corroborating that no command-level Dispose can be their releaser. Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com> --- corpus/ownership-lab/h28-command/README.txt | 59 +++ .../h28-command/evidence/MANIFEST.txt | 34 ++ .../evidence/engine-CommandDispose.txt | 18 + .../mssqlite-SqliteCommand-Dispose.txt | 22 + ...qliteCommand-DisposePreparedStatements.txt | 32 ++ ...SqliteCommand-PrepareAndEnumerate-head.txt | 16 + ...SqliteCommand-preparedStatements-field.txt | 12 + ...ite-SqliteConnection-commands-weakrefs.txt | 12 + ...ssqlite-SqliteDataReader-Close-Dispose.txt | 52 +++ .../mysqlconnector-MySqlCommand-Dispose.txt | 16 + ...-10.0.3-NpgsqlActivitySource-IsEnabled.txt | 12 + ...10.0.3-NpgsqlActivitySource-SourceName.txt | 13 + ....0.3-NpgsqlCommand-CreateCachedCommand.txt | 13 + .../npgsql-10.0.3-NpgsqlCommand-Dispose.txt | 26 ++ .../npgsql-10.0.3-NpgsqlCommand-Reset.txt | 24 + ...10.0.3-NpgsqlCommand-TraceCommandStart.txt | 36 ++ ...-10.0.3-NpgsqlCommand-TraceCommandStop.txt | 18 + ....3-NpgsqlCommand-ctor-SuppressFinalize.txt | 20 + ...-10.0.3-NpgsqlConnection-CreateCommand.txt | 25 ++ ...gsqlConnector-PreparedStatementManager.txt | 12 + ...npgsql-10.0.3-NpgsqlDataReader-Cleanup.txt | 90 ++++ ...l-10.0.3-NpgsqlDataReader-Close-public.txt | 12 + ...npgsql-10.0.3-NpgsqlDataReader-Dispose.txt | 36 ++ .../npgsql-main-NpgsqlCommand-Dispose.txt | 26 ++ ...sql-main-NpgsqlDataReader-Close-public.txt | 12 + .../runtime-v10.0.12-DataTable-Load.txt | 40 ++ ...untime-v10.0.12-DbCommand-DisposeAsync.txt | 16 + .../runtime-v10.0.12-DbCommand-class.txt | 14 + ...me-v10.0.12-DbDataReader-Close-Dispose.txt | 42 ++ .../evidence/runtime-v10.0.12-IDbCommand.txt | 13 + .../runtime-v8.0.20-Activity-Dispose.txt | 22 + .../runtime-v8.0.20-Activity-IDisposable.txt | 12 + .../runtime-v8.0.20-DataTable-Load.txt | 40 ++ .../evidence/sqlclient-SqlCommand-Dispose.txt | 24 + .../sqlitepclraw-sqlite3_stmt-SafeHandle.txt | 36 ++ .../h28-command/falsifier/Program.cs | 254 +++++++++++ .../h28-command/falsifier/h28cmd.csproj | 23 + .../h28-command/fx/CommandDispose.cs | 163 +++++++ .../h28-command/fx/CommandDispose.own | 70 +++ corpus/ownership-lab/h28-command/run.sh | 100 +++++ .../scripts/derive_source_evidence.py | 322 +++++++++++++ docs/notes/h28-npgsql-command-resolution.md | 423 ++++++++++++++++++ 42 files changed, 2262 insertions(+) create mode 100644 corpus/ownership-lab/h28-command/README.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/MANIFEST.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/engine-CommandDispose.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-Dispose.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-DisposePreparedStatements.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-PrepareAndEnumerate-head.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-preparedStatements-field.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteConnection-commands-weakrefs.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteDataReader-Close-Dispose.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/mysqlconnector-MySqlCommand-Dispose.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlActivitySource-IsEnabled.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlActivitySource-SourceName.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-CreateCachedCommand.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-Dispose.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-Reset.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-TraceCommandStart.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-TraceCommandStop.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-ctor-SuppressFinalize.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlConnection-CreateCommand.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlConnector-PreparedStatementManager.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-Cleanup.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-Close-public.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-Dispose.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/npgsql-main-NpgsqlCommand-Dispose.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/npgsql-main-NpgsqlDataReader-Close-public.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-DataTable-Load.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-DbCommand-DisposeAsync.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-DbCommand-class.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-DbDataReader-Close-Dispose.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-IDbCommand.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/runtime-v8.0.20-Activity-Dispose.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/runtime-v8.0.20-Activity-IDisposable.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/runtime-v8.0.20-DataTable-Load.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/sqlclient-SqlCommand-Dispose.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/sqlitepclraw-sqlite3_stmt-SafeHandle.txt create mode 100644 corpus/ownership-lab/h28-command/falsifier/Program.cs create mode 100644 corpus/ownership-lab/h28-command/falsifier/h28cmd.csproj create mode 100644 corpus/ownership-lab/h28-command/fx/CommandDispose.cs create mode 100644 corpus/ownership-lab/h28-command/fx/CommandDispose.own create mode 100755 corpus/ownership-lab/h28-command/run.sh create mode 100755 corpus/ownership-lab/h28-command/scripts/derive_source_evidence.py create mode 100644 docs/notes/h28-npgsql-command-resolution.md diff --git a/corpus/ownership-lab/h28-command/README.txt b/corpus/ownership-lab/h28-command/README.txt new file mode 100644 index 00000000..4688ff5d --- /dev/null +++ b/corpus/ownership-lab/h28-command/README.txt @@ -0,0 +1,59 @@ +H-28-CMD: the DbCommand half of the `DataTable.Load(reader)` family. + +WHAT THIS IS + The original H-28 (corpus/ownership-lab/h28/, on the research branch + research/ownership-semantics-lab-v1 at 298b305 -- NOT on main) asked about argument + ownership transfer. Its runtime falsifier settled `DataTable.Load(reader)`: the reader IS + closed (RELEASE_IF_LAST_RESULT_SET, not BORROW). The same record then wrote down one + sentence and never classified it: + + "what remains is the never-disposed DbCommand `cmd` (an object leak without a + protocol consequence)" + -- h28/scripts/h28_anchors_record.py, consequence_for_the_demanding_instance + + This directory closes that sentence. Read docs/notes/h28-npgsql-command-resolution.md. + + Verdict: H28-PROVIDER-SPECIFIC. Not disposing the command is a real resource defect on + Microsoft.Data.Sqlite and releases nothing on Npgsql / Microsoft.Data.SqlClient / + MySqlConnector. Details, citations and falsifiers are in the report. + +LAYOUT + fx/CommandDispose.cs the fixture, 9 methods (A/B/C/D + 5 controls), written against the + ABSTRACT ADO.NET types on purpose -- that is what the extractor's + IsOwningFactory matches, so one fixture shows the analyzer's verdict + for every provider at once. Expectations are marked PREDICTED: the + Roslyn extractor was not runnable where this was produced. + fx/CommandDispose.own the ENGINE-RUNNABLE reduction of the same family. Needs only + python3. This half WAS executed; see evidence/. + falsifier/Program.cs the runtime falsifier: probes P1-P6 over variants A/B/C/D. + falsifier/h28cmd.csproj net8.0, Npgsql 10.0.3, Microsoft.Data.Sqlite 10.0.12 -- the exact + versions of the original H-28 falsifier, so the two runs compare. + run.sh driver. `engine` always runs; `runtime` prints SKIP-WHY and exits 0 + when there is no .NET SDK / NuGet / PostgreSQL, rather than faking it. + scripts/derive_source_evidence.py + re-fetches all 31 pinned source citations and rewrites evidence/. + Non-zero exit = the report's source arm is stale. + evidence/*.txt the artifacts every claim in the report cites. Each carries repo, + ref, path, git blob sha, file sha256 and the extracted line range, + so a quotation is checkable against git rather than against memory. + +REPRODUCE + corpus/ownership-lab/h28-command/run.sh engine # python3 only; ~1s + corpus/ownership-lab/h28-command/run.sh runtime # needs .NET 8 SDK + NuGet + PostgreSQL + python3 corpus/ownership-lab/h28-command/scripts/derive_source_evidence.py + + The runtime half starts PostgreSQL itself via pgserver (PyPI, self-contained PostgreSQL 16 on + a Unix socket) -- the same harness the original H-28 falsifier used -- or takes H28_PG_DSN. + +WHAT WAS AND WAS NOT EXECUTED WHERE THIS WAS PRODUCED + executed : the engine half (evidence/engine-CommandDispose.txt), the source derivation of + all 31 citations (evidence/*.txt + MANIFEST.txt), a live PostgreSQL 16.2 via + pgserver (used only for the server-side lifecycle checks quoted in the report). + NOT executed: the Roslyn extractor (no .NET SDK, no NuGet feed) and the runtime falsifier + (same). fx/CommandDispose.cs expectations and falsifier probes P2/P4/P6 are + therefore PREDICTED from source, and the report says so at each one. + +NO PRODUCTION CHANGE + Nothing here alters the extractor, the core, the vocabulary or any diagnostic. corpus/ + ownership-lab/ is not globbed by tests/test_corpus.py (only corpus/real-world/ is), so these + fixtures are inert data until someone decides to act on the report's section 10. diff --git a/corpus/ownership-lab/h28-command/evidence/MANIFEST.txt b/corpus/ownership-lab/h28-command/evidence/MANIFEST.txt new file mode 100644 index 00000000..b1a5c26d --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/MANIFEST.txt @@ -0,0 +1,34 @@ +# H-28-CMD evidence manifest: one line per pinned citation. +# OK = re-derived now; MISSING/MARKER-NOT-FOUND = the report is stale. +OK npgsql-10.0.3-NpgsqlCommand-Dispose.txt npgsql/npgsql@v10.0.3 sha256=6bdde5901d32a633 lines=1712 +OK npgsql-10.0.3-NpgsqlCommand-Reset.txt npgsql/npgsql@v10.0.3 sha256=6bdde5901d32a633 lines=1728 +OK npgsql-10.0.3-NpgsqlCommand-ctor-SuppressFinalize.txt npgsql/npgsql@v10.0.3 sha256=6bdde5901d32a633 lines=120 +OK npgsql-10.0.3-NpgsqlCommand-CreateCachedCommand.txt npgsql/npgsql@v10.0.3 sha256=6bdde5901d32a633 lines=166 +OK npgsql-10.0.3-NpgsqlCommand-TraceCommandStart.txt npgsql/npgsql@v10.0.3 sha256=6bdde5901d32a633 lines=1748 +OK npgsql-10.0.3-NpgsqlCommand-TraceCommandStop.txt npgsql/npgsql@v10.0.3 sha256=6bdde5901d32a633 lines=1792 +OK npgsql-10.0.3-NpgsqlConnection-CreateCommand.txt npgsql/npgsql@v10.0.3 sha256=02ed57055a9e1f02 lines=549 +OK npgsql-10.0.3-NpgsqlDataReader-Dispose.txt npgsql/npgsql@v10.0.3 sha256=9c109626d6627b55 lines=1008 +OK npgsql-10.0.3-NpgsqlDataReader-Close-public.txt npgsql/npgsql@v10.0.3 sha256=9c109626d6627b55 lines=1073 +OK npgsql-10.0.3-NpgsqlDataReader-Cleanup.txt npgsql/npgsql@v10.0.3 sha256=9c109626d6627b55 lines=1141 +OK npgsql-10.0.3-NpgsqlActivitySource-IsEnabled.txt npgsql/npgsql@v10.0.3 sha256=b7ef8df7f2f66573 lines=17 +OK npgsql-10.0.3-NpgsqlActivitySource-SourceName.txt npgsql/npgsql@v10.0.3 sha256=b7ef8df7f2f66573 lines=15 +OK npgsql-10.0.3-NpgsqlConnector-PreparedStatementManager.txt npgsql/npgsql@v10.0.3 sha256=7a13d79578e0a087 lines=164 +OK npgsql-main-NpgsqlCommand-Dispose.txt npgsql/npgsql@main sha256=fe55e7bb660c4a51 lines=1632 +OK npgsql-main-NpgsqlDataReader-Close-public.txt npgsql/npgsql@main sha256=8dfddffefbbb77c7 lines=1096 +OK runtime-v8.0.20-DataTable-Load.txt dotnet/runtime@v8.0.20 sha256=469f053f148bd5e4 lines=4969 +OK runtime-v10.0.12-DataTable-Load.txt dotnet/runtime@v10.0.12 sha256=dd4002e7407b1b15 lines=4974 +OK runtime-v10.0.12-DbCommand-class.txt dotnet/runtime@v10.0.12 sha256=c8f6f56c13b82f05 lines=11 +OK runtime-v10.0.12-DbCommand-DisposeAsync.txt dotnet/runtime@v10.0.12 sha256=c8f6f56c13b82f05 lines=245 +OK runtime-v10.0.12-DbDataReader-Close-Dispose.txt dotnet/runtime@v10.0.12 sha256=c2f3c2871cbc269f lines=34 +OK runtime-v10.0.12-IDbCommand.txt dotnet/runtime@v10.0.12 sha256=5fee96c4567d3894 lines=8 +OK runtime-v8.0.20-Activity-Dispose.txt dotnet/runtime@v8.0.20 sha256=2fcfbffca75351dd lines=1006 +OK runtime-v8.0.20-Activity-IDisposable.txt dotnet/runtime@v8.0.20 sha256=2fcfbffca75351dd lines=56 +OK mssqlite-SqliteCommand-Dispose.txt dotnet/efcore@main sha256=7ac8596c1cd999a3 lines=221 +OK mssqlite-SqliteCommand-DisposePreparedStatements.txt dotnet/efcore@main sha256=7ac8596c1cd999a3 lines=604 +OK mssqlite-SqliteCommand-PrepareAndEnumerate-head.txt dotnet/efcore@main sha256=7ac8596c1cd999a3 lines=547 +OK mssqlite-SqliteCommand-preparedStatements-field.txt dotnet/efcore@main sha256=7ac8596c1cd999a3 lines=31 +OK mssqlite-SqliteDataReader-Close-Dispose.txt dotnet/efcore@main sha256=5c3e1c054fceb256 lines=272 +OK mssqlite-SqliteConnection-commands-weakrefs.txt dotnet/efcore@main sha256=7ab4e87bd49e75a8 lines=32 +OK sqlitepclraw-sqlite3_stmt-SafeHandle.txt ericsink/SQLitePCL.raw@main sha256=0cb6c489786c3acf lines=196 +OK sqlclient-SqlCommand-Dispose.txt dotnet/SqlClient@main sha256=fc8200df4b130136 lines=1883 +OK mysqlconnector-MySqlCommand-Dispose.txt mysql-net/MySqlConnector@master sha256=4369c492ce1c1fc1 lines=377 diff --git a/corpus/ownership-lab/h28-command/evidence/engine-CommandDispose.txt b/corpus/ownership-lab/h28-command/evidence/engine-CommandDispose.txt new file mode 100644 index 00000000..93978a8e --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/engine-CommandDispose.txt @@ -0,0 +1,18 @@ +corpus/ownership-lab/h28-command/fx/CommandDispose.own:42:3: error: [OWN001] 'command' is owned but not released at end of function (leaks on at least one path) [resource: disposable] + 42 | release reader; // DataTable.Load(reader) -> reader.Close() + ^ + note: 'command' acquired here at corpus/ownership-lab/h28-command/fx/CommandDispose.own:40 +corpus/ownership-lab/h28-command/fx/CommandDispose.own:69:32: error: [OWN001] 'command' is owned but not released at end of function (leaks on at least one path) [resource: disposable] + 69 | let reader = acquire Reader(command); + ^ + note: 'command' acquired here at corpus/ownership-lab/h28-command/fx/CommandDispose.own:68 +corpus/ownership-lab/h28-command/fx/CommandDispose.own:69:7: error: [OWN001] 'reader' is owned but not released at end of function (leaks on at least one path) [resource: disposable] + 69 | let reader = acquire Reader(command); + ^ + note: 'reader' acquired here at corpus/ownership-lab/h28-command/fx/CommandDispose.own:69 + +3 errors. +# exit code: 1 (OWN001 is error-severity, so non-zero is the expected result) +# command : python3 -m ownlang check corpus/ownership-lab/h28-command/fx/CommandDispose.own +# cwd : repository root +# python : Python 3.11.2 diff --git a/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-Dispose.txt b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-Dispose.txt new file mode 100644 index 00000000..b9aca0c4 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-Dispose.txt @@ -0,0 +1,22 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : dotnet/efcore +# ref : main +# path : src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs +# blob sha : 7436572dcc87189532e757de1717e4972b1c23f3 +# file sha256: 7ac8596c1cd999a3967f30890a0e4bbd982b549e716b873106827377bc232588 +# file bytes : 26373 +# marker : 'protected override void Dispose(bool disposing)' +# region : lines 221..232 +# derived by: scripts/derive_source_evidence.py + 221| protected override void Dispose(bool disposing) + 222| { + 223| DisposePreparedStatements(disposing); + 224| + 225| if (disposing) + 226| { + 227| _connection?.RemoveCommand(this); + 228| } + 229| + 230| base.Dispose(disposing); + 231| } + 232| diff --git a/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-DisposePreparedStatements.txt b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-DisposePreparedStatements.txt new file mode 100644 index 00000000..44ef434d --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-DisposePreparedStatements.txt @@ -0,0 +1,32 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : dotnet/efcore +# ref : main +# path : src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs +# blob sha : 7436572dcc87189532e757de1717e4972b1c23f3 +# file sha256: 7ac8596c1cd999a3967f30890a0e4bbd982b549e716b873106827377bc232588 +# file bytes : 26373 +# marker : 'private void DisposePreparedStatements(bool disposing = true)' +# region : lines 604..625 +# derived by: scripts/derive_source_evidence.py + 604| private void DisposePreparedStatements(bool disposing = true) + 605| { + 606| if (disposing + 607| && DataReader != null) + 608| { + 609| DataReader.Dispose(); + 610| DataReader = null; + 611| } + 612| + 613| if (_preparedStatements != null) + 614| { + 615| foreach (var (stmt, _) in _preparedStatements) + 616| { + 617| stmt.Dispose(); + 618| } + 619| + 620| _preparedStatements.Clear(); + 621| } + 622| + 623| _prepared = false; + 624| } + 625| } diff --git a/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-PrepareAndEnumerate-head.txt b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-PrepareAndEnumerate-head.txt new file mode 100644 index 00000000..1ab993b2 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-PrepareAndEnumerate-head.txt @@ -0,0 +1,16 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : dotnet/efcore +# ref : main +# path : src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs +# blob sha : 7436572dcc87189532e757de1717e4972b1c23f3 +# file sha256: 7ac8596c1cd999a3967f30890a0e4bbd982b549e716b873106827377bc232588 +# file bytes : 26373 +# marker : 'private IEnumerable<(sqlite3_stmt Statement, int ParamCount)> PrepareAndEnumerateStatements()' +# region : lines 547..552 +# derived by: scripts/derive_source_evidence.py + 547| private IEnumerable<(sqlite3_stmt Statement, int ParamCount)> PrepareAndEnumerateStatements() + 548| { + 549| DisposePreparedStatements(disposing: false); + 550| + 551| var byteCount = Encoding.UTF8.GetByteCount(_commandText); + 552| var sql = new byte[byteCount + 1]; diff --git a/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-preparedStatements-field.txt b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-preparedStatements-field.txt new file mode 100644 index 00000000..5570fedf --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-preparedStatements-field.txt @@ -0,0 +1,12 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : dotnet/efcore +# ref : main +# path : src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs +# blob sha : 7436572dcc87189532e757de1717e4972b1c23f3 +# file sha256: 7ac8596c1cd999a3967f30890a0e4bbd982b549e716b873106827377bc232588 +# file bytes : 26373 +# marker : '_preparedStatements =' +# region : lines 31..32 +# derived by: scripts/derive_source_evidence.py + 31| private readonly List<(sqlite3_stmt Statement, int ParamCount)> _preparedStatements = [with(1)]; + 32| private SqliteConnection? _connection; diff --git a/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteConnection-commands-weakrefs.txt b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteConnection-commands-weakrefs.txt new file mode 100644 index 00000000..c9f635bf --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteConnection-commands-weakrefs.txt @@ -0,0 +1,12 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : dotnet/efcore +# ref : main +# path : src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs +# blob sha : 22e183f1e7be5f329feb2de1ab46b5e722f107ba +# file sha256: 7ab4e87bd49e75a8081f443be91a3b04ba5ca458ca05874f559c54b946ec6a9f +# file bytes : 59980 +# marker : 'List> _commands' +# region : lines 32..33 +# derived by: scripts/derive_source_evidence.py + 32| private readonly List> _commands = []; + 33| diff --git a/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteDataReader-Close-Dispose.txt b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteDataReader-Close-Dispose.txt new file mode 100644 index 00000000..5abd0088 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteDataReader-Close-Dispose.txt @@ -0,0 +1,52 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : dotnet/efcore +# ref : main +# path : src/Microsoft.Data.Sqlite.Core/SqliteDataReader.cs +# blob sha : 105e6e4a2df2206c651fb393564a90fad686686a +# file sha256: 5c3e1c054fceb256fc9b2642db5f16cf870f726aa29e681d36ed4933ef989e9b +# file bytes : 36674 +# marker : 'public override void Close()' +# region : lines 272..313 +# derived by: scripts/derive_source_evidence.py + 272| public override void Close() + 273| => Dispose(true); + 274| + 275| /// + 276| /// Releases any resources used by the data reader and closes it. + 277| /// + 278| /// + 279| /// to release managed and unmanaged resources; + 280| /// to release only unmanaged resources. + 281| /// + 282| protected override void Dispose(bool disposing) + 283| { + 284| if (!disposing || _closed) + 285| { + 286| return; + 287| } + 288| + 289| _command.DataReader = null; + 290| + 291| _record?.Dispose(); + 292| _record = null; + 293| + 294| if (_stmtEnumerator != null) + 295| { + 296| try + 297| { + 298| while (NextResult()) + 299| { + 300| } + 301| } + 302| catch + 303| { + 304| } + 305| } + 306| + 307| _stmtEnumerator?.Dispose(); + 308| + 309| _closed = true; + 310| + 311| if (_closeConnection) + 312| { + 313| _command.Connection!.Close(); diff --git a/corpus/ownership-lab/h28-command/evidence/mysqlconnector-MySqlCommand-Dispose.txt b/corpus/ownership-lab/h28-command/evidence/mysqlconnector-MySqlCommand-Dispose.txt new file mode 100644 index 00000000..634e7482 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/mysqlconnector-MySqlCommand-Dispose.txt @@ -0,0 +1,16 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : mysql-net/MySqlConnector +# ref : master +# path : src/MySqlConnector/MySqlCommand.cs +# blob sha : 7712fac730d089d7d725cc1c0078053c88e06e0c +# file sha256: 4369c492ce1c1fc1191b0684be67c6c6dead95b4545ffb7590ec65ab4ecf7d7b +# file bytes : 19512 +# marker : 'protected override void Dispose(bool disposing)' +# region : lines 377..382 +# derived by: scripts/derive_source_evidence.py + 377| protected override void Dispose(bool disposing) + 378| { + 379| m_isDisposed = true; + 380| base.Dispose(disposing); + 381| } + 382| diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlActivitySource-IsEnabled.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlActivitySource-IsEnabled.txt new file mode 100644 index 00000000..952148ce --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlActivitySource-IsEnabled.txt @@ -0,0 +1,12 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : npgsql/npgsql +# ref : v10.0.3 +# path : src/Npgsql/NpgsqlActivitySource.cs +# blob sha : be4e257c480267e0b2afbc76a8e223a656449e3e +# file sha256: b7ef8df7f2f665738739110335ee577fa56c8239da1be0df4320694537e28316 +# file bytes : 7511 +# marker : 'internal static bool IsEnabled' +# region : lines 17..18 +# derived by: scripts/derive_source_evidence.py + 17| internal static bool IsEnabled => Source.HasListeners(); + 18| diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlActivitySource-SourceName.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlActivitySource-SourceName.txt new file mode 100644 index 00000000..61aa300f --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlActivitySource-SourceName.txt @@ -0,0 +1,13 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : npgsql/npgsql +# ref : v10.0.3 +# path : src/Npgsql/NpgsqlActivitySource.cs +# blob sha : be4e257c480267e0b2afbc76a8e223a656449e3e +# file sha256: b7ef8df7f2f665738739110335ee577fa56c8239da1be0df4320694537e28316 +# file bytes : 7511 +# marker : 'static readonly ActivitySource Source = new("Npgsql"' +# region : lines 15..17 +# derived by: scripts/derive_source_evidence.py + 15| static readonly ActivitySource Source = new("Npgsql", GetLibraryVersion()); + 16| + 17| internal static bool IsEnabled => Source.HasListeners(); diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-CreateCachedCommand.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-CreateCachedCommand.txt new file mode 100644 index 00000000..09592f46 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-CreateCachedCommand.txt @@ -0,0 +1,13 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : npgsql/npgsql +# ref : v10.0.3 +# path : src/Npgsql/NpgsqlCommand.cs +# blob sha : 8ddbb2e5fb846d1905c237aa351ac622171e1994 +# file sha256: 6bdde5901d32a6331a0d22367673a83bcce01d9fe0782bbf6829594e52837b6f +# file bytes : 84694 +# marker : 'internal static NpgsqlCommand CreateCachedCommand' +# region : lines 166..168 +# derived by: scripts/derive_source_evidence.py + 166| internal static NpgsqlCommand CreateCachedCommand(NpgsqlConnection connection) + 167| => new(null, connection) { IsCacheable = true }; + 168| diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-Dispose.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-Dispose.txt new file mode 100644 index 00000000..aab3dd67 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-Dispose.txt @@ -0,0 +1,26 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : npgsql/npgsql +# ref : v10.0.3 +# path : src/Npgsql/NpgsqlCommand.cs +# blob sha : 8ddbb2e5fb846d1905c237aa351ac622171e1994 +# file sha256: 6bdde5901d32a6331a0d22367673a83bcce01d9fe0782bbf6829594e52837b6f +# file bytes : 84694 +# marker : 'protected override void Dispose(bool disposing)' +# region : lines 1712..1727 +# derived by: scripts/derive_source_evidence.py + 1712| protected override void Dispose(bool disposing) + 1713| { + 1714| ResetTransaction(); + 1715| + 1716| State = CommandState.Disposed; + 1717| + 1718| if (IsCacheable && InternalConnection is not null && InternalConnection.CachedCommand is null) + 1719| { + 1720| Reset(); + 1721| InternalConnection.CachedCommand = this; + 1722| return; + 1723| } + 1724| + 1725| IsCacheable = false; + 1726| } + 1727| diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-Reset.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-Reset.txt new file mode 100644 index 00000000..df39bed7 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-Reset.txt @@ -0,0 +1,24 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : npgsql/npgsql +# ref : v10.0.3 +# path : src/Npgsql/NpgsqlCommand.cs +# blob sha : 8ddbb2e5fb846d1905c237aa351ac622171e1994 +# file sha256: 6bdde5901d32a6331a0d22367673a83bcce01d9fe0782bbf6829594e52837b6f +# file bytes : 84694 +# marker : 'internal void Reset()' +# region : lines 1728..1741 +# derived by: scripts/derive_source_evidence.py + 1728| internal void Reset() + 1729| { + 1730| // TODO: Optimize NpgsqlParameterCollection to recycle NpgsqlParameter instances as well + 1731| // TODO: Statements isn't cleared/recycled, leaving this for now, since it'll be replaced by the new batching API + 1732| _commandText = string.Empty; + 1733| CommandType = CommandType.Text; + 1734| // Can be null if it's owned by batch + 1735| _parameters?.Clear(); + 1736| _timeout = null; + 1737| AllResultTypesAreUnknown = false; + 1738| Debug.Assert(_unknownResultTypeList is null); + 1739| EnableErrorBarriers = false; + 1740| } + 1741| diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-TraceCommandStart.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-TraceCommandStart.txt new file mode 100644 index 00000000..99ccab02 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-TraceCommandStart.txt @@ -0,0 +1,36 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : npgsql/npgsql +# ref : v10.0.3 +# path : src/Npgsql/NpgsqlCommand.cs +# blob sha : 8ddbb2e5fb846d1905c237aa351ac622171e1994 +# file sha256: 6bdde5901d32a6331a0d22367673a83bcce01d9fe0782bbf6829594e52837b6f +# file bytes : 84694 +# marker : 'internal void TraceCommandStart(NpgsqlTracingOptions tracingOptions' +# region : lines 1748..1773 +# derived by: scripts/derive_source_evidence.py + 1748| internal void TraceCommandStart(NpgsqlTracingOptions tracingOptions, bool? prepared) + 1749| { + 1750| Debug.Assert(CurrentActivity is null); + 1751| + 1752| if (NpgsqlActivitySource.IsEnabled) + 1753| { + 1754| var enableTracing = WrappingBatch is not null + 1755| ? tracingOptions.BatchFilter?.Invoke(WrappingBatch) ?? true + 1756| : tracingOptions.CommandFilter?.Invoke(this) ?? true; + 1757| + 1758| if (enableTracing) + 1759| { + 1760| var spanName = WrappingBatch is not null + 1761| ? tracingOptions.BatchSpanNameProvider?.Invoke(WrappingBatch) + 1762| : tracingOptions.CommandSpanNameProvider?.Invoke(this); + 1763| + 1764| CurrentActivity = NpgsqlActivitySource.CommandStart( + 1765| WrappingBatch is not null ? GetBatchFullCommandText() : CommandText, + 1766| CommandType, + 1767| prepared, + 1768| spanName); + 1769| } + 1770| } + 1771| } + 1772| + 1773| internal void TraceCommandEnrich(NpgsqlConnector connector) diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-TraceCommandStop.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-TraceCommandStop.txt new file mode 100644 index 00000000..3d1679cf --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-TraceCommandStop.txt @@ -0,0 +1,18 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : npgsql/npgsql +# ref : v10.0.3 +# path : src/Npgsql/NpgsqlCommand.cs +# blob sha : 8ddbb2e5fb846d1905c237aa351ac622171e1994 +# file sha256: 6bdde5901d32a6331a0d22367673a83bcce01d9fe0782bbf6829594e52837b6f +# file bytes : 84694 +# marker : 'internal void TraceCommandStop()' +# region : lines 1792..1799 +# derived by: scripts/derive_source_evidence.py + 1792| internal void TraceCommandStop() + 1793| { + 1794| if (CurrentActivity is not null) + 1795| { + 1796| CurrentActivity.Dispose(); + 1797| CurrentActivity = null; + 1798| } + 1799| } diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-ctor-SuppressFinalize.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-ctor-SuppressFinalize.txt new file mode 100644 index 00000000..24679d05 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-ctor-SuppressFinalize.txt @@ -0,0 +1,20 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : npgsql/npgsql +# ref : v10.0.3 +# path : src/Npgsql/NpgsqlCommand.cs +# blob sha : 8ddbb2e5fb846d1905c237aa351ac622171e1994 +# file sha256: 6bdde5901d32a6331a0d22367673a83bcce01d9fe0782bbf6829594e52837b6f +# file bytes : 84694 +# marker : 'public NpgsqlCommand(string? cmdText, NpgsqlConnection? connection)' +# region : lines 120..129 +# derived by: scripts/derive_source_evidence.py + 120| public NpgsqlCommand(string? cmdText, NpgsqlConnection? connection) + 121| { + 122| GC.SuppressFinalize(this); + 123| InternalBatchCommands = new List(1); + 124| _commandText = cmdText ?? string.Empty; + 125| InternalConnection = connection; + 126| CommandType = CommandType.Text; + 127| } + 128| + 129| /// diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlConnection-CreateCommand.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlConnection-CreateCommand.txt new file mode 100644 index 00000000..feb6ec5c --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlConnection-CreateCommand.txt @@ -0,0 +1,25 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : npgsql/npgsql +# ref : v10.0.3 +# path : src/Npgsql/NpgsqlConnection.cs +# blob sha : 5973efc92db9e0b5734884257bbfcda176099e64 +# file sha256: 02ed57055a9e1f025c631590d10c4040af1abddf4fa53d04cd07eaf35675d93b +# file bytes : 87404 +# marker : 'public new NpgsqlCommand CreateCommand()' +# region : lines 549..563 +# derived by: scripts/derive_source_evidence.py + 549| public new NpgsqlCommand CreateCommand() + 550| { + 551| CheckDisposed(); + 552| + 553| var cachedCommand = CachedCommand; + 554| if (cachedCommand is not null) + 555| { + 556| CachedCommand = null; + 557| cachedCommand.State = CommandState.Idle; + 558| return cachedCommand; + 559| } + 560| + 561| return NpgsqlCommand.CreateCachedCommand(this); + 562| } + 563| diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlConnector-PreparedStatementManager.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlConnector-PreparedStatementManager.txt new file mode 100644 index 00000000..3a88a3de --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlConnector-PreparedStatementManager.txt @@ -0,0 +1,12 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : npgsql/npgsql +# ref : v10.0.3 +# path : src/Npgsql/Internal/NpgsqlConnector.cs +# blob sha : 663577ea1a89fbf818fd92cf81429a2e069ec186 +# file sha256: 7a13d79578e0a0874c5842532d450d496f7d52c0f66d3fa7c1a2984a83465274 +# file bytes : 138455 +# marker : 'internal PreparedStatementManager PreparedStatementManager' +# region : lines 164..165 +# derived by: scripts/derive_source_evidence.py + 164| internal PreparedStatementManager PreparedStatementManager { get; } + 165| diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-Cleanup.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-Cleanup.txt new file mode 100644 index 00000000..6a699336 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-Cleanup.txt @@ -0,0 +1,90 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : npgsql/npgsql +# ref : v10.0.3 +# path : src/Npgsql/NpgsqlDataReader.cs +# blob sha : 55753dc7f6c7500fe61fbf23e913a61e84fa287c +# file sha256: 9c109626d6627b55aef9768827ce4aa860d3934c224d009048ba57060431607c +# file bytes : 92885 +# marker : 'internal async Task Cleanup(bool async, bool connectionClosing' +# region : lines 1141..1220 +# derived by: scripts/derive_source_evidence.py + 1141| internal async Task Cleanup(bool async, bool connectionClosing = false, bool isDisposing = false) + 1142| { + 1143| LogMessages.ReaderCleanup(_commandLogger, Connector.Id); + 1144| + 1145| // If multiplexing isn't on, _sendTask contains the task for the writing of this command. + 1146| // Make sure that this task, which may have executed asynchronously and in parallel with the reading, + 1147| // has completed, throwing any exceptions it generated. If we don't do this, there's the possibility of a race condition where the + 1148| // user executes a new command after reader.Dispose() returns, but some additional write stuff is still finishing up from the last + 1149| // command. + 1150| if (_sendTask is { Status: not TaskStatus.RanToCompletion }) + 1151| { + 1152| // If the connector is broken, we have no reason to wait for the sendTask to complete + 1153| // as we're not going to send anything else over it + 1154| // and that can lead to deadlocks (concurrent write and read failure, see #4804) + 1155| if (Connector.IsBroken) + 1156| { + 1157| // Prevent unobserved Task notifications by observing the failed Task exception. + 1158| _ = _sendTask.ContinueWith(t => _ = t.Exception, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Current); + 1159| } + 1160| else + 1161| { + 1162| try + 1163| { + 1164| if (async) + 1165| await _sendTask.ConfigureAwait(false); + 1166| else + 1167| _sendTask.GetAwaiter().GetResult(); + 1168| } + 1169| catch (Exception e) + 1170| { + 1171| // TODO: think of a better way to handle exceptions, see #1323 and #3163 + 1172| _commandLogger.LogDebug(e, "Exception caught while sending the request", Connector.Id); + 1173| } + 1174| } + 1175| } + 1176| + 1177| if (ColumnInfoCache is { } cache) + 1178| { + 1179| ColumnInfoCache = null; + 1180| ArrayPool.Shared.Return(cache, clearArray: true); + 1181| } + 1182| + 1183| State = ReaderState.Closed; + 1184| Command.State = CommandState.Idle; + 1185| Connector.CurrentReader = null; + 1186| if (_commandLogger.IsEnabled(LogLevel.Information)) + 1187| Command.LogExecutingCompleted(Connector, executing: false); + 1188| NpgsqlEventSource.Log.CommandStop(); + 1189| Connector.DataSource.MetricsReporter.ReportCommandStop(_startTimestamp); + 1190| Connector.EndUserAction(); + 1191| + 1192| // The reader shouldn't be unbound, if we're disposing - so the state is set prematurely + 1193| if (isDisposing) + 1194| State = ReaderState.Disposed; + 1195| + 1196| if (_connection?.ConnectorBindingScope == ConnectorBindingScope.Reader) + 1197| { + 1198| UnbindIfNecessary(); + 1199| + 1200| // TODO: Refactor... Use proper scope + 1201| _connection.Connector = null; + 1202| Connector.Connection = null; + 1203| _connection.ConnectorBindingScope = ConnectorBindingScope.None; + 1204| + 1205| // If the reader is being closed as part of the connection closing, we don't apply + 1206| // the reader's CommandBehavior.CloseConnection + 1207| if (_behavior.HasFlag(CommandBehavior.CloseConnection) && !connectionClosing) + 1208| _connection.Close(); + 1209| + 1210| Connector.ReaderCompleted.SetResult(null); + 1211| } + 1212| else if (_behavior.HasFlag(CommandBehavior.CloseConnection) && !connectionClosing) + 1213| { + 1214| Debug.Assert(_connection is not null); + 1215| _connection.Close(); + 1216| } + 1217| + 1218| if (ReaderClosed != null) + 1219| { + 1220| ReaderClosed(this, EventArgs.Empty); diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-Close-public.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-Close-public.txt new file mode 100644 index 00000000..d412f9d6 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-Close-public.txt @@ -0,0 +1,12 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : npgsql/npgsql +# ref : v10.0.3 +# path : src/Npgsql/NpgsqlDataReader.cs +# blob sha : 55753dc7f6c7500fe61fbf23e913a61e84fa287c +# file sha256: 9c109626d6627b55aef9768827ce4aa860d3934c224d009048ba57060431607c +# file bytes : 92885 +# marker : 'public override void Close() => Close(connectionClosing: false' +# region : lines 1073..1074 +# derived by: scripts/derive_source_evidence.py + 1073| public override void Close() => Close(connectionClosing: false, async: false, isDisposing: false).GetAwaiter().GetResult(); + 1074| diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-Dispose.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-Dispose.txt new file mode 100644 index 00000000..c8a4cc0c --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-Dispose.txt @@ -0,0 +1,36 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : npgsql/npgsql +# ref : v10.0.3 +# path : src/Npgsql/NpgsqlDataReader.cs +# blob sha : 55753dc7f6c7500fe61fbf23e913a61e84fa287c +# file sha256: 9c109626d6627b55aef9768827ce4aa860d3934c224d009048ba57060431607c +# file bytes : 92885 +# marker : 'protected override void Dispose(bool disposing)' +# region : lines 1008..1033 +# derived by: scripts/derive_source_evidence.py + 1008| protected override void Dispose(bool disposing) + 1009| { + 1010| try + 1011| { + 1012| Close(connectionClosing: false, async: false, isDisposing: true).GetAwaiter().GetResult(); + 1013| } + 1014| catch (Exception ex) + 1015| { + 1016| // In the case of a PostgresException (or multiple ones, if we have error barriers), the reader's state has already been set + 1017| // to Disposed in Close above; in multiplexing, we also unbind the connector (with its reader), and at that point it can be used + 1018| // by other consumers. Therefore, we only set the state fo Disposed if the exception *wasn't* a PostgresException. + 1019| if (!(ex is PostgresException || + 1020| ex is NpgsqlException { InnerException: AggregateException aggregateException } && + 1021| AllPostgresExceptions(aggregateException.InnerExceptions))) + 1022| { + 1023| State = ReaderState.Disposed; + 1024| } + 1025| + 1026| throw; + 1027| } + 1028| finally + 1029| { + 1030| Command.TraceCommandStop(); + 1031| } + 1032| } + 1033| diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-main-NpgsqlCommand-Dispose.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-main-NpgsqlCommand-Dispose.txt new file mode 100644 index 00000000..da205303 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-main-NpgsqlCommand-Dispose.txt @@ -0,0 +1,26 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : npgsql/npgsql +# ref : main +# path : src/Npgsql/NpgsqlCommand.cs +# blob sha : c8fef976b2afd55a883612b71a748cea592036ee +# file sha256: fe55e7bb660c4a51591cb31b31535ea65bbc51ded6229ea059558fb6ad3ca8e2 +# file bytes : 80130 +# marker : 'protected override void Dispose(bool disposing)' +# region : lines 1632..1647 +# derived by: scripts/derive_source_evidence.py + 1632| protected override void Dispose(bool disposing) + 1633| { + 1634| ResetTransaction(); + 1635| + 1636| State = CommandState.Disposed; + 1637| + 1638| if (IsCacheable && InternalConnection is not null && InternalConnection.CachedCommand is null) + 1639| { + 1640| Reset(); + 1641| InternalConnection.CachedCommand = this; + 1642| return; + 1643| } + 1644| + 1645| IsCacheable = false; + 1646| } + 1647| diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-main-NpgsqlDataReader-Close-public.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-main-NpgsqlDataReader-Close-public.txt new file mode 100644 index 00000000..24a8c705 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-main-NpgsqlDataReader-Close-public.txt @@ -0,0 +1,12 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : npgsql/npgsql +# ref : main +# path : src/Npgsql/NpgsqlDataReader.cs +# blob sha : 596e7fab8f744a853f78fb27e0f7dfcf72e3e746 +# file sha256: 8dfddffefbbb77c750cf6096c08833b8e16e14f344dee6c33e8e51043b7987cd +# file bytes : 90069 +# marker : 'public override void Close() => Close(connectionClosing: false' +# region : lines 1096..1097 +# derived by: scripts/derive_source_evidence.py + 1096| public override void Close() => Close(connectionClosing: false, async: false, isDisposing: false).GetAwaiter().GetResult(); + 1097| diff --git a/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-DataTable-Load.txt b/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-DataTable-Load.txt new file mode 100644 index 00000000..ffa512ea --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-DataTable-Load.txt @@ -0,0 +1,40 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : dotnet/runtime +# ref : v10.0.12 +# path : src/libraries/System.Data.Common/src/System/Data/DataTable.cs +# blob sha : 996f52cc479710fb75f95292a07d2237bf770d28 +# file sha256: dd4002e7407b1b1503b597151179de865d1d9692de24855f0998a52770a13fbb +# file bytes : 297223 +# marker : 'public virtual void Load(IDataReader reader, LoadOption loadOption, FillErrorEventHandler? errorHandler)' +# region : lines 4974..5003 +# derived by: scripts/derive_source_evidence.py + 4974| public virtual void Load(IDataReader reader, LoadOption loadOption, FillErrorEventHandler? errorHandler) + 4975| { + 4976| long logScopeId = DataCommonEventSource.Log.EnterScope(" {0}, loadOption={1}", ObjectID, loadOption); + 4977| try + 4978| { + 4979| if (PrimaryKey.Length == 0) + 4980| { + 4981| DataTableReader? dtReader = reader as DataTableReader; + 4982| if (dtReader != null && dtReader.CurrentDataTable == this) + 4983| { + 4984| return; // if not return, it will go to infinite loop + 4985| } + 4986| } + 4987| Common.LoadAdapter adapter = new Common.LoadAdapter(); + 4988| adapter.FillLoadOption = loadOption; + 4989| adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey; + 4990| if (null != errorHandler) + 4991| { + 4992| adapter.FillError += errorHandler; + 4993| } + 4994| adapter.FillFromReader(new DataTable[] { this }, reader, 0, 0); + 4995| + 4996| if (!reader.IsClosed && !reader.NextResult()) + 4997| { + 4998| reader.Close(); + 4999| } + 5000| } + 5001| finally + 5002| { + 5003| DataCommonEventSource.Log.ExitScope(logScopeId); diff --git a/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-DbCommand-DisposeAsync.txt b/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-DbCommand-DisposeAsync.txt new file mode 100644 index 00000000..454bcfb0 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-DbCommand-DisposeAsync.txt @@ -0,0 +1,16 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : dotnet/runtime +# ref : v10.0.12 +# path : src/libraries/System.Data.Common/src/System/Data/Common/DbCommand.cs +# blob sha : 5538ea867a81b3e8e31868576302e6d2150875dd +# file sha256: c8f6f56c13b82f0556f5c5235dafe96e6c434fbea4476ad0087220eea85d033f +# file bytes : 8678 +# marker : 'public virtual ValueTask DisposeAsync()' +# region : lines 245..250 +# derived by: scripts/derive_source_evidence.py + 245| public virtual ValueTask DisposeAsync() + 246| { + 247| Dispose(); + 248| return default; + 249| } + 250| } diff --git a/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-DbCommand-class.txt b/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-DbCommand-class.txt new file mode 100644 index 00000000..731babfc --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-DbCommand-class.txt @@ -0,0 +1,14 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : dotnet/runtime +# ref : v10.0.12 +# path : src/libraries/System.Data.Common/src/System/Data/Common/DbCommand.cs +# blob sha : 5538ea867a81b3e8e31868576302e6d2150875dd +# file sha256: c8f6f56c13b82f0556f5c5235dafe96e6c434fbea4476ad0087220eea85d033f +# file bytes : 8678 +# marker : 'public abstract class DbCommand' +# region : lines 11..14 +# derived by: scripts/derive_source_evidence.py + 11| public abstract class DbCommand : Component, IDbCommand, IAsyncDisposable + 12| { + 13| protected DbCommand() : base() + 14| { diff --git a/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-DbDataReader-Close-Dispose.txt b/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-DbDataReader-Close-Dispose.txt new file mode 100644 index 00000000..0af72b4b --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-DbDataReader-Close-Dispose.txt @@ -0,0 +1,42 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : dotnet/runtime +# ref : v10.0.12 +# path : src/libraries/System.Data.Common/src/System/Data/Common/DbDataReader.cs +# blob sha : 77f2184ea1b03483fa3fad44b482ed8f49295a4e +# file sha256: c2f3c2871cbc269fcb5ffb2c471c92336812a76ce642b22dd65228dad125a91e +# file bytes : 11831 +# marker : 'public virtual void Close() { }' +# region : lines 34..65 +# derived by: scripts/derive_source_evidence.py + 34| public virtual void Close() { } + 35| + 36| public virtual Task CloseAsync() + 37| { + 38| try + 39| { + 40| Close(); + 41| return Task.CompletedTask; + 42| } + 43| catch (Exception e) + 44| { + 45| return Task.FromException(e); + 46| } + 47| } + 48| + 49| [EditorBrowsable(EditorBrowsableState.Never)] + 50| public void Dispose() => Dispose(true); + 51| + 52| protected virtual void Dispose(bool disposing) + 53| { + 54| if (disposing) + 55| { + 56| Close(); + 57| } + 58| } + 59| + 60| public virtual ValueTask DisposeAsync() + 61| { + 62| Dispose(); + 63| return default; + 64| } + 65| diff --git a/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-IDbCommand.txt b/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-IDbCommand.txt new file mode 100644 index 00000000..12d96957 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-IDbCommand.txt @@ -0,0 +1,13 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : dotnet/runtime +# ref : v10.0.12 +# path : src/libraries/System.Data.Common/src/System/Data/IDbCommand.cs +# blob sha : 823f7345cd66bab797f48a9fdb819f61c8ed19d9 +# file sha256: 5fee96c4567d38943b5127b8f7cc6f335fe6dc54d80a7f88328de4b97a83b826 +# file bytes : 869 +# marker : 'public interface IDbCommand' +# region : lines 8..10 +# derived by: scripts/derive_source_evidence.py + 8| public interface IDbCommand : IDisposable + 9| { + 10| IDbConnection? Connection { get; set; } diff --git a/corpus/ownership-lab/h28-command/evidence/runtime-v8.0.20-Activity-Dispose.txt b/corpus/ownership-lab/h28-command/evidence/runtime-v8.0.20-Activity-Dispose.txt new file mode 100644 index 00000000..3c5681fb --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/runtime-v8.0.20-Activity-Dispose.txt @@ -0,0 +1,22 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : dotnet/runtime +# ref : v8.0.20 +# path : src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs +# blob sha : db4d5e2308108780e377661566053c665036a997 +# file sha256: 2fcfbffca75351dd8512078a608d5b90800fab9d36fe49e3da65d67166e88aa5 +# file bytes : 82453 +# marker : 'Dispose will stop the Activity if it is already started' +# region : lines 1006..1017 +# derived by: scripts/derive_source_evidence.py + 1006| /// Dispose will stop the Activity if it is already started and notify any event listeners. Nothing will happen otherwise. + 1007| /// + 1008| public void Dispose() + 1009| { + 1010| if (!IsStopped) + 1011| { + 1012| Stop(); + 1013| } + 1014| + 1015| Dispose(true); + 1016| GC.SuppressFinalize(this); + 1017| } diff --git a/corpus/ownership-lab/h28-command/evidence/runtime-v8.0.20-Activity-IDisposable.txt b/corpus/ownership-lab/h28-command/evidence/runtime-v8.0.20-Activity-IDisposable.txt new file mode 100644 index 00000000..33d87283 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/runtime-v8.0.20-Activity-IDisposable.txt @@ -0,0 +1,12 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : dotnet/runtime +# ref : v8.0.20 +# path : src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs +# blob sha : db4d5e2308108780e377661566053c665036a997 +# file sha256: 2fcfbffca75351dd8512078a608d5b90800fab9d36fe49e3da65d67166e88aa5 +# file bytes : 82453 +# marker : 'public partial class Activity : IDisposable' +# region : lines 56..57 +# derived by: scripts/derive_source_evidence.py + 56| public partial class Activity : IDisposable + 57| { diff --git a/corpus/ownership-lab/h28-command/evidence/runtime-v8.0.20-DataTable-Load.txt b/corpus/ownership-lab/h28-command/evidence/runtime-v8.0.20-DataTable-Load.txt new file mode 100644 index 00000000..d3b0b8e2 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/runtime-v8.0.20-DataTable-Load.txt @@ -0,0 +1,40 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : dotnet/runtime +# ref : v8.0.20 +# path : src/libraries/System.Data.Common/src/System/Data/DataTable.cs +# blob sha : 71813c5d78fd29a44a98d8113274794a781ece50 +# file sha256: 469f053f148bd5e44788eb5b9c449f5413bdb0733d66cc744afbc70f9511d20c +# file bytes : 293743 +# marker : 'public virtual void Load(IDataReader reader, LoadOption loadOption, FillErrorEventHandler? errorHandler)' +# region : lines 4969..4998 +# derived by: scripts/derive_source_evidence.py + 4969| public virtual void Load(IDataReader reader, LoadOption loadOption, FillErrorEventHandler? errorHandler) + 4970| { + 4971| long logScopeId = DataCommonEventSource.Log.EnterScope(" {0}, loadOption={1}", ObjectID, loadOption); + 4972| try + 4973| { + 4974| if (PrimaryKey.Length == 0) + 4975| { + 4976| DataTableReader? dtReader = reader as DataTableReader; + 4977| if (dtReader != null && dtReader.CurrentDataTable == this) + 4978| { + 4979| return; // if not return, it will go to infinite loop + 4980| } + 4981| } + 4982| Common.LoadAdapter adapter = new Common.LoadAdapter(); + 4983| adapter.FillLoadOption = loadOption; + 4984| adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey; + 4985| if (null != errorHandler) + 4986| { + 4987| adapter.FillError += errorHandler; + 4988| } + 4989| adapter.FillFromReader(new DataTable[] { this }, reader, 0, 0); + 4990| + 4991| if (!reader.IsClosed && !reader.NextResult()) + 4992| { + 4993| reader.Close(); + 4994| } + 4995| } + 4996| finally + 4997| { + 4998| DataCommonEventSource.Log.ExitScope(logScopeId); diff --git a/corpus/ownership-lab/h28-command/evidence/sqlclient-SqlCommand-Dispose.txt b/corpus/ownership-lab/h28-command/evidence/sqlclient-SqlCommand-Dispose.txt new file mode 100644 index 00000000..a7586555 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/sqlclient-SqlCommand-Dispose.txt @@ -0,0 +1,24 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : dotnet/SqlClient +# ref : main +# path : src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs +# blob sha : 775792720397ee98a529aabe274918b807e39b69 +# file sha256: fc8200df4b13013629b3a19ad5108978bd5782568b1d5afd6a514555fc7a22a3 +# file bytes : 144099 +# marker : 'protected override void Dispose(bool disposing)' +# region : lines 1883..1896 +# derived by: scripts/derive_source_evidence.py + 1883| protected override void Dispose(bool disposing) + 1884| { + 1885| if (disposing) + 1886| { + 1887| // Release managed objects + 1888| _cachedMetaData = null; + 1889| + 1890| // Reset async cache information to allow a second async execute + 1891| CachedAsyncState?.ResetAsyncState(); + 1892| } + 1893| + 1894| // Release unmanaged objects + 1895| base.Dispose(disposing); + 1896| } diff --git a/corpus/ownership-lab/h28-command/evidence/sqlitepclraw-sqlite3_stmt-SafeHandle.txt b/corpus/ownership-lab/h28-command/evidence/sqlitepclraw-sqlite3_stmt-SafeHandle.txt new file mode 100644 index 00000000..0fdcf4b2 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/sqlitepclraw-sqlite3_stmt-SafeHandle.txt @@ -0,0 +1,36 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : ericsink/SQLitePCL.raw +# ref : main +# path : src/SQLitePCLRaw.core/handles.cs +# blob sha : e7a73ebcda5243a9c36c716b2f98ad4191424ace +# file sha256: 0cb6c489786c3acf237ef0760fd8aa2703e8c0bf500301fe746ac1dd102b13bd +# file bytes : 11664 +# marker : 'public class sqlite3_stmt : SafeHandle' +# region : lines 196..221 +# derived by: scripts/derive_source_evidence.py + 196| public class sqlite3_stmt : SafeHandle + 197| { + 198| private sqlite3 _db; + 199| + 200| internal static sqlite3_stmt From(IntPtr p, sqlite3 db) + 201| { + 202| var h = new sqlite3_stmt(); + 203| h.SetHandle(p); + 204| db.add_stmt(h); + 205| h._db = db; + 206| return h; + 207| } + 208| + 209| sqlite3_stmt() : base(IntPtr.Zero, true) + 210| { + 211| } + 212| + 213| public override bool IsInvalid => handle == IntPtr.Zero; + 214| + 215| protected override bool ReleaseHandle() + 216| { + 217| int rc = raw.internal_sqlite3_finalize(handle); + 218| // TODO check rc? + 219| _db.remove_stmt(this); + 220| return true; + 221| } diff --git a/corpus/ownership-lab/h28-command/falsifier/Program.cs b/corpus/ownership-lab/h28-command/falsifier/Program.cs new file mode 100644 index 00000000..9624769f --- /dev/null +++ b/corpus/ownership-lab/h28-command/falsifier/Program.cs @@ -0,0 +1,254 @@ +// H-28-CMD runtime falsifier: what does the ABSENCE of DbCommand.Dispose() actually cost? +// +// This is the program that was NOT runnable in the sandbox which produced the report +// (no .NET SDK, no NuGet feed -- see docs/notes/h28-npgsql-command-resolution.md section 6). +// It is written to be run as-is on a machine that has both, and every probe exists because it +// separates two hypotheses that source reading alone could not settle. +// +// ../run.sh runtime (starts PostgreSQL via pgserver, then dotnet run) +// dotnet run -c Release # Npgsql arm skipped if no PostgreSQL +// dotnet run -c Release -- sqlite-only # SQLite arm only +// +// Pinned to the original H-28 evidence: net8.0, Npgsql 10.0.3, Microsoft.Data.Sqlite 10.0.12, +// PostgreSQL 16 (see h28cmd.csproj and ../README.txt). +// +// Probes +// P1 reader.IsClosed after Load reproduces H-28 F1 (RELEASE, not BORROW) +// P2 command reusable after Load "the reader bounds the command's lifetime" vs +// "the reader leaves it Idle" (NpgsqlCleanup sets Idle) +// P3 connection still usable protocol leak vs object leak +// P4 Activity started/stopped THE discriminating probe for the claim that only +// reader.Dispose() stops the command's Activity. Npgsql +// only: its ActivitySource is named "Npgsql" and +// Microsoft.Data.Sqlite has no ActivitySource at all. +// P5 pg_prepared_statements whether command.Dispose() releases server-side state +// P6 RSS delta over N iterations whether an undisposed command retains NATIVE memory +// (the Microsoft.Data.Sqlite sqlite3_stmt arm) +// +// Deliberately returns List, not an iterator: `yield return` is illegal inside a +// try/catch, and almost every probe here needs one. +using System.Data; +using System.Data.Common; +using System.Diagnostics; + +static class Probe +{ + // ---- P4: count Activity starts and stops for Npgsql's source --------------------- + // NpgsqlActivitySource.IsEnabled is Source.HasListeners(), so WITHOUT this listener the + // whole tracing arm is inert (CurrentActivity stays null) and P4 would prove nothing. + static int _started, _stopped; + + internal static readonly ActivityListener Listener = new() + { + ShouldListenTo = s => s.Name == "Npgsql", + Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllData, + SampleUsingParentId = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllData, + ActivityStarted = _ => Interlocked.Increment(ref _started), + ActivityStopped = _ => Interlocked.Increment(ref _stopped), + }; + + static void ResetActivity() { _started = 0; _stopped = 0; } + static string ActivityReport() => $"started={_started} stopped={_stopped}"; + + // ---- P6: process RSS. GC.GetTotalMemory is deliberately NOT used: it measures the ---- + // managed heap, and the resource in question (sqlite3_stmt) is native. One snapshot + // proves nothing, so P6 is a DELTA over N iterations with an explicit finalization control. + static long Rss() + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + return Environment.WorkingSet; + } + + // ---- the four variants of the family; disposeCommand/disposeReader are the knobs ---- + internal static List Run(string name, DbConnection conn, string sql, + bool disposeCommand, bool disposeReader, bool load) + { + var o = new List(); + ResetActivity(); + + DbCommand command = conn.CreateCommand(); + command.CommandText = sql; + DbDataReader reader = command.ExecuteReader(); + + int rows = 0; + if (load) + { + var table = new DataTable(); + table.Load(reader); // BCL: Close() iff this is the last result set + rows = table.Rows.Count; + } + else + { + while (reader.Read()) rows++; + } + + // P1 -- the H-28 F1 reproduction + o.Add($"{name} P1 reader.IsClosed after Load = {reader.IsClosed} (rows={rows})"); + + if (disposeReader) reader.Dispose(); + + // P2 -- is the command still logically usable? A model claiming the reader's lifetime + // bounds the command's must predict a throw here. + try + { + command.CommandText = "select 1"; + o.Add($"{name} P2 command reuse after the reader closed = OK ({command.ExecuteScalar()})"); + } + catch (Exception e) + { + o.Add($"{name} P2 command reuse threw {e.GetType().Name}: {e.Message}"); + } + + if (disposeCommand) command.Dispose(); + + // P4 -- the discriminating tracing probe (Npgsql only; 0/0 on SQLite is expected) + o.Add($"{name} P4 Activity {ActivityReport()}"); + + // P3 -- protocol leak (connection busy) or only an object leak? + try + { + using var c2 = conn.CreateCommand(); + c2.CommandText = "select 42"; + o.Add($"{name} P3 connection after the family = OK ({c2.ExecuteScalar()}) state={conn.State}"); + } + catch (Exception e) + { + o.Add($"{name} P3 connection threw {e.GetType().Name}: {e.Message}"); + } + return o; + } + + // ---- P5 -- does command.Dispose() release SERVER-SIDE prepared state? -------------- + internal static List Prepared(string name, DbConnection conn, string sql) + { + var o = new List(); + int Count() + { + using var c = conn.CreateCommand(); + c.CommandText = "select count(*) from pg_prepared_statements"; + return Convert.ToInt32(c.ExecuteScalar()); + } + + var cmd = conn.CreateCommand(); + cmd.CommandText = sql; + Exception err = null; + try { cmd.Prepare(); } catch (Exception e) { err = e; } + if (err != null) + { + o.Add($"{name} P5 Prepare threw {err.GetType().Name}: {err.Message}"); + return o; + } + cmd.ExecuteNonQuery(); + var afterPrepare = Count(); + cmd.Dispose(); // the arm under test + var afterDispose = Count(); + o.Add($"{name} P5 pg_prepared_statements after Prepare={afterPrepare} " + + $"after command.Dispose()={afterDispose} " + + (afterDispose == afterPrepare + ? "-> command.Dispose() released NO server-side prepared state" + : "-> command.Dispose() DID release server-side prepared state")); + return o; + } + + // ---- P6 -- native retention over N iterations ------------------------------------ + internal static List NativeRetention(string name, Func open, string sql, + bool disposeCommand, int n = 20000) + { + var o = new List(); + using var conn = open(); + conn.Open(); + // warm up, so the delta is not the provider's first-touch cost + for (int i = 0; i < 200; i++) + { + var w = conn.CreateCommand(); w.CommandText = sql; + var r = w.ExecuteReader(); var t = new DataTable(); t.Load(r); + if (disposeCommand) w.Dispose(); + } + var before = Rss(); + for (int i = 0; i < n; i++) + { + var c = conn.CreateCommand(); c.CommandText = sql; + var r = c.ExecuteReader(); var t = new DataTable(); t.Load(r); + if (disposeCommand) c.Dispose(); + } + var after = Rss(); + o.Add($"{name} P6 n={n} disposeCommand={disposeCommand} " + + $"RSS {before / 1024}KiB -> {after / 1024}KiB (delta {(after - before) / 1024}KiB)"); + return o; + } +} + +static class Program +{ + const string Sql = "select 1 as a union all select 2 union all select 3"; + + static readonly (string Name, bool Cmd, bool Rdr)[] Variants = + { + ("B_neither", false, false), // the H-28 demanding instance + ("C_reader_only", false, true ), // reader.Dispose(): the arm that stops the Activity + ("D_command_only", true, false), // command.Dispose() after Load + ("A_both", true, true ), // fully released control + }; + + static async Task Main(string[] args) + { + ActivitySource.AddActivityListener(Probe.Listener); + var pgDsn = Environment.GetEnvironmentVariable("H28_PG_DSN") + ?? "Host=127.0.0.1;Port=5432;Username=postgres;Password=postgres;Database=postgres"; + var mode = args.Length > 0 ? args[0] : "all"; + + Console.WriteLine($"# H-28-CMD falsifier net={Environment.Version} " + + $"npgsql={typeof(Npgsql.NpgsqlConnection).Assembly.GetName().Version} " + + $"sqlite={typeof(Microsoft.Data.Sqlite.SqliteConnection).Assembly.GetName().Version}"); + + // ---- Microsoft.Data.Sqlite: the arm where command.Dispose() DOES matter -------- + Console.WriteLine("\n## Microsoft.Data.Sqlite (in-memory)"); + using (var conn = new Microsoft.Data.Sqlite.SqliteConnection("Data Source=:memory:")) + { + conn.Open(); + foreach (var v in Variants) + foreach (var line in Probe.Run(v.Name, conn, Sql, v.Cmd, v.Rdr, load: true)) + Console.WriteLine(line); + foreach (var line in Probe.NativeRetention("sqlite_nodispose", + () => new Microsoft.Data.Sqlite.SqliteConnection("Data Source=:memory:"), + Sql, disposeCommand: false)) Console.WriteLine(line); + foreach (var line in Probe.NativeRetention("sqlite_dispose", + () => new Microsoft.Data.Sqlite.SqliteConnection("Data Source=:memory:"), + Sql, disposeCommand: true)) Console.WriteLine(line); + } + if (mode == "sqlite-only") return; + + // ---- Npgsql: the arm where command.Dispose() releases nothing ------------------ + Console.WriteLine("\n## Npgsql (real PostgreSQL)"); + var pg = new Npgsql.NpgsqlConnection(pgDsn); + try { await pg.OpenAsync(); } + catch (Exception e) + { + Console.WriteLine($"# SKIP the Npgsql arm: cannot reach PostgreSQL at {pgDsn}: " + + $"{e.GetType().Name}: {e.Message}"); + Console.WriteLine("# Start one with ../run.sh runtime (pgserver) or set H28_PG_DSN."); + return; + } + using (pg) + { + var version = (string)await pg.ExecuteScalarAsync("select version()"); + Console.WriteLine($"# server: {version.Split('\n')[0]}"); + foreach (var v in Variants) + foreach (var line in Probe.Run(v.Name, pg, "select generate_series(1,3) as a", + v.Cmd, v.Rdr, load: true)) + Console.WriteLine(line); + foreach (var line in Probe.Prepared("npgsql", pg, "select $1::int")) + Console.WriteLine(line); + } + + Console.WriteLine("\n## how to read this"); + Console.WriteLine("# P4 stopped=0 on B_neither/D_command_only and stopped=1 on C_reader_only/A_both"); + Console.WriteLine("# => only reader.Dispose() stops the command's Activity; command.Dispose() does not."); + Console.WriteLine("# (P4 is 0/0 for every SQLite variant: Microsoft.Data.Sqlite has no ActivitySource.)"); + Console.WriteLine("# P2 OK everywhere => the reader does not end the command's logical lifetime."); + Console.WriteLine("# P5 unchanged => command.Dispose() releases no server-side prepared state."); + Console.WriteLine("# P6 delta(nodispose) >> delta(dispose) => native retention is real on SQLite only."); + } +} diff --git a/corpus/ownership-lab/h28-command/falsifier/h28cmd.csproj b/corpus/ownership-lab/h28-command/falsifier/h28cmd.csproj new file mode 100644 index 00000000..d7754c40 --- /dev/null +++ b/corpus/ownership-lab/h28-command/falsifier/h28cmd.csproj @@ -0,0 +1,23 @@ + + + + Exe + net8.0 + disable + enable + h28cmd-falsifier + H28Cmd + true + + + + + + diff --git a/corpus/ownership-lab/h28-command/fx/CommandDispose.cs b/corpus/ownership-lab/h28-command/fx/CommandDispose.cs new file mode 100644 index 00000000..59aa881b --- /dev/null +++ b/corpus/ownership-lab/h28-command/fx/CommandDispose.cs @@ -0,0 +1,163 @@ +// H-28-CMD fixture: the DbCommand half of the DataTable.Load(reader) family. +// +// Deliberately written against the ABSTRACT ADO.NET types (DbConnection / DbCommand / +// DbDataReader), not against Npgsql, because that is exactly what Own.NET's +// IsOwningFactory matches on (frontend/roslyn/OwnSharp.Extractor/Program.cs, the ADO.NET +// tranche: receiver implements IDbConnection, return implements IDbCommand). The fixture +// therefore shows the analyzer's verdict for EVERY provider at once -- which is the point: +// the runtime semantics are NOT the same for every provider (see ../README.txt and +// docs/notes/h28-npgsql-command-resolution.md). +// +// The method name carries the expectation. Run with the flow-locals default: +// dotnet ownsharp-extract.dll --flow-locals fx/CommandDispose.cs -o cmd.facts.json +// python -m ownlang ownir cmd.facts.json +// +// NOT EXECUTED IN THE SANDBOX THAT PRODUCED THIS COMMIT: no .NET SDK and no NuGet feed are +// reachable there (see docs/notes/h28-npgsql-command-resolution.md section 3). The +// expectations below are derived by source reading of the extractor at this commit, with +// line citations, and are marked PREDICTED until this file has been through a real build. +using System.Data; +using System.Data.Common; + +public static class CommandDispose +{ + // ---- the H-28 demanding instance and its three nearest neighbours ------------- + + // B: victor-wiki/DatabaseManager DbInterpreter.GetDataTableAsync:699 verbatim in shape. + // The reader's obligation is untracked by the argument-escape rule at `table.Load(reader)` + // (Program.cs, escapedLocals: `idn.Parent is ArgumentSyntax && !poolBuffers && !consumedArg + // && !IsAdoptedArgOfBoundedWrapper`). The command is NOT an argument anywhere, so it stays + // tracked and unreleased. + // PREDICTED: OWN001 on `command`; SILENT on `reader`. + public static DataTable B_load_neither_disposed(DbConnection connection, string sql) + { + var command = connection.CreateCommand(); + command.CommandText = sql; + var reader = command.ExecuteReader(); + + var table = new DataTable(); + table.Load(reader); // witnessed: closes the reader when it is the last result set + return table; + } + + // A: the fully-released control. PREDICTED: clean. + public static DataTable A_load_both_disposed(DbConnection connection, string sql) + { + using var command = connection.CreateCommand(); + command.CommandText = sql; + using var reader = command.ExecuteReader(); + + var table = new DataTable(); + table.Load(reader); + return table; + } + + // C: the reader explicitly released, the command not. This is the arm that matters for + // Npgsql tracing: reader.Dispose() is the ONLY thing that stops the command's Activity + // (NpgsqlDataReader.Dispose -> Command.TraceCommandStop; NpgsqlDataReader.Close does not). + // PREDICTED: OWN001 on `command`. + public static DataTable C_reader_disposed_command_not(DbConnection connection, string sql) + { + var command = connection.CreateCommand(); + command.CommandText = sql; + var reader = command.ExecuteReader(); + + var table = new DataTable(); + table.Load(reader); + reader.Dispose(); + return table; + } + + // D: the command released after Load, the reader not (Load already closed it). + // For Microsoft.Data.Sqlite this is the ONLY variant in the family that releases the + // native sqlite3_stmt handles at a deterministic point. PREDICTED: clean. + public static DataTable D_command_disposed_after_load(DbConnection connection, string sql) + { + var command = connection.CreateCommand(); + command.CommandText = sql; + var reader = command.ExecuteReader(); + + var table = new DataTable(); + table.Load(reader); + command.Dispose(); + return table; + } + + // ---- controls that pin the two rules the prediction rests on ------------------- + + // E: no DataTable.Load at all -- the reader obligation is never handed to an argument, so + // it stays tracked. This is corpus/real-world/ado-executereader-leak with the command + // owned instead of borrowed. PREDICTED: OWN001 on `command` AND OWN001 on `reader`. + // If E reports only one of the two, the escape rule -- not the acquire rule -- is what + // silenced the reader in B. + public static int E_no_load_both_leak(DbConnection connection, string sql) + { + var command = connection.CreateCommand(); + command.CommandText = sql; + var reader = command.ExecuteReader(); + var n = 0; + while (reader.Read()) + n++; + return n; + } + + // F: the reader released by an explicit Close() instead of an argument pass. + // Program.cs credits Close/Dispose/DisposeAsync as a release, so this isolates + // "released by a call the analyzer recognises" from "released by a callee it does not". + // PREDICTED: OWN001 on `command` only. + public static int F_reader_closed_command_not(DbConnection connection, string sql) + { + var command = connection.CreateCommand(); + command.CommandText = sql; + var reader = command.ExecuteReader(); + var n = 0; + while (reader.Read()) + n++; + reader.Close(); + return n; + } + + // G: the command is still logically usable after the reader closes. NpgsqlDataReader + // .Cleanup sets `Command.State = CommandState.Idle`, so reuse is legitimate -- the + // command's lifetime is NOT bounded by the reader's. A model that treated reader close + // as ending the command would mispredict this. PREDICTED: clean. + public static int G_command_reused_after_reader_closed(DbConnection connection, string sql) + { + using var command = connection.CreateCommand(); + command.CommandText = sql; + using (var reader = command.ExecuteReader()) + { + var t = new DataTable(); + t.Load(reader); + } + return command.ExecuteNonQuery(); // legal: the reader's Cleanup left it Idle + } + + // H: the prepared-statement arm. Server-side prepared state is the one resource in this + // family whose lifetime is NOT the command's: Npgsql registers it on the CONNECTOR + // (NpgsqlConnector.PreparedStatementManager), and NpgsqlCommand.Dispose never issues a + // DEALLOCATE. So disposing the command does not release it, and not disposing the command + // does not retain it. PREDICTED: OWN001 on `command` -- a finding whose fix would not + // change any server-side state. + public static int H_prepared_command_not_disposed(DbConnection connection, string sql) + { + var command = connection.CreateCommand(); + command.CommandText = sql; + command.Prepare(); + var n = command.ExecuteNonQuery(); + return n; + } + + // I: the dispose-optional control. DataTable IS IDisposable but Program.cs's + // IsDisposeOptional exempts System.Data.DataTable/DataSet/DataView, so `table` must not + // become a candidate. Proves the OWN001 in B is about the command, not the table. + // PREDICTED: OWN001 on `command` only, never on `table`. + public static DataTable I_table_is_dispose_optional(DbConnection connection, string sql) + { + var command = connection.CreateCommand(); + command.CommandText = sql; + var table = new DataTable(); + table.Load(command.ExecuteReader()); + return table; + } +} diff --git a/corpus/ownership-lab/h28-command/fx/CommandDispose.own b/corpus/ownership-lab/h28-command/fx/CommandDispose.own new file mode 100644 index 00000000..c3e89a82 --- /dev/null +++ b/corpus/ownership-lab/h28-command/fx/CommandDispose.own @@ -0,0 +1,70 @@ +// H-28-CMD: the ENGINE-RUNNABLE half of the fixture. +// +// fx/CommandDispose.cs needs the Roslyn extractor, which needs a .NET SDK; this file needs +// only `python -m ownlang`, so the engine arm of the finding is reproducible anywhere. +// +// Honest frame (the same one corpus/real-world/README.md states): this is a MANUAL REDUCTION +// of the C#, not C# the checker read. It models the two obligations the extractor's +// IsOwningFactory mints for this family -- IDbConnection.CreateCommand() -> DbCommand and +// IDbCommand.ExecuteReader() -> DbDataReader -- and it models the ONE runtime fact the +// extractor does not know: that DataTable.Load(reader) closes the reader. That last line is +// the whole point of the reduction: it is a witnessed callee effect that today has to be +// hand-written here because the argument-escape rule throws the call away instead. +module H28Cmd + +resource Command { + acquire open + release dispose + kind "disposable" + emit_type "DbCommand" + emit_acquire "{args}.CreateCommand()" + emit_release "{0}.Dispose()" +} + +resource Reader { + acquire open + release dispose + kind "disposable" + emit_type "DbDataReader" + emit_acquire "{args}.ExecuteReader()" + emit_release "{0}.Dispose()" +} + +// B -- the demanding instance (victor-wiki/DatabaseManager DbInterpreter.GetDataTableAsync:699). +// table.Load(reader) closed the reader: witnessed RELEASE_IF_LAST_RESULT_SET by the H-28 +// runtime falsifier (corpus/ownership-lab/h28/falsifier/falsifier.out, F1 IsClosed=True on +// Sqlite AND Npgsql). The command is never released. +// EXPECTED: OWN001 on 'command' -- and this is exactly the finding whose correctness is +// provider-dependent, which no .own model and no OwnIR fact can currently express. +fn B_load_neither_disposed(conn: int) { + let command = acquire Command(conn); + let reader = acquire Reader(command); + release reader; // DataTable.Load(reader) -> reader.Close() + // no `release command;` +} + +// A -- control: both released. EXPECTED: clean. +fn A_load_both_released(conn: int) { + let command = acquire Command(conn); + let reader = acquire Reader(command); + release reader; + release command; +} + +// D -- control: the command released after Load. EXPECTED: clean. This is the only variant +// that deterministically releases Microsoft.Data.Sqlite's native sqlite3_stmt handles. +fn D_command_released_after_load(conn: int) { + let command = acquire Command(conn); + let reader = acquire Reader(command); + release reader; + release command; +} + +// E -- control with NO Load: the reader obligation is never handed to a callee, so it stays +// tracked. EXPECTED: OWN001 on 'command' AND OWN001 on 'reader' -- two findings, which is how +// you can tell "the reader was released" apart from "the reader was untracked by the escape +// rule" without reading the extractor. +fn E_no_load_both_leak(conn: int) { + let command = acquire Command(conn); + let reader = acquire Reader(command); +} diff --git a/corpus/ownership-lab/h28-command/run.sh b/corpus/ownership-lab/h28-command/run.sh new file mode 100755 index 00000000..d999bb76 --- /dev/null +++ b/corpus/ownership-lab/h28-command/run.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# H-28-CMD reproduction driver. Two halves, deliberately separable: +# +# half 1 (ENGINE) fx/CommandDispose.own -> python -m ownlang check +# needs only python3. Runs anywhere, including the sandbox that +# produced the report. Artifact: evidence/engine-CommandDispose.txt +# +# half 2 (RUNTIME) falsifier/ -> dotnet run against a real PostgreSQL +# needs a .NET SDK, a NuGet feed, and PostgreSQL. Artifact: +# evidence/falsifier.out +# If any of those is missing this half prints SKIP-WHY and exits 0: +# a missing runtime arm is recorded, never silently pretended. +# +# Usage: corpus/ownership-lab/h28-command/run.sh [engine|runtime|all] +set -uo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT="$(cd "$HERE/../../.." && pwd)" +EV="$HERE/evidence" +MODE="${1:-all}" +mkdir -p "$EV" + +engine_half() { + echo "=== half 1: ENGINE (python -m ownlang check) ===" + local out="$EV/engine-CommandDispose.txt" + ( cd "$ROOT" && python3 -m ownlang check \ + corpus/ownership-lab/h28-command/fx/CommandDispose.own ) >"$out" 2>&1 + local rc=$? + { + echo "# exit code: $rc (OWN001 is error-severity, so non-zero is the expected result)" + echo "# command : python3 -m ownlang check corpus/ownership-lab/h28-command/fx/CommandDispose.own" + echo "# cwd : repository root" + echo "# python : $(python3 --version 2>&1)" + } >>"$out" + cat "$out" + # The prediction the report makes: 3 findings -- OWN001 on 'command' in B, and OWN001 on + # BOTH 'command' and 'reader' in E. A and D clean. Any other shape means the report is stale. + local n_cmd n_reader + n_cmd=$(grep -c "'command' is owned but not released" "$out") + n_reader=$(grep -c "'reader' is owned but not released" "$out") + echo + if [ "$n_cmd" = "2" ] && [ "$n_reader" = "1" ]; then + echo "PREDICTION HOLDS: 2x OWN001 'command' (B, E) + 1x OWN001 'reader' (E), A/D clean." + return 0 + fi + echo "PREDICTION BROKE: got command=$n_cmd reader=$n_reader; expected command=2 reader=1." + echo "Re-read docs/notes/h28-npgsql-command-resolution.md section 7 before trusting it." + return 1 +} + +runtime_half() { + echo + echo "=== half 2: RUNTIME (real PostgreSQL + Npgsql + Microsoft.Data.Sqlite) ===" + if ! command -v dotnet >/dev/null 2>&1; then + echo "SKIP-WHY: no .NET SDK on PATH. The report's runtime arm was NOT executed;" + echo " its claims are source-derived (evidence/*.txt) and marked PREDICTED." + echo " Install .NET 8 SDK + reach api.nuget.org, then re-run: $0 runtime" + return 0 + fi + if ! curl -sS -m 8 -o /dev/null https://api.nuget.org/v3/index.json 2>/dev/null; then + echo "SKIP-WHY: dotnet is present but api.nuget.org is unreachable, so Npgsql 10.0.3 and" + echo " Microsoft.Data.Sqlite 10.0.12 cannot be restored." + return 0 + fi + + # PostgreSQL: pgserver (PyPI) is what the original H-28 falsifier used -- a self-contained + # PostgreSQL 16 on a Unix socket, no root, no container. + local dsn="${H28_PG_DSN:-}" + if [ -z "$dsn" ] && python3 -c "import pgserver" 2>/dev/null; then + echo "starting PostgreSQL via pgserver ..." + dsn=$(python3 - "$HERE" <<'PY' +import os, re, sys, pgserver +d = "/tmp/h28cmd-pgdata" +srv = pgserver.get_server(d, cleanup_mode=None) +uri = srv.get_uri() # postgresql://postgres:@/postgres?host= +m = re.search(r"host=([^&\s]+)", uri) +print(f"Host={m.group(1) if m else d};Username=postgres;Database=postgres" if m else "") +PY +) + fi + if [ -z "$dsn" ]; then + echo "SKIP-WHY: no PostgreSQL. Either 'pip install pgserver' or export H28_PG_DSN." + echo " The SQLite arm still runs: dotnet run --project falsifier -- sqlite-only" + fi + export H28_PG_DSN="$dsn" + echo "DSN: ${H28_PG_DSN:-}" + + local out="$EV/falsifier.out" + ( cd "$HERE/falsifier" && dotnet run -c Release ) 2>&1 | tee "$out" + local rc=${PIPESTATUS[0]} + echo "# exit code: $rc" >>"$out" + return $rc +} + +case "$MODE" in + engine) engine_half ;; + runtime) runtime_half ;; + all) engine_half; runtime_half ;; + *) echo "usage: $0 [engine|runtime|all]" >&2; exit 2 ;; +esac diff --git a/corpus/ownership-lab/h28-command/scripts/derive_source_evidence.py b/corpus/ownership-lab/h28-command/scripts/derive_source_evidence.py new file mode 100755 index 00000000..d7a11cd7 --- /dev/null +++ b/corpus/ownership-lab/h28-command/scripts/derive_source_evidence.py @@ -0,0 +1,322 @@ +#!/usr/bin/env python3 +"""H-28-CMD evidence deriver: re-fetch every source citation used by +docs/notes/h28-npgsql-command-resolution.md from its PINNED upstream ref and +write the exact cited region to evidence/.txt. + +Nothing in the report is a hand-copied quotation: each artifact here carries the +repo, the ref (a tag where one exists, so the bytes are immutable), the path, the +byte count, the sha256 of the fetched file and the line range that was extracted. +Re-run this and diff evidence/ to re-verify the whole source arm of the finding. + + python3 corpus/ownership-lab/h28-command/scripts/derive_source_evidence.py + +Read-only: touches no production code, builds nothing, needs only github.com + +api.github.com. Set GH_TOKEN for a higher rate limit (works unauthenticated too). +Exit code is non-zero if any pinned citation could not be re-derived, so this +doubles as a staleness gate on the report. +""" + +from __future__ import annotations + +import hashlib +import json +import os +import sys +import urllib.error +import urllib.request + +HERE = os.path.dirname(os.path.abspath(__file__)) +OUT = os.path.join(HERE, "..", "evidence") + +API = "https://api.github.com/repos/{repo}/contents/{path}?ref={ref}" + +# (artifact name, repo, ref, path, marker that starts the region, max lines) +# The marker is matched literally; the region runs from the marker's line to +# marker+maxlines, so the citation is stable even if the file shifts. +CITATIONS: list[tuple[str, str, str, str, str, int]] = [ + # ---- Npgsql: what NpgsqlCommand.Dispose actually releases ----------------- + ( + "npgsql-10.0.3-NpgsqlCommand-Dispose.txt", + "npgsql/npgsql", "v10.0.3", "src/Npgsql/NpgsqlCommand.cs", + "protected override void Dispose(bool disposing)", 16, + ), + ( + "npgsql-10.0.3-NpgsqlCommand-Reset.txt", + "npgsql/npgsql", "v10.0.3", "src/Npgsql/NpgsqlCommand.cs", + "internal void Reset()", 14, + ), + ( + "npgsql-10.0.3-NpgsqlCommand-ctor-SuppressFinalize.txt", + "npgsql/npgsql", "v10.0.3", "src/Npgsql/NpgsqlCommand.cs", + "public NpgsqlCommand(string? cmdText, NpgsqlConnection? connection)", 10, + ), + ( + "npgsql-10.0.3-NpgsqlCommand-CreateCachedCommand.txt", + "npgsql/npgsql", "v10.0.3", "src/Npgsql/NpgsqlCommand.cs", + "internal static NpgsqlCommand CreateCachedCommand", 3, + ), + ( + "npgsql-10.0.3-NpgsqlCommand-TraceCommandStart.txt", + "npgsql/npgsql", "v10.0.3", "src/Npgsql/NpgsqlCommand.cs", + "internal void TraceCommandStart(NpgsqlTracingOptions tracingOptions", 26, + ), + ( + "npgsql-10.0.3-NpgsqlCommand-TraceCommandStop.txt", + "npgsql/npgsql", "v10.0.3", "src/Npgsql/NpgsqlCommand.cs", + "internal void TraceCommandStop()", 8, + ), + ( + "npgsql-10.0.3-NpgsqlConnection-CreateCommand.txt", + "npgsql/npgsql", "v10.0.3", "src/Npgsql/NpgsqlConnection.cs", + "public new NpgsqlCommand CreateCommand()", 15, + ), + ( + "npgsql-10.0.3-NpgsqlDataReader-Dispose.txt", + "npgsql/npgsql", "v10.0.3", "src/Npgsql/NpgsqlDataReader.cs", + "protected override void Dispose(bool disposing)", 26, + ), + ( + "npgsql-10.0.3-NpgsqlDataReader-Close-public.txt", + "npgsql/npgsql", "v10.0.3", "src/Npgsql/NpgsqlDataReader.cs", + "public override void Close() => Close(connectionClosing: false", 2, + ), + ( + "npgsql-10.0.3-NpgsqlDataReader-Cleanup.txt", + "npgsql/npgsql", "v10.0.3", "src/Npgsql/NpgsqlDataReader.cs", + "internal async Task Cleanup(bool async, bool connectionClosing", 80, + ), + ( + "npgsql-10.0.3-NpgsqlActivitySource-IsEnabled.txt", + "npgsql/npgsql", "v10.0.3", "src/Npgsql/NpgsqlActivitySource.cs", + "internal static bool IsEnabled", 2, + ), + ( + # The ActivitySource NAME is load-bearing for falsifier probe P4: the listener must + # match it or NpgsqlActivitySource.IsEnabled stays false and the whole tracing arm + # is inert (CurrentActivity never assigned), so P4 would prove nothing. + "npgsql-10.0.3-NpgsqlActivitySource-SourceName.txt", + "npgsql/npgsql", "v10.0.3", "src/Npgsql/NpgsqlActivitySource.cs", + "static readonly ActivitySource Source = new(\"Npgsql\"", 3, + ), + ( + "npgsql-10.0.3-NpgsqlConnector-PreparedStatementManager.txt", + "npgsql/npgsql", "v10.0.3", "src/Npgsql/Internal/NpgsqlConnector.cs", + "internal PreparedStatementManager PreparedStatementManager", 2, + ), + # ---- Npgsql main: is it still the same? ----------------------------------- + ( + "npgsql-main-NpgsqlCommand-Dispose.txt", + "npgsql/npgsql", "main", "src/Npgsql/NpgsqlCommand.cs", + "protected override void Dispose(bool disposing)", 16, + ), + ( + "npgsql-main-NpgsqlDataReader-Close-public.txt", + "npgsql/npgsql", "main", "src/Npgsql/NpgsqlDataReader.cs", + "public override void Close() => Close(connectionClosing: false", 2, + ), + # ---- BCL: the contract, at the ref the old falsifier ran on and at HEAD --- + ( + "runtime-v8.0.20-DataTable-Load.txt", + "dotnet/runtime", "v8.0.20", + "src/libraries/System.Data.Common/src/System/Data/DataTable.cs", + "public virtual void Load(IDataReader reader, LoadOption loadOption, " + "FillErrorEventHandler? errorHandler)", 30, + ), + ( + "runtime-v10.0.12-DataTable-Load.txt", + "dotnet/runtime", "v10.0.12", + "src/libraries/System.Data.Common/src/System/Data/DataTable.cs", + "public virtual void Load(IDataReader reader, LoadOption loadOption, " + "FillErrorEventHandler? errorHandler)", 30, + ), + ( + "runtime-v10.0.12-DbCommand-class.txt", + "dotnet/runtime", "v10.0.12", + "src/libraries/System.Data.Common/src/System/Data/Common/DbCommand.cs", + "public abstract class DbCommand", 4, + ), + ( + "runtime-v10.0.12-DbCommand-DisposeAsync.txt", + "dotnet/runtime", "v10.0.12", + "src/libraries/System.Data.Common/src/System/Data/Common/DbCommand.cs", + "public virtual ValueTask DisposeAsync()", 6, + ), + ( + "runtime-v10.0.12-DbDataReader-Close-Dispose.txt", + "dotnet/runtime", "v10.0.12", + "src/libraries/System.Data.Common/src/System/Data/Common/DbDataReader.cs", + "public virtual void Close() { }", 32, + ), + ( + "runtime-v10.0.12-IDbCommand.txt", + "dotnet/runtime", "v10.0.12", + "src/libraries/System.Data.Common/src/System/Data/IDbCommand.cs", + "public interface IDbCommand", 3, + ), + ( + "runtime-v8.0.20-Activity-Dispose.txt", + "dotnet/runtime", "v8.0.20", + "src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs", + "Dispose will stop the Activity if it is already started", 12, + ), + ( + "runtime-v8.0.20-Activity-IDisposable.txt", + "dotnet/runtime", "v8.0.20", + "src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs", + "public partial class Activity : IDisposable", 2, + ), + # ---- Cross-provider: does command.Dispose release anything there? --------- + ( + "mssqlite-SqliteCommand-Dispose.txt", + "dotnet/efcore", "main", + "src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs", + "protected override void Dispose(bool disposing)", 12, + ), + ( + "mssqlite-SqliteCommand-DisposePreparedStatements.txt", + "dotnet/efcore", "main", + "src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs", + "private void DisposePreparedStatements(bool disposing = true)", 22, + ), + ( + "mssqlite-SqliteCommand-PrepareAndEnumerate-head.txt", + "dotnet/efcore", "main", + "src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs", + "private IEnumerable<(sqlite3_stmt Statement, int ParamCount)> " + "PrepareAndEnumerateStatements()", 6, + ), + ( + "mssqlite-SqliteCommand-preparedStatements-field.txt", + "dotnet/efcore", "main", + "src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs", + "_preparedStatements =", 2, + ), + ( + "mssqlite-SqliteDataReader-Close-Dispose.txt", + "dotnet/efcore", "main", + "src/Microsoft.Data.Sqlite.Core/SqliteDataReader.cs", + "public override void Close()", 42, + ), + ( + "mssqlite-SqliteConnection-commands-weakrefs.txt", + "dotnet/efcore", "main", + "src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs", + "List> _commands", 2, + ), + ( + "sqlitepclraw-sqlite3_stmt-SafeHandle.txt", + "ericsink/SQLitePCL.raw", "main", + "src/SQLitePCLRaw.core/handles.cs", + "public class sqlite3_stmt : SafeHandle", 26, + ), + ( + "sqlclient-SqlCommand-Dispose.txt", + "dotnet/SqlClient", "main", + "src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs", + "protected override void Dispose(bool disposing)", 14, + ), + ( + "mysqlconnector-MySqlCommand-Dispose.txt", + "mysql-net/MySqlConnector", "master", + "src/MySqlConnector/MySqlCommand.cs", + "protected override void Dispose(bool disposing)", 6, + ), +] + + +def _get(url: str, accept: str) -> bytes | None: + tok = os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN") + req = urllib.request.Request(url, headers={"User-Agent": "own-net-h28cmd", + "Accept": accept}) + if tok: + req.add_header("Authorization", f"Bearer {tok}") + try: + with urllib.request.urlopen(req, timeout=120) as r: + return r.read() + except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError, OSError) as e: + print(f" FETCH-FAIL {url} ({accept}): {e}", file=sys.stderr) + return None + + +def fetch(repo: str, path: str, ref: str) -> tuple[bytes, str] | None: + """Return (raw bytes, git blob sha) for one pinned path, or None. + + Two calls: the raw accept gives the file bytes, the json accept gives the git + blob sha. The sha is what makes each citation checkable against git itself + rather than against whatever the API happened to serve. + """ + url = API.format(repo=repo, path=path, ref=ref) + raw = _get(url, "application/vnd.github.raw+json") + if raw is None: + return None + sha = "?" + meta = _get(url, "application/vnd.github+json") + if meta is not None: + try: + sha = str(json.loads(meta).get("sha", "?")) + except (ValueError, AttributeError): + pass + return raw, sha + + +def region(text: str, marker: str, maxlines: int) -> tuple[int, str] | None: + lines = text.split("\n") + for i, line in enumerate(lines): + if marker in line: + chunk = lines[i:i + maxlines] + start = i + 1 + body = "\n".join(f"{start + j:5d}| {ln}" for j, ln in enumerate(chunk)) + return start, body + return None + + +def main() -> int: + os.makedirs(OUT, exist_ok=True) + manifest: list[str] = [] + failed = 0 + for name, repo, ref, path, marker, maxlines in CITATIONS: + got = fetch(repo, path, ref) + if got is None: + failed += 1 + manifest.append(f"MISSING\t{name}\t{repo}@{ref}\t{path}") + continue + raw, sha = got + text = raw.decode("utf-8", "replace") + reg = region(text, marker, maxlines) + if reg is None: + failed += 1 + manifest.append(f"MARKER-NOT-FOUND\t{name}\t{repo}@{ref}\t{path}\t{marker!r}") + continue + start, body = reg + digest = hashlib.sha256(raw).hexdigest() + header = ( + f"# H-28-CMD source citation (auto-derived, do not edit by hand)\n" + f"# repo : {repo}\n" + f"# ref : {ref}\n" + f"# path : {path}\n" + f"# blob sha : {sha}\n" + f"# file sha256: {digest}\n" + f"# file bytes : {len(raw)}\n" + f"# marker : {marker!r}\n" + f"# region : lines {start}..{start + maxlines - 1}\n" + f"# derived by: scripts/derive_source_evidence.py\n" + ) + with open(os.path.join(OUT, name), "w") as f: + f.write(header + body.rstrip("\n") + "\n") + manifest.append(f"OK\t{name}\t{repo}@{ref}\tsha256={digest[:16]}\tlines={start}") + print(f" ok {name} ({repo}@{ref} line {start})") + + with open(os.path.join(OUT, "MANIFEST.txt"), "w") as f: + f.write("# H-28-CMD evidence manifest: one line per pinned citation.\n") + f.write("# OK = re-derived now; MISSING/MARKER-NOT-FOUND = the report is stale.\n") + f.write("\n".join(manifest) + "\n") + print(f"\n{len(CITATIONS) - failed}/{len(CITATIONS)} citations re-derived " + f"-> {os.path.relpath(OUT)}") + if failed: + print(f"{failed} FAILED: the report's source arm is not reproducible as pinned", + file=sys.stderr) + return 1 if failed else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/docs/notes/h28-npgsql-command-resolution.md b/docs/notes/h28-npgsql-command-resolution.md new file mode 100644 index 00000000..50664a4d --- /dev/null +++ b/docs/notes/h28-npgsql-command-resolution.md @@ -0,0 +1,423 @@ +# H-28-CMD — what the absence of `DbCommand.Dispose()` actually means + +Status: **research note, no production change**. Verdict: **H28-PROVIDER-SPECIFIC**. +Base: `main` = `e889f8b`. Artifacts and reproduction: [`corpus/ownership-lab/h28-command/`](../../corpus/ownership-lab/h28-command/). + +```csharp +var command = connection.CreateCommand(); +var reader = command.ExecuteReader(); +var table = new DataTable(); +table.Load(reader); // neither command nor reader is disposed +``` + +Citations named `evidence/…txt` are generated artifacts under that directory, each carrying +repo, ref, path, git blob sha, file sha256 and line range. Re-derive all 32 with +`scripts/derive_source_evidence.py` — non-zero exit means this note is stale. + +--- + +## 1. The original uncertainty + +H-28 was registered as *argument / callee ownership transfer*. Its "demanding instance" was +`victor-wiki/DatabaseManager` `DbInterpreter.GetDataTableAsync:699`, described in the +preregistration as *"connection is a parameter, command a local never disposed"*. H-28's own +runtime falsifier settled the **reader** and explicitly declined to settle the **command**: + +> `DataTable.Load(IDataReader)` closes the reader … the demanding instance is NOT a lease / +> protocol leak … **what remains is the never-disposed `DbCommand` `cmd` (an object leak +> without a protocol consequence)**. — `h28/scripts/h28_anchors_record.py` + +That is a description, not a classification: never falsified, never entered into the +witnessed-callee table, never reached a gate. This note closes it against the five candidate +classifications (real defect / provider-specific / benign / not classifiable / obsolete). + +--- + +## 2. Recovered prior evidence + +**H-28 is not on `main`.** It lives on `research/ownership-semantics-lab-v1` @ `298b305`, under +`corpus/ownership-lab/h28/` (36 files). The preregistration JSON itself lives in a separate +`Own.NET-paperwork` repository that is **not reachable**; its content is nevertheless +recoverable verbatim because `h28/scripts/h28_prereg.py` is the program that writes it. + +| claim | primary evidence | status | +|---|---|---| +| H-28 = argument/callee transfer, not command lifetime | `scripts/h28_prereg.py` (`track`, `hypothesis`) | confirmed | +| demanding instance = `DbInterpreter.GetDataTableAsync:699` | `h28_prereg.py` → `anchors.demanding_instance` | confirmed | +| `DataTable.Load(reader)` **closes** the reader; BORROW refuted | `falsifier/falsifier.out` (F1 `IsClosed=True` on sqlite **and** npgsql) → `RELEASE_IF_LAST_RESULT_SET` | confirmed | +| CA2000 (default options) reports **neither** command nor reader | `analyzers/ca2000/{Repro2.cs,ca2000-warnings.txt}` — CA2000 tracks `new`, not factory results | confirmed | +| "the never-disposed `DbCommand`" | `h28_anchors_record.py` → `consequence_for_the_demanding_instance` | **recorded, never classified** ← this note | +| gate → `RECORD_AND_STOP` (1 of 10 primary candidates confirmed) | `manual-read.json`, `scripts/h28final.py`, `h28-census-v1.json` | confirmed | +| the escape rule that drops an argument-passed local | `h28_prereg.py` → `necessary_condition_verified_by_code_reading`; issue [#382](https://github.com/PhysShell/Own.NET/issues/382) (open) | confirmed | + +Two provenance warnings. **Name collision:** `H28` in +`docs/notes/p022-bridge-verdict-checkpoint4b.md:216` is a *different* H28 (a bridge-verdict +hypothesis) and must not be conflated. **Stale facts:** `corpus/ownership-lab/h29/promotion/promo-u.facts.json` +— the only committed OwnIR facts mentioning `CreateCommand`/`ExecuteReader` — is **schema v1** +while the core is **v2**; `ownlang ownir` refuses it, so it is not evidence of current behaviour. + +--- + +## 3. Versions + +| component | version | source | +|---|---|---| +| .NET / Npgsql / MS.Data.Sqlite (old falsifier) | net8.0 / **10.0.3** / 10.0.12 | `h28/falsifier/w28.csproj` | +| PostgreSQL (old falsifier) | 16, pgserver, socket `/tmp/pgsc` | `h28_anchors_record.py` | +| PostgreSQL (this note) | **16.2**, `pgserver` 0.1.4 (PyPI) | started and queried here, §6 | +| BCL reference | `dotnet/runtime` `v8.0.20` **and** `v10.0.12` | the old TFM and current | +| Npgsql re-read | `v10.0.3` **and** `main` | §5.1 | +| cross-provider | `dotnet/efcore` main, `dotnet/SqlClient` main, `mysql-net/MySqlConnector` master, `ericsink/SQLitePCL.raw` main | §5.3 | +| Own.NET | `main` @ `e889f8b`, extractor TFM net8.0 | §7 | + +`DbCommand.cs`, `DbDataReader.cs`, `DbConnection.cs`, `IDataReader.cs`, `IDbCommand.cs` differ +between `v8.0.20` and `v10.0.12` **only in `using`-directive order**; `DataTable.Load`'s body is +unchanged. The contract did not move. + +--- + +## 4. Ownership semantics and the BCL contract + +| object | created by | owned by | what ends its logical lifetime | what it can hold | does its `Dispose` free another object? | +|---|---|---|---|---|---| +| `DbConnection` | caller / pool / DI | caller | `Close()`/`Dispose()` | the connector: socket, buffers, server session | yes — Npgsql's connection close also closes its current reader | +| `DbCommand` | `connection.CreateCommand()` | **caller**, nothing else takes it | `Dispose()`, or never | **provider-dependent** (§5) | **provider-dependent** — Sqlite: its reader + native statements; Npgsql: nothing | +| `DbDataReader` | `command.ExecuteReader()` | **caller** | `Close()` or `Dispose()` | connector binding, a pooled `ColumnInfo[]`, and (Npgsql) the command's `Activity` | no — but it discharges one field **of the command** (§5.2) | +| `DataTable` | `new DataTable()` | caller | nothing (dispose-optional) | managed rows only | n/a — `Load` never touches the command | + +**Guaranteed by the API contract.** `IDbCommand : IDisposable`, `IDataReader : IDisposable` with +a separate `Close()` — the interface guarantees `Dispose` *exists*, not what it frees +[`evidence/runtime-v10.0.12-IDbCommand.txt`]. `DbCommand : Component, IDbCommand, +IAsyncDisposable` and **does not override `Dispose` at all**; its only disposal member is +`DisposeAsync() { Dispose(); return default; }`. So BCL-level `DbCommand.Dispose()` *is* +`Component.Dispose()` and **all** command resource semantics are the provider's +[`evidence/runtime-v10.0.12-DbCommand-class.txt`, `evidence/runtime-v10.0.12-DbCommand-DisposeAsync.txt`]. +`DbDataReader.Close()` is `public virtual void Close() { }` — empty — and `Dispose()` → +`Dispose(true)` → `Close()`, so for a reader the two run the *same* provider cleanup +[`evidence/runtime-v10.0.12-DbDataReader-Close-Dispose.txt`]. + +**What `System.Data` currently does** (an implementation fact, not a contract) — +`DataTable.Load(IDataReader, LoadOption, FillErrorEventHandler?)`, `v10.0.12` line 4974, +identical logic at `v8.0.20` line 4969 [`evidence/runtime-v10.0.12-DataTable-Load.txt`]: + +```csharp +adapter.FillFromReader(new DataTable[] { this }, reader, 0, 0); +if (!reader.IsClosed && !reader.NextResult()) +{ + reader.Close(); +} +``` + +Three consequences, all load-bearing: (1) it calls **`Close()`, not `Dispose()`**; (2) it closes +**conditionally** — on a multi-result-set reader `NextResult()` is `true` and the reader is +**left open**, which is exactly the witnessed label `RELEASE_IF_LAST_RESULT_SET` and a case the +original falsifier never exercised; (3) `Load` **never touches the command** — it holds no +reference to it, so nothing about `Load` can discharge a command obligation. + +--- + +## 5. What the providers actually do + +### 5.1 Npgsql — `NpgsqlCommand.Dispose()` releases nothing + +`v10.0.3` line 1712 [`evidence/npgsql-10.0.3-NpgsqlCommand-Dispose.txt`], unchanged at `main` +[`evidence/npgsql-main-NpgsqlCommand-Dispose.txt`]: + +```csharp +protected override void Dispose(bool disposing) +{ + ResetTransaction(); // _transaction = null (managed field) + State = CommandState.Disposed; // managed int flag + if (IsCacheable && InternalConnection is not null && InternalConnection.CachedCommand is null) + { + Reset(); // recycle into the connection's one-slot command cache + InternalConnection.CachedCommand = this; + return; + } + IsCacheable = false; +} +``` + +* `Reset()` clears `_commandText`, `CommandType`, `_parameters`, `_timeout` and two flags — + **managed only**; no connector, buffer or prepared statement [`evidence/npgsql-10.0.3-NpgsqlCommand-Reset.txt`]. +* **No finalizer** (no `~NpgsqlCommand` in the file) and both public constructors call + `GC.SuppressFinalize(this)` [`evidence/npgsql-10.0.3-NpgsqlCommand-ctor-SuppressFinalize.txt`] — an undisposed + command leaves **nothing on the finalizer queue**. +* `connection.CreateCommand()` returns `CreateCachedCommand(this)` ⇒ `IsCacheable = true` + [`evidence/npgsql-10.0.3-NpgsqlConnection-CreateCommand.txt`, `evidence/npgsql-10.0.3-NpgsqlCommand-CreateCachedCommand.txt`], + so the cacheable branch is the live one for user code: the **only** observable effect of + `Dispose()` is recycling the command into the connection's single `CachedCommand` slot. +* Server-side prepared statements belong to the **connector** + (`NpgsqlConnector.PreparedStatementManager`, created in its constructor, cleared via + `ClearAll()` from `NpgsqlConnector`) — never from `NpgsqlCommand`, which holds only + `_connectorPreparedOn` to notice preparation on a *different* connector + [`evidence/npgsql-10.0.3-NpgsqlConnector-PreparedStatementManager.txt`]. + **`command.Dispose()` issues no `DEALLOCATE`.** + +### 5.2 Npgsql — the reader holds a cleanup duty over one of the *command's* fields + +`ExecuteReader` calls `TraceCommandStart`, which — **only if `NpgsqlActivitySource.IsEnabled`, +i.e. `Source.HasListeners()`** — stores a `System.Diagnostics.Activity` in +`command.CurrentActivity` [`evidence/npgsql-10.0.3-NpgsqlCommand-TraceCommandStart.txt`, +`evidence/npgsql-10.0.3-NpgsqlActivitySource-IsEnabled.txt`]; the source is named `"Npgsql"`, +which is what falsifier probe P4 must match or the arm stays inert +[`evidence/npgsql-10.0.3-NpgsqlActivitySource-SourceName.txt`]). `Activity` **is** `IDisposable` in +.NET 8 and 10, and *"Dispose will stop the Activity if it is already started and notify any +event listeners"* [`evidence/runtime-v8.0.20-Activity-IDisposable.txt`, +`evidence/runtime-v8.0.20-Activity-Dispose.txt`]. + +The only stopper is `NpgsqlCommand.TraceCommandStop()` +[`evidence/npgsql-10.0.3-NpgsqlCommand-TraceCommandStop.txt`], and a **repo-wide** grep of `npgsql@v10.0.3` finds +exactly two call sites — both in the reader's `Dispose`: + +``` +src/Npgsql/NpgsqlDataReader.cs:1030 Command.TraceCommandStop(); // Dispose(bool) +src/Npgsql/NpgsqlDataReader.cs:1058 Command.TraceCommandStop(); // DisposeAsync() +``` + +Neither `NpgsqlDataReader.Close()` (`isDisposing: false` +[`evidence/npgsql-10.0.3-NpgsqlDataReader-Close-public.txt`]) nor `NpgsqlCommand.Dispose()` calls it; inside +`Cleanup`, `isDisposing` gates only `State = ReaderState.Disposed` +[`evidence/npgsql-10.0.3-NpgsqlDataReader-Cleanup.txt`]. So in this family, **with a tracing +listener attached**: the span never ends (an OTel batch exporter exports on end, so it is never +exported), `Activity.Current` is not popped, disposing the *command* would not help, and a +second `ExecuteReader()` on the same command hits `Debug.Assert(CurrentActivity is null)` and — +in release — orphans the previous Activity. + +`Cleanup` also shows what the reader genuinely releases on **Close**, not only Dispose: it +returns its pooled `ColumnInfo[]` to `ArrayPool.Shared`, sets +`Connector.CurrentReader = null`, calls `Connector.EndUserAction()`, and sets +**`Command.State = CommandState.Idle`** — which is what makes the command legally *reusable* +after the reader closes. That is not ownership of the command. + +### 5.3 Cross-provider — the falsifier for "Dispose is always optional" + +| provider | `command.Dispose()` body | releases a resource? | owns its reader? | finalizer? | +|---|---|---|---|---| +| **Npgsql** 10.0.3 / main | `ResetTransaction(); State=Disposed;` + optional recycle | **no** | no | no (`GC.SuppressFinalize`) | +| **Microsoft.Data.Sqlite** | `DisposePreparedStatements(disposing); _connection?.RemoveCommand(this); base.Dispose(disposing);` | **YES — native `sqlite3_stmt`** | **YES** (`DataReader.Dispose()`) | n/a — the SafeHandle below has one | +| **Microsoft.Data.SqlClient** | `_cachedMetaData = null; CachedAsyncState?.ResetAsyncState(); base.Dispose(disposing);` | **no** (despite its `// Release unmanaged objects` comment) | no | no | +| **MySqlConnector** | `m_isDisposed = true; base.Dispose(disposing);` | **no** | no | no (`GC.SuppressFinalize`) | + +[`evidence/npgsql-10.0.3-NpgsqlCommand-Dispose.txt`, `mssqlite-SqliteCommand-Dispose.txt`, +`sqlclient-SqlCommand-Dispose.txt`, `mysqlconnector-MySqlCommand-Dispose.txt`] + +The SQLite arm: `ExecuteReader` → `GetStatements()` → for an unprepared command, +`PrepareAndEnumerateStatements()`, an iterator calling `sqlite3_prepare_v2` that adds every +native statement to the command's `_preparedStatements` +[`evidence/mssqlite-SqliteCommand-PrepareAndEnumerate-head.txt`, +`evidence/mssqlite-SqliteCommand-preparedStatements-field.txt`] — so a **plain** `ExecuteReader()` +puts native handles on the **command**. `SqliteDataReader.Close()` → `Dispose(true)` disposes +only `_stmtEnumerator` and nulls `_command.DataReader`; it does **not** finalize the statements, +which live on the command [`evidence/mssqlite-SqliteDataReader-Close-Dispose.txt`]. And +`sqlite3_stmt : SafeHandle` with `ownsHandle: true` and +`ReleaseHandle() => raw.internal_sqlite3_finalize(handle)` +[`evidence/sqlitepclraw-sqlite3_stmt-SafeHandle.txt`] — `SafeHandle` carries a critical +finalizer, so without `Dispose` the `sqlite3_finalize` is deferred to non-deterministic +finalization. ⇒ **`command.Dispose()` is the only deterministic releaser of those handles in +this family**; variants B and C genuinely defer a native release to the GC. + +Two intermediate hypotheses of my own were refuted and are recorded so they are not re-derived: +*"SQLite accumulates statements across executions"* — false, `PrepareAndEnumerateStatements()` +opens with `DisposePreparedStatements(disposing: false)`; *"non-disposal extends the command's +lifetime via the connection"* — false, `SqliteConnection._commands` is a +`List>` [`evidence/mssqlite-SqliteConnection-commands-weakrefs.txt`]. + +--- + +## 6. Runtime observations + +**Executed here.** PostgreSQL **16.2** was started via `pgserver` 0.1.4 (self-contained PyPI +wheel, Unix socket) — the harness class the original H-28 falsifier used — and queried with its +bundled `psql` to pin the server-side half independently of any source reading: + +| observation | result | +|---|---| +| `PREPARE p1(int) AS SELECT $1`, then `pg_prepared_statements` in the same session | listed | +| same query from a **new** session | `0` — per-backend | +| `DECLARE c1 CURSOR …`, then `pg_cursors` | listed; gone with the session | + +⇒ server-side prepared state and cursors are **session-scoped**, bound to the *connection* — +which is why no command-level `Dispose` can be their releaser. This corroborates §5.1. + +**Not executed here: the .NET half.** There is no .NET SDK in this sandbox and no reachable +NuGet feed — `api.nuget.org`, `nuget.org`, `nuget.pkg.github.com`, `dotnet.microsoft.com`, +`builds.dotnet.microsoft.com`, `pkgs.dev.azure.com`, `objects.githubusercontent.com`, +`deb.debian.org` and `cache.nixos.org` are all unreachable; the ~909k-entry PyPI simple index and +the npm registry were searched and publish no modern .NET runtime (npm's `node-api-dotnet` ships +managed shims without `coreclr`). `run.sh runtime` prints `SKIP-WHY` and exits 0 rather than +pretending. The falsifier is committed **ready to run** +(`falsifier/`, pinned to net8.0 / Npgsql 10.0.3 / MS.Data.Sqlite 10.0.12 so its output compares +directly with the original `falsifier.out`); every probe exists to separate two hypotheses: + +| probe | separates | +|---|---| +| P1 `reader.IsClosed` after `Load` | reproduces H-28 F1 (`RELEASE` vs `BORROW`) | +| P2 command reusable after the reader closed | "the reader bounds the command's lifetime" vs "leaves it `Idle`" (§5.2 predicts OK) | +| P3 second command on the same connection | protocol leak vs object leak | +| **P4 `ActivityListener` started/stopped counts** | **the §5.2 claim**: `stopped=0` for B and D, `stopped=1` for C and A | +| P5 `pg_prepared_statements` before/after `command.Dispose()` | whether command disposal releases server-side state (§5.1 predicts no change) | +| P6 RSS delta over N iterations, ± `command.Dispose()` | the §5.3 SQLite native-retention arm | + +P4 matters most: §5.2 is the only load-bearing claim resting on `Activity` lifecycle semantics +rather than on a plainly readable `Dispose` body. P4 is **Npgsql-only** — Microsoft.Data.Sqlite +has no `ActivitySource`/`DiagnosticSource` anywhere in `SqliteCommand.cs` or `SqliteDataReader.cs` +(`grep -c 'ActivitySource\|DiagnosticSource'` over the fetched files at `dotnet/efcore@main`: 0), +so `0/0` on the SQLite variants is the expected reading, not a failed probe. P6 uses process RSS, **not** +`GC.GetTotalMemory` (the resource is native), and is a delta over N iterations with an explicit +finalization control — one snapshot cannot separate retention from allocation noise. + +--- + +## 7. What current Own.NET does + +### 7.1 Extraction (source-derived — the extractor was not runnable here) + +Line numbers are `frontend/roslyn/OwnSharp.Extractor/Program.cs` @ `e889f8b`. + +1. **`command` becomes a tracked owned local.** `IsOwningFactory` (L4832) recognises the ADO.NET + tranche by method name **plus both resolved types implementing the `System.Data` interfaces**: + `CreateCommand` with `IDbConnection`→`IDbCommand` (L4880-4882), `ExecuteReader` + `IDbCommand`→`IDataReader` (L4877-4879), `BeginTransaction` (L4883-4885). The flow pass adds + the local to `candidates` (L7035-7036). +2. **No exemption applies.** `IsDisposeOptional` (L2593-2606) is a closed list — `Task`/`ValueTask`, + `DataTable`/`DataSet`/`DataView`, `StringWriter`/`StringReader`; `DbCommand` is absent. + `HasEmptyDisposeBody` cannot apply either: since the #238 soundness gate it is confined to + types implementing `IEnumerator`, which `DbCommand` does not. +3. **`reader`'s obligation is silently dropped** at `table.Load(reader)`: the argument-escape + rule (L7155-7162) untracks any candidate whose identifier is an `ArgumentSyntax` unless it is + a pool buffer, a bare-statement `consumedArg`, or an adopted ctor argument of a bounded + wrapper; then `tracked.ExceptWith(escapedLocals)` (L7182). `DataTable.Load` is external, so no + carve-out applies. This is precisely the row of + [#382](https://github.com/PhysShell/Own.NET/issues/382) reading *"anything else, and the + argument is a tracked local → an escape: the local is untracked"*. +4. `table` is not a candidate (dispose-optional), and `Close`/`Dispose`/`DisposeAsync` are + credited as releases (e.g. L3476, L4143) — which is what makes fixture variant F differ from B. + +**Predicted verdict:** `OWN001` (error severity, non-zero exit) on **`command`**, silence on +**`reader`**. + +### 7.2 Engine (executed) + +`fx/CommandDispose.own` is a hand reduction of the same family — the honest frame +`corpus/real-world/README.md` states: a model, not C# the checker read. + +``` +$ corpus/ownership-lab/h28-command/run.sh engine +…:42:3: error: [OWN001] 'command' is owned but not released at end of function … +…:69:32: error: [OWN001] 'command' is owned but not released at end of function … +…:69:7: error: [OWN001] 'reader' is owned but not released at end of function … +3 errors. # exit 1 +PREDICTION HOLDS: 2x OWN001 'command' (B, E) + 1x OWN001 'reader' (E), A/D clean. +``` +[`evidence/engine-CommandDispose.txt`] + +Given the obligation the core is correct: B reports the command, A and D are clean, and E (no +`Load`, so no escape) reports **both** — which separates the escape rule from the acquire rule +without reading the extractor. + +### 7.3 Where the model diverges from the runtime semantics + +* **The acquire rule is provider-blind.** `IsOwningFactory` matches `System.Data` *interface* + types precisely so it covers every provider — stated as a feature in its own comment. But §5.3 + shows `Dispose`'s effect is not uniform: real native release on Microsoft.Data.Sqlite, nothing + on the other three. `IsDisposeOptional` is the only channel for "disposing frees nothing", and + it is keyed on **namespace + type name** with no provider dimension and no implementation + input. Nothing in the model can carry "Dispose-effect: none" for Npgsql and "Dispose-effect: + native statements" for SQLite. +* **The escape drop is accidentally right, and only sometimes.** For a single-result-set reader, + dropping `reader` agrees with the witnessed `RELEASE_IF_LAST_RESULT_SET`. For a + **multi-result-set** reader `Load` leaves it **open** (§4.1) — there the drop is a false + negative. Own.NET is silent in both cases and cannot distinguish them, because the call fact + never reaches the core. +* **On Npgsql the finding is inverted.** With tracing enabled the object whose disposal is + load-bearing is the **reader** (§5.2) — the one Own.NET is silent about — while the + error-severity `OWN001` falls on the command, whose `Dispose` provably frees nothing (§5.1). +* **Coverage.** No committed `.facts.json` anywhere (main *or* the research branch) contains + `CreateCommand`, `DbCommand` or `DbDataReader`, and + `corpus/real-world/ado-executereader-leak/before.cs` deliberately makes the command a *borrowed + parameter* ("the only leak is `reader`"). The never-disposed-`CreateCommand()` shape has never + been through Own.NET end-to-end; `fx/CommandDispose.cs` is its first fixture. + +--- + +## 8. Falsifiers + +| if it seems that… | falsifier attempted | result | +|---|---|---| +| the command **must** be disposed | find a provider where non-disposal is provably harmless | **found**: Npgsql (§5.1), also SqlClient and MySqlConnector (§5.3) | +| disposing the command is **optional** | find a provider/path leaving an observable resource | **found**: Microsoft.Data.Sqlite — native `sqlite3_stmt` SafeHandles on the command, unreleased by reader `Close`/`Dispose`, deterministically released only by `command.Dispose()` (§5.3) | +| the **reader owns the command** | prove or refute from implementation | **refuted as ownership**: `Cleanup` sets `Command.State = Idle` — it makes the command *reusable*. A partial cleanup duty over exactly one field (`CurrentActivity`, via `reader.Dispose` → `TraceCommandStop`) is not ownership, and `Close()` does not discharge it (§5.2) | +| `DataTable.Load` discharges the command | read `Load` | **refuted**: no reference to the command (§4.1) | +| `Load` always closes the reader | read the condition | **refuted as universal**: `!reader.NextResult()` — a multi-result-set reader stays **open** (§4.1) | +| undisposed commands accumulate SQLite statements | read `PrepareAndEnumerateStatements` | **refuted**: opens with `DisposePreparedStatements(disposing: false)` (§5.3) | +| non-disposal extends command lifetime via the connection | read `SqliteConnection._commands` | **refuted**: weak references (§5.3) | +| **Own.NET is simply wrong** | localise source → facts → verdict | **partly refuted**: extraction is faithful and the core is correct given the facts (§7.2). The defect is not in a stage — it is the missing provider dimension on the acquire plus the escape rule discarding the one call fact that decides the reader (§7.3) | +| the question is obsolete | — | **refuted**: the residual was recorded but never classified (§2), and the answer changes an engineering decision (§9) | + +**Not falsified but unproven at runtime:** §5.2's Activity consequence and §5.3's native +retention. Both are source-derived and marked PREDICTED; P4 and P6 exist to settle them (§6). + +--- + +## 9. Verdict + +# H28-PROVIDER-SPECIFIC + +> **The absence of `Dispose()` on a `DbCommand` in the `DataTable.Load(reader)` family is not one +> fact. It is a real resource/lifetime defect on Microsoft.Data.Sqlite and releases nothing on +> Npgsql, Microsoft.Data.SqlClient and MySqlConnector. No provider-independent ownership rule can +> classify it, because the effect of `DbCommand.Dispose()` is entirely a provider implementation +> detail — the BCL declares no `Dispose` on `DbCommand` at all.** + +1. The old residual *"an object leak without a protocol consequence"* is **true for Npgsql, false + for Microsoft.Data.Sqlite**. It was written from a two-provider falsifier whose SQLite arm + never looked at the command. +2. Own.NET's `OWN001` on the undisposed command is **sound for Microsoft.Data.Sqlite** and + **unsupported for Npgsql**, where it is an error-severity, build-failing finding about an + object whose `Dispose` provably frees nothing. +3. That is **not** an argument for exempting `DbCommand`: the analyzer cannot tell the two apart + where the obligation is minted, and the SQLite case is a genuine native-handle deferral. + Suppressing `DbCommand` to remove the Npgsql false positive would delete a true positive + elsewhere. This is the decision the note exists to enable. +4. The sharper defect in this family is **not** the command. It is that `reader` — load-bearing on + Npgsql (§5.2) and a real leak on a multi-result-set reader (§4.1) — is dropped by the escape + rule with no callee fact recorded. On Npgsql with tracing on the verdict is **inverted**: + error on the harmless object, silence on the harmful one. + +**Pivot record.** The investigation moved from "is the command a leak?" to "what is the ownership +relation `connection → command → reader`?", because the command question resolved to a provider +table while the *relation* is where a reproducible, ownership-semantic, practically consequential +witness turned up (§5.2, §7.3). + +--- + +## 10. Minimal next step + +A model gap is proved (§7.3), so — and only so — the smallest experiment that would close it. It +is **an instance of [#382](https://github.com/PhysShell/Own.NET/issues/382), not a parallel +architecture**: #382 already names this exact lowering and this exact loss (*"the lowering still +destroys two facts: that an interprocedural call happened, and which callee parameter received +which resource"*). The connection is demonstrable: the witnessed H-28 label +`DataTable.Load → RELEASE_IF_LAST_RESULT_SET` *is* a callee-parameter effect, which is the +vocabulary #382 proposes to stop guessing at in the frontend. `proven_call` (OwnIR v2, H1 — +`docs/notes/h1-proven-call.md`) is the landed precedent for the transport: the frontend emits a +call-shaped op, the core decides. + +| | | +|---|---| +| **Observed semantics** | `DataTable.Load(reader)` calls `reader.Close()` iff the first result set is the last, else the reader stays open; it never affects the command. `DbCommand.Dispose()`'s effect is provider-defined: nothing (Npgsql/SqlClient/MySqlConnector) or native statements + the reader (Microsoft.Data.Sqlite). | +| **Current representation** | The `Load` call is erased — `reader` joins `escapedLocals`, no `call` op (L7155-7182). The command's obligation is minted by `IsOwningFactory` (L4880) with no dispose-effect attribute; `IsDisposeOptional` (L2593) is the only "Dispose frees nothing" channel, keyed on namespace+type name. | +| **Missing fact** | (a) that an external call received the tracked reader, with the callee identity, so a witnessed-effect table can be consulted at all; (b) **conditionally** — the effect depends on `NextResult()`, which no static fact can settle. | +| **Smallest representation change** | #382's, restricted: emit a `call` op for an argument pass to an **external** callee instead of a bare escape, carrying `(callee, argument → parameter)`. No new lattice, no new diagnostic code, no provider special-casing. (b) then resolves to *may-release*, not *release* — the honest answer, needing no new vocabulary. | +| **Positive fixture** | `fx/CommandDispose.cs` variant **B** (single result set): `reader` carries a `call` fact to `DataTable.Load`, the core consults the witnessed label and reports `may-released`, leaving `OWN001` on `command` as the only finding. | +| **Negative / control fixture** | Variant **E** (no `Load`) must still report `OWN001` on **both** — proving the `call` fact did not become a blanket exemption. **A**/**D** must stay clean. | +| **Mutation / falsifier** | A multi-result-set reader passed to `Load`: `reader.IsClosed` is `false` afterwards (§4.1). Any representation recording `Load` as an unconditional release is falsified by it. Runtime probe **P4** falsifies §5.2 if `stopped=1` on variant B. | +| **Expected verdict delta** | B: `OWN001 command` unchanged, `reader` moves from *silently untracked* to *tracked-with-a-witnessed-may-release*. E: unchanged (2 findings). No finding is added or removed on this fixture — the delta is that the reader's disposition becomes a **recorded fact** instead of an erasure, which is what makes the multi-result-set false negative reachable later. | +| **Soundness risk** | Low and bounded, *provided* the label is entered as `RELEASE_IF_LAST_RESULT_SET` (conditional), never as `RELEASE`; unconditional would suppress a real leak on multi-result-set readers — a false negative worse than today's silence because it would look justified. Second risk: a `call` op for external callees widens what reaches the core, so #382's own gate applies unchanged (the H-28 `RECORD_AND_STOP` balance: ~1 true positive against ~110 sites each needing a proven effect) and must be re-checked before any default flips. | + +**Explicitly not proposed:** a TLA+ model, a P-037 change, a new summary lattice, provider-wide +Npgsql special cases, a universal ADO.NET model, a Roslyn frontend rewrite, or a new diagnostic +code. The provider dimension on `IsOwningFactory` (§7.3) is deliberately **left open**: it needs a +decision about where provider knowledge may live at all, which is larger than H-28 and should not +be settled by it. From 008a21736e83139954d4ed1b34ab89fc8a194a00 Mon Sep 17 00:00:00 2001 From: PhysShell <45852143+PhysShell@users.noreply.github.com> Date: Sat, 10 Oct 2026 11:24:08 +0000 Subject: [PATCH 2/7] test(h28): correct command falsifier and verdict Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com> --- .github/workflows/h28-command-falsifier.yml | 46 ++ corpus/ownership-lab/h28-command/README.txt | 107 +-- .../h28-command/evidence/MANIFEST.txt | 31 +- .../efcore-v10.0.12-SQLitePCLRawVersion.txt | 14 + .../efcore-v10.0.12-default-net-target.txt | 14 + .../evidence/engine-CommandDispose.txt | 25 +- .../h28-command/evidence/local-validation.txt | 159 +++++ .../mssqlite-SqliteCommand-Dispose.txt | 34 +- ...qliteCommand-DisposePreparedStatements.txt | 54 +- ...SqliteCommand-PrepareAndEnumerate-head.txt | 22 +- ...SqliteCommand-preparedStatements-field.txt | 14 +- .../mssqlite-SqliteConnection-Handle.txt | 13 + ...ite-SqliteConnection-commands-weakrefs.txt | 14 +- ...ssqlite-SqliteDataReader-Close-Dispose.txt | 94 +-- .../mssqlite-v10.0.12-Core-targets.txt | 14 + ...-v10.0.12-Microsoft.Data.Sqlite-target.txt | 14 + .../mysqlconnector-MySqlCommand-Dispose.txt | 2 +- ...-10.0.3-NpgsqlActivitySource-IsEnabled.txt | 2 +- ...10.0.3-NpgsqlActivitySource-SourceName.txt | 2 +- ....0.3-NpgsqlCommand-CreateCachedCommand.txt | 2 +- .../npgsql-10.0.3-NpgsqlCommand-Dispose.txt | 8 +- .../npgsql-10.0.3-NpgsqlCommand-Reset.txt | 2 +- ...10.0.3-NpgsqlCommand-TraceCommandStart.txt | 8 +- ....3-NpgsqlCommand-ctor-SuppressFinalize.txt | 2 +- ...ql-10.0.3-NpgsqlCommand-finalizer-scan.txt | 11 + ...-10.0.3-NpgsqlConnection-CreateCommand.txt | 6 +- ...gsqlConnector-PreparedStatementManager.txt | 2 +- ...npgsql-10.0.3-NpgsqlDataReader-Cleanup.txt | 18 +- ...l-10.0.3-NpgsqlDataReader-Close-public.txt | 2 +- ...npgsql-10.0.3-NpgsqlDataReader-Dispose.txt | 4 +- ...l-10.0.3-NpgsqlDataReader-DisposeAsync.txt | 38 ++ .../npgsql-main-NpgsqlCommand-Dispose.txt | 8 +- ...sql-main-NpgsqlDataReader-Close-public.txt | 2 +- .../npgsql-v10.0.3-target-frameworks.txt | 14 + .../evidence/required-gates-local.txt | 27 + .../runtime-v10.0.12-DataTable-Load.txt | 2 +- ...me-v10.0.12-DbDataReader-Close-Dispose.txt | 10 +- .../runtime-v10.0.12-SafeHandle-finalizer.txt | 26 + .../runtime-v8.0.20-Activity-Dispose.txt | 2 +- .../runtime-v8.0.20-DataTable-Load.txt | 2 +- .../runtime-v8.0.20-SafeHandle-finalizer.txt | 26 + .../evidence/source-derivation-local.txt | 49 ++ .../evidence/sqlclient-SqlCommand-Dispose.txt | 4 +- .../sqlitepclraw-sqlite3_stmt-SafeHandle.txt | 62 +- .../sqlitepclraw-v2.1.12-enable-next-stmt.txt | 26 + ...sqlitepclraw-v2.1.12-find-stmt-enabled.txt | 26 + ...sqlitepclraw-v2.1.12-sqlite3_next_stmt.txt | 23 + .../h28-command/falsifier/Program.cs | 517 +++++++++----- .../h28-command/falsifier/h28cmd.csproj | 2 + .../falsifier/run_with_pgserver.py | 60 ++ .../h28-command/fx/CommandDispose.cs | 42 +- .../h28-command/fx/CommandDispose.own | 20 +- corpus/ownership-lab/h28-command/run.sh | 213 ++++-- .../scripts/derive_source_evidence.py | 154 ++++- .../h28-command/scripts/verify_g3.py | 144 ++++ docs/notes/h28-npgsql-command-resolution.md | 645 ++++++++---------- 56 files changed, 1981 insertions(+), 903 deletions(-) create mode 100644 .github/workflows/h28-command-falsifier.yml create mode 100644 corpus/ownership-lab/h28-command/evidence/efcore-v10.0.12-SQLitePCLRawVersion.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/efcore-v10.0.12-default-net-target.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/local-validation.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteConnection-Handle.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/mssqlite-v10.0.12-Core-targets.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/mssqlite-v10.0.12-Microsoft.Data.Sqlite-target.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-finalizer-scan.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-DisposeAsync.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/npgsql-v10.0.3-target-frameworks.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/required-gates-local.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-SafeHandle-finalizer.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/runtime-v8.0.20-SafeHandle-finalizer.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/source-derivation-local.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/sqlitepclraw-v2.1.12-enable-next-stmt.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/sqlitepclraw-v2.1.12-find-stmt-enabled.txt create mode 100644 corpus/ownership-lab/h28-command/evidence/sqlitepclraw-v2.1.12-sqlite3_next_stmt.txt create mode 100755 corpus/ownership-lab/h28-command/falsifier/run_with_pgserver.py create mode 100755 corpus/ownership-lab/h28-command/scripts/verify_g3.py diff --git a/.github/workflows/h28-command-falsifier.yml b/.github/workflows/h28-command-falsifier.yml new file mode 100644 index 00000000..72ad52a3 --- /dev/null +++ b/.github/workflows/h28-command-falsifier.yml @@ -0,0 +1,46 @@ +name: H-28 DbCommand falsifier + +on: + pull_request: + paths: + - '.github/workflows/h28-command-falsifier.yml' + - 'corpus/ownership-lab/h28-command/**' + - 'frontend/roslyn/OwnSharp.Extractor/**' + - 'ownlang/**' + - 'docs/notes/h28-npgsql-command-resolution.md' + workflow_dispatch: + +permissions: + contents: read + +jobs: + h28-required-gates: + name: H-28 required runtime + extractor gates + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: '3.11' + - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4 + with: + dotnet-version: '8.0.x' + - name: Install self-contained PostgreSQL 16 harness + run: python -m pip install 'pgserver==0.1.4' + - name: Run mandatory G1-G3 gates (SKIP is failure) + run: corpus/ownership-lab/h28-command/run.sh all-required + - name: Preserve source facts and runtime observations + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: h28-command-falsifier + if-no-files-found: warn + path: | + corpus/ownership-lab/h28-command/evidence/g3-CommandDispose.facts.json + corpus/ownership-lab/h28-command/evidence/g3-CommandDispose.summaries.json + corpus/ownership-lab/h28-command/evidence/g3-CommandDispose.sarif.json + corpus/ownership-lab/h28-command/evidence/g3-CommandDispose.log + corpus/ownership-lab/h28-command/evidence/g3-*.log + corpus/ownership-lab/h28-command/evidence/runtime-*.log + corpus/ownership-lab/h28-command/evidence/runtime-*.out diff --git a/corpus/ownership-lab/h28-command/README.txt b/corpus/ownership-lab/h28-command/README.txt index 4688ff5d..287ad8ea 100644 --- a/corpus/ownership-lab/h28-command/README.txt +++ b/corpus/ownership-lab/h28-command/README.txt @@ -1,59 +1,72 @@ H-28-CMD: the DbCommand half of the `DataTable.Load(reader)` family. -WHAT THIS IS - The original H-28 (corpus/ownership-lab/h28/, on the research branch - research/ownership-semantics-lab-v1 at 298b305 -- NOT on main) asked about argument - ownership transfer. Its runtime falsifier settled `DataTable.Load(reader)`: the reader IS - closed (RELEASE_IF_LAST_RESULT_SET, not BORROW). The same record then wrote down one - sentence and never classified it: +STATUS + Final verdict: H28-INCONCLUSIVE pending G1/G2/G3. Provider source suggests a provider-specific + difference, but the .NET runtime falsifier and production Roslyn -> OwnIR -> core path have not + run yet. Read docs/notes/h28-npgsql-command-resolution.md before interpreting any artifact. + + The original H-28 (research/ownership-semantics-lab-v1@298b305, not on main) settled + `DataTable.Load(reader)` for a one-result reader: it IS closed (`RELEASE_IF_LAST_RESULT_SET`, + not BORROW). That same record wrote down one sentence and never classified it: "what remains is the never-disposed DbCommand `cmd` (an object leak without a protocol consequence)" -- h28/scripts/h28_anchors_record.py, consequence_for_the_demanding_instance - This directory closes that sentence. Read docs/notes/h28-npgsql-command-resolution.md. - - Verdict: H28-PROVIDER-SPECIFIC. Not disposing the command is a real resource defect on - Microsoft.Data.Sqlite and releases nothing on Npgsql / Microsoft.Data.SqlClient / - MySqlConnector. Details, citations and falsifiers are in the report. + This directory turns that residual into falsifiable source and runtime checks. It does not yet + call OWN001 a false positive, claim a native leak on Npgsql, or claim a proven model gap. LAYOUT - fx/CommandDispose.cs the fixture, 9 methods (A/B/C/D + 5 controls), written against the - ABSTRACT ADO.NET types on purpose -- that is what the extractor's - IsOwningFactory matches, so one fixture shows the analyzer's verdict - for every provider at once. Expectations are marked PREDICTED: the - Roslyn extractor was not runnable where this was produced. - fx/CommandDispose.own the ENGINE-RUNNABLE reduction of the same family. Needs only - python3. This half WAS executed; see evidence/. - falsifier/Program.cs the runtime falsifier: probes P1-P6 over variants A/B/C/D. - falsifier/h28cmd.csproj net8.0, Npgsql 10.0.3, Microsoft.Data.Sqlite 10.0.12 -- the exact - versions of the original H-28 falsifier, so the two runs compare. - run.sh driver. `engine` always runs; `runtime` prints SKIP-WHY and exits 0 - when there is no .NET SDK / NuGet / PostgreSQL, rather than faking it. + fx/CommandDispose.cs nine C# methods (A/B/C/D + controls), using abstract ADO.NET types. + The extraction expectations are PREDICTED until G3 runs. + fx/CommandDispose.own engine-only manual reduction. This is not C# extraction evidence. + falsifier/Program.cs P1-P5 and G2/P6. P4 is isolated from P1-P3; P6 counts live SQLite + statements via sqlite3_next_stmt, not RSS or forced GC. + falsifier/h28cmd.csproj net8.0, Npgsql 10.0.3, Microsoft.Data.Sqlite 10.0.12; compiles + both the falsifier and fx/CommandDispose.cs. + falsifier/run_with_pgserver.py + keeps the temporary pgserver alive while the .NET test runs. + scripts/verify_g3.py production Roslyn -> OwnIR -> summaries -> core SARIF; exact expected + finding anchors are checked, and all intermediate artifacts saved. scripts/derive_source_evidence.py - re-fetches all 31 pinned source citations and rewrites evidence/. - Non-zero exit = the report's source arm is stale. - evidence/*.txt the artifacts every claim in the report cites. Each carries repo, - ref, path, git blob sha, file sha256 and the extracted line range, - so a quotation is checkable against git rather than against memory. - -REPRODUCE - corpus/ownership-lab/h28-command/run.sh engine # python3 only; ~1s - corpus/ownership-lab/h28-command/run.sh runtime # needs .NET 8 SDK + NuGet + PostgreSQL + re-fetches 44 upstream excerpts plus the Npgsql finalizer negative + scan. Includes the SQLitePCLRaw opt-in required by sqlite3_next_stmt; + TFM evidence confirms Npgsql 10.0.3 targets net8.0 and + Microsoft.Data.Sqlite 10.0.12 has a netstandard2.0 asset usable by + this net8.0 falsifier. Non-zero = drift. + run.sh PASS/FAIL/SKIP driver. `all-required` fails on any SKIP. + evidence/*.txt source excerpts with repository/ref/path/git blob sha/file sha256/ + line range. Engine output is saved. G3/runtime output is created + here by the required workflow if the gates run. + .github/workflows/h28-command-falsifier.yml + runs `all-required` on the PR and preserves results as an artifact. + +GATES + ./corpus/ownership-lab/h28-command/run.sh engine # Python only; current manual core pass + ./corpus/ownership-lab/h28-command/run.sh g3-required # production extractor and core + ./corpus/ownership-lab/h28-command/run.sh runtime-required # compile + provider runtime + ./corpus/ownership-lab/h28-command/run.sh all-required # mandatory; SKIP is non-zero python3 corpus/ownership-lab/h28-command/scripts/derive_source_evidence.py - The runtime half starts PostgreSQL itself via pgserver (PyPI, self-contained PostgreSQL 16 on - a Unix socket) -- the same harness the original H-28 falsifier used -- or takes H28_PG_DSN. - -WHAT WAS AND WAS NOT EXECUTED WHERE THIS WAS PRODUCED - executed : the engine half (evidence/engine-CommandDispose.txt), the source derivation of - all 31 citations (evidence/*.txt + MANIFEST.txt), a live PostgreSQL 16.2 via - pgserver (used only for the server-side lifecycle checks quoted in the report). - NOT executed: the Roslyn extractor (no .NET SDK, no NuGet feed) and the runtime falsifier - (same). fx/CommandDispose.cs expectations and falsifier probes P2/P4/P6 are - therefore PREDICTED from source, and the report says so at each one. - -NO PRODUCTION CHANGE - Nothing here alters the extractor, the core, the vocabulary or any diagnostic. corpus/ - ownership-lab/ is not globbed by tests/test_corpus.py (only corpus/real-world/ is), so these - fixtures are inert data until someone decides to act on the report's section 10. + Runtime dependencies: .NET 8 SDK, packages available from NuGet or already restored in cache, + and PostgreSQL 16. The workflow installs pinned pgserver 0.1.4 from PyPI; locally either + `pip install pgserver==0.1.4` or set H28_PG_DSN. There is no curl-only NuGet gate: the script attempts restore with + `--ignore-failed-sources`, so a warm package cache can work offline. + +WHAT HAS RUN IN THE PRODUCING SANDBOX + PASS: Python engine reduction, matching three manual OWN001 expectations; 33/33 real-world + corpus cases; whole-tree Ruff; mypy (60 source files); 44 source excerpts + finalizer scan; + Python/shell syntax checks, C# grammar parse (not compilation), and pgserver supervisor + API/Unix-socket DSN preflight (not a .NET/provider falsifier). + BLOCKED: G1 runtime-project build, P4 Npgsql Activity probe, G2 native SQLite statement probe, + P5 Npgsql prepared-state probe, and G3 production C# extraction. No .NET SDK is present. + `all-required` ran and returned 1 on these SKIPs; see evidence/required-gates-local.txt. + INCOMPLETE: `python tests/run_tests.py` passed its early analysis/codegen checks, then stopped at + checkpoint validation because this checkout is shallow and `cargo` is absent. It is + not counted as a green full-suite run. See evidence/local-validation.txt. + +SCOPE + No production analyzer code, OwnIR vocabulary, ownership semantics, or diagnostics are changed. + The normal real-world corpus suite does not glob `corpus/ownership-lab/`; these are experiment + fixtures. Any follow-up architecture work, including #382, waits for G3 to reproduce the + predicted extraction loss. diff --git a/corpus/ownership-lab/h28-command/evidence/MANIFEST.txt b/corpus/ownership-lab/h28-command/evidence/MANIFEST.txt index b1a5c26d..b4ac588b 100644 --- a/corpus/ownership-lab/h28-command/evidence/MANIFEST.txt +++ b/corpus/ownership-lab/h28-command/evidence/MANIFEST.txt @@ -1,5 +1,6 @@ -# H-28-CMD evidence manifest: one line per pinned citation. -# OK = re-derived now; MISSING/MARKER-NOT-FOUND = the report is stale. +# H-28-CMD source evidence manifest (declared refs). +# OK/SCAN = re-derived now; MISSING/MARKER-NOT-FOUND = stale or unavailable. +OK npgsql-v10.0.3-target-frameworks.txt npgsql/npgsql@v10.0.3 sha256=fd36c9e18a2c4765 lines=8 OK npgsql-10.0.3-NpgsqlCommand-Dispose.txt npgsql/npgsql@v10.0.3 sha256=6bdde5901d32a633 lines=1712 OK npgsql-10.0.3-NpgsqlCommand-Reset.txt npgsql/npgsql@v10.0.3 sha256=6bdde5901d32a633 lines=1728 OK npgsql-10.0.3-NpgsqlCommand-ctor-SuppressFinalize.txt npgsql/npgsql@v10.0.3 sha256=6bdde5901d32a633 lines=120 @@ -8,6 +9,7 @@ OK npgsql-10.0.3-NpgsqlCommand-TraceCommandStart.txt npgsql/npgsql@v10.0.3 sha25 OK npgsql-10.0.3-NpgsqlCommand-TraceCommandStop.txt npgsql/npgsql@v10.0.3 sha256=6bdde5901d32a633 lines=1792 OK npgsql-10.0.3-NpgsqlConnection-CreateCommand.txt npgsql/npgsql@v10.0.3 sha256=02ed57055a9e1f02 lines=549 OK npgsql-10.0.3-NpgsqlDataReader-Dispose.txt npgsql/npgsql@v10.0.3 sha256=9c109626d6627b55 lines=1008 +OK npgsql-10.0.3-NpgsqlDataReader-DisposeAsync.txt npgsql/npgsql@v10.0.3 sha256=9c109626d6627b55 lines=1037 OK npgsql-10.0.3-NpgsqlDataReader-Close-public.txt npgsql/npgsql@v10.0.3 sha256=9c109626d6627b55 lines=1073 OK npgsql-10.0.3-NpgsqlDataReader-Cleanup.txt npgsql/npgsql@v10.0.3 sha256=9c109626d6627b55 lines=1141 OK npgsql-10.0.3-NpgsqlActivitySource-IsEnabled.txt npgsql/npgsql@v10.0.3 sha256=b7ef8df7f2f66573 lines=17 @@ -23,12 +25,23 @@ OK runtime-v10.0.12-DbDataReader-Close-Dispose.txt dotnet/runtime@v10.0.12 sha25 OK runtime-v10.0.12-IDbCommand.txt dotnet/runtime@v10.0.12 sha256=5fee96c4567d3894 lines=8 OK runtime-v8.0.20-Activity-Dispose.txt dotnet/runtime@v8.0.20 sha256=2fcfbffca75351dd lines=1006 OK runtime-v8.0.20-Activity-IDisposable.txt dotnet/runtime@v8.0.20 sha256=2fcfbffca75351dd lines=56 -OK mssqlite-SqliteCommand-Dispose.txt dotnet/efcore@main sha256=7ac8596c1cd999a3 lines=221 -OK mssqlite-SqliteCommand-DisposePreparedStatements.txt dotnet/efcore@main sha256=7ac8596c1cd999a3 lines=604 -OK mssqlite-SqliteCommand-PrepareAndEnumerate-head.txt dotnet/efcore@main sha256=7ac8596c1cd999a3 lines=547 -OK mssqlite-SqliteCommand-preparedStatements-field.txt dotnet/efcore@main sha256=7ac8596c1cd999a3 lines=31 -OK mssqlite-SqliteDataReader-Close-Dispose.txt dotnet/efcore@main sha256=5c3e1c054fceb256 lines=272 -OK mssqlite-SqliteConnection-commands-weakrefs.txt dotnet/efcore@main sha256=7ab4e87bd49e75a8 lines=32 -OK sqlitepclraw-sqlite3_stmt-SafeHandle.txt ericsink/SQLitePCL.raw@main sha256=0cb6c489786c3acf lines=196 +OK mssqlite-SqliteCommand-Dispose.txt dotnet/efcore@v10.0.12 sha256=c5d427a5d7016152 lines=216 +OK mssqlite-SqliteCommand-DisposePreparedStatements.txt dotnet/efcore@v10.0.12 sha256=c5d427a5d7016152 lines=520 +OK mssqlite-SqliteCommand-PrepareAndEnumerate-head.txt dotnet/efcore@v10.0.12 sha256=c5d427a5d7016152 lines=464 +OK mssqlite-SqliteCommand-preparedStatements-field.txt dotnet/efcore@v10.0.12 sha256=c5d427a5d7016152 lines=30 +OK mssqlite-SqliteDataReader-Close-Dispose.txt dotnet/efcore@v10.0.12 sha256=0712b68bd0b03688 lines=227 +OK mssqlite-SqliteConnection-commands-weakrefs.txt dotnet/efcore@v10.0.12 sha256=0fd8dd0cd1ca7e6f lines=31 +OK mssqlite-SqliteConnection-Handle.txt dotnet/efcore@v10.0.12 sha256=0fd8dd0cd1ca7e6f lines=129 +OK efcore-v10.0.12-SQLitePCLRawVersion.txt dotnet/efcore@v10.0.12 sha256=a31b3e681cac5f15 lines=34 +OK mssqlite-v10.0.12-Microsoft.Data.Sqlite-target.txt dotnet/efcore@v10.0.12 sha256=00448ec2082b4c14 lines=18 +OK mssqlite-v10.0.12-Core-targets.txt dotnet/efcore@v10.0.12 sha256=b58e8f7587a04c49 lines=18 +OK efcore-v10.0.12-default-net-target.txt dotnet/efcore@v10.0.12 sha256=a31b3e681cac5f15 lines=14 +OK sqlitepclraw-v2.1.12-sqlite3_next_stmt.txt ericsink/SQLitePCL.raw@v2.1.12 sha256=cab23c3ea0012e85 lines=1028 +OK sqlitepclraw-v2.1.12-enable-next-stmt.txt ericsink/SQLitePCL.raw@v2.1.12 sha256=9afd074db410ba78 lines=300 +OK sqlitepclraw-v2.1.12-find-stmt-enabled.txt ericsink/SQLitePCL.raw@v2.1.12 sha256=9afd074db410ba78 lines=323 +OK runtime-v8.0.20-SafeHandle-finalizer.txt dotnet/runtime@v8.0.20 sha256=3df9d6928d6a8bcb lines=86 +OK runtime-v10.0.12-SafeHandle-finalizer.txt dotnet/runtime@v10.0.12 sha256=3df9d6928d6a8bcb lines=86 +OK sqlitepclraw-sqlite3_stmt-SafeHandle.txt ericsink/SQLitePCL.raw@v2.1.12 sha256=9afd074db410ba78 lines=195 OK sqlclient-SqlCommand-Dispose.txt dotnet/SqlClient@main sha256=fc8200df4b130136 lines=1883 OK mysqlconnector-MySqlCommand-Dispose.txt mysql-net/MySqlConnector@master sha256=4369c492ce1c1fc1 lines=377 +SCAN npgsql-10.0.3-NpgsqlCommand-finalizer-scan.txt npgsql/npgsql@v10.0.3 sha256=6bdde5901d32a633 matches=0 diff --git a/corpus/ownership-lab/h28-command/evidence/efcore-v10.0.12-SQLitePCLRawVersion.txt b/corpus/ownership-lab/h28-command/evidence/efcore-v10.0.12-SQLitePCLRawVersion.txt new file mode 100644 index 00000000..1b78a01d --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/efcore-v10.0.12-SQLitePCLRawVersion.txt @@ -0,0 +1,14 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : dotnet/efcore +# ref : v10.0.12 +# path : eng/Versions.props +# blob sha : 4299bcff02df10cacebb45435c16cd0af9f901f3 +# file sha256: a31b3e681cac5f1537ef29e7b81976b4f61533affd7c5857de3d19f469be7c6e +# file bytes : 2726 +# marker : '' +# region : lines 34..37 +# derived by: scripts/derive_source_evidence.py + 34| 2.1.12 + 35| 2.1.11 + 36| 2.1.11 + 37| 2.1.11 diff --git a/corpus/ownership-lab/h28-command/evidence/efcore-v10.0.12-default-net-target.txt b/corpus/ownership-lab/h28-command/evidence/efcore-v10.0.12-default-net-target.txt new file mode 100644 index 00000000..e1791310 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/efcore-v10.0.12-default-net-target.txt @@ -0,0 +1,14 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : dotnet/efcore +# ref : v10.0.12 +# path : eng/Versions.props +# blob sha : 4299bcff02df10cacebb45435c16cd0af9f901f3 +# file sha256: a31b3e681cac5f1537ef29e7b81976b4f61533affd7c5857de3d19f469be7c6e +# file bytes : 2726 +# marker : 'net10.0' +# region : lines 14..17 +# derived by: scripts/derive_source_evidence.py + 14| net10.0 + 15| + 16| + 17| False diff --git a/corpus/ownership-lab/h28-command/evidence/engine-CommandDispose.txt b/corpus/ownership-lab/h28-command/evidence/engine-CommandDispose.txt index 93978a8e..f1d98186 100644 --- a/corpus/ownership-lab/h28-command/evidence/engine-CommandDispose.txt +++ b/corpus/ownership-lab/h28-command/evidence/engine-CommandDispose.txt @@ -1,18 +1,17 @@ -corpus/ownership-lab/h28-command/fx/CommandDispose.own:42:3: error: [OWN001] 'command' is owned but not released at end of function (leaks on at least one path) [resource: disposable] - 42 | release reader; // DataTable.Load(reader) -> reader.Close() +corpus/ownership-lab/h28-command/fx/CommandDispose.own:43:3: error: [OWN001] 'command' is owned but not released at end of function (leaks on at least one path) [resource: disposable] + 43 | release reader; // DataTable.Load(reader) -> reader.Close() ^ - note: 'command' acquired here at corpus/ownership-lab/h28-command/fx/CommandDispose.own:40 -corpus/ownership-lab/h28-command/fx/CommandDispose.own:69:32: error: [OWN001] 'command' is owned but not released at end of function (leaks on at least one path) [resource: disposable] - 69 | let reader = acquire Reader(command); + note: 'command' acquired here at corpus/ownership-lab/h28-command/fx/CommandDispose.own:41 +corpus/ownership-lab/h28-command/fx/CommandDispose.own:71:32: error: [OWN001] 'command' is owned but not released at end of function (leaks on at least one path) [resource: disposable] + 71 | let reader = acquire Reader(command); ^ - note: 'command' acquired here at corpus/ownership-lab/h28-command/fx/CommandDispose.own:68 -corpus/ownership-lab/h28-command/fx/CommandDispose.own:69:7: error: [OWN001] 'reader' is owned but not released at end of function (leaks on at least one path) [resource: disposable] - 69 | let reader = acquire Reader(command); + note: 'command' acquired here at corpus/ownership-lab/h28-command/fx/CommandDispose.own:70 +corpus/ownership-lab/h28-command/fx/CommandDispose.own:71:7: error: [OWN001] 'reader' is owned but not released at end of function (leaks on at least one path) [resource: disposable] + 71 | let reader = acquire Reader(command); ^ - note: 'reader' acquired here at corpus/ownership-lab/h28-command/fx/CommandDispose.own:69 + note: 'reader' acquired here at corpus/ownership-lab/h28-command/fx/CommandDispose.own:71 3 errors. -# exit code: 1 (OWN001 is error-severity, so non-zero is the expected result) -# command : python3 -m ownlang check corpus/ownership-lab/h28-command/fx/CommandDispose.own -# cwd : repository root -# python : Python 3.11.2 +# exit code: 1 (OWN001 is error severity, so a finding run exits 1) +# command: python3 -m ownlang check corpus/ownership-lab/h28-command/fx/CommandDispose.own +# python: Python 3.11.2 diff --git a/corpus/ownership-lab/h28-command/evidence/local-validation.txt b/corpus/ownership-lab/h28-command/evidence/local-validation.txt new file mode 100644 index 00000000..5a08164f --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/local-validation.txt @@ -0,0 +1,159 @@ +$ python3 tests/test_corpus.py +real-world corpus (corpus/real-world/): + ado-executereader-leak OWN001 + arraypool-aliased-receiver OWN003 + arraypool-double-return OWN003 + arraypool-field-fullspan-overread OWN025 + arraypool-fullspan-overread OWN025 + arraypool-length-overread OWN025 + arraypool-memory-view-escape OWN002 + arraypool-span-view-after-return OWN002 + arraypool-use-after-return OWN002 + arraypool-view-into-field-overread OWN025 + field-dispose-via-exchange OWN001 + field-dispose-via-helper OWN001 + field-noop-dispose-wrapper OWN001 + local-dispose-via-using-statement OWN001 + memorypool-double-dispose OWN003 + memorypool-using-owner-escape OWN002 + memorypool-using-statement-view-escape OWN002 + memorypool-using-view-escape OWN002 + memorypool-view-after-dispose OWN002 + nethermind-patriciatree-arraypool-leak OWN001 + ownership-handoff-consume OWN001,OWN002 + ownership-handoff-use OWN002 + ownership-handoff-use-transitive OWN002 + pool-transfer-via-wrapper-local OWN001 + screentogif-loaded-subscription OWN001 + screentogif-systemevents-leak OWN001 + sharex-rfc2898-derivebytes-leak OWN001 + sharex-shapemanager-menuform-leak OWN001 + socket-accept-leak OWN001 + subscription-self-owned-property OWN001 + tcplistener-accept-leak OWN001 + tcplistener-acceptsocket-leak OWN001 + using-statement-throw-releases OWN001 +corpus: 33/33 cases match their expected diagnostics +# exit: 0 + +$ ruff check . +All checks passed! +# exit: 0 + +$ mypy +Success: no issues found in 60 source files +# exit: 0 + +$ python3 -m py_compile corpus/ownership-lab/h28-command/scripts/derive_source_evidence.py corpus/ownership-lab/h28-command/scripts/verify_g3.py corpus/ownership-lab/h28-command/falsifier/run_with_pgserver.py +# exit: 0 + +$ bash -n corpus/ownership-lab/h28-command/run.sh +# exit: 0 + +$ C# syntax grammar parse (tree-sitter; not a compiler) +corpus/ownership-lab/h28-command/falsifier/Program.cs: PASS; syntax nodes=0 +corpus/ownership-lab/h28-command/fx/CommandDispose.cs: PASS; syntax nodes=0 +# exit: 0 + +$ python3 tests/run_tests.py + +analysis: 132/132 passed, 0 failed +codegen: 43/43 generated cleanly +golden: PASS +buffer: PASS +escape: PASS +branchy: PASS +nesting: PASS +ordering: PASS +helper: PASS +byte variants OK: 10 variants measured by both readers, 7 raw-variant (one canonical identity, 7 distinct byte sequences), 3 invalid, 0 disagreements +ok[constants-artifact-shape]: 5 permitted keys and no others; 3 rationals as reduced pairs, 1 count and 1 ladder as exact integers +ok[constants-accepted-by-policy]: q=19/20, M=2, R_runs=5, ladder=[5, 15, 45] stopping at 45, G=1/10; accepted by the frozen implementation and byte-identical on the round trip +ok[constants-bound-to-freeze]: bound to policy c3068ed7fa88… and harness b92f08c990fc…, both equal to what step 4 froze +ok[constants-no-empirical]: none of ['A_abs', 'R_rel', 'elapsed_ns', 'measurements', 'selected_N'] appears, and no value is a float + +calibration constants controls: 4 passed, 0 failed +ok[freeze-artifact-shape]: 9 permitted keys and no others, every digest well-formed, and the only numbers present are the 1 recorded byte lengths +FAIL[freeze-ancestry]: the frozen source commit b4f657a0abdf does not read as an ancestor of HEAD e524972d5a54; the history here is truncated, so this answer is not trustworthy either way and the job needs fetch-depth: 0 +ok[freeze-source-set]: 1 file(s) recorded, each path, blob sha1 and byte length equal to git's own at b4f657a0abdf +ok[freeze-source-root-clean]: every committed path under scripts/calibration/ is a *.py the digest covers, at the frozen commit and at HEAD +ok[freeze-digest]: c3068ed7fa88… recomputed from 1 git blob(s) at b4f657a0abdf, under the framing the artifact states +ok[freeze-source-unchanged]: all 1 policy source blob(s) are byte-identical at HEAD and at b4f657a0abdf +ok[freeze-harness-untouched]: b92f08c990fc… unchanged, in the working tree and at HEAD + +calibration freeze controls: 6 passed, 1 failed +ok[calib-no-defaults]: 51 callables inspected, no numeric default and no module-level value anywhere +ok[calib-design-set]: all five constants required; every range refused by its own field; the ladder's last rung is the mandatory stop +ok[calib-representation]: floats, decimal strings, bools, zero and negative denominators and unreduced pairs are all refused; counts are integers and medians stay exact +ok[calib-cell-verdict]: inner inclusive, outer strict, the three invalid conditions each refused by name, a zero bound admits only exact agreement, and the bound sits at the midpoint +ok[calib-symmetry]: 4563 ordered median pairs across three envelopes, and no verdict depends on which run was recorded first +ok[calib-aggregation]: precedence invalid > not-reproducible > inconclusive > reproducible; one failing cell in a hundred still fails the pair; a pair-level condition overrides every cell +ok[calib-fit]: no grid point beats the enumerated optimum; identical data give identical constants; the bound stays non-negative; and an empty, rank-deficient, negative or mis-sized corpus is refused +ok[calib-select-n]: the smallest rung within (1 + G) of the best width wins; a rung far outside the margin loses; G = 0 picks the argmin; and a missing rung or empty universe is refused +ok[calib-exact-domain]: 17 float and non-exact inputs refused at the door rather than converted, all 12 computed quantities come back Fraction, and all 17 public entry points are accounted for +ok[calib-purity]: imports are ['__future__', 'collections.abc', 'dataclasses', 'fractions', 'itertools', 'math'] and nothing else; no mutable module-level state + +calibration policy controls: 10 passed, 0 failed +certify (Tier A): 114/114 checks pass +certify (Tier B): SKIP (non-required mode; set OWN_TIERB_REQUIRED=1) +FAIL: /home/user/Own.NET/tests/fixtures/cfg_parity.json is stale (the corpus or the lowering changed); regenerate with 'python tests/test_cfg_fixtures.py --write' and re-run the Rust side (cd rust && cargo test) +cfg_json: 20/20 CFG-seam checks pass +FAIL[checkpoint-status]: mutation campaign: source commit 4c1c9d819721 is not an ancestor of HEAD — the run describes a history this tree does not contain; re-run the campaign +FAIL[checkpoint-status]: mutation campaign p022-shadow-cp1: source commit 4c1c9d819721 is not an ancestor of HEAD — the run describes a history this tree does not contain; re-run the campaign +FAIL[checkpoint-status]: mutation campaign p022-shadow-cp2: source commit 4c1c9d819721 is not an ancestor of HEAD — the run describes a history this tree does not contain; re-run the campaign +FAIL[checkpoint-status]: mutation campaign p022-shadow-cp3: source commit 4c1c9d819721 is not an ancestor of HEAD — the run describes a history this tree does not contain; re-run the campaign +FAIL[checkpoint-status]: mutation campaign p022-shadow-cp4: source commit 4c1c9d819721 is not an ancestor of HEAD — the run describes a history this tree does not contain; re-run the campaign +FAIL[checkpoint-status]: mutation campaign p022-shadow-acc-1: source commit 1c6a611f1ed8 is not an ancestor of HEAD — the run describes a history this tree does not contain; re-run the campaign +FAIL[checkpoint-status]: mutation campaign p022-shadow-acc-2: source commit 1c6a611f1ed8 is not an ancestor of HEAD — the run describes a history this tree does not contain; re-run the campaign +FAIL[checkpoint-status]: mutation campaign p022-shadow-sweep-1: source commit 565de6d49f3d is not an ancestor of HEAD — the run describes a history this tree does not contain; re-run the campaign +FAIL[checkpoint-status]: shadow sweep: source commit 321ab8b40e52 is not an ancestor of HEAD — the run describes a history this tree does not contain; re-run the sweep +FAIL[checkpoint-status]: mutation campaign p022-cp5-1: source commit 4c1c9d819721 is not an ancestor of HEAD — the run describes a history this tree does not contain; re-run the campaign +FAIL[checkpoint-status]: mutation campaign p022-cp5-2: source commit 4c1c9d819721 is not an ancestor of HEAD — the run describes a history this tree does not contain; re-run the campaign +FAIL[checkpoint-status]: mutation campaign p022-cp5-3: source commit 4c1c9d819721 is not an ancestor of HEAD — the run describes a history this tree does not contain; re-run the campaign +FAIL[checkpoint-status]: mutation campaign p022-cp4b-1: source commit 4c1c9d819721 is not an ancestor of HEAD — the run describes a history this tree does not contain; re-run the campaign +FAIL[checkpoint-status]: mutation campaign p022-cp4b-2: source commit 4c1c9d819721 is not an ancestor of HEAD — the run describes a history this tree does not contain; re-run the campaign +FAIL[checkpoint-status]: mutation campaign p022-coord-1: source commit 4c1c9d819721 is not an ancestor of HEAD — the run describes a history this tree does not contain; re-run the campaign +FAIL[checkpoint-status]: mutation campaign p022-coord-2: source commit 4c1c9d819721 is not an ancestor of HEAD — the run describes a history this tree does not contain; re-run the campaign +FAIL[checkpoint-status]: mutation campaign p022-cli-1: source commit b5d9272a0a85 is not an ancestor of HEAD — the run describes a history this tree does not contain; re-run the campaign +FAIL[checkpoint-status]: mutation campaign p022-stage1-1: source commit f988c8e1be7f is not an ancestor of HEAD — the run describes a history this tree does not contain; re-run the campaign +FAIL[checkpoint-status]: mutation campaign p022-stage1-windows: source commit baf3771cd010 is not an ancestor of HEAD — the run describes a history this tree does not contain; re-run the campaign +FAIL[checkpoint-status]: mutation campaign p022-stage2-1: source commit 0d6686eddc91 is not an ancestor of HEAD — the run describes a history this tree does not contain; re-run the campaign +Traceback (most recent call last): + File "/home/user/Own.NET/tests/run_tests.py", line 1111, in + raise SystemExit(run()) + ^^^^^ + File "/home/user/Own.NET/tests/run_tests.py", line 1096, in run + module_rcs.append(runner(mod) if runner is not None else mod.run()) + ^^^^^^^^^ + File "/home/user/Own.NET/tests/test_checkpoint_status.py", line 106, in run + anchors = _anchors() + ^^^^^^^^^^ + File "/home/user/Own.NET/tests/test_checkpoint_status.py", line 98, in _anchors + problems.extend(f"{rel}: {p}" for p in validate(definition)) + ^^^^^^^^^^^^^^^^^^^^ + File "/home/user/Own.NET/tests/../scripts/mutate_campaign.py", line 691, in validate + problems += validate_catchers(definition) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/Own.NET/tests/../scripts/mutate_campaign.py", line 722, in validate_catchers + layers = {x.id: x for x in _layers_of(definition)} + ^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/Own.NET/tests/../scripts/mutate_campaign.py", line 599, in _layers_of + for pkg in workspace_packages(workspace)) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/Own.NET/tests/../scripts/mutate_campaign.py", line 490, in workspace_packages + out = subprocess.run( + ^^^^^^^^^^^^^^^ + File "/usr/lib/python3.11/subprocess.py", line 548, in run + with Popen(*popenargs, **kwargs) as process: + ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.11/subprocess.py", line 1024, in __init__ + self._execute_child(args, executable, preexec_fn, close_fds, + File "/usr/lib/python3.11/subprocess.py", line 1901, in _execute_child + raise child_exception_type(errno_num, err_msg, err_filename) +FileNotFoundError: [Errno 2] No such file or directory: 'cargo' +# exit: 1 + + +$ pgserver 0.1.4 supervisor API preflight (not a .NET/provider falsifier) +# started a temporary server, obtained a Unix-socket URI, converted to Npgsql DSN. +# exit: 0 (temporary server cleaned up) diff --git a/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-Dispose.txt b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-Dispose.txt index b9aca0c4..62dee430 100644 --- a/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-Dispose.txt +++ b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-Dispose.txt @@ -1,22 +1,22 @@ # H-28-CMD source citation (auto-derived, do not edit by hand) # repo : dotnet/efcore -# ref : main +# ref : v10.0.12 # path : src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs -# blob sha : 7436572dcc87189532e757de1717e4972b1c23f3 -# file sha256: 7ac8596c1cd999a3967f30890a0e4bbd982b549e716b873106827377bc232588 -# file bytes : 26373 +# blob sha : 01b3eed85551a660e1502d0c09b02361b9242bd0 +# file sha256: c5d427a5d70161528f63b9f789af93b0b24af3d7118dbfd92912db808ac1f5fb +# file bytes : 21934 # marker : 'protected override void Dispose(bool disposing)' -# region : lines 221..232 +# region : lines 216..227 # derived by: scripts/derive_source_evidence.py - 221| protected override void Dispose(bool disposing) - 222| { - 223| DisposePreparedStatements(disposing); - 224| - 225| if (disposing) - 226| { - 227| _connection?.RemoveCommand(this); - 228| } - 229| - 230| base.Dispose(disposing); - 231| } - 232| + 216| protected override void Dispose(bool disposing) + 217| { + 218| DisposePreparedStatements(disposing); + 219| + 220| if (disposing) + 221| { + 222| _connection?.RemoveCommand(this); + 223| } + 224| + 225| base.Dispose(disposing); + 226| } + 227| diff --git a/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-DisposePreparedStatements.txt b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-DisposePreparedStatements.txt index 44ef434d..70f59eb5 100644 --- a/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-DisposePreparedStatements.txt +++ b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-DisposePreparedStatements.txt @@ -1,32 +1,32 @@ # H-28-CMD source citation (auto-derived, do not edit by hand) # repo : dotnet/efcore -# ref : main +# ref : v10.0.12 # path : src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs -# blob sha : 7436572dcc87189532e757de1717e4972b1c23f3 -# file sha256: 7ac8596c1cd999a3967f30890a0e4bbd982b549e716b873106827377bc232588 -# file bytes : 26373 +# blob sha : 01b3eed85551a660e1502d0c09b02361b9242bd0 +# file sha256: c5d427a5d70161528f63b9f789af93b0b24af3d7118dbfd92912db808ac1f5fb +# file bytes : 21934 # marker : 'private void DisposePreparedStatements(bool disposing = true)' -# region : lines 604..625 +# region : lines 520..541 # derived by: scripts/derive_source_evidence.py - 604| private void DisposePreparedStatements(bool disposing = true) - 605| { - 606| if (disposing - 607| && DataReader != null) - 608| { - 609| DataReader.Dispose(); - 610| DataReader = null; - 611| } - 612| - 613| if (_preparedStatements != null) - 614| { - 615| foreach (var (stmt, _) in _preparedStatements) - 616| { - 617| stmt.Dispose(); - 618| } - 619| - 620| _preparedStatements.Clear(); - 621| } - 622| - 623| _prepared = false; - 624| } - 625| } + 520| private void DisposePreparedStatements(bool disposing = true) + 521| { + 522| if (disposing + 523| && DataReader != null) + 524| { + 525| DataReader.Dispose(); + 526| DataReader = null; + 527| } + 528| + 529| if (_preparedStatements != null) + 530| { + 531| foreach (var (stmt, _) in _preparedStatements) + 532| { + 533| stmt.Dispose(); + 534| } + 535| + 536| _preparedStatements.Clear(); + 537| } + 538| + 539| _prepared = false; + 540| } + 541| diff --git a/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-PrepareAndEnumerate-head.txt b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-PrepareAndEnumerate-head.txt index 1ab993b2..e3064b41 100644 --- a/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-PrepareAndEnumerate-head.txt +++ b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-PrepareAndEnumerate-head.txt @@ -1,16 +1,16 @@ # H-28-CMD source citation (auto-derived, do not edit by hand) # repo : dotnet/efcore -# ref : main +# ref : v10.0.12 # path : src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs -# blob sha : 7436572dcc87189532e757de1717e4972b1c23f3 -# file sha256: 7ac8596c1cd999a3967f30890a0e4bbd982b549e716b873106827377bc232588 -# file bytes : 26373 +# blob sha : 01b3eed85551a660e1502d0c09b02361b9242bd0 +# file sha256: c5d427a5d70161528f63b9f789af93b0b24af3d7118dbfd92912db808ac1f5fb +# file bytes : 21934 # marker : 'private IEnumerable<(sqlite3_stmt Statement, int ParamCount)> PrepareAndEnumerateStatements()' -# region : lines 547..552 +# region : lines 464..469 # derived by: scripts/derive_source_evidence.py - 547| private IEnumerable<(sqlite3_stmt Statement, int ParamCount)> PrepareAndEnumerateStatements() - 548| { - 549| DisposePreparedStatements(disposing: false); - 550| - 551| var byteCount = Encoding.UTF8.GetByteCount(_commandText); - 552| var sql = new byte[byteCount + 1]; + 464| private IEnumerable<(sqlite3_stmt Statement, int ParamCount)> PrepareAndEnumerateStatements() + 465| { + 466| DisposePreparedStatements(disposing: false); + 467| + 468| var byteCount = Encoding.UTF8.GetByteCount(_commandText); + 469| var sql = new byte[byteCount + 1]; diff --git a/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-preparedStatements-field.txt b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-preparedStatements-field.txt index 5570fedf..c1d3e2ce 100644 --- a/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-preparedStatements-field.txt +++ b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteCommand-preparedStatements-field.txt @@ -1,12 +1,12 @@ # H-28-CMD source citation (auto-derived, do not edit by hand) # repo : dotnet/efcore -# ref : main +# ref : v10.0.12 # path : src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs -# blob sha : 7436572dcc87189532e757de1717e4972b1c23f3 -# file sha256: 7ac8596c1cd999a3967f30890a0e4bbd982b549e716b873106827377bc232588 -# file bytes : 26373 +# blob sha : 01b3eed85551a660e1502d0c09b02361b9242bd0 +# file sha256: c5d427a5d70161528f63b9f789af93b0b24af3d7118dbfd92912db808ac1f5fb +# file bytes : 21934 # marker : '_preparedStatements =' -# region : lines 31..32 +# region : lines 30..31 # derived by: scripts/derive_source_evidence.py - 31| private readonly List<(sqlite3_stmt Statement, int ParamCount)> _preparedStatements = [with(1)]; - 32| private SqliteConnection? _connection; + 30| private readonly List<(sqlite3_stmt Statement, int ParamCount)> _preparedStatements = new(1); + 31| private SqliteConnection? _connection; diff --git a/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteConnection-Handle.txt b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteConnection-Handle.txt new file mode 100644 index 00000000..8c00bfcc --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteConnection-Handle.txt @@ -0,0 +1,13 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : dotnet/efcore +# ref : v10.0.12 +# path : src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs +# blob sha : 3062c0f923c5358326da200bc01e7448bb756477 +# file sha256: 0fd8dd0cd1ca7e6f9ad7fa1301cfd77a743c33be3276550fad94b20557b096b6 +# file bytes : 38343 +# marker : 'public virtual sqlite3? Handle' +# region : lines 129..131 +# derived by: scripts/derive_source_evidence.py + 129| public virtual sqlite3? Handle + 130| => _innerConnection?.Handle; + 131| diff --git a/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteConnection-commands-weakrefs.txt b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteConnection-commands-weakrefs.txt index c9f635bf..898130e6 100644 --- a/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteConnection-commands-weakrefs.txt +++ b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteConnection-commands-weakrefs.txt @@ -1,12 +1,12 @@ # H-28-CMD source citation (auto-derived, do not edit by hand) # repo : dotnet/efcore -# ref : main +# ref : v10.0.12 # path : src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs -# blob sha : 22e183f1e7be5f329feb2de1ab46b5e722f107ba -# file sha256: 7ab4e87bd49e75a8081f443be91a3b04ba5ca458ca05874f559c54b946ec6a9f -# file bytes : 59980 +# blob sha : 3062c0f923c5358326da200bc01e7448bb756477 +# file sha256: 0fd8dd0cd1ca7e6f9ad7fa1301cfd77a743c33be3276550fad94b20557b096b6 +# file bytes : 38343 # marker : 'List> _commands' -# region : lines 32..33 +# region : lines 31..32 # derived by: scripts/derive_source_evidence.py - 32| private readonly List> _commands = []; - 33| + 31| private readonly List> _commands = []; + 32| diff --git a/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteDataReader-Close-Dispose.txt b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteDataReader-Close-Dispose.txt index 5abd0088..498fafe9 100644 --- a/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteDataReader-Close-Dispose.txt +++ b/corpus/ownership-lab/h28-command/evidence/mssqlite-SqliteDataReader-Close-Dispose.txt @@ -1,52 +1,52 @@ # H-28-CMD source citation (auto-derived, do not edit by hand) # repo : dotnet/efcore -# ref : main +# ref : v10.0.12 # path : src/Microsoft.Data.Sqlite.Core/SqliteDataReader.cs -# blob sha : 105e6e4a2df2206c651fb393564a90fad686686a -# file sha256: 5c3e1c054fceb256fc9b2642db5f16cf870f726aa29e681d36ed4933ef989e9b -# file bytes : 36674 +# blob sha : 2537670988b0bef85011b4f42a4fb2e77e572f44 +# file sha256: 0712b68bd0b036886b3ea5c5209ba257a4d6b3ba9ac51655dbb2c4c374946968 +# file bytes : 33555 # marker : 'public override void Close()' -# region : lines 272..313 +# region : lines 227..268 # derived by: scripts/derive_source_evidence.py - 272| public override void Close() - 273| => Dispose(true); - 274| - 275| /// - 276| /// Releases any resources used by the data reader and closes it. - 277| /// - 278| /// - 279| /// to release managed and unmanaged resources; - 280| /// to release only unmanaged resources. - 281| /// - 282| protected override void Dispose(bool disposing) - 283| { - 284| if (!disposing || _closed) - 285| { - 286| return; - 287| } - 288| - 289| _command.DataReader = null; - 290| - 291| _record?.Dispose(); - 292| _record = null; - 293| - 294| if (_stmtEnumerator != null) - 295| { - 296| try - 297| { - 298| while (NextResult()) - 299| { - 300| } - 301| } - 302| catch - 303| { - 304| } - 305| } - 306| - 307| _stmtEnumerator?.Dispose(); - 308| - 309| _closed = true; - 310| - 311| if (_closeConnection) - 312| { - 313| _command.Connection!.Close(); + 227| public override void Close() + 228| => Dispose(true); + 229| + 230| /// + 231| /// Releases any resources used by the data reader and closes it. + 232| /// + 233| /// + 234| /// to release managed and unmanaged resources; + 235| /// to release only unmanaged resources. + 236| /// + 237| protected override void Dispose(bool disposing) + 238| { + 239| if (!disposing || _closed) + 240| { + 241| return; + 242| } + 243| + 244| _command.DataReader = null; + 245| + 246| _record?.Dispose(); + 247| _record = null; + 248| + 249| if (_stmtEnumerator != null) + 250| { + 251| try + 252| { + 253| while (NextResult()) + 254| { + 255| } + 256| } + 257| catch + 258| { + 259| } + 260| } + 261| + 262| _stmtEnumerator?.Dispose(); + 263| + 264| _closed = true; + 265| + 266| if (_closeConnection) + 267| { + 268| _command.Connection!.Close(); diff --git a/corpus/ownership-lab/h28-command/evidence/mssqlite-v10.0.12-Core-targets.txt b/corpus/ownership-lab/h28-command/evidence/mssqlite-v10.0.12-Core-targets.txt new file mode 100644 index 00000000..ac81bb22 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/mssqlite-v10.0.12-Core-targets.txt @@ -0,0 +1,14 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : dotnet/efcore +# ref : v10.0.12 +# path : src/Microsoft.Data.Sqlite.Core/Microsoft.Data.Sqlite.Core.csproj +# blob sha : 2305284d616c335902c96cb7201ab772049621c2 +# file sha256: b58e8f7587a04c49ae53754041763dc05dbf152f9e93686add6de6866482edef +# file bytes : 2725 +# marker : '$(NetMinimum);netstandard2.0' +# region : lines 18..21 +# derived by: scripts/derive_source_evidence.py + 18| $(NetMinimum);netstandard2.0 + 19| 3.6 + 20| true + 21| Microsoft.Data.Sqlite.Core.ruleset diff --git a/corpus/ownership-lab/h28-command/evidence/mssqlite-v10.0.12-Microsoft.Data.Sqlite-target.txt b/corpus/ownership-lab/h28-command/evidence/mssqlite-v10.0.12-Microsoft.Data.Sqlite-target.txt new file mode 100644 index 00000000..ad2fdc8f --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/mssqlite-v10.0.12-Microsoft.Data.Sqlite-target.txt @@ -0,0 +1,14 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : dotnet/efcore +# ref : v10.0.12 +# path : src/Microsoft.Data.Sqlite/Microsoft.Data.Sqlite.csproj +# blob sha : 90b848797e7f019abdd4db4eaca4f5f140cab72e +# file sha256: 00448ec2082b4c14414f0ceb565f7b6f529142afb58968279e6969c50869fa8b +# file bytes : 1475 +# marker : 'netstandard2.0' +# region : lines 18..21 +# derived by: scripts/derive_source_evidence.py + 18| netstandard2.0 + 19| 3.6 + 20| SQLite;Data;ADO.NET + 21| false diff --git a/corpus/ownership-lab/h28-command/evidence/mysqlconnector-MySqlCommand-Dispose.txt b/corpus/ownership-lab/h28-command/evidence/mysqlconnector-MySqlCommand-Dispose.txt index 634e7482..2ac2cfb0 100644 --- a/corpus/ownership-lab/h28-command/evidence/mysqlconnector-MySqlCommand-Dispose.txt +++ b/corpus/ownership-lab/h28-command/evidence/mysqlconnector-MySqlCommand-Dispose.txt @@ -13,4 +13,4 @@ 379| m_isDisposed = true; 380| base.Dispose(disposing); 381| } - 382| + 382| diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlActivitySource-IsEnabled.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlActivitySource-IsEnabled.txt index 952148ce..244600c0 100644 --- a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlActivitySource-IsEnabled.txt +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlActivitySource-IsEnabled.txt @@ -9,4 +9,4 @@ # region : lines 17..18 # derived by: scripts/derive_source_evidence.py 17| internal static bool IsEnabled => Source.HasListeners(); - 18| + 18| diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlActivitySource-SourceName.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlActivitySource-SourceName.txt index 61aa300f..e66e99fd 100644 --- a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlActivitySource-SourceName.txt +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlActivitySource-SourceName.txt @@ -9,5 +9,5 @@ # region : lines 15..17 # derived by: scripts/derive_source_evidence.py 15| static readonly ActivitySource Source = new("Npgsql", GetLibraryVersion()); - 16| + 16| 17| internal static bool IsEnabled => Source.HasListeners(); diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-CreateCachedCommand.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-CreateCachedCommand.txt index 09592f46..4af6b495 100644 --- a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-CreateCachedCommand.txt +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-CreateCachedCommand.txt @@ -10,4 +10,4 @@ # derived by: scripts/derive_source_evidence.py 166| internal static NpgsqlCommand CreateCachedCommand(NpgsqlConnection connection) 167| => new(null, connection) { IsCacheable = true }; - 168| + 168| diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-Dispose.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-Dispose.txt index aab3dd67..82e088ac 100644 --- a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-Dispose.txt +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-Dispose.txt @@ -11,16 +11,16 @@ 1712| protected override void Dispose(bool disposing) 1713| { 1714| ResetTransaction(); - 1715| + 1715| 1716| State = CommandState.Disposed; - 1717| + 1717| 1718| if (IsCacheable && InternalConnection is not null && InternalConnection.CachedCommand is null) 1719| { 1720| Reset(); 1721| InternalConnection.CachedCommand = this; 1722| return; 1723| } - 1724| + 1724| 1725| IsCacheable = false; 1726| } - 1727| + 1727| diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-Reset.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-Reset.txt index df39bed7..6d4bcc15 100644 --- a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-Reset.txt +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-Reset.txt @@ -21,4 +21,4 @@ 1738| Debug.Assert(_unknownResultTypeList is null); 1739| EnableErrorBarriers = false; 1740| } - 1741| + 1741| diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-TraceCommandStart.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-TraceCommandStart.txt index 99ccab02..94d58083 100644 --- a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-TraceCommandStart.txt +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-TraceCommandStart.txt @@ -11,19 +11,19 @@ 1748| internal void TraceCommandStart(NpgsqlTracingOptions tracingOptions, bool? prepared) 1749| { 1750| Debug.Assert(CurrentActivity is null); - 1751| + 1751| 1752| if (NpgsqlActivitySource.IsEnabled) 1753| { 1754| var enableTracing = WrappingBatch is not null 1755| ? tracingOptions.BatchFilter?.Invoke(WrappingBatch) ?? true 1756| : tracingOptions.CommandFilter?.Invoke(this) ?? true; - 1757| + 1757| 1758| if (enableTracing) 1759| { 1760| var spanName = WrappingBatch is not null 1761| ? tracingOptions.BatchSpanNameProvider?.Invoke(WrappingBatch) 1762| : tracingOptions.CommandSpanNameProvider?.Invoke(this); - 1763| + 1763| 1764| CurrentActivity = NpgsqlActivitySource.CommandStart( 1765| WrappingBatch is not null ? GetBatchFullCommandText() : CommandText, 1766| CommandType, @@ -32,5 +32,5 @@ 1769| } 1770| } 1771| } - 1772| + 1772| 1773| internal void TraceCommandEnrich(NpgsqlConnector connector) diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-ctor-SuppressFinalize.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-ctor-SuppressFinalize.txt index 24679d05..70af8a08 100644 --- a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-ctor-SuppressFinalize.txt +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-ctor-SuppressFinalize.txt @@ -16,5 +16,5 @@ 125| InternalConnection = connection; 126| CommandType = CommandType.Text; 127| } - 128| + 128| 129| /// diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-finalizer-scan.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-finalizer-scan.txt new file mode 100644 index 00000000..9cfdf100 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlCommand-finalizer-scan.txt @@ -0,0 +1,11 @@ +# H-28-CMD negative source scan (auto-derived, do not edit by hand) +# repo : npgsql/npgsql +# ref : v10.0.3 +# path : src/Npgsql/NpgsqlCommand.cs +# blob sha : 8ddbb2e5fb846d1905c237aa351ac622171e1994 +# file sha256: 6bdde5901d32a6331a0d22367673a83bcce01d9fe0782bbf6829594e52837b6f +# file bytes : 84694 +# regex : '~\\s*NpgsqlCommand\\s*\\(' +# matches : 0 +# derived by: scripts/derive_source_evidence.py + diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlConnection-CreateCommand.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlConnection-CreateCommand.txt index feb6ec5c..a9012137 100644 --- a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlConnection-CreateCommand.txt +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlConnection-CreateCommand.txt @@ -11,7 +11,7 @@ 549| public new NpgsqlCommand CreateCommand() 550| { 551| CheckDisposed(); - 552| + 552| 553| var cachedCommand = CachedCommand; 554| if (cachedCommand is not null) 555| { @@ -19,7 +19,7 @@ 557| cachedCommand.State = CommandState.Idle; 558| return cachedCommand; 559| } - 560| + 560| 561| return NpgsqlCommand.CreateCachedCommand(this); 562| } - 563| + 563| diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlConnector-PreparedStatementManager.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlConnector-PreparedStatementManager.txt index 3a88a3de..868a33c8 100644 --- a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlConnector-PreparedStatementManager.txt +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlConnector-PreparedStatementManager.txt @@ -9,4 +9,4 @@ # region : lines 164..165 # derived by: scripts/derive_source_evidence.py 164| internal PreparedStatementManager PreparedStatementManager { get; } - 165| + 165| diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-Cleanup.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-Cleanup.txt index 6a699336..2a7e9416 100644 --- a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-Cleanup.txt +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-Cleanup.txt @@ -11,7 +11,7 @@ 1141| internal async Task Cleanup(bool async, bool connectionClosing = false, bool isDisposing = false) 1142| { 1143| LogMessages.ReaderCleanup(_commandLogger, Connector.Id); - 1144| + 1144| 1145| // If multiplexing isn't on, _sendTask contains the task for the writing of this command. 1146| // Make sure that this task, which may have executed asynchronously and in parallel with the reading, 1147| // has completed, throwing any exceptions it generated. If we don't do this, there's the possibility of a race condition where the @@ -43,13 +43,13 @@ 1173| } 1174| } 1175| } - 1176| + 1176| 1177| if (ColumnInfoCache is { } cache) 1178| { 1179| ColumnInfoCache = null; 1180| ArrayPool.Shared.Return(cache, clearArray: true); 1181| } - 1182| + 1182| 1183| State = ReaderState.Closed; 1184| Command.State = CommandState.Idle; 1185| Connector.CurrentReader = null; @@ -58,25 +58,25 @@ 1188| NpgsqlEventSource.Log.CommandStop(); 1189| Connector.DataSource.MetricsReporter.ReportCommandStop(_startTimestamp); 1190| Connector.EndUserAction(); - 1191| + 1191| 1192| // The reader shouldn't be unbound, if we're disposing - so the state is set prematurely 1193| if (isDisposing) 1194| State = ReaderState.Disposed; - 1195| + 1195| 1196| if (_connection?.ConnectorBindingScope == ConnectorBindingScope.Reader) 1197| { 1198| UnbindIfNecessary(); - 1199| + 1199| 1200| // TODO: Refactor... Use proper scope 1201| _connection.Connector = null; 1202| Connector.Connection = null; 1203| _connection.ConnectorBindingScope = ConnectorBindingScope.None; - 1204| + 1204| 1205| // If the reader is being closed as part of the connection closing, we don't apply 1206| // the reader's CommandBehavior.CloseConnection 1207| if (_behavior.HasFlag(CommandBehavior.CloseConnection) && !connectionClosing) 1208| _connection.Close(); - 1209| + 1209| 1210| Connector.ReaderCompleted.SetResult(null); 1211| } 1212| else if (_behavior.HasFlag(CommandBehavior.CloseConnection) && !connectionClosing) @@ -84,7 +84,7 @@ 1214| Debug.Assert(_connection is not null); 1215| _connection.Close(); 1216| } - 1217| + 1217| 1218| if (ReaderClosed != null) 1219| { 1220| ReaderClosed(this, EventArgs.Empty); diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-Close-public.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-Close-public.txt index d412f9d6..e6ef527c 100644 --- a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-Close-public.txt +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-Close-public.txt @@ -9,4 +9,4 @@ # region : lines 1073..1074 # derived by: scripts/derive_source_evidence.py 1073| public override void Close() => Close(connectionClosing: false, async: false, isDisposing: false).GetAwaiter().GetResult(); - 1074| + 1074| diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-Dispose.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-Dispose.txt index c8a4cc0c..68444c97 100644 --- a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-Dispose.txt +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-Dispose.txt @@ -25,7 +25,7 @@ 1022| { 1023| State = ReaderState.Disposed; 1024| } - 1025| + 1025| 1026| throw; 1027| } 1028| finally @@ -33,4 +33,4 @@ 1030| Command.TraceCommandStop(); 1031| } 1032| } - 1033| + 1033| diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-DisposeAsync.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-DisposeAsync.txt new file mode 100644 index 00000000..fb24e175 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-10.0.3-NpgsqlDataReader-DisposeAsync.txt @@ -0,0 +1,38 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : npgsql/npgsql +# ref : v10.0.3 +# path : src/Npgsql/NpgsqlDataReader.cs +# blob sha : 55753dc7f6c7500fe61fbf23e913a61e84fa287c +# file sha256: 9c109626d6627b55aef9768827ce4aa860d3934c224d009048ba57060431607c +# file bytes : 92885 +# marker : 'public override async ValueTask DisposeAsync()' +# region : lines 1037..1064 +# derived by: scripts/derive_source_evidence.py + 1037| public override async ValueTask DisposeAsync() + 1038| { + 1039| try + 1040| { + 1041| await Close(connectionClosing: false, async: true, isDisposing: true).ConfigureAwait(false); + 1042| } + 1043| catch (Exception ex) + 1044| { + 1045| // In the case of a PostgresException (or multiple ones, if we have error barriers), the reader's state has already been set + 1046| // to Disposed in Close above; in multiplexing, we also unbind the connector (with its reader), and at that point it can be used + 1047| // by other consumers. Therefore, we only set the state to Disposed if the exception *wasn't* a PostgresException. + 1048| if (!(ex is PostgresException || + 1049| ex is NpgsqlException { InnerException: AggregateException aggregateException } && + 1050| AllPostgresExceptions(aggregateException.InnerExceptions))) + 1051| { + 1052| State = ReaderState.Disposed; + 1053| } + 1054| throw; + 1055| } + 1056| finally + 1057| { + 1058| Command.TraceCommandStop(); + 1059| } + 1060| } + 1061| + 1062| static bool AllPostgresExceptions(ReadOnlyCollection collection) + 1063| { + 1064| foreach (var exception in collection) diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-main-NpgsqlCommand-Dispose.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-main-NpgsqlCommand-Dispose.txt index da205303..c16f9051 100644 --- a/corpus/ownership-lab/h28-command/evidence/npgsql-main-NpgsqlCommand-Dispose.txt +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-main-NpgsqlCommand-Dispose.txt @@ -11,16 +11,16 @@ 1632| protected override void Dispose(bool disposing) 1633| { 1634| ResetTransaction(); - 1635| + 1635| 1636| State = CommandState.Disposed; - 1637| + 1637| 1638| if (IsCacheable && InternalConnection is not null && InternalConnection.CachedCommand is null) 1639| { 1640| Reset(); 1641| InternalConnection.CachedCommand = this; 1642| return; 1643| } - 1644| + 1644| 1645| IsCacheable = false; 1646| } - 1647| + 1647| diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-main-NpgsqlDataReader-Close-public.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-main-NpgsqlDataReader-Close-public.txt index 24a8c705..bf532c95 100644 --- a/corpus/ownership-lab/h28-command/evidence/npgsql-main-NpgsqlDataReader-Close-public.txt +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-main-NpgsqlDataReader-Close-public.txt @@ -9,4 +9,4 @@ # region : lines 1096..1097 # derived by: scripts/derive_source_evidence.py 1096| public override void Close() => Close(connectionClosing: false, async: false, isDisposing: false).GetAwaiter().GetResult(); - 1097| + 1097| diff --git a/corpus/ownership-lab/h28-command/evidence/npgsql-v10.0.3-target-frameworks.txt b/corpus/ownership-lab/h28-command/evidence/npgsql-v10.0.3-target-frameworks.txt new file mode 100644 index 00000000..af6665b7 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/npgsql-v10.0.3-target-frameworks.txt @@ -0,0 +1,14 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : npgsql/npgsql +# ref : v10.0.3 +# path : src/Npgsql/Npgsql.csproj +# blob sha : 01aaa5013daba8a6d3d034593477ea04049c5044 +# file sha256: fd36c9e18a2c47656732ed6a6173cfe459f36b706b1798153f82ce4803da2894 +# file bytes : 2005 +# marker : 'net8.0;net9.0;net10.0' +# region : lines 8..11 +# derived by: scripts/derive_source_evidence.py + 8| net8.0;net9.0;net10.0 + 9| $(NoWarn);CA2017 + 10| $(NoWarn);NPG9001 + 11| $(NoWarn);NPG9002 diff --git a/corpus/ownership-lab/h28-command/evidence/required-gates-local.txt b/corpus/ownership-lab/h28-command/evidence/required-gates-local.txt new file mode 100644 index 00000000..b860e3df --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/required-gates-local.txt @@ -0,0 +1,27 @@ +=== ENGINE: handwritten OwnLang reduction === +corpus/ownership-lab/h28-command/fx/CommandDispose.own:43:3: error: [OWN001] 'command' is owned but not released at end of function (leaks on at least one path) [resource: disposable] + 43 | release reader; // DataTable.Load(reader) -> reader.Close() + ^ + note: 'command' acquired here at corpus/ownership-lab/h28-command/fx/CommandDispose.own:41 +corpus/ownership-lab/h28-command/fx/CommandDispose.own:71:32: error: [OWN001] 'command' is owned but not released at end of function (leaks on at least one path) [resource: disposable] + 71 | let reader = acquire Reader(command); + ^ + note: 'command' acquired here at corpus/ownership-lab/h28-command/fx/CommandDispose.own:70 +corpus/ownership-lab/h28-command/fx/CommandDispose.own:71:7: error: [OWN001] 'reader' is owned but not released at end of function (leaks on at least one path) [resource: disposable] + 71 | let reader = acquire Reader(command); + ^ + note: 'reader' acquired here at corpus/ownership-lab/h28-command/fx/CommandDispose.own:71 + +3 errors. +# exit code: 1 (OWN001 is error severity, so a finding run exits 1) +# command: python3 -m ownlang check corpus/ownership-lab/h28-command/fx/CommandDispose.own +# python: Python 3.11.2 +PASS ENGINE: expected 2 command findings (B/E), 1 reader finding (E); A/D clean. +=== G3: production Roslyn -> OwnIR -> summaries -> core === +SKIP G3 requires the .NET 8 SDK +FAIL required gate cannot skip: G3 requires the .NET 8 SDK +=== G1+G2: compiled provider falsifier === +SKIP runtime falsifier requires the .NET 8 SDK +FAIL required gate cannot skip: runtime falsifier requires the .NET 8 SDK + +# all-required exit code: 1 diff --git a/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-DataTable-Load.txt b/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-DataTable-Load.txt index ffa512ea..8cc28bab 100644 --- a/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-DataTable-Load.txt +++ b/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-DataTable-Load.txt @@ -29,7 +29,7 @@ 4992| adapter.FillError += errorHandler; 4993| } 4994| adapter.FillFromReader(new DataTable[] { this }, reader, 0, 0); - 4995| + 4995| 4996| if (!reader.IsClosed && !reader.NextResult()) 4997| { 4998| reader.Close(); diff --git a/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-DbDataReader-Close-Dispose.txt b/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-DbDataReader-Close-Dispose.txt index 0af72b4b..9d8883fe 100644 --- a/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-DbDataReader-Close-Dispose.txt +++ b/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-DbDataReader-Close-Dispose.txt @@ -9,7 +9,7 @@ # region : lines 34..65 # derived by: scripts/derive_source_evidence.py 34| public virtual void Close() { } - 35| + 35| 36| public virtual Task CloseAsync() 37| { 38| try @@ -22,10 +22,10 @@ 45| return Task.FromException(e); 46| } 47| } - 48| + 48| 49| [EditorBrowsable(EditorBrowsableState.Never)] 50| public void Dispose() => Dispose(true); - 51| + 51| 52| protected virtual void Dispose(bool disposing) 53| { 54| if (disposing) @@ -33,10 +33,10 @@ 56| Close(); 57| } 58| } - 59| + 59| 60| public virtual ValueTask DisposeAsync() 61| { 62| Dispose(); 63| return default; 64| } - 65| + 65| diff --git a/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-SafeHandle-finalizer.txt b/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-SafeHandle-finalizer.txt new file mode 100644 index 00000000..c5dcd31a --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/runtime-v10.0.12-SafeHandle-finalizer.txt @@ -0,0 +1,26 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : dotnet/runtime +# ref : v10.0.12 +# path : src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeHandle.cs +# blob sha : ef8860aed8e79dc0f5820d4d504081a59b596f5a +# file sha256: 3df9d6928d6a8bcbce983d5c53ac85a513abe5621ba9adef0e4ffd179d97a7dc +# file bytes : 12716 +# marker : '~SafeHandle()' +# region : lines 86..101 +# derived by: scripts/derive_source_evidence.py + 86| ~SafeHandle() + 87| { + 88| if (_fullyInitialized) + 89| { + 90| Dispose(disposing: false); + 91| } + 92| } + 93| + 94| internal bool OwnsHandle => _ownsHandle; + 95| + 96| protected internal void SetHandle(IntPtr handle) => this.handle = handle; + 97| + 98| public IntPtr DangerousGetHandle() => handle; + 99| + 100| public bool IsClosed => (_state & StateBits.Closed) == StateBits.Closed; + 101| diff --git a/corpus/ownership-lab/h28-command/evidence/runtime-v8.0.20-Activity-Dispose.txt b/corpus/ownership-lab/h28-command/evidence/runtime-v8.0.20-Activity-Dispose.txt index 3c5681fb..d7edfa71 100644 --- a/corpus/ownership-lab/h28-command/evidence/runtime-v8.0.20-Activity-Dispose.txt +++ b/corpus/ownership-lab/h28-command/evidence/runtime-v8.0.20-Activity-Dispose.txt @@ -16,7 +16,7 @@ 1011| { 1012| Stop(); 1013| } - 1014| + 1014| 1015| Dispose(true); 1016| GC.SuppressFinalize(this); 1017| } diff --git a/corpus/ownership-lab/h28-command/evidence/runtime-v8.0.20-DataTable-Load.txt b/corpus/ownership-lab/h28-command/evidence/runtime-v8.0.20-DataTable-Load.txt index d3b0b8e2..b7c569cf 100644 --- a/corpus/ownership-lab/h28-command/evidence/runtime-v8.0.20-DataTable-Load.txt +++ b/corpus/ownership-lab/h28-command/evidence/runtime-v8.0.20-DataTable-Load.txt @@ -29,7 +29,7 @@ 4987| adapter.FillError += errorHandler; 4988| } 4989| adapter.FillFromReader(new DataTable[] { this }, reader, 0, 0); - 4990| + 4990| 4991| if (!reader.IsClosed && !reader.NextResult()) 4992| { 4993| reader.Close(); diff --git a/corpus/ownership-lab/h28-command/evidence/runtime-v8.0.20-SafeHandle-finalizer.txt b/corpus/ownership-lab/h28-command/evidence/runtime-v8.0.20-SafeHandle-finalizer.txt new file mode 100644 index 00000000..dc1c5a11 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/runtime-v8.0.20-SafeHandle-finalizer.txt @@ -0,0 +1,26 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : dotnet/runtime +# ref : v8.0.20 +# path : src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeHandle.cs +# blob sha : ef8860aed8e79dc0f5820d4d504081a59b596f5a +# file sha256: 3df9d6928d6a8bcbce983d5c53ac85a513abe5621ba9adef0e4ffd179d97a7dc +# file bytes : 12716 +# marker : '~SafeHandle()' +# region : lines 86..101 +# derived by: scripts/derive_source_evidence.py + 86| ~SafeHandle() + 87| { + 88| if (_fullyInitialized) + 89| { + 90| Dispose(disposing: false); + 91| } + 92| } + 93| + 94| internal bool OwnsHandle => _ownsHandle; + 95| + 96| protected internal void SetHandle(IntPtr handle) => this.handle = handle; + 97| + 98| public IntPtr DangerousGetHandle() => handle; + 99| + 100| public bool IsClosed => (_state & StateBits.Closed) == StateBits.Closed; + 101| diff --git a/corpus/ownership-lab/h28-command/evidence/source-derivation-local.txt b/corpus/ownership-lab/h28-command/evidence/source-derivation-local.txt new file mode 100644 index 00000000..f5e74f51 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/source-derivation-local.txt @@ -0,0 +1,49 @@ +$ python3 corpus/ownership-lab/h28-command/scripts/derive_source_evidence.py + ok npgsql-v10.0.3-target-frameworks.txt (npgsql/npgsql@v10.0.3 line 8) + ok npgsql-10.0.3-NpgsqlCommand-Dispose.txt (npgsql/npgsql@v10.0.3 line 1712) + ok npgsql-10.0.3-NpgsqlCommand-Reset.txt (npgsql/npgsql@v10.0.3 line 1728) + ok npgsql-10.0.3-NpgsqlCommand-ctor-SuppressFinalize.txt (npgsql/npgsql@v10.0.3 line 120) + ok npgsql-10.0.3-NpgsqlCommand-CreateCachedCommand.txt (npgsql/npgsql@v10.0.3 line 166) + ok npgsql-10.0.3-NpgsqlCommand-TraceCommandStart.txt (npgsql/npgsql@v10.0.3 line 1748) + ok npgsql-10.0.3-NpgsqlCommand-TraceCommandStop.txt (npgsql/npgsql@v10.0.3 line 1792) + ok npgsql-10.0.3-NpgsqlConnection-CreateCommand.txt (npgsql/npgsql@v10.0.3 line 549) + ok npgsql-10.0.3-NpgsqlDataReader-Dispose.txt (npgsql/npgsql@v10.0.3 line 1008) + ok npgsql-10.0.3-NpgsqlDataReader-DisposeAsync.txt (npgsql/npgsql@v10.0.3 line 1037) + ok npgsql-10.0.3-NpgsqlDataReader-Close-public.txt (npgsql/npgsql@v10.0.3 line 1073) + ok npgsql-10.0.3-NpgsqlDataReader-Cleanup.txt (npgsql/npgsql@v10.0.3 line 1141) + ok npgsql-10.0.3-NpgsqlActivitySource-IsEnabled.txt (npgsql/npgsql@v10.0.3 line 17) + ok npgsql-10.0.3-NpgsqlActivitySource-SourceName.txt (npgsql/npgsql@v10.0.3 line 15) + ok npgsql-10.0.3-NpgsqlConnector-PreparedStatementManager.txt (npgsql/npgsql@v10.0.3 line 164) + ok npgsql-main-NpgsqlCommand-Dispose.txt (npgsql/npgsql@main line 1632) + ok npgsql-main-NpgsqlDataReader-Close-public.txt (npgsql/npgsql@main line 1096) + ok runtime-v8.0.20-DataTable-Load.txt (dotnet/runtime@v8.0.20 line 4969) + ok runtime-v10.0.12-DataTable-Load.txt (dotnet/runtime@v10.0.12 line 4974) + ok runtime-v10.0.12-DbCommand-class.txt (dotnet/runtime@v10.0.12 line 11) + ok runtime-v10.0.12-DbCommand-DisposeAsync.txt (dotnet/runtime@v10.0.12 line 245) + ok runtime-v10.0.12-DbDataReader-Close-Dispose.txt (dotnet/runtime@v10.0.12 line 34) + ok runtime-v10.0.12-IDbCommand.txt (dotnet/runtime@v10.0.12 line 8) + ok runtime-v8.0.20-Activity-Dispose.txt (dotnet/runtime@v8.0.20 line 1006) + ok runtime-v8.0.20-Activity-IDisposable.txt (dotnet/runtime@v8.0.20 line 56) + ok mssqlite-SqliteCommand-Dispose.txt (dotnet/efcore@v10.0.12 line 216) + ok mssqlite-SqliteCommand-DisposePreparedStatements.txt (dotnet/efcore@v10.0.12 line 520) + ok mssqlite-SqliteCommand-PrepareAndEnumerate-head.txt (dotnet/efcore@v10.0.12 line 464) + ok mssqlite-SqliteCommand-preparedStatements-field.txt (dotnet/efcore@v10.0.12 line 30) + ok mssqlite-SqliteDataReader-Close-Dispose.txt (dotnet/efcore@v10.0.12 line 227) + ok mssqlite-SqliteConnection-commands-weakrefs.txt (dotnet/efcore@v10.0.12 line 31) + ok mssqlite-SqliteConnection-Handle.txt (dotnet/efcore@v10.0.12 line 129) + ok efcore-v10.0.12-SQLitePCLRawVersion.txt (dotnet/efcore@v10.0.12 line 34) + ok mssqlite-v10.0.12-Microsoft.Data.Sqlite-target.txt (dotnet/efcore@v10.0.12 line 18) + ok mssqlite-v10.0.12-Core-targets.txt (dotnet/efcore@v10.0.12 line 18) + ok efcore-v10.0.12-default-net-target.txt (dotnet/efcore@v10.0.12 line 14) + ok sqlitepclraw-v2.1.12-sqlite3_next_stmt.txt (ericsink/SQLitePCL.raw@v2.1.12 line 1028) + ok sqlitepclraw-v2.1.12-enable-next-stmt.txt (ericsink/SQLitePCL.raw@v2.1.12 line 300) + ok sqlitepclraw-v2.1.12-find-stmt-enabled.txt (ericsink/SQLitePCL.raw@v2.1.12 line 323) + ok runtime-v8.0.20-SafeHandle-finalizer.txt (dotnet/runtime@v8.0.20 line 86) + ok runtime-v10.0.12-SafeHandle-finalizer.txt (dotnet/runtime@v10.0.12 line 86) + ok sqlitepclraw-sqlite3_stmt-SafeHandle.txt (ericsink/SQLitePCL.raw@v2.1.12 line 195) + ok sqlclient-SqlCommand-Dispose.txt (dotnet/SqlClient@main line 1883) + ok mysqlconnector-MySqlCommand-Dispose.txt (mysql-net/MySqlConnector@master line 377) + scan npgsql-10.0.3-NpgsqlCommand-finalizer-scan.txt (npgsql/npgsql@v10.0.3: 0 match(es)) + +44/44 excerpts and 1/1 negative scans re-derived -> corpus/ownership-lab/h28-command/evidence +# exit: 0 diff --git a/corpus/ownership-lab/h28-command/evidence/sqlclient-SqlCommand-Dispose.txt b/corpus/ownership-lab/h28-command/evidence/sqlclient-SqlCommand-Dispose.txt index a7586555..07226e4b 100644 --- a/corpus/ownership-lab/h28-command/evidence/sqlclient-SqlCommand-Dispose.txt +++ b/corpus/ownership-lab/h28-command/evidence/sqlclient-SqlCommand-Dispose.txt @@ -14,11 +14,11 @@ 1886| { 1887| // Release managed objects 1888| _cachedMetaData = null; - 1889| + 1889| 1890| // Reset async cache information to allow a second async execute 1891| CachedAsyncState?.ResetAsyncState(); 1892| } - 1893| + 1893| 1894| // Release unmanaged objects 1895| base.Dispose(disposing); 1896| } diff --git a/corpus/ownership-lab/h28-command/evidence/sqlitepclraw-sqlite3_stmt-SafeHandle.txt b/corpus/ownership-lab/h28-command/evidence/sqlitepclraw-sqlite3_stmt-SafeHandle.txt index 0fdcf4b2..7c6c4b56 100644 --- a/corpus/ownership-lab/h28-command/evidence/sqlitepclraw-sqlite3_stmt-SafeHandle.txt +++ b/corpus/ownership-lab/h28-command/evidence/sqlitepclraw-sqlite3_stmt-SafeHandle.txt @@ -1,36 +1,36 @@ # H-28-CMD source citation (auto-derived, do not edit by hand) # repo : ericsink/SQLitePCL.raw -# ref : main +# ref : v2.1.12 # path : src/SQLitePCLRaw.core/handles.cs -# blob sha : e7a73ebcda5243a9c36c716b2f98ad4191424ace -# file sha256: 0cb6c489786c3acf237ef0760fd8aa2703e8c0bf500301fe746ac1dd102b13bd -# file bytes : 11664 +# blob sha : 0973a6de5a4e18374193dce87e4ded3b7d736fbf +# file sha256: 9afd074db410ba78fdeb89540fc05a42c52d64a924a041b907f64198830e6c0a +# file bytes : 11066 # marker : 'public class sqlite3_stmt : SafeHandle' -# region : lines 196..221 +# region : lines 195..220 # derived by: scripts/derive_source_evidence.py - 196| public class sqlite3_stmt : SafeHandle - 197| { - 198| private sqlite3 _db; - 199| - 200| internal static sqlite3_stmt From(IntPtr p, sqlite3 db) - 201| { - 202| var h = new sqlite3_stmt(); - 203| h.SetHandle(p); - 204| db.add_stmt(h); - 205| h._db = db; - 206| return h; - 207| } - 208| - 209| sqlite3_stmt() : base(IntPtr.Zero, true) - 210| { - 211| } - 212| - 213| public override bool IsInvalid => handle == IntPtr.Zero; - 214| - 215| protected override bool ReleaseHandle() - 216| { - 217| int rc = raw.internal_sqlite3_finalize(handle); - 218| // TODO check rc? - 219| _db.remove_stmt(this); - 220| return true; - 221| } + 195| public class sqlite3_stmt : SafeHandle + 196| { + 197| private sqlite3 _db; + 198| + 199| internal static sqlite3_stmt From(IntPtr p, sqlite3 db) + 200| { + 201| var h = new sqlite3_stmt(); + 202| h.SetHandle(p); + 203| db.add_stmt(h); + 204| h._db = db; + 205| return h; + 206| } + 207| + 208| sqlite3_stmt() : base(IntPtr.Zero, true) + 209| { + 210| } + 211| + 212| public override bool IsInvalid => handle == IntPtr.Zero; + 213| + 214| protected override bool ReleaseHandle() + 215| { + 216| int rc = raw.internal_sqlite3_finalize(handle); + 217| // TODO check rc? + 218| _db.remove_stmt(this); + 219| return true; + 220| } diff --git a/corpus/ownership-lab/h28-command/evidence/sqlitepclraw-v2.1.12-enable-next-stmt.txt b/corpus/ownership-lab/h28-command/evidence/sqlitepclraw-v2.1.12-enable-next-stmt.txt new file mode 100644 index 00000000..1a969d64 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/sqlitepclraw-v2.1.12-enable-next-stmt.txt @@ -0,0 +1,26 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : ericsink/SQLitePCL.raw +# ref : v2.1.12 +# path : src/SQLitePCLRaw.core/handles.cs +# blob sha : 0973a6de5a4e18374193dce87e4ded3b7d736fbf +# file sha256: 9afd074db410ba78fdeb89540fc05a42c52d64a924a041b907f64198830e6c0a +# file bytes : 11066 +# marker : 'public void enable_sqlite3_next_stmt(bool enabled)' +# region : lines 300..315 +# derived by: scripts/derive_source_evidence.py + 300| public void enable_sqlite3_next_stmt(bool enabled) + 301| { + 302| if (enabled) + 303| { + 304| if (_stmts == null) + 305| { + 306| _stmts = new ConcurrentDictionary(); + 307| } + 308| } + 309| else + 310| { + 311| _stmts = null; + 312| } + 313| } + 314| + 315| internal void add_stmt(sqlite3_stmt stmt) diff --git a/corpus/ownership-lab/h28-command/evidence/sqlitepclraw-v2.1.12-find-stmt-enabled.txt b/corpus/ownership-lab/h28-command/evidence/sqlitepclraw-v2.1.12-find-stmt-enabled.txt new file mode 100644 index 00000000..6ff094b3 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/sqlitepclraw-v2.1.12-find-stmt-enabled.txt @@ -0,0 +1,26 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : ericsink/SQLitePCL.raw +# ref : v2.1.12 +# path : src/SQLitePCLRaw.core/handles.cs +# blob sha : 0973a6de5a4e18374193dce87e4ded3b7d736fbf +# file sha256: 9afd074db410ba78fdeb89540fc05a42c52d64a924a041b907f64198830e6c0a +# file bytes : 11066 +# marker : 'internal sqlite3_stmt find_stmt(IntPtr p)' +# region : lines 323..338 +# derived by: scripts/derive_source_evidence.py + 323| internal sqlite3_stmt find_stmt(IntPtr p) + 324| { + 325| if (_stmts != null) + 326| { + 327| return _stmts[p]; + 328| } + 329| else + 330| { + 331| // any change to the wording of this error message might break a test case + 332| throw new Exception("The sqlite3_next_stmt() function is disabled. To enable it, call sqlite3.enable_sqlite3_next_stmt(true) immediately after opening the sqlite3 connection."); + 333| } + 334| } + 335| + 336| internal void remove_stmt(sqlite3_stmt s) + 337| { + 338| if (_stmts != null) diff --git a/corpus/ownership-lab/h28-command/evidence/sqlitepclraw-v2.1.12-sqlite3_next_stmt.txt b/corpus/ownership-lab/h28-command/evidence/sqlitepclraw-v2.1.12-sqlite3_next_stmt.txt new file mode 100644 index 00000000..258c1ce9 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/sqlitepclraw-v2.1.12-sqlite3_next_stmt.txt @@ -0,0 +1,23 @@ +# H-28-CMD source citation (auto-derived, do not edit by hand) +# repo : ericsink/SQLitePCL.raw +# ref : v2.1.12 +# path : src/SQLitePCLRaw.core/raw.cs +# blob sha : a2765aac08e9f4a2141fe693864f449ebaf0c68d +# file sha256: cab23c3ea0012e858af2c92eb1ecb05b8e0eab908108bc1334a82f181f493208 +# file bytes : 60531 +# marker : 'static public sqlite3_stmt sqlite3_next_stmt(sqlite3 db, sqlite3_stmt stmt)' +# region : lines 1028..1040 +# derived by: scripts/derive_source_evidence.py + 1028| static public sqlite3_stmt sqlite3_next_stmt(sqlite3 db, sqlite3_stmt stmt) + 1029| { + 1030| IntPtr p = Provider.sqlite3_next_stmt(db, (stmt != null) ? stmt.ptr : IntPtr.Zero); + 1031| + 1032| if (p == IntPtr.Zero) + 1033| { + 1034| return null; + 1035| } + 1036| else + 1037| { + 1038| return db.find_stmt(p); + 1039| } + 1040| } diff --git a/corpus/ownership-lab/h28-command/falsifier/Program.cs b/corpus/ownership-lab/h28-command/falsifier/Program.cs index 9624769f..3dc395f9 100644 --- a/corpus/ownership-lab/h28-command/falsifier/Program.cs +++ b/corpus/ownership-lab/h28-command/falsifier/Program.cs @@ -1,254 +1,415 @@ -// H-28-CMD runtime falsifier: what does the ABSENCE of DbCommand.Dispose() actually cost? +// H-28-CMD falsifier: the provider effect of not disposing a DbCommand. // -// This is the program that was NOT runnable in the sandbox which produced the report -// (no .NET SDK, no NuGet feed -- see docs/notes/h28-npgsql-command-resolution.md section 6). -// It is written to be run as-is on a machine that has both, and every probe exists because it -// separates two hypotheses that source reading alone could not settle. +// Run through ../run.sh (the supervisor keeps pgserver alive while dotnet runs). +// Pinned to the original H-28 evidence: net8.0, Npgsql 10.0.3, +// Microsoft.Data.Sqlite 10.0.12, PostgreSQL 16. // -// ../run.sh runtime (starts PostgreSQL via pgserver, then dotnet run) -// dotnet run -c Release # Npgsql arm skipped if no PostgreSQL -// dotnet run -c Release -- sqlite-only # SQLite arm only +// G1 / P4: use a dedicated Npgsql command+reader for each treatment. Record Activity +// listener counts immediately after DataTable.Load(), then immediately after the selected +// Dispose call(s). No P2/P3 SQL runs between those observations. P1-P3 run separately with +// no ActivityListener, so they cannot create or overwrite the Activity being measured. // -// Pinned to the original H-28 evidence: net8.0, Npgsql 10.0.3, Microsoft.Data.Sqlite 10.0.12, -// PostgreSQL 16 (see h28cmd.csproj and ../README.txt). +// G2 / P6: enumerate live native sqlite3_stmt handles with SQLitePCL.raw.sqlite3_next_stmt +// via SqliteConnection.Handle. No RSS proxy, no GC, no memory snapshot. Count at baseline, +// after ExecuteReader, after DataTable.Load/reader.Close, after reader.Dispose, after +// command.Dispose, and on a second execution of the same command. // -// Probes -// P1 reader.IsClosed after Load reproduces H-28 F1 (RELEASE, not BORROW) -// P2 command reusable after Load "the reader bounds the command's lifetime" vs -// "the reader leaves it Idle" (NpgsqlCleanup sets Idle) -// P3 connection still usable protocol leak vs object leak -// P4 Activity started/stopped THE discriminating probe for the claim that only -// reader.Dispose() stops the command's Activity. Npgsql -// only: its ActivitySource is named "Npgsql" and -// Microsoft.Data.Sqlite has no ActivitySource at all. -// P5 pg_prepared_statements whether command.Dispose() releases server-side state -// P6 RSS delta over N iterations whether an undisposed command retains NATIVE memory -// (the Microsoft.Data.Sqlite sqlite3_stmt arm) -// -// Deliberately returns List, not an iterator: `yield return` is illegal inside a -// try/catch, and almost every probe here needs one. +// G3 is the separate production Roslyn -> OwnIR -> summaries -> core gate in ../run.sh g3. +// This project also compiles ../fx/CommandDispose.cs as part of its build. using System.Data; using System.Data.Common; using System.Diagnostics; +using Microsoft.Data.Sqlite; +using Npgsql; static class Probe { - // ---- P4: count Activity starts and stops for Npgsql's source --------------------- - // NpgsqlActivitySource.IsEnabled is Source.HasListeners(), so WITHOUT this listener the - // whole tracing arm is inert (CurrentActivity stays null) and P4 would prove nothing. + // ActivityListener callbacks can run on provider threads; use Interlocked for both writes + // and reads. The listener is installed only after P1-P3 finish. static int _started, _stopped; - internal static readonly ActivityListener Listener = new() + internal static ActivityListener NewNpgsqlListener() => new() { - ShouldListenTo = s => s.Name == "Npgsql", - Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllData, - SampleUsingParentId = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllData, + ShouldListenTo = source => source.Name == "Npgsql", + Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData, + SampleUsingParentId = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData, ActivityStarted = _ => Interlocked.Increment(ref _started), ActivityStopped = _ => Interlocked.Increment(ref _stopped), }; - static void ResetActivity() { _started = 0; _stopped = 0; } - static string ActivityReport() => $"started={_started} stopped={_stopped}"; - - // ---- P6: process RSS. GC.GetTotalMemory is deliberately NOT used: it measures the ---- - // managed heap, and the resource in question (sqlite3_stmt) is native. One snapshot - // proves nothing, so P6 is a DELTA over N iterations with an explicit finalization control. - static long Rss() + static void ResetActivity() { - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - return Environment.WorkingSet; + Interlocked.Exchange(ref _started, 0); + Interlocked.Exchange(ref _stopped, 0); } - // ---- the four variants of the family; disposeCommand/disposeReader are the knobs ---- - internal static List Run(string name, DbConnection conn, string sql, - bool disposeCommand, bool disposeReader, bool load) - { - var o = new List(); - ResetActivity(); + static string ActivityReport() + => $"started={Interlocked.CompareExchange(ref _started, 0, 0)} " + + $"stopped={Interlocked.CompareExchange(ref _stopped, 0, 0)}"; + // P1-P3 only. No ActivityListener is installed during this helper. Cleanup is unconditional + // and happens only AFTER all reported observations, so arms do not leak objects into the + // following run; the intended dispose flags still determine the P1-P3 observations. + internal static List Lifecycle(string name, DbConnection conn, string sql, + bool disposeCommand, bool disposeReader) + { + var output = new List(); DbCommand command = conn.CreateCommand(); - command.CommandText = sql; - DbDataReader reader = command.ExecuteReader(); - - int rows = 0; - if (load) + DbDataReader reader = null; + bool commandDisposed = false, readerDisposed = false; + try { + command.CommandText = sql; + reader = command.ExecuteReader(); var table = new DataTable(); - table.Load(reader); // BCL: Close() iff this is the last result set - rows = table.Rows.Count; + table.Load(reader); + output.Add($"{name} P1 reader.IsClosed after Load={reader.IsClosed}; rows={table.Rows.Count}"); + + if (disposeReader) + { + reader.Dispose(); + readerDisposed = true; + } + + // P2 is a separate probe from P4. It deliberately executes a second command on the + // same object; its output is never used as evidence about Activity lifecycle. + try + { + command.CommandText = "select 1"; + output.Add($"{name} P2 command reuse after reader close=OK ({command.ExecuteScalar()})"); + } + catch (Exception e) + { + output.Add($"{name} P2 command reuse threw {e.GetType().Name}: {e.Message}"); + } + + if (disposeCommand) + { + command.Dispose(); + commandDisposed = true; + } + + // P3 is also separate from P4: a fresh command tests whether the connection remains + // usable after the family. Its query is never in the Activity measurement. + try + { + using var c2 = conn.CreateCommand(); + c2.CommandText = "select 42"; + output.Add($"{name} P3 connection usable=OK ({c2.ExecuteScalar()}); state={conn.State}"); + } + catch (Exception e) + { + output.Add($"{name} P3 connection threw {e.GetType().Name}: {e.Message}"); + } } - else + finally { - while (reader.Read()) rows++; + if (reader is not null && !readerDisposed) + reader.Dispose(); + if (!commandDisposed) + command.Dispose(); } + return output; + } - // P1 -- the H-28 F1 reproduction - o.Add($"{name} P1 reader.IsClosed after Load = {reader.IsClosed} (rows={rows})"); - - if (disposeReader) reader.Dispose(); - - // P2 -- is the command still logically usable? A model claiming the reader's lifetime - // bounds the command's must predict a throw here. + // P4: this helper performs exactly ONE ExecuteReader + DataTable.Load. It records directly + // after Load, then after reader.Dispose when requested, then after command.Dispose when + // requested. No other query executes in this helper. In the final cleanup of a negative + // control, disposal happens only after the snapshots were captured. + internal static List ActivityLifecycle(string name, DbConnection conn, string sql, + bool disposeReader, bool disposeCommand) + { + var output = new List(); + ResetActivity(); + DbCommand command = conn.CreateCommand(); + DbDataReader reader = null; + bool readerDisposed = false, commandDisposed = false; try { - command.CommandText = "select 1"; - o.Add($"{name} P2 command reuse after the reader closed = OK ({command.ExecuteScalar()})"); + command.CommandText = sql; + reader = command.ExecuteReader(); + var table = new DataTable(); + table.Load(reader); + output.Add($"{name} P4 immediately after Load: {ActivityReport()} " + + $"reader.IsClosed={reader.IsClosed}; rows={table.Rows.Count}"); + + if (disposeReader) + { + reader.Dispose(); + readerDisposed = true; + output.Add($"{name} P4 immediately after reader.Dispose(): {ActivityReport()}"); + } + if (disposeCommand) + { + command.Dispose(); + commandDisposed = true; + output.Add($"{name} P4 immediately after command.Dispose(): {ActivityReport()}"); + } + } + finally + { + // For B/C/D these are containment cleanups, not measured treatment events. Their + // callbacks are intentionally not sampled into the already-recorded output. + if (reader is not null && !readerDisposed) + reader.Dispose(); + if (!commandDisposed) + command.Dispose(); } - catch (Exception e) + return output; + } + + // G2/P6: direct, connection-wide count of native sqlite3_stmt objects. SqliteConnection.Handle + // is public in Microsoft.Data.Sqlite 10.0.12; sqlite3_next_stmt walks live statements without + // preparing another query and without triggering GC/finalization. + static int LiveSqliteStatements(SqliteConnection connection) + { + var db = connection.Handle ?? throw new InvalidOperationException("SqliteConnection.Handle is null"); + int count = 0; + var statement = SQLitePCL.raw.sqlite3_next_stmt(db, null); + while (statement is not null) { - o.Add($"{name} P2 command reuse threw {e.GetType().Name}: {e.Message}"); + count++; + statement = SQLitePCL.raw.sqlite3_next_stmt(db, statement); } + return count; + } - if (disposeCommand) command.Dispose(); + internal static List SqliteStatementLifecycle(string name, + bool disposeReader, + bool disposeCommand, + bool executeTwice = false) + { + var output = new List(); + using var connection = new SqliteConnection("Data Source=:memory:"); + connection.Open(); + // SQLitePCLRaw disables its wrapper lookup table by default; the native walk itself + // works, but sqlite3_next_stmt maps raw pointers back to SafeHandles through this opt-in. + var db = connection.Handle ?? throw new InvalidOperationException("SqliteConnection.Handle is null"); + db.enable_sqlite3_next_stmt(true); + int baseline = LiveSqliteStatements(connection); + output.Add($"{name} P6 before query={baseline} (delta=0)"); - // P4 -- the discriminating tracing probe (Npgsql only; 0/0 on SQLite is expected) - o.Add($"{name} P4 Activity {ActivityReport()}"); + var command = connection.CreateCommand(); + command.CommandText = "select 1 as a union all select 2 union all select 3"; + SqliteDataReader reader = command.ExecuteReader(); + int afterExecute = LiveSqliteStatements(connection); + output.Add($"{name} P6 after ExecuteReader={afterExecute} (delta={afterExecute - baseline})"); - // P3 -- protocol leak (connection busy) or only an object leak? - try + var table = new DataTable(); + table.Load(reader); + int afterLoad = LiveSqliteStatements(connection); + output.Add($"{name} P6 after DataTable.Load/reader.Close={afterLoad} " + + $"(delta={afterLoad - baseline})"); + + if (disposeReader) { - using var c2 = conn.CreateCommand(); - c2.CommandText = "select 42"; - o.Add($"{name} P3 connection after the family = OK ({c2.ExecuteScalar()}) state={conn.State}"); + reader.Dispose(); + int afterReaderDispose = LiveSqliteStatements(connection); + output.Add($"{name} P6 after reader.Dispose={afterReaderDispose} " + + $"(delta={afterReaderDispose - baseline})"); } - catch (Exception e) + if (disposeCommand) { - o.Add($"{name} P3 connection threw {e.GetType().Name}: {e.Message}"); + command.Dispose(); + int afterCommandDispose = LiveSqliteStatements(connection); + output.Add($"{name} P6 after command.Dispose={afterCommandDispose} " + + $"(delta={afterCommandDispose - baseline})"); } - return o; - } - // ---- P5 -- does command.Dispose() release SERVER-SIDE prepared state? -------------- - internal static List Prepared(string name, DbConnection conn, string sql) - { - var o = new List(); - int Count() + // Re-execution has its own arm. It reuses THIS command. On its next ExecuteReader, + // PrepareAndEnumerateStatements must finalize the first statement before preparing the + // second; the live count should therefore remain baseline+1, not grow to baseline+2. + if (executeTwice) { - using var c = conn.CreateCommand(); - c.CommandText = "select count(*) from pg_prepared_statements"; - return Convert.ToInt32(c.ExecuteScalar()); + command.CommandText = "select 4 as a union all select 5"; + var reader2 = command.ExecuteReader(); + int afterSecondExecute = LiveSqliteStatements(connection); + output.Add($"{name} P6 after second ExecuteReader={afterSecondExecute} " + + $"(delta={afterSecondExecute - baseline})"); + var table2 = new DataTable(); + table2.Load(reader2); + int afterSecondLoad = LiveSqliteStatements(connection); + output.Add($"{name} P6 after second Load={afterSecondLoad} " + + $"(delta={afterSecondLoad - baseline})"); + command.Dispose(); + int afterFinalDispose = LiveSqliteStatements(connection); + output.Add($"{name} P6 after final command.Dispose={afterFinalDispose} " + + $"(delta={afterFinalDispose - baseline})"); } - - var cmd = conn.CreateCommand(); - cmd.CommandText = sql; - Exception err = null; - try { cmd.Prepare(); } catch (Exception e) { err = e; } - if (err != null) + else { - o.Add($"{name} P5 Prepare threw {err.GetType().Name}: {err.Message}"); - return o; + // Unmeasured containment cleanup: preserve the measured before/after stages above. + reader.Dispose(); + command.Dispose(); } - cmd.ExecuteNonQuery(); - var afterPrepare = Count(); - cmd.Dispose(); // the arm under test - var afterDispose = Count(); - o.Add($"{name} P5 pg_prepared_statements after Prepare={afterPrepare} " - + $"after command.Dispose()={afterDispose} " - + (afterDispose == afterPrepare - ? "-> command.Dispose() released NO server-side prepared state" - : "-> command.Dispose() DID release server-side prepared state")); - return o; + return output; } - // ---- P6 -- native retention over N iterations ------------------------------------ - internal static List NativeRetention(string name, Func open, string sql, - bool disposeCommand, int n = 20000) + // P5: an actual Npgsql prepared statement, before and after the command's Dispose. This is + // the direct provider experiment; the standalone psql session check is not a substitute. + internal static (List Output, int Before, int AfterPrepare, int AfterDispose) + PreparedStatementLifecycle(DbConnection connection) { - var o = new List(); - using var conn = open(); - conn.Open(); - // warm up, so the delta is not the provider's first-touch cost - for (int i = 0; i < 200; i++) - { - var w = conn.CreateCommand(); w.CommandText = sql; - var r = w.ExecuteReader(); var t = new DataTable(); t.Load(r); - if (disposeCommand) w.Dispose(); - } - var before = Rss(); - for (int i = 0; i < n; i++) + var output = new List(); + int Count() { - var c = conn.CreateCommand(); c.CommandText = sql; - var r = c.ExecuteReader(); var t = new DataTable(); t.Load(r); - if (disposeCommand) c.Dispose(); + using var countCommand = connection.CreateCommand(); + countCommand.CommandText = "select count(*) from pg_prepared_statements"; + return Convert.ToInt32(countCommand.ExecuteScalar()); } - var after = Rss(); - o.Add($"{name} P6 n={n} disposeCommand={disposeCommand} " - + $"RSS {before / 1024}KiB -> {after / 1024}KiB (delta {(after - before) / 1024}KiB)"); - return o; + + int before = Count(); + var command = connection.CreateCommand(); + command.CommandText = "select $1::int"; + command.Parameters.Add(new Npgsql.NpgsqlParameter { Value = 7 }); + command.Prepare(); + command.ExecuteScalar(); + int afterPrepare = Count(); + command.Dispose(); + int afterDispose = Count(); + output.Add($"Npgsql P5 pg_prepared_statements: before={before}; " + + $"after Prepare={afterPrepare}; after command.Dispose={afterDispose}"); + return (output, before, afterPrepare, afterDispose); } } static class Program { const string Sql = "select 1 as a union all select 2 union all select 3"; - static readonly (string Name, bool Cmd, bool Rdr)[] Variants = { - ("B_neither", false, false), // the H-28 demanding instance - ("C_reader_only", false, true ), // reader.Dispose(): the arm that stops the Activity - ("D_command_only", true, false), // command.Dispose() after Load - ("A_both", true, true ), // fully released control + ("B_neither", false, false), + ("C_reader_only", false, true), + ("D_command_only", true, false), + ("A_both", true, true), }; - static async Task Main(string[] args) + static int _failures; + static void Check(bool condition, string message) + { + Console.WriteLine($"{(condition ? "PASS" : "FAIL")} {message}"); + if (!condition) _failures++; + } + + static void Main(string[] args) { - ActivitySource.AddActivityListener(Probe.Listener); - var pgDsn = Environment.GetEnvironmentVariable("H28_PG_DSN") - ?? "Host=127.0.0.1;Port=5432;Username=postgres;Password=postgres;Database=postgres"; + var pgDsn = Environment.GetEnvironmentVariable("H28_PG_DSN"); var mode = args.Length > 0 ? args[0] : "all"; + Console.WriteLine($"# H-28-CMD falsifier net={Environment.Version}; " + + $"Npgsql={typeof(NpgsqlConnection).Assembly.GetName().Version}; " + + $"Microsoft.Data.Sqlite={typeof(SqliteConnection).Assembly.GetName().Version}"); - Console.WriteLine($"# H-28-CMD falsifier net={Environment.Version} " - + $"npgsql={typeof(Npgsql.NpgsqlConnection).Assembly.GetName().Version} " - + $"sqlite={typeof(Microsoft.Data.Sqlite.SqliteConnection).Assembly.GetName().Version}"); + // G2/P6: direct native statements, separate isolated in-memory connections for B/C/D/A. + Console.WriteLine("\n## Microsoft.Data.Sqlite: direct sqlite3_next_stmt counts"); + foreach (var v in Variants) + { + var rows = Probe.SqliteStatementLifecycle(v.Name, disposeReader: v.Rdr, + disposeCommand: v.Cmd); + foreach (var row in rows) Console.WriteLine(row); + int baselineSnapshot = ReadDelta(rows, "before query", expected: 0); + int afterExecute = ReadDelta(rows, "after ExecuteReader", expected: 1); + int afterLoad = ReadDelta(rows, "after DataTable.Load/reader.Close", expected: 1); + int afterReader = v.Rdr + ? ReadDelta(rows, "after reader.Dispose", expected: 1) + : afterLoad; + int afterCommand = v.Cmd + ? ReadDelta(rows, "after command.Dispose", expected: 0) + : afterReader; + Check(baselineSnapshot == 0 && afterExecute == 1 && afterLoad == 1 + && afterReader == 1 && afterCommand == (v.Cmd ? 0 : 1), + $"SQLite {v.Name}: reader.Dispose leaves the statement; command.Dispose finalizes it"); + } + var reuseRows = Probe.SqliteStatementLifecycle("sqlite_reuse", disposeReader: false, + disposeCommand: false, executeTwice: true); + foreach (var row in reuseRows) Console.WriteLine(row); + int secondExecute = ReadDelta(reuseRows, "after second ExecuteReader", expected: 1); + int secondLoad = ReadDelta(reuseRows, "after second Load", expected: 1); + int finalDispose = ReadDelta(reuseRows, "after final command.Dispose", expected: 0); + Check(secondExecute == 1 && secondLoad == 1 && finalDispose == 0, + "SQLite repeated execution replaces rather than accumulates statements"); - // ---- Microsoft.Data.Sqlite: the arm where command.Dispose() DOES matter -------- - Console.WriteLine("\n## Microsoft.Data.Sqlite (in-memory)"); - using (var conn = new Microsoft.Data.Sqlite.SqliteConnection("Data Source=:memory:")) + if (mode == "sqlite-only") { - conn.Open(); - foreach (var v in Variants) - foreach (var line in Probe.Run(v.Name, conn, Sql, v.Cmd, v.Rdr, load: true)) - Console.WriteLine(line); - foreach (var line in Probe.NativeRetention("sqlite_nodispose", - () => new Microsoft.Data.Sqlite.SqliteConnection("Data Source=:memory:"), - Sql, disposeCommand: false)) Console.WriteLine(line); - foreach (var line in Probe.NativeRetention("sqlite_dispose", - () => new Microsoft.Data.Sqlite.SqliteConnection("Data Source=:memory:"), - Sql, disposeCommand: true)) Console.WriteLine(line); + Console.WriteLine($"\n{(_failures == 0 ? "PASS" : "FAIL")} G2 SQLite-only; failures={_failures}"); + Environment.ExitCode = _failures == 0 ? 0 : 1; + return; } - if (mode == "sqlite-only") return; - // ---- Npgsql: the arm where command.Dispose() releases nothing ------------------ - Console.WriteLine("\n## Npgsql (real PostgreSQL)"); - var pg = new Npgsql.NpgsqlConnection(pgDsn); - try { await pg.OpenAsync(); } - catch (Exception e) + if (string.IsNullOrWhiteSpace(pgDsn)) { - Console.WriteLine($"# SKIP the Npgsql arm: cannot reach PostgreSQL at {pgDsn}: " - + $"{e.GetType().Name}: {e.Message}"); - Console.WriteLine("# Start one with ../run.sh runtime (pgserver) or set H28_PG_DSN."); + Console.Error.WriteLine("FAIL G1: H28_PG_DSN was not supplied; no PostgreSQL runtime was tested."); + Environment.ExitCode = 2; return; } - using (pg) + + Console.WriteLine("\n## Npgsql: P1-P3 (ActivityListener not installed)"); + using var pg = new NpgsqlConnection(pgDsn); + pg.Open(); + using (var versionCommand = pg.CreateCommand()) + { + versionCommand.CommandText = "select version()"; + var version = Convert.ToString(versionCommand.ExecuteScalar()); + Console.WriteLine($"# server: {version?.Split('\n')[0]}"); + } + foreach (var v in Variants) + foreach (var row in Probe.Lifecycle(v.Name, pg, Sql, v.Cmd, v.Rdr)) + Console.WriteLine(row); + + Console.WriteLine("\n## Npgsql: isolated P4 Activity lifecycle"); + using (var listener = Probe.NewNpgsqlListener()) { - var version = (string)await pg.ExecuteScalarAsync("select version()"); - Console.WriteLine($"# server: {version.Split('\n')[0]}"); + ActivitySource.AddActivityListener(listener); foreach (var v in Variants) - foreach (var line in Probe.Run(v.Name, pg, "select generate_series(1,3) as a", - v.Cmd, v.Rdr, load: true)) - Console.WriteLine(line); - foreach (var line in Probe.Prepared("npgsql", pg, "select $1::int")) - Console.WriteLine(line); + { + var rows = Probe.ActivityLifecycle(v.Name, pg, "select generate_series(1,3) as a", + disposeReader: v.Rdr, disposeCommand: v.Cmd); + foreach (var row in rows) Console.WriteLine(row); + var afterLoad = Snapshot(rows, "immediately after Load", started: 1, stopped: 0); + bool actionsCorrect = true; + if (v.Rdr) + actionsCorrect &= Snapshot(rows, "immediately after reader.Dispose()", + started: 1, stopped: 1); + if (v.Cmd) + actionsCorrect &= Snapshot(rows, "immediately after command.Dispose()", + started: 1, stopped: v.Rdr ? 1 : 0); + Check(afterLoad && actionsCorrect, $"Npgsql isolated Activity stages for {v.Name}"); + } } - Console.WriteLine("\n## how to read this"); - Console.WriteLine("# P4 stopped=0 on B_neither/D_command_only and stopped=1 on C_reader_only/A_both"); - Console.WriteLine("# => only reader.Dispose() stops the command's Activity; command.Dispose() does not."); - Console.WriteLine("# (P4 is 0/0 for every SQLite variant: Microsoft.Data.Sqlite has no ActivitySource.)"); - Console.WriteLine("# P2 OK everywhere => the reader does not end the command's logical lifetime."); - Console.WriteLine("# P5 unchanged => command.Dispose() releases no server-side prepared state."); - Console.WriteLine("# P6 delta(nodispose) >> delta(dispose) => native retention is real on SQLite only."); + Console.WriteLine("\n## Npgsql: P5 direct prepared-statement lifecycle"); + var prepared = Probe.PreparedStatementLifecycle(pg); + foreach (var row in prepared.Output) Console.WriteLine(row); + Check(prepared.AfterPrepare > prepared.Before + && prepared.AfterDispose == prepared.AfterPrepare, + "Npgsql command.Dispose leaves the explicit prepared statement registered"); + Console.WriteLine($"\n{(_failures == 0 ? "PASS" : "FAIL")} runtime falsifier; failures={_failures}"); + Environment.ExitCode = _failures == 0 ? 0 : 1; + } + + static int ReadDelta(List rows, string stage, int expected) + { + var row = rows.SingleOrDefault(x => x.Contains(stage, StringComparison.Ordinal)); + if (row is null) return int.MinValue; + var marker = "(delta="; + int start = row.IndexOf(marker, StringComparison.Ordinal); + if (start < 0) return int.MinValue; + start += marker.Length; + int end = row.IndexOf(')', start); + if (end < 0 || !int.TryParse(row.AsSpan(start, end - start), out var delta)) + return int.MinValue; + if (delta != expected) + Console.WriteLine($"FAIL expected delta={expected} at {stage}, got {delta}"); + return delta; + } + + static bool Snapshot(List rows, string stage, int started, int stopped) + { + var row = rows.SingleOrDefault(x => x.Contains(stage, StringComparison.Ordinal)); + bool ok = row is not null && row.Contains($"started={started} stopped={stopped}", + StringComparison.Ordinal); + if (!ok) + Console.WriteLine($"FAIL expected Activity started={started} stopped={stopped} at {stage}; " + + $"observed: {row ?? ""}"); + return ok; } } diff --git a/corpus/ownership-lab/h28-command/falsifier/h28cmd.csproj b/corpus/ownership-lab/h28-command/falsifier/h28cmd.csproj index d7754c40..3425a39f 100644 --- a/corpus/ownership-lab/h28-command/falsifier/h28cmd.csproj +++ b/corpus/ownership-lab/h28-command/falsifier/h28cmd.csproj @@ -19,5 +19,7 @@ + + diff --git a/corpus/ownership-lab/h28-command/falsifier/run_with_pgserver.py b/corpus/ownership-lab/h28-command/falsifier/run_with_pgserver.py new file mode 100755 index 00000000..df2a968c --- /dev/null +++ b/corpus/ownership-lab/h28-command/falsifier/run_with_pgserver.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Start a temporary pgserver and keep it alive while the Npgsql falsifier runs. + +Called by run.sh only when no explicit H28_PG_DSN was supplied. pgserver must be +installed (`python3 -m pip install pgserver`). The server and its data directory are +cleaned in finally; dotnet is a child process, not a shell substitution that could +outlive the PostgreSQL handle. +""" +from __future__ import annotations + +import os +import subprocess +import sys +import tempfile +from pathlib import Path +from urllib.parse import parse_qs, unquote, urlsplit + + +def connection_string(uri: str) -> str: + parsed = urlsplit(uri) + query = parse_qs(parsed.query) + host = query.get("host", [None])[0] or parsed.hostname or "127.0.0.1" + parts = [f"Host={host}", f"Username={unquote(parsed.username or 'postgres')}"] + database = unquote(parsed.path.lstrip("/")) + if database: + parts.append(f"Database={database}") + if parsed.port: + parts.append(f"Port={parsed.port}") + if parsed.password: + parts.append(f"Password={unquote(parsed.password)}") + return ";".join(parts) + + +def main() -> int: + if len(sys.argv) != 2: + print("usage: run_with_pgserver.py ", file=sys.stderr) + return 2 + try: + import pgserver + except ImportError: + print("SKIP: pgserver is not installed; pip install pgserver or set H28_PG_DSN", + file=sys.stderr) + return 3 + + server = None + with tempfile.TemporaryDirectory(prefix="h28cmd-pg-") as temp: + try: + server = pgserver.get_server(Path(temp) / "pgdata", cleanup_mode="delete") + dsn = connection_string(server.get_uri(database="postgres")) + env = os.environ.copy() + env["H28_PG_DSN"] = dsn + print("# pgserver is ready; running Npgsql falsifier against PostgreSQL 16", flush=True) + return subprocess.run(["dotnet", sys.argv[1]], env=env, check=False).returncode + finally: + if server is not None: + server.cleanup() + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/corpus/ownership-lab/h28-command/fx/CommandDispose.cs b/corpus/ownership-lab/h28-command/fx/CommandDispose.cs index 59aa881b..10c3367e 100644 --- a/corpus/ownership-lab/h28-command/fx/CommandDispose.cs +++ b/corpus/ownership-lab/h28-command/fx/CommandDispose.cs @@ -3,10 +3,10 @@ // Deliberately written against the ABSTRACT ADO.NET types (DbConnection / DbCommand / // DbDataReader), not against Npgsql, because that is exactly what Own.NET's // IsOwningFactory matches on (frontend/roslyn/OwnSharp.Extractor/Program.cs, the ADO.NET -// tranche: receiver implements IDbConnection, return implements IDbCommand). The fixture -// therefore shows the analyzer's verdict for EVERY provider at once -- which is the point: -// the runtime semantics are NOT the same for every provider (see ../README.txt and -// docs/notes/h28-npgsql-command-resolution.md). +// tranche: receiver implements IDbConnection, return implements IDbCommand). This makes it a +// useful source-level fixture for the provider-agnostic acquisition rule; it does NOT run the +// same C# against each provider. Runtime/provider expectations remain predictions until G1/G2 run +// (see ../README.txt and docs/notes/h28-npgsql-command-resolution.md). // // The method name carries the expectation. Run with the flow-locals default: // dotnet ownsharp-extract.dll --flow-locals fx/CommandDispose.cs -o cmd.facts.json @@ -52,10 +52,10 @@ public static DataTable A_load_both_disposed(DbConnection connection, string sql return table; } - // C: the reader explicitly released, the command not. This is the arm that matters for - // Npgsql tracing: reader.Dispose() is the ONLY thing that stops the command's Activity - // (NpgsqlDataReader.Dispose -> Command.TraceCommandStop; NpgsqlDataReader.Close does not). - // PREDICTED: OWN001 on `command`. + // C: the reader explicitly released, the command not. In Npgsql 10.0.3 source, reader + // Dispose/DisposeAsync call Command.TraceCommandStop while public Close uses the + // non-disposing close path. This predicts an Activity difference, not a runtime result; + // the isolated listener probe is G1/P4. PREDICTED: OWN001 on `command`. public static DataTable C_reader_disposed_command_not(DbConnection connection, string sql) { var command = connection.CreateCommand(); @@ -68,9 +68,10 @@ public static DataTable C_reader_disposed_command_not(DbConnection connection, s return table; } - // D: the command released after Load, the reader not (Load already closed it). - // For Microsoft.Data.Sqlite this is the ONLY variant in the family that releases the - // native sqlite3_stmt handles at a deterministic point. PREDICTED: clean. + // D: the command released after Load, the reader not (Load already closed it). For + // Microsoft.Data.Sqlite 10.0.12, source predicts that command.Dispose deterministically + // finalizes the command-held sqlite3_stmt handles. G2 measures this directly; it has not + // run here. PREDICTED: clean. public static DataTable D_command_disposed_after_load(DbConnection connection, string sql) { var command = connection.CreateCommand(); @@ -117,10 +118,9 @@ public static int F_reader_closed_command_not(DbConnection connection, string sq return n; } - // G: the command is still logically usable after the reader closes. NpgsqlDataReader - // .Cleanup sets `Command.State = CommandState.Idle`, so reuse is legitimate -- the - // command's lifetime is NOT bounded by the reader's. A model that treated reader close - // as ending the command would mispredict this. PREDICTED: clean. + // G: the source indicates reader cleanup sets `Command.State = CommandState.Idle`; this + // predicts reuse is possible and that the command lifetime is not bounded by reader close. + // P2 measures same-command reuse separately. PREDICTED: clean. public static int G_command_reused_after_reader_closed(DbConnection connection, string sql) { using var command = connection.CreateCommand(); @@ -133,12 +133,12 @@ public static int G_command_reused_after_reader_closed(DbConnection connection, return command.ExecuteNonQuery(); // legal: the reader's Cleanup left it Idle } - // H: the prepared-statement arm. Server-side prepared state is the one resource in this - // family whose lifetime is NOT the command's: Npgsql registers it on the CONNECTOR - // (NpgsqlConnector.PreparedStatementManager), and NpgsqlCommand.Dispose never issues a - // DEALLOCATE. So disposing the command does not release it, and not disposing the command - // does not retain it. PREDICTED: OWN001 on `command` -- a finding whose fix would not - // change any server-side state. + // H: the prepared-statement arm. Npgsql source routes prepared-statement management through + // the connector's PreparedStatementManager, and its reviewed command Dispose body shows no + // explicit native-handle release or DEALLOCATE. Connector/session ownership alone does not + // prove the command cannot issue DEALLOCATE. P5 directly compares pg_prepared_statements + // before Prepare, after Prepare, and after this command's Dispose. Until then the server-side + // effect is unresolved. PREDICTED by extractor source only: OWN001 on `command`. public static int H_prepared_command_not_disposed(DbConnection connection, string sql) { var command = connection.CreateCommand(); diff --git a/corpus/ownership-lab/h28-command/fx/CommandDispose.own b/corpus/ownership-lab/h28-command/fx/CommandDispose.own index c3e89a82..02a532e9 100644 --- a/corpus/ownership-lab/h28-command/fx/CommandDispose.own +++ b/corpus/ownership-lab/h28-command/fx/CommandDispose.own @@ -5,11 +5,11 @@ // // Honest frame (the same one corpus/real-world/README.md states): this is a MANUAL REDUCTION // of the C#, not C# the checker read. It models the two obligations the extractor's -// IsOwningFactory mints for this family -- IDbConnection.CreateCommand() -> DbCommand and -// IDbCommand.ExecuteReader() -> DbDataReader -- and it models the ONE runtime fact the -// extractor does not know: that DataTable.Load(reader) closes the reader. That last line is -// the whole point of the reduction: it is a witnessed callee effect that today has to be -// hand-written here because the argument-escape rule throws the call away instead. +// IsOwningFactory appears to mint for this family -- IDbConnection.CreateCommand() -> DbCommand +// and IDbCommand.ExecuteReader() -> DbDataReader -- and it models the H-28 runtime fact that +// DataTable.Load(reader) closes the reader. The production extractor -> OwnIR path has NOT yet +// run here (G3 is pending). This hand-written reduction encodes the candidate callee effect +// because source inspection predicts the argument-escape rule may drop the call instead. module H28Cmd resource Command { @@ -34,8 +34,9 @@ resource Reader { // table.Load(reader) closed the reader: witnessed RELEASE_IF_LAST_RESULT_SET by the H-28 // runtime falsifier (corpus/ownership-lab/h28/falsifier/falsifier.out, F1 IsClosed=True on // Sqlite AND Npgsql). The command is never released. -// EXPECTED: OWN001 on 'command' -- and this is exactly the finding whose correctness is -// provider-dependent, which no .own model and no OwnIR fact can currently express. +// EXPECTED for this manual reduction: OWN001 on 'command'. This does not decide whether the +// diagnostic's practical implication is misleading for a provider; provider effects are not +// encoded in this reduction, and the production C# extraction result is still pending G3. fn B_load_neither_disposed(conn: int) { let command = acquire Command(conn); let reader = acquire Reader(command); @@ -51,8 +52,9 @@ fn A_load_both_released(conn: int) { release command; } -// D -- control: the command released after Load. EXPECTED: clean. This is the only variant -// that deterministically releases Microsoft.Data.Sqlite's native sqlite3_stmt handles. +// D -- control: the command released after Load. EXPECTED: clean in this manual reduction. +// Source predicts this is the arm that deterministically finalizes Microsoft.Data.Sqlite's +// command-held sqlite3_stmt handles; G2 must confirm it before treating that as a result. fn D_command_released_after_load(conn: int) { let command = acquire Command(conn); let reader = acquire Reader(command); diff --git a/corpus/ownership-lab/h28-command/run.sh b/corpus/ownership-lab/h28-command/run.sh index d999bb76..1ec1d1c5 100755 --- a/corpus/ownership-lab/h28-command/run.sh +++ b/corpus/ownership-lab/h28-command/run.sh @@ -1,17 +1,15 @@ #!/usr/bin/env bash -# H-28-CMD reproduction driver. Two halves, deliberately separable: +# H-28-CMD gates. Results are explicitly PASS / FAIL / SKIP. # -# half 1 (ENGINE) fx/CommandDispose.own -> python -m ownlang check -# needs only python3. Runs anywhere, including the sandbox that -# produced the report. Artifact: evidence/engine-CommandDispose.txt +# engine Python-only handwritten reduction. Always runnable. +# g3 G3: production Roslyn -> OwnIR -> summaries -> core verdict. +# runtime G1+G2: compile the C# falsifier, direct SQLite stmt counts, isolated Npgsql P4. +# all run every gate; environment SKIPs are reported but do not fail local use. +# all-required mandatory/CI gate; ANY SKIP is a non-zero failure. +# g3-required / runtime-required are strict single-gate forms. # -# half 2 (RUNTIME) falsifier/ -> dotnet run against a real PostgreSQL -# needs a .NET SDK, a NuGet feed, and PostgreSQL. Artifact: -# evidence/falsifier.out -# If any of those is missing this half prints SKIP-WHY and exits 0: -# a missing runtime arm is recorded, never silently pretended. -# -# Usage: corpus/ownership-lab/h28-command/run.sh [engine|runtime|all] +# NuGet is not probed with curl: dotnet restore is attempted first, so an offline but warm +# package cache can run. A restore failure is an explicit SKIP; a build/test failure is FAIL. set -uo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -20,81 +18,168 @@ EV="$HERE/evidence" MODE="${1:-all}" mkdir -p "$EV" +skip_status() { + local label="$1" strict="$2" + echo "SKIP $label" + if [ "$strict" = 1 ]; then + echo "FAIL required gate cannot skip: $label" + return 2 + fi + return 3 +} + engine_half() { - echo "=== half 1: ENGINE (python -m ownlang check) ===" - local out="$EV/engine-CommandDispose.txt" + echo "=== ENGINE: handwritten OwnLang reduction ===" + local out="$EV/engine-CommandDispose.txt" rc n_cmd n_reader ( cd "$ROOT" && python3 -m ownlang check \ corpus/ownership-lab/h28-command/fx/CommandDispose.own ) >"$out" 2>&1 - local rc=$? + rc=$? { - echo "# exit code: $rc (OWN001 is error-severity, so non-zero is the expected result)" - echo "# command : python3 -m ownlang check corpus/ownership-lab/h28-command/fx/CommandDispose.own" - echo "# cwd : repository root" - echo "# python : $(python3 --version 2>&1)" + echo "# exit code: $rc (OWN001 is error severity, so a finding run exits 1)" + echo "# command: python3 -m ownlang check corpus/ownership-lab/h28-command/fx/CommandDispose.own" + echo "# python: $(python3 --version 2>&1)" } >>"$out" cat "$out" - # The prediction the report makes: 3 findings -- OWN001 on 'command' in B, and OWN001 on - # BOTH 'command' and 'reader' in E. A and D clean. Any other shape means the report is stale. - local n_cmd n_reader n_cmd=$(grep -c "'command' is owned but not released" "$out") n_reader=$(grep -c "'reader' is owned but not released" "$out") - echo - if [ "$n_cmd" = "2" ] && [ "$n_reader" = "1" ]; then - echo "PREDICTION HOLDS: 2x OWN001 'command' (B, E) + 1x OWN001 'reader' (E), A/D clean." + if [ "$rc" = 1 ] && [ "$n_cmd" = 2 ] && [ "$n_reader" = 1 ]; then + echo "PASS ENGINE: expected 2 command findings (B/E), 1 reader finding (E); A/D clean." return 0 fi - echo "PREDICTION BROKE: got command=$n_cmd reader=$n_reader; expected command=2 reader=1." - echo "Re-read docs/notes/h28-npgsql-command-resolution.md section 7 before trusting it." + echo "FAIL ENGINE: rc=$rc command=$n_cmd reader=$n_reader; expected rc=1 command=2 reader=1." return 1 } -runtime_half() { - echo - echo "=== half 2: RUNTIME (real PostgreSQL + Npgsql + Microsoft.Data.Sqlite) ===" - if ! command -v dotnet >/dev/null 2>&1; then - echo "SKIP-WHY: no .NET SDK on PATH. The report's runtime arm was NOT executed;" - echo " its claims are source-derived (evidence/*.txt) and marked PREDICTED." - echo " Install .NET 8 SDK + reach api.nuget.org, then re-run: $0 runtime" +restore_project() { + local label="$1" project="$2" strict="$3" log="$EV/$1-restore.log" + echo "# dotnet restore $project --ignore-failed-sources -p:NuGetAudit=false" + if dotnet restore "$project" --ignore-failed-sources --disable-parallel \ + -p:NuGetAudit=false >"$log" 2>&1; then + tail -8 "$log" return 0 fi - if ! curl -sS -m 8 -o /dev/null https://api.nuget.org/v3/index.json 2>/dev/null; then - echo "SKIP-WHY: dotnet is present but api.nuget.org is unreachable, so Npgsql 10.0.3 and" - echo " Microsoft.Data.Sqlite 10.0.12 cannot be restored." + echo "# restore output: $log" + tail -20 "$log" + skip_status "$label dependencies unavailable (restore failed; warm cache was tried)" "$strict" +} + +g3_half() { + local strict="$1" + echo "=== G3: production Roslyn -> OwnIR -> summaries -> core ===" + if ! command -v dotnet >/dev/null 2>&1; then + skip_status "G3 requires the .NET 8 SDK" "$strict" + return $? + fi + local project="$ROOT/frontend/roslyn/OwnSharp.Extractor/OwnSharp.Extractor.csproj" + restore_project "g3" "$project" "$strict" + local restore_rc=$? + if [ "$restore_rc" -ne 0 ]; then return "$restore_rc"; fi + if python3 "$HERE/scripts/verify_g3.py"; then return 0 fi + echo "FAIL G3: see $EV/g3-CommandDispose.log and the emitted facts/SARIF artifacts." + return 1 +} + +runtime_half() { + local strict="$1" + echo "=== G1+G2: compiled provider falsifier ===" + if ! command -v dotnet >/dev/null 2>&1; then + skip_status "runtime falsifier requires the .NET 8 SDK" "$strict" + return $? + fi + local project="$HERE/falsifier/h28cmd.csproj" dll="$HERE/falsifier/bin/Release/net8.0/h28cmd-falsifier.dll" + restore_project "runtime" "$project" "$strict" + local restore_rc=$? + if [ "$restore_rc" -ne 0 ]; then return "$restore_rc"; fi - # PostgreSQL: pgserver (PyPI) is what the original H-28 falsifier used -- a self-contained - # PostgreSQL 16 on a Unix socket, no root, no container. - local dsn="${H28_PG_DSN:-}" - if [ -z "$dsn" ] && python3 -c "import pgserver" 2>/dev/null; then - echo "starting PostgreSQL via pgserver ..." - dsn=$(python3 - "$HERE" <<'PY' -import os, re, sys, pgserver -d = "/tmp/h28cmd-pgdata" -srv = pgserver.get_server(d, cleanup_mode=None) -uri = srv.get_uri() # postgresql://postgres:@/postgres?host= -m = re.search(r"host=([^&\s]+)", uri) -print(f"Host={m.group(1) if m else d};Username=postgres;Database=postgres" if m else "") -PY -) + local build_log="$EV/runtime-build.log" + if ! dotnet build "$project" -c Release --no-restore --nologo -v q >"$build_log" 2>&1; then + echo "FAIL G1: runtime falsifier / abstract C# fixture did not compile." + cat "$build_log" + return 1 + fi + cat "$build_log" + if [ ! -f "$dll" ]; then + echo "FAIL G1: build succeeded but expected executable is missing: $dll" + return 1 fi - if [ -z "$dsn" ]; then - echo "SKIP-WHY: no PostgreSQL. Either 'pip install pgserver' or export H28_PG_DSN." - echo " The SQLite arm still runs: dotnet run --project falsifier -- sqlite-only" + echo "PASS G1: falsifier and CommandDispose.cs compile under net8.0." + + # Run G2 by itself first. This makes SQLite observability useful even if no PostgreSQL + # server/pgserver is present; it does not replace the mandatory Npgsql run below. + local sqlite_out="$EV/runtime-sqlite-only.out" rc + dotnet "$dll" sqlite-only >"$sqlite_out" 2>&1 + rc=$? + cat "$sqlite_out" + if [ "$rc" -ne 0 ]; then + echo "FAIL G2: direct sqlite3_next_stmt checks failed (rc=$rc)." + return 1 fi - export H28_PG_DSN="$dsn" - echo "DSN: ${H28_PG_DSN:-}" + grep -q '^PASS G2' "$sqlite_out" || { + echo "FAIL G2: no PASS marker in $sqlite_out"; return 1; + } + echo "PASS G2: direct native statement-lifecycle checks passed." - local out="$EV/falsifier.out" - ( cd "$HERE/falsifier" && dotnet run -c Release ) 2>&1 | tee "$out" - local rc=${PIPESTATUS[0]} - echo "# exit code: $rc" >>"$out" - return $rc + local full_out="$EV/runtime-full.out" + if [ -n "${H28_PG_DSN:-}" ]; then + echo "# Using caller-supplied H28_PG_DSN." + H28_PG_DSN="$H28_PG_DSN" dotnet "$dll" >"$full_out" 2>&1 + rc=$? + elif python3 -c "import pgserver" >/dev/null 2>&1; then + python3 "$HERE/falsifier/run_with_pgserver.py" "$dll" >"$full_out" 2>&1 + rc=$? + if [ "$rc" = 3 ]; then + cat "$full_out" + skip_status "PostgreSQL arm requires pgserver or H28_PG_DSN" "$strict" + return $? + fi + else + skip_status "Npgsql G1/P4/P5 requires pgserver (pip install pgserver) or H28_PG_DSN" "$strict" + return $? + fi + cat "$full_out" + if [ "$rc" -ne 0 ]; then + echo "FAIL G1/P4/P5: Npgsql runtime assertions failed (rc=$rc)." + return 1 + fi + grep -q '^PASS runtime falsifier' "$full_out" || { + echo "FAIL G1/P4/P5: no PASS marker in $full_out"; return 1; + } + echo "PASS G1/P4/P5: Npgsql runtime assertions passed." + return 0 } case "$MODE" in - engine) engine_half ;; - runtime) runtime_half ;; - all) engine_half; runtime_half ;; - *) echo "usage: $0 [engine|runtime|all]" >&2; exit 2 ;; + engine) engine_half ;; + g3) + g3_half 0; rc=$? + [ "$rc" = 3 ] && exit 0 + exit "$rc" + ;; + g3-required) g3_half 1 ;; + runtime) + runtime_half 0; rc=$? + [ "$rc" = 3 ] && exit 0 + exit "$rc" + ;; + runtime-required) runtime_half 1 ;; + all) + overall=0 + engine_half || overall=1 + g3_half 0; rc=$?; [ "$rc" != 0 ] && [ "$rc" != 3 ] && overall=1 + runtime_half 0; rc=$?; [ "$rc" != 0 ] && [ "$rc" != 3 ] && overall=1 + exit "$overall" + ;; + all-required) + overall=0 + engine_half || overall=1 + g3_half 1 || overall=1 + runtime_half 1 || overall=1 + exit "$overall" + ;; + *) + echo "usage: $0 [engine|g3|g3-required|runtime|runtime-required|all|all-required]" >&2 + exit 2 + ;; esac diff --git a/corpus/ownership-lab/h28-command/scripts/derive_source_evidence.py b/corpus/ownership-lab/h28-command/scripts/derive_source_evidence.py index d7a11cd7..86dc2063 100755 --- a/corpus/ownership-lab/h28-command/scripts/derive_source_evidence.py +++ b/corpus/ownership-lab/h28-command/scripts/derive_source_evidence.py @@ -1,18 +1,19 @@ #!/usr/bin/env python3 """H-28-CMD evidence deriver: re-fetch every source citation used by -docs/notes/h28-npgsql-command-resolution.md from its PINNED upstream ref and -write the exact cited region to evidence/.txt. +docs/notes/h28-npgsql-command-resolution.md from its declared upstream ref and +write the cited region to evidence/.txt. Tagged refs are pinned; moving refs +are identified as ancillary in the report. -Nothing in the report is a hand-copied quotation: each artifact here carries the -repo, the ref (a tag where one exists, so the bytes are immutable), the path, the -byte count, the sha256 of the fetched file and the line range that was extracted. +Nothing in the report is a hand-copied quotation: each excerpt carries the repo, +ref, path, git blob SHA, file SHA-256, byte count and line range. Negative source +claims have scan artifacts that record the searched file and zero/non-zero matches. Re-run this and diff evidence/ to re-verify the whole source arm of the finding. python3 corpus/ownership-lab/h28-command/scripts/derive_source_evidence.py Read-only: touches no production code, builds nothing, needs only github.com + api.github.com. Set GH_TOKEN for a higher rate limit (works unauthenticated too). -Exit code is non-zero if any pinned citation could not be re-derived, so this +Exit code is non-zero if any declared source excerpt or scan could not be re-derived, so this doubles as a staleness gate on the report. """ @@ -21,6 +22,7 @@ import hashlib import json import os +import re import sys import urllib.error import urllib.request @@ -35,6 +37,11 @@ # marker+maxlines, so the citation is stable even if the file shifts. CITATIONS: list[tuple[str, str, str, str, str, int]] = [ # ---- Npgsql: what NpgsqlCommand.Dispose actually releases ----------------- + ( + "npgsql-v10.0.3-target-frameworks.txt", + "npgsql/npgsql", "v10.0.3", "src/Npgsql/Npgsql.csproj", + "net8.0;net9.0;net10.0", 4, + ), ( "npgsql-10.0.3-NpgsqlCommand-Dispose.txt", "npgsql/npgsql", "v10.0.3", "src/Npgsql/NpgsqlCommand.cs", @@ -75,6 +82,11 @@ "npgsql/npgsql", "v10.0.3", "src/Npgsql/NpgsqlDataReader.cs", "protected override void Dispose(bool disposing)", 26, ), + ( + "npgsql-10.0.3-NpgsqlDataReader-DisposeAsync.txt", + "npgsql/npgsql", "v10.0.3", "src/Npgsql/NpgsqlDataReader.cs", + "public override async ValueTask DisposeAsync()", 28, + ), ( "npgsql-10.0.3-NpgsqlDataReader-Close-public.txt", "npgsql/npgsql", "v10.0.3", "src/Npgsql/NpgsqlDataReader.cs", @@ -168,44 +180,102 @@ # ---- Cross-provider: does command.Dispose release anything there? --------- ( "mssqlite-SqliteCommand-Dispose.txt", - "dotnet/efcore", "main", + "dotnet/efcore", "v10.0.12", "src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs", "protected override void Dispose(bool disposing)", 12, ), ( "mssqlite-SqliteCommand-DisposePreparedStatements.txt", - "dotnet/efcore", "main", + "dotnet/efcore", "v10.0.12", "src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs", "private void DisposePreparedStatements(bool disposing = true)", 22, ), ( "mssqlite-SqliteCommand-PrepareAndEnumerate-head.txt", - "dotnet/efcore", "main", + "dotnet/efcore", "v10.0.12", "src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs", "private IEnumerable<(sqlite3_stmt Statement, int ParamCount)> " "PrepareAndEnumerateStatements()", 6, ), ( "mssqlite-SqliteCommand-preparedStatements-field.txt", - "dotnet/efcore", "main", + "dotnet/efcore", "v10.0.12", "src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs", "_preparedStatements =", 2, ), ( "mssqlite-SqliteDataReader-Close-Dispose.txt", - "dotnet/efcore", "main", + "dotnet/efcore", "v10.0.12", "src/Microsoft.Data.Sqlite.Core/SqliteDataReader.cs", "public override void Close()", 42, ), ( "mssqlite-SqliteConnection-commands-weakrefs.txt", - "dotnet/efcore", "main", + "dotnet/efcore", "v10.0.12", "src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs", "List> _commands", 2, ), + ( + "mssqlite-SqliteConnection-Handle.txt", + "dotnet/efcore", "v10.0.12", + "src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs", + "public virtual sqlite3? Handle", 3, + ), + ( + "efcore-v10.0.12-SQLitePCLRawVersion.txt", + "dotnet/efcore", "v10.0.12", "eng/Versions.props", + "", 4, + ), + ( + "mssqlite-v10.0.12-Microsoft.Data.Sqlite-target.txt", + "dotnet/efcore", "v10.0.12", + "src/Microsoft.Data.Sqlite/Microsoft.Data.Sqlite.csproj", + "netstandard2.0", 4, + ), + ( + "mssqlite-v10.0.12-Core-targets.txt", + "dotnet/efcore", "v10.0.12", + "src/Microsoft.Data.Sqlite.Core/Microsoft.Data.Sqlite.Core.csproj", + "$(NetMinimum);netstandard2.0", 4, + ), + ( + "efcore-v10.0.12-default-net-target.txt", + "dotnet/efcore", "v10.0.12", "eng/Versions.props", + "net10.0", 4, + ), + ( + "sqlitepclraw-v2.1.12-sqlite3_next_stmt.txt", + "ericsink/SQLitePCL.raw", "v2.1.12", + "src/SQLitePCLRaw.core/raw.cs", + "static public sqlite3_stmt sqlite3_next_stmt(sqlite3 db, sqlite3_stmt stmt)", 13, + ), + ( + "sqlitepclraw-v2.1.12-enable-next-stmt.txt", + "ericsink/SQLitePCL.raw", "v2.1.12", + "src/SQLitePCLRaw.core/handles.cs", + "public void enable_sqlite3_next_stmt(bool enabled)", 16, + ), + ( + "sqlitepclraw-v2.1.12-find-stmt-enabled.txt", + "ericsink/SQLitePCL.raw", "v2.1.12", + "src/SQLitePCLRaw.core/handles.cs", + "internal sqlite3_stmt find_stmt(IntPtr p)", 16, + ), + ( + "runtime-v8.0.20-SafeHandle-finalizer.txt", + "dotnet/runtime", "v8.0.20", + "src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeHandle.cs", + "~SafeHandle()", 16, + ), + ( + "runtime-v10.0.12-SafeHandle-finalizer.txt", + "dotnet/runtime", "v10.0.12", + "src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeHandle.cs", + "~SafeHandle()", 16, + ), ( "sqlitepclraw-sqlite3_stmt-SafeHandle.txt", - "ericsink/SQLitePCL.raw", "main", + "ericsink/SQLitePCL.raw", "v2.1.12", "src/SQLitePCLRaw.core/handles.cs", "public class sqlite3_stmt : SafeHandle", 26, ), @@ -223,6 +293,17 @@ ), ] +# (artifact name, repo, ref, path, regular expression). These preserve negative claims +# such as "no finalizer declaration" as a checkable full-file scan instead of treating an +# excerpt as proof of absence. +NEGATIVE_SCANS: list[tuple[str, str, str, str, str]] = [ + ( + "npgsql-10.0.3-NpgsqlCommand-finalizer-scan.txt", + "npgsql/npgsql", "v10.0.3", "src/Npgsql/NpgsqlCommand.cs", + r"~\s*NpgsqlCommand\s*\(", + ), +] + def _get(url: str, accept: str) -> bytes | None: tok = os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN") @@ -265,8 +346,11 @@ def region(text: str, marker: str, maxlines: int) -> tuple[int, str] | None: if marker in line: chunk = lines[i:i + maxlines] start = i + 1 - body = "\n".join(f"{start + j:5d}| {ln}" for j, ln in enumerate(chunk)) - return start, body + rendered = [] + for j, line in enumerate(chunk): + line = line.rstrip(" \t") + rendered.append(f"{start + j:5d}| {line if line else ''}") + return start, "\n".join(rendered) return None @@ -306,11 +390,45 @@ def main() -> int: manifest.append(f"OK\t{name}\t{repo}@{ref}\tsha256={digest[:16]}\tlines={start}") print(f" ok {name} ({repo}@{ref} line {start})") + scan_count = 0 + for name, repo, ref, path, pattern in NEGATIVE_SCANS: + got = fetch(repo, path, ref) + if got is None: + failed += 1 + manifest.append(f"MISSING\t{name}\t{repo}@{ref}\t{path}") + continue + raw, sha = got + digest = hashlib.sha256(raw).hexdigest() + text = raw.decode("utf-8", "replace") + hits = [(i, line) for i, line in enumerate(text.splitlines(), 1) + if re.search(pattern, line)] + body = "\n".join(f"{line_no:5d}| {line}" for line_no, line in hits) + if not hits: + body = "" + header = ( + f"# H-28-CMD negative source scan (auto-derived, do not edit by hand)\n" + f"# repo : {repo}\n" + f"# ref : {ref}\n" + f"# path : {path}\n" + f"# blob sha : {sha}\n" + f"# file sha256: {digest}\n" + f"# file bytes : {len(raw)}\n" + f"# regex : {pattern!r}\n" + f"# matches : {len(hits)}\n" + f"# derived by: scripts/derive_source_evidence.py\n" + ) + with open(os.path.join(OUT, name), "w", encoding="utf-8") as f: + f.write(header + body + "\n") + scan_count += 1 + manifest.append(f"SCAN\t{name}\t{repo}@{ref}\tsha256={digest[:16]}\tmatches={len(hits)}") + print(f" scan {name} ({repo}@{ref}: {len(hits)} match(es))") + with open(os.path.join(OUT, "MANIFEST.txt"), "w") as f: - f.write("# H-28-CMD evidence manifest: one line per pinned citation.\n") - f.write("# OK = re-derived now; MISSING/MARKER-NOT-FOUND = the report is stale.\n") + f.write("# H-28-CMD source evidence manifest (declared refs).\n") + f.write("# OK/SCAN = re-derived now; MISSING/MARKER-NOT-FOUND = stale or unavailable.\n") f.write("\n".join(manifest) + "\n") - print(f"\n{len(CITATIONS) - failed}/{len(CITATIONS)} citations re-derived " + print(f"\n{len(CITATIONS) - failed}/{len(CITATIONS)} excerpts and " + f"{scan_count}/{len(NEGATIVE_SCANS)} negative scans re-derived " f"-> {os.path.relpath(OUT)}") if failed: print(f"{failed} FAILED: the report's source arm is not reproducible as pinned", diff --git a/corpus/ownership-lab/h28-command/scripts/verify_g3.py b/corpus/ownership-lab/h28-command/scripts/verify_g3.py new file mode 100755 index 00000000..d9306a31 --- /dev/null +++ b/corpus/ownership-lab/h28-command/scripts/verify_g3.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +"""G3 gate: production C# extractor -> OwnIR -> summaries -> production-core verdict. + +Requires the .NET 8 SDK and a successful restore of OwnSharp.Extractor. This is the +end-to-end check that the manual .own reduction cannot provide. It writes the source +facts, solved summaries, SARIF diagnostics and command log into ../evidence/. + +Expected OWN001 acquisition anchors (line numbers are derived from the named methods, +not duplicated as magic constants): + B: command only; C: command only; E: command + reader; F: command; + H: command; I: command. A, D and G: clean. + +Exit non-zero on any mismatch. A missing SDK/restore is a SKIP in run.sh, and the +strict runtime-required/all-required gates convert that SKIP into a failing status. +""" +from __future__ import annotations + +import json +import subprocess +import sys +from pathlib import Path +from typing import Any + +ROOT = Path(__file__).resolve().parents[4] +HERE = Path(__file__).resolve().parents[1] +SOURCE = HERE / "fx" / "CommandDispose.cs" +EXTRACTOR = ROOT / "frontend" / "roslyn" / "OwnSharp.Extractor" +DLL = EXTRACTOR / "bin" / "Release" / "net8.0" / "ownsharp-extract.dll" +EVIDENCE = HERE / "evidence" +FACTS = EVIDENCE / "g3-CommandDispose.facts.json" +SUMMARIES = EVIDENCE / "g3-CommandDispose.summaries.json" +SARIF = EVIDENCE / "g3-CommandDispose.sarif.json" +LOG = EVIDENCE / "g3-CommandDispose.log" + + +def run(label: str, argv: list[str], *, + expected: set[int] | None = None) -> subprocess.CompletedProcess[str]: + p = subprocess.run(argv, cwd=ROOT, text=True, capture_output=True, check=False) + with LOG.open("a", encoding="utf-8") as f: + f.write(f"\n$ {' '.join(argv)}\n") + f.write(f"# exit={p.returncode}\n") + if p.stdout: + f.write(p.stdout) + if not p.stdout.endswith("\n"): + f.write("\n") + if p.stderr: + f.write("# stderr\n" + p.stderr) + if not p.stderr.endswith("\n"): + f.write("\n") + if expected is not None and p.returncode not in expected: + raise RuntimeError(f"{label}: exit {p.returncode}, expected {sorted(expected)}; " + f"stderr tail: {p.stderr[-1200:]}") + return p + + +def source_anchor(method: str, fragment: str) -> int: + lines = SOURCE.read_text(encoding="utf-8").splitlines() + try: + start = next(i for i, line in enumerate(lines) if f" {method}(" in line) + except StopIteration as e: + raise RuntimeError(f"fixture method not found: {method}") from e + end = next((i for i in range(start + 1, len(lines)) + if lines[i].lstrip().startswith("public static ")), len(lines)) + for i in range(start, end): + if fragment in lines[i]: + return i + 1 + raise RuntimeError(f"anchor {fragment!r} not found in {method}") + + +def diagnostics(doc: dict[str, Any]) -> set[tuple[str, int]]: + result: set[tuple[str, int]] = set() + for run_doc in doc.get("runs", []): + for finding in run_doc.get("results", []): + locations = finding.get("locations", []) + if not locations: + continue + physical = locations[0].get("physicalLocation", {}) + line = physical.get("region", {}).get("startLine") + if isinstance(line, int): + result.add((str(finding.get("ruleId", "")), line)) + return result + + +def main() -> int: + EVIDENCE.mkdir(parents=True, exist_ok=True) + LOG.write_text("# G3: current production Roslyn extractor -> OwnIR summaries -> core\n", + encoding="utf-8") + run("build extractor", ["dotnet", "build", str(EXTRACTOR), "-c", "Release", + "--no-restore", "--nologo", "-v", "q"], expected={0}) + if not DLL.exists(): + raise RuntimeError(f"extractor DLL missing after successful build: {DLL}") + run("extract C# fixture", ["dotnet", str(DLL), str(SOURCE), "--flow-locals", + "-o", str(FACTS)], expected={0}) + if not FACTS.exists(): + raise RuntimeError("extractor exited successfully but did not write its OwnIR facts") + summaries = run("solve summaries", [sys.executable, "-m", "ownlang", "summaries", + str(FACTS)], expected={0}) + SUMMARIES.write_text(summaries.stdout, encoding="utf-8") + + # OwnIR has a non-zero exit when it emits active OWN001 findings. Exit 0 or 1 are + # both structurally valid here; SARIF parsing plus exact finding anchors is the verdict. + core = run("check OwnIR", [sys.executable, "-m", "ownlang", "ownir", str(FACTS), + "--format", "sarif", "--verbosity", "verbose"], + expected={0, 1}) + try: + sarif_doc = json.loads(core.stdout) + except json.JSONDecodeError as e: + raise RuntimeError(f"core stdout is not SARIF JSON: {core.stdout[:500]!r}") from e + SARIF.write_text(json.dumps(sarif_doc, indent=2, ensure_ascii=False) + "\n", + encoding="utf-8") + + expected: set[tuple[str, int]] = set() + for method in ("B_load_neither_disposed", "C_reader_disposed_command_not", + "F_reader_closed_command_not", "H_prepared_command_not_disposed", + "I_table_is_dispose_optional"): + expected.add(("OWN001", source_anchor(method, "var command = connection.CreateCommand();"))) + for fragment in ("var command = connection.CreateCommand();", + "var reader = command.ExecuteReader();"): + expected.add(("OWN001", source_anchor("E_no_load_both_leak", fragment))) + + actual = diagnostics(sarif_doc) + if actual != expected: + missing = sorted(expected - actual) + extra = sorted(actual - expected) + print("FAIL G3: extracted C# verdict differs from the source-reading prediction.") + print(f" expected OWN001 anchors: {sorted(expected)}") + print(f" actual diagnostic anchors: {sorted(actual)}") + print(f" missing: {missing}; extra: {extra}") + return 1 + + print("PASS G3: production C# -> OwnIR -> summaries -> core matches the fixture contract.") + print(f" facts : {FACTS.relative_to(ROOT)}") + print(f" summaries: {SUMMARIES.relative_to(ROOT)}") + print(f" SARIF : {SARIF.relative_to(ROOT)}") + print(f" findings: {sorted(actual)}") + return 0 + + +if __name__ == "__main__": + try: + sys.exit(main()) + except (OSError, RuntimeError, json.JSONDecodeError) as e: + print(f"FAIL G3: {e}", file=sys.stderr) + sys.exit(1) diff --git a/docs/notes/h28-npgsql-command-resolution.md b/docs/notes/h28-npgsql-command-resolution.md index 50664a4d..763d36d0 100644 --- a/docs/notes/h28-npgsql-command-resolution.md +++ b/docs/notes/h28-npgsql-command-resolution.md @@ -1,423 +1,364 @@ -# H-28-CMD — what the absence of `DbCommand.Dispose()` actually means +# H-28-CMD — resolving the undisposed `DbCommand` question -Status: **research note, no production change**. Verdict: **H28-PROVIDER-SPECIFIC**. -Base: `main` = `e889f8b`. Artifacts and reproduction: [`corpus/ownership-lab/h28-command/`](../../corpus/ownership-lab/h28-command/). +**Status: inconclusive pending three required gates. No production-code change.** +Current verdict: **H28-INCONCLUSIVE**. This is deliberately not the earlier +`H28-PROVIDER-SPECIFIC` verdict: source reading gives a provider-specific *prediction*, but the +real provider run and the production Roslyn → OwnIR → core run have not yet completed. +Artifacts and required reproductions: [`corpus/ownership-lab/h28-command/`](../../corpus/ownership-lab/h28-command/). ```csharp var command = connection.CreateCommand(); var reader = command.ExecuteReader(); var table = new DataTable(); -table.Load(reader); // neither command nor reader is disposed +table.Load(reader); // neither command nor reader is explicitly disposed ``` -Citations named `evidence/…txt` are generated artifacts under that directory, each carrying -repo, ref, path, git blob sha, file sha256 and line range. Re-derive all 32 with -`scripts/derive_source_evidence.py` — non-zero exit means this note is stale. +Every `evidence/*.txt` source excerpt is re-derived from its declared upstream ref and records +the git blob SHA, file SHA-256, path and line range; tagged refs are pinned, while moving `main` / +`master` snapshots are explicitly ancillary. The Npgsql finalizer absence also has a saved +full-file negative-scan artifact. Run `python3 corpus/ownership-lab/h28-command/scripts/derive_source_evidence.py` +to re-derive all 44 excerpts and that scan; non-zero means a source citation is stale. --- -## 1. The original uncertainty +## 1. Original uncertainty -H-28 was registered as *argument / callee ownership transfer*. Its "demanding instance" was +H-28 was registered as *argument / callee ownership transfer*. Its demanding instance was `victor-wiki/DatabaseManager` `DbInterpreter.GetDataTableAsync:699`, described in the -preregistration as *"connection is a parameter, command a local never disposed"*. H-28's own -runtime falsifier settled the **reader** and explicitly declined to settle the **command**: +preregistration as *"connection is a parameter, command a local never disposed"*. + +The old H-28 runtime falsifier settled the **reader** and explicitly left the **command** open: > `DataTable.Load(IDataReader)` closes the reader … the demanding instance is NOT a lease / -> protocol leak … **what remains is the never-disposed `DbCommand` `cmd` (an object leak -> without a protocol consequence)**. — `h28/scripts/h28_anchors_record.py` +> protocol leak … **what remains is the never-disposed DbCommand `cmd` (an object leak without a +> protocol consequence)**. + +— `research/ownership-semantics-lab-v1@298b305`, +`corpus/ownership-lab/h28/scripts/h28_anchors_record.py`, field +`consequence_for_the_demanding_instance`. -That is a description, not a classification: never falsified, never entered into the -witnessed-callee table, never reached a gate. This note closes it against the five candidate -classifications (real defect / provider-specific / benign / not classifiable / obsolete). +That last sentence was a description, not a classification: it was never falsified, never +entered in the witnessed-callee table, and never reached a gate. That is the question addressed +here. + +**Scope correction.** The earlier committed version pivoted from this provider/lifetime question +to a #382 argument-lowering model gap, treating a source-derived extractor prediction as if G3 +had proved it. That was premature. This revision returns to the original H-28 command question; +#382 stays a candidate follow-up only if real Roslyn → OwnIR → verdict evidence establishes that +specific loss. No architecture conclusion is drawn in advance. --- ## 2. Recovered prior evidence -**H-28 is not on `main`.** It lives on `research/ownership-semantics-lab-v1` @ `298b305`, under -`corpus/ownership-lab/h28/` (36 files). The preregistration JSON itself lives in a separate -`Own.NET-paperwork` repository that is **not reachable**; its content is nevertheless -recoverable verbatim because `h28/scripts/h28_prereg.py` is the program that writes it. +**H-28 is not on `main`.** Its primary artifacts are on `research/ownership-semantics-lab-v1` +@ `298b305`, under `corpus/ownership-lab/h28/` (36 files). The preregistration JSON lives in a +separate `Own.NET-paperwork` repository that is not reachable from this checkout; its contents +are recoverable from the committed generator `h28/scripts/h28_prereg.py`. | claim | primary evidence | status | |---|---|---| -| H-28 = argument/callee transfer, not command lifetime | `scripts/h28_prereg.py` (`track`, `hypothesis`) | confirmed | -| demanding instance = `DbInterpreter.GetDataTableAsync:699` | `h28_prereg.py` → `anchors.demanding_instance` | confirmed | -| `DataTable.Load(reader)` **closes** the reader; BORROW refuted | `falsifier/falsifier.out` (F1 `IsClosed=True` on sqlite **and** npgsql) → `RELEASE_IF_LAST_RESULT_SET` | confirmed | -| CA2000 (default options) reports **neither** command nor reader | `analyzers/ca2000/{Repro2.cs,ca2000-warnings.txt}` — CA2000 tracks `new`, not factory results | confirmed | -| "the never-disposed `DbCommand`" | `h28_anchors_record.py` → `consequence_for_the_demanding_instance` | **recorded, never classified** ← this note | -| gate → `RECORD_AND_STOP` (1 of 10 primary candidates confirmed) | `manual-read.json`, `scripts/h28final.py`, `h28-census-v1.json` | confirmed | -| the escape rule that drops an argument-passed local | `h28_prereg.py` → `necessary_condition_verified_by_code_reading`; issue [#382](https://github.com/PhysShell/Own.NET/issues/382) (open) | confirmed | - -Two provenance warnings. **Name collision:** `H28` in -`docs/notes/p022-bridge-verdict-checkpoint4b.md:216` is a *different* H28 (a bridge-verdict -hypothesis) and must not be conflated. **Stale facts:** `corpus/ownership-lab/h29/promotion/promo-u.facts.json` -— the only committed OwnIR facts mentioning `CreateCommand`/`ExecuteReader` — is **schema v1** -while the core is **v2**; `ownlang ownir` refuses it, so it is not evidence of current behaviour. +| H-28 = argument/callee transfer, not command lifetime | `h28/scripts/h28_prereg.py` (`track`, `hypothesis`) | recovered | +| demanding instance = `DbInterpreter.GetDataTableAsync:699` | same file → `anchors.demanding_instance` | recovered | +| `DataTable.Load(reader)` closes the reader in the tested single-result case | `h28/falsifier/falsifier.out`, F1 `IsClosed=True` for Sqlite and Npgsql; `h28_anchors_record.py` → `RELEASE_IF_LAST_RESULT_SET` | measured in the old run | +| CA2000 (default options) does not report command or reader | `h28/analyzers/ca2000/{Repro2.cs,ca2000-warnings.txt}` | recorded | +| the undisposed `DbCommand` consequence | `h28_anchors_record.py` → `consequence_for_the_demanding_instance` | recorded, **not classified** | +| census gate → `RECORD_AND_STOP` | `h28/manual-read.json`, `h28/scripts/h28final.py`, `h28/h28-census-v1.json` | recovered | +| argument escape/drop rule | `h28/scripts/h28_prereg.py` → `necessary_condition_verified_by_code_reading`; current issue [#382](https://github.com/PhysShell/Own.NET/issues/382) | source observation; end-to-end witness pending | + +Name collision: `H28` in `docs/notes/p022-bridge-verdict-checkpoint4b.md:216` is an unrelated +bridge-verdict hypothesis. It is not this ownership-lab H-28. + +The old `h29/promotion/promo-u.facts.json` cannot stand in for a current production run: it is +OwnIR schema v1, while the current core accepts v2, and the core refuses it as stale. --- -## 3. Versions +## 3. Versions and source references -| component | version | source | +| component | version / ref | evidence | |---|---|---| -| .NET / Npgsql / MS.Data.Sqlite (old falsifier) | net8.0 / **10.0.3** / 10.0.12 | `h28/falsifier/w28.csproj` | -| PostgreSQL (old falsifier) | 16, pgserver, socket `/tmp/pgsc` | `h28_anchors_record.py` | -| PostgreSQL (this note) | **16.2**, `pgserver` 0.1.4 (PyPI) | started and queried here, §6 | -| BCL reference | `dotnet/runtime` `v8.0.20` **and** `v10.0.12` | the old TFM and current | -| Npgsql re-read | `v10.0.3` **and** `main` | §5.1 | -| cross-provider | `dotnet/efcore` main, `dotnet/SqlClient` main, `mysql-net/MySqlConnector` master, `ericsink/SQLitePCL.raw` main | §5.3 | -| Own.NET | `main` @ `e889f8b`, extractor TFM net8.0 | §7 | - -`DbCommand.cs`, `DbDataReader.cs`, `DbConnection.cs`, `IDataReader.cs`, `IDbCommand.cs` differ -between `v8.0.20` and `v10.0.12` **only in `using`-directive order**; `DataTable.Load`'s body is -unchanged. The contract did not move. +| old runtime falsifier | .NET `net8.0`, Npgsql `10.0.3`, Microsoft.Data.Sqlite `10.0.12`, PostgreSQL 16 | `h28/falsifier/w28.csproj`, `h28/falsifier/falsifier.out` | +| BCL | `dotnet/runtime` `v8.0.20` and `v10.0.12` | pinned excerpts under `evidence/runtime-*` | +| Npgsql | tag `v10.0.3`; project targets `net8.0`, `net9.0`, `net10.0`; current source snapshot `main` | `evidence/npgsql-v10.0.3-target-frameworks.txt`, `evidence/npgsql-10.0.3-*`, `evidence/npgsql-main-*` | +| SQLite provider | `dotnet/efcore` tag `v10.0.12` | `evidence/mssqlite-*` | +| SQLite package TFM | `Microsoft.Data.Sqlite` package project targets `netstandard2.0`; its Core project targets `$(NetMinimum);netstandard2.0`, with EF Core 10's default .NET target `net10.0`. A `net8.0` falsifier selects the compatible `netstandard2.0` assets; .NET 10 is not required for this direct package use. | `evidence/mssqlite-v10.0.12-Microsoft.Data.Sqlite-target.txt`, `evidence/mssqlite-v10.0.12-Core-targets.txt`, `evidence/efcore-v10.0.12-default-net-target.txt` | +| SQLite native wrapper | `SQLitePCL.raw` tag `v2.1.12` (the version declared by EF Core 10.0.12) | `evidence/efcore-v10.0.12-SQLitePCLRawVersion.txt`, `evidence/sqlitepclraw-v2.1.12-*` | +| Own.NET | `main` @ `e889f8b`, extractor TFM `net8.0` | `frontend/roslyn/OwnSharp.Extractor/OwnSharp.Extractor.csproj` | + +The cited BCL contracts and `DataTable.Load` body were compared at both runtime refs. The old +H-28 runtime falsifier only exercised the single-result-set path; the multi-result conditional +is source evidence, not a measurement from that run. --- -## 4. Ownership semantics and the BCL contract +## 4. Ownership semantics and BCL contract -| object | created by | owned by | what ends its logical lifetime | what it can hold | does its `Dispose` free another object? | +| object | created by | owner after creation | logical-lifetime operation | possible held state | does its disposal free another object? | |---|---|---|---|---|---| -| `DbConnection` | caller / pool / DI | caller | `Close()`/`Dispose()` | the connector: socket, buffers, server session | yes — Npgsql's connection close also closes its current reader | -| `DbCommand` | `connection.CreateCommand()` | **caller**, nothing else takes it | `Dispose()`, or never | **provider-dependent** (§5) | **provider-dependent** — Sqlite: its reader + native statements; Npgsql: nothing | -| `DbDataReader` | `command.ExecuteReader()` | **caller** | `Close()` or `Dispose()` | connector binding, a pooled `ColumnInfo[]`, and (Npgsql) the command's `Activity` | no — but it discharges one field **of the command** (§5.2) | -| `DataTable` | `new DataTable()` | caller | nothing (dispose-optional) | managed rows only | n/a — `Load` never touches the command | - -**Guaranteed by the API contract.** `IDbCommand : IDisposable`, `IDataReader : IDisposable` with -a separate `Close()` — the interface guarantees `Dispose` *exists*, not what it frees -[`evidence/runtime-v10.0.12-IDbCommand.txt`]. `DbCommand : Component, IDbCommand, -IAsyncDisposable` and **does not override `Dispose` at all**; its only disposal member is -`DisposeAsync() { Dispose(); return default; }`. So BCL-level `DbCommand.Dispose()` *is* -`Component.Dispose()` and **all** command resource semantics are the provider's -[`evidence/runtime-v10.0.12-DbCommand-class.txt`, `evidence/runtime-v10.0.12-DbCommand-DisposeAsync.txt`]. -`DbDataReader.Close()` is `public virtual void Close() { }` — empty — and `Dispose()` → -`Dispose(true)` → `Close()`, so for a reader the two run the *same* provider cleanup +| `DbConnection` | caller / pool / DI | caller | `Close()` / `Dispose()` | provider connector, socket, buffers, server session | provider-specific; Npgsql connection shutdown also closes its current reader | +| `DbCommand` | `connection.CreateCommand()` | caller, absent explicit transfer | `Dispose()` ends command use; provider effects vary | provider-specific | provider-specific; see §5 | +| `DbDataReader` | `command.ExecuteReader()` | caller, absent explicit transfer | `Close()` or `Dispose()` | connector binding, provider buffers; Npgsql also stores an `Activity` on its command | not in the BCL contract; provider code may clean up reader-owned or associated state | +| `DataTable` | caller via `new` | caller | dispose-optional in Own.NET's current list | managed rows | `Load` has no command reference | + +### Contract versus implementation + +**BCL contract:** `IDbCommand : IDisposable`; `IDataReader : IDisposable` with a distinct +`Close()` method. Those interfaces guarantee the members, not what a provider releases +[`evidence/runtime-v10.0.12-IDbCommand.txt`]. `DbCommand : Component, IDbCommand, IAsyncDisposable` +and does not override `Dispose`; its `DisposeAsync()` calls `Dispose()` +[`evidence/runtime-v10.0.12-DbCommand-class.txt`, +`evidence/runtime-v10.0.12-DbCommand-DisposeAsync.txt`]. The actual meaning of command disposal +therefore depends on the provider. `DbDataReader.Close()` is an empty virtual default and its +base `Dispose()` calls `Close()`; provider implementations define the cleanup [`evidence/runtime-v10.0.12-DbDataReader-Close-Dispose.txt`]. -**What `System.Data` currently does** (an implementation fact, not a contract) — -`DataTable.Load(IDataReader, LoadOption, FillErrorEventHandler?)`, `v10.0.12` line 4974, -identical logic at `v8.0.20` line 4969 [`evidence/runtime-v10.0.12-DataTable-Load.txt`]: +**Current `System.Data` implementation, not a universal interface guarantee:** +`DataTable.Load` delegates to `FillFromReader`, then: ```csharp -adapter.FillFromReader(new DataTable[] { this }, reader, 0, 0); if (!reader.IsClosed && !reader.NextResult()) { reader.Close(); } ``` -Three consequences, all load-bearing: (1) it calls **`Close()`, not `Dispose()`**; (2) it closes -**conditionally** — on a multi-result-set reader `NextResult()` is `true` and the reader is -**left open**, which is exactly the witnessed label `RELEASE_IF_LAST_RESULT_SET` and a case the -original falsifier never exercised; (3) `Load` **never touches the command** — it holds no -reference to it, so nothing about `Load` can discharge a command obligation. +— `dotnet/runtime` `v10.0.12`, `DataTable.cs:4974`; the corresponding code is also present at +`v8.0.20` [`evidence/runtime-v10.0.12-DataTable-Load.txt`, +`evidence/runtime-v8.0.20-DataTable-Load.txt`]. It calls **`Close()`, not `Dispose()`**; on a +multi-result reader it may leave the reader open when `NextResult()` returns true; and it does +not reference or dispose the command. The original H-28 test measured only the one-result case. --- -## 5. What the providers actually do - -### 5.1 Npgsql — `NpgsqlCommand.Dispose()` releases nothing - -`v10.0.3` line 1712 [`evidence/npgsql-10.0.3-NpgsqlCommand-Dispose.txt`], unchanged at `main` -[`evidence/npgsql-main-NpgsqlCommand-Dispose.txt`]: - -```csharp -protected override void Dispose(bool disposing) -{ - ResetTransaction(); // _transaction = null (managed field) - State = CommandState.Disposed; // managed int flag - if (IsCacheable && InternalConnection is not null && InternalConnection.CachedCommand is null) - { - Reset(); // recycle into the connection's one-slot command cache - InternalConnection.CachedCommand = this; - return; - } - IsCacheable = false; -} -``` - -* `Reset()` clears `_commandText`, `CommandType`, `_parameters`, `_timeout` and two flags — - **managed only**; no connector, buffer or prepared statement [`evidence/npgsql-10.0.3-NpgsqlCommand-Reset.txt`]. -* **No finalizer** (no `~NpgsqlCommand` in the file) and both public constructors call - `GC.SuppressFinalize(this)` [`evidence/npgsql-10.0.3-NpgsqlCommand-ctor-SuppressFinalize.txt`] — an undisposed - command leaves **nothing on the finalizer queue**. -* `connection.CreateCommand()` returns `CreateCachedCommand(this)` ⇒ `IsCacheable = true` - [`evidence/npgsql-10.0.3-NpgsqlConnection-CreateCommand.txt`, `evidence/npgsql-10.0.3-NpgsqlCommand-CreateCachedCommand.txt`], - so the cacheable branch is the live one for user code: the **only** observable effect of - `Dispose()` is recycling the command into the connection's single `CachedCommand` slot. -* Server-side prepared statements belong to the **connector** - (`NpgsqlConnector.PreparedStatementManager`, created in its constructor, cleared via - `ClearAll()` from `NpgsqlConnector`) — never from `NpgsqlCommand`, which holds only - `_connectorPreparedOn` to notice preparation on a *different* connector - [`evidence/npgsql-10.0.3-NpgsqlConnector-PreparedStatementManager.txt`]. - **`command.Dispose()` issues no `DEALLOCATE`.** - -### 5.2 Npgsql — the reader holds a cleanup duty over one of the *command's* fields - -`ExecuteReader` calls `TraceCommandStart`, which — **only if `NpgsqlActivitySource.IsEnabled`, -i.e. `Source.HasListeners()`** — stores a `System.Diagnostics.Activity` in -`command.CurrentActivity` [`evidence/npgsql-10.0.3-NpgsqlCommand-TraceCommandStart.txt`, -`evidence/npgsql-10.0.3-NpgsqlActivitySource-IsEnabled.txt`]; the source is named `"Npgsql"`, -which is what falsifier probe P4 must match or the arm stays inert -[`evidence/npgsql-10.0.3-NpgsqlActivitySource-SourceName.txt`]). `Activity` **is** `IDisposable` in -.NET 8 and 10, and *"Dispose will stop the Activity if it is already started and notify any -event listeners"* [`evidence/runtime-v8.0.20-Activity-IDisposable.txt`, -`evidence/runtime-v8.0.20-Activity-Dispose.txt`]. - -The only stopper is `NpgsqlCommand.TraceCommandStop()` -[`evidence/npgsql-10.0.3-NpgsqlCommand-TraceCommandStop.txt`], and a **repo-wide** grep of `npgsql@v10.0.3` finds -exactly two call sites — both in the reader's `Dispose`: - -``` -src/Npgsql/NpgsqlDataReader.cs:1030 Command.TraceCommandStop(); // Dispose(bool) -src/Npgsql/NpgsqlDataReader.cs:1058 Command.TraceCommandStop(); // DisposeAsync() -``` - -Neither `NpgsqlDataReader.Close()` (`isDisposing: false` -[`evidence/npgsql-10.0.3-NpgsqlDataReader-Close-public.txt`]) nor `NpgsqlCommand.Dispose()` calls it; inside -`Cleanup`, `isDisposing` gates only `State = ReaderState.Disposed` -[`evidence/npgsql-10.0.3-NpgsqlDataReader-Cleanup.txt`]. So in this family, **with a tracing -listener attached**: the span never ends (an OTel batch exporter exports on end, so it is never -exported), `Activity.Current` is not popped, disposing the *command* would not help, and a -second `ExecuteReader()` on the same command hits `Debug.Assert(CurrentActivity is null)` and — -in release — orphans the previous Activity. - -`Cleanup` also shows what the reader genuinely releases on **Close**, not only Dispose: it -returns its pooled `ColumnInfo[]` to `ArrayPool.Shared`, sets -`Connector.CurrentReader = null`, calls `Connector.EndUserAction()`, and sets -**`Command.State = CommandState.Idle`** — which is what makes the command legally *reusable* -after the reader closes. That is not ownership of the command. - -### 5.3 Cross-provider — the falsifier for "Dispose is always optional" - -| provider | `command.Dispose()` body | releases a resource? | owns its reader? | finalizer? | -|---|---|---|---|---| -| **Npgsql** 10.0.3 / main | `ResetTransaction(); State=Disposed;` + optional recycle | **no** | no | no (`GC.SuppressFinalize`) | -| **Microsoft.Data.Sqlite** | `DisposePreparedStatements(disposing); _connection?.RemoveCommand(this); base.Dispose(disposing);` | **YES — native `sqlite3_stmt`** | **YES** (`DataReader.Dispose()`) | n/a — the SafeHandle below has one | -| **Microsoft.Data.SqlClient** | `_cachedMetaData = null; CachedAsyncState?.ResetAsyncState(); base.Dispose(disposing);` | **no** (despite its `// Release unmanaged objects` comment) | no | no | -| **MySqlConnector** | `m_isDisposed = true; base.Dispose(disposing);` | **no** | no | no (`GC.SuppressFinalize`) | - -[`evidence/npgsql-10.0.3-NpgsqlCommand-Dispose.txt`, `mssqlite-SqliteCommand-Dispose.txt`, -`sqlclient-SqlCommand-Dispose.txt`, `mysqlconnector-MySqlCommand-Dispose.txt`] - -The SQLite arm: `ExecuteReader` → `GetStatements()` → for an unprepared command, -`PrepareAndEnumerateStatements()`, an iterator calling `sqlite3_prepare_v2` that adds every -native statement to the command's `_preparedStatements` -[`evidence/mssqlite-SqliteCommand-PrepareAndEnumerate-head.txt`, -`evidence/mssqlite-SqliteCommand-preparedStatements-field.txt`] — so a **plain** `ExecuteReader()` -puts native handles on the **command**. `SqliteDataReader.Close()` → `Dispose(true)` disposes -only `_stmtEnumerator` and nulls `_command.DataReader`; it does **not** finalize the statements, -which live on the command [`evidence/mssqlite-SqliteDataReader-Close-Dispose.txt`]. And -`sqlite3_stmt : SafeHandle` with `ownsHandle: true` and -`ReleaseHandle() => raw.internal_sqlite3_finalize(handle)` -[`evidence/sqlitepclraw-sqlite3_stmt-SafeHandle.txt`] — `SafeHandle` carries a critical -finalizer, so without `Dispose` the `sqlite3_finalize` is deferred to non-deterministic -finalization. ⇒ **`command.Dispose()` is the only deterministic releaser of those handles in -this family**; variants B and C genuinely defer a native release to the GC. - -Two intermediate hypotheses of my own were refuted and are recorded so they are not re-derived: -*"SQLite accumulates statements across executions"* — false, `PrepareAndEnumerateStatements()` -opens with `DisposePreparedStatements(disposing: false)`; *"non-disposal extends the command's -lifetime via the connection"* — false, `SqliteConnection._commands` is a -`List>` [`evidence/mssqlite-SqliteConnection-commands-weakrefs.txt`]. +## 5. Provider source observations (not yet runtime conclusions) + +### 5.1 Npgsql `Dispose`: narrow claim only + +In Npgsql `v10.0.3`, `NpgsqlCommand.Dispose(bool)` calls `ResetTransaction()`, sets +`CommandState.Disposed`, and, for a cacheable command with an empty connection cache, calls +`Reset()` and stores itself in `InternalConnection.CachedCommand`; otherwise it clears +`IsCacheable` [`evidence/npgsql-10.0.3-NpgsqlCommand-Dispose.txt`]. `Reset()` clears command text, +command type, parameters, timeout and flags [`evidence/npgsql-10.0.3-NpgsqlCommand-Reset.txt`]. +`CreateCommand()` returns a command constructed with `IsCacheable=true` +[`evidence/npgsql-10.0.3-NpgsqlConnection-CreateCommand.txt`, +`evidence/npgsql-10.0.3-NpgsqlCommand-CreateCachedCommand.txt`]. + +Therefore **“Npgsql `Dispose` releases nothing” was too broad and is withdrawn**. It has an +observable effect on command state and can recycle the object into the connection's one-slot +cache. The source body shows no explicit release of a native handle or `DEALLOCATE` of a +server-side prepared statement. That does **not** prove a command is contractually dispose-optional, +that no other state is affected, or that `OWN001` is a false positive. + +The provider source has no `~NpgsqlCommand` declaration and its public constructors call +`GC.SuppressFinalize(this)` [`evidence/npgsql-10.0.3-NpgsqlCommand-ctor-SuppressFinalize.txt`; +full-file scan is reproducible from the pinned source]. Prepared statements are managed through +the connector's `PreparedStatementManager` +[`evidence/npgsql-10.0.3-NpgsqlConnector-PreparedStatementManager.txt`]. **Connection/session +ownership does not logically prevent a command from issuing `DEALLOCATE`**; the relevant +falsifier is instead P5, which directly compares `pg_prepared_statements` before and after +`NpgsqlCommand.Dispose()` in the same PostgreSQL session. That provider runtime test is pending. + +### 5.2 Npgsql reader `Close` versus `Dispose`: Activity hypothesis + +When an Npgsql activity listener exists, `NpgsqlCommand.TraceCommandStart` stores the command +activity in `CurrentActivity` [`evidence/npgsql-10.0.3-NpgsqlCommand-TraceCommandStart.txt`, +`evidence/npgsql-10.0.3-NpgsqlActivitySource-IsEnabled.txt`, +`evidence/npgsql-10.0.3-NpgsqlActivitySource-SourceName.txt`]. `Activity` is disposable, and its +BCL `Dispose()` stops it [`evidence/runtime-v8.0.20-Activity-IDisposable.txt`, +`evidence/runtime-v8.0.20-Activity-Dispose.txt`]. In the pinned Npgsql source, reader `Dispose` and `DisposeAsync` invoke `TraceCommandStop`; public +reader `Close()` routes through the close path with `isDisposing:false` and does not invoke the +reader-dispose trace-stop path [`evidence/npgsql-10.0.3-NpgsqlDataReader-Dispose.txt`, +`evidence/npgsql-10.0.3-NpgsqlDataReader-DisposeAsync.txt`, +`evidence/npgsql-10.0.3-NpgsqlDataReader-Close-public.txt`, +`evidence/npgsql-10.0.3-NpgsqlCommand-TraceCommandStop.txt`]. + +This yields a **source-derived prediction, not yet a measured leak**: `DataTable.Load` calls +`reader.Close()`; if that closes the Npgsql reader without the `Dispose` path, an attached +listener may observe the command Activity remain started. The new P4 isolates one query per +variant and records immediately after `Load`, after `reader.Dispose()`, and after +`command.Dispose()`; no P2/P3 query is allowed between those snapshots. The Npgsql `ActivitySource` +is named `"Npgsql"`, which the listener must match. A failed P4 kills this hypothesis. + +The same reader cleanup sets `Command.State = CommandState.Idle`, ends the connector user action, +clears `CurrentReader` and returns a pooled `ColumnInfo[]` +[`evidence/npgsql-10.0.3-NpgsqlDataReader-Cleanup.txt`]. `Idle` is evidence of command reuse, +not evidence that the reader owns or disposes the command. + +### 5.3 Microsoft.Data.Sqlite: direct statement-count hypothesis + +The cited provider version is pinned to the falsifier's **Microsoft.Data.Sqlite 10.0.12**. +`SqliteCommand` holds prepared `sqlite3_stmt` objects in `_preparedStatements`; an ordinary +`ExecuteReader()` uses `PrepareAndEnumerateStatements()`, which prepares a native statement and +first disposes any previous prepared set [`evidence/mssqlite-SqliteCommand-preparedStatements-field.txt`, +`evidence/mssqlite-SqliteCommand-PrepareAndEnumerate-head.txt`]. `SqliteCommand.Dispose(bool)` +calls `DisposePreparedStatements` and removes the command from the connection +[`evidence/mssqlite-SqliteCommand-Dispose.txt`, +`evidence/mssqlite-SqliteCommand-DisposePreparedStatements.txt`]. The reader's `Close()` delegates +to its reader disposal; that clears the command's `DataReader` and disposes the statement +**enumerator**, not the command's `_preparedStatements` +[`evidence/mssqlite-SqliteDataReader-Close-Dispose.txt`]. + +`sqlite3_stmt` is an owning `SafeHandle`; its `ReleaseHandle()` calls +`sqlite3_finalize` [`evidence/sqlitepclraw-sqlite3_stmt-SafeHandle.txt`], and the pinned BCL +`SafeHandle` has a finalizer [`evidence/runtime-v8.0.20-SafeHandle-finalizer.txt`, +`evidence/runtime-v10.0.12-SafeHandle-finalizer.txt`]. Thus source +predicts that command disposal is the **deterministic release path** for those command-held +handles; without it, SafeHandle finalization is a later GC fallback. This matters to a resource-lifetime +analysis, but does not turn source reading into a runtime measurement. + +G2 now observes the resource directly using public `SqliteConnection.Handle` +[`evidence/mssqlite-SqliteConnection-Handle.txt`] and +`SQLitePCL.raw.sqlite3_next_stmt` [`evidence/efcore-v10.0.12-SQLitePCLRawVersion.txt`, +`evidence/sqlitepclraw-v2.1.12-sqlite3_next_stmt.txt`]. SQLitePCLRaw v2 disables its managed +pointer-to-`SafeHandle` lookup by default, so the harness calls +`db.enable_sqlite3_next_stmt(true)` immediately after opening the connection; +`sqlite3_stmt.From` registers each resulting handle, and `find_stmt` maps each enumerated pointer +back to that handle [`evidence/sqlitepclraw-v2.1.12-enable-next-stmt.txt`, +`evidence/sqlitepclraw-v2.1.12-find-stmt-enabled.txt`, +`evidence/sqlitepclraw-sqlite3_stmt-SafeHandle.txt`]. It neither samples RSS nor forces GC. +Expected differences from the direct pre-query baseline: 0 at baseline; +1 after `ExecuteReader`; +still +1 after `DataTable.Load`/reader close and after `reader.Dispose`; back to 0 after +`command.Dispose`. A second execution on the **same command** must replace the old statement +(count stays at baseline + 1), not accumulate to baseline + 2. These are assertions in the +executable harness, not results yet. + +The historical side-claim that SqliteConnection strongly retains commands is not used: the +pinned 10.0.12 source shows a `List>` +[`evidence/mssqlite-SqliteConnection-commands-weakrefs.txt`]. + +### 5.4 Other provider snapshots are ancillary + +The artifacts also retain current-source snapshots for Microsoft.Data.SqlClient and +MySqlConnector. They are on moving repository refs and are not runtime-tested here; they are +context only, not the basis of this report's verdict. No universal conclusion is drawn from them. --- -## 6. Runtime observations - -**Executed here.** PostgreSQL **16.2** was started via `pgserver` 0.1.4 (self-contained PyPI -wheel, Unix socket) — the harness class the original H-28 falsifier used — and queried with its -bundled `psql` to pin the server-side half independently of any source reading: - -| observation | result | -|---|---| -| `PREPARE p1(int) AS SELECT $1`, then `pg_prepared_statements` in the same session | listed | -| same query from a **new** session | `0` — per-backend | -| `DECLARE c1 CURSOR …`, then `pg_cursors` | listed; gone with the session | - -⇒ server-side prepared state and cursors are **session-scoped**, bound to the *connection* — -which is why no command-level `Dispose` can be their releaser. This corroborates §5.1. - -**Not executed here: the .NET half.** There is no .NET SDK in this sandbox and no reachable -NuGet feed — `api.nuget.org`, `nuget.org`, `nuget.pkg.github.com`, `dotnet.microsoft.com`, -`builds.dotnet.microsoft.com`, `pkgs.dev.azure.com`, `objects.githubusercontent.com`, -`deb.debian.org` and `cache.nixos.org` are all unreachable; the ~909k-entry PyPI simple index and -the npm registry were searched and publish no modern .NET runtime (npm's `node-api-dotnet` ships -managed shims without `coreclr`). `run.sh runtime` prints `SKIP-WHY` and exits 0 rather than -pretending. The falsifier is committed **ready to run** -(`falsifier/`, pinned to net8.0 / Npgsql 10.0.3 / MS.Data.Sqlite 10.0.12 so its output compares -directly with the original `falsifier.out`); every probe exists to separate two hypotheses: - -| probe | separates | -|---|---| -| P1 `reader.IsClosed` after `Load` | reproduces H-28 F1 (`RELEASE` vs `BORROW`) | -| P2 command reusable after the reader closed | "the reader bounds the command's lifetime" vs "leaves it `Idle`" (§5.2 predicts OK) | -| P3 second command on the same connection | protocol leak vs object leak | -| **P4 `ActivityListener` started/stopped counts** | **the §5.2 claim**: `stopped=0` for B and D, `stopped=1` for C and A | -| P5 `pg_prepared_statements` before/after `command.Dispose()` | whether command disposal releases server-side state (§5.1 predicts no change) | -| P6 RSS delta over N iterations, ± `command.Dispose()` | the §5.3 SQLite native-retention arm | - -P4 matters most: §5.2 is the only load-bearing claim resting on `Activity` lifecycle semantics -rather than on a plainly readable `Dispose` body. P4 is **Npgsql-only** — Microsoft.Data.Sqlite -has no `ActivitySource`/`DiagnosticSource` anywhere in `SqliteCommand.cs` or `SqliteDataReader.cs` -(`grep -c 'ActivitySource\|DiagnosticSource'` over the fetched files at `dotnet/efcore@main`: 0), -so `0/0` on the SQLite variants is the expected reading, not a failed probe. P6 uses process RSS, **not** -`GC.GetTotalMemory` (the resource is native), and is a delta over N iterations with an explicit -finalization control — one snapshot cannot separate retention from allocation noise. +## 6. Runtime and executable evidence status + +**This sandbox has no `dotnet` executable**, so it cannot restore/build the falsifier or run the +production Roslyn extractor. Earlier network checks also found the NuGet hosts unreachable here. +The previous attempt to query PostgreSQL 16.2 via `pgserver` only established session-scoped +server catalog behavior; it **does not prove** what Npgsql `Command.Dispose()` does and is not +used as evidence for P5. The report withdraws that earlier inference. + +**Local validation actually run:** source derivation passed for all 44 excerpts plus the pinned +Npgsql finalizer scan; `tests/test_corpus.py` passed 33/33; whole-tree `ruff check .`, `mypy` +(60 source files), Python syntax/lint checks, `bash -n`, and a C# grammar parse passed. The +pinned pgserver 0.1.4 supervisor API also started a temporary PostgreSQL server and produced the +expected Unix-socket Npgsql connection string; no .NET provider command was run. The C# +grammar parse is not a compiler result. The repository's `python tests/run_tests.py` did not +complete: its early analysis/codegen checks passed, but later history checks need a non-shallow +clone and the checkpoint test invokes `cargo`, which is not installed. This is not recorded as a +green full-suite run. Finally, `run.sh all-required` was executed in this sandbox and correctly +returned 1 because it could not run G3 or G1/G2 without the .NET 8 SDK; exact output is saved at +`evidence/required-gates-local.txt`. The manual OwnIR engine subcheck inside that command passed, +but it is not a substitute for G3. The local validation transcript, including the incomplete +repository suite's environment failure, is saved at `evidence/local-validation.txt`; the source +fetch/re-derivation transcript is `evidence/source-derivation-local.txt`. + +The executable under `falsifier/` is corrected and bounded: + +| gate / probe | direct observation | falsifier | +|---|---|---| +| **G1** compile | `dotnet build` of the runtime harness; the build also compiles `fx/CommandDispose.cs` | a compile error means the harness is not runnable | +| **P1–P3** lifecycle controls | reader closed after `Load`; command reuse; next command on same connection | separates reader/command logical usability and protocol busy state | +| **P4** Npgsql Activity | one `ExecuteReader` + one `Load`; snapshots at the three lifecycle points; no interposed query | no started/stopped difference at the direct snapshots kills the Activity claim | +| **G2 / P6** SQLite native state | `sqlite3_next_stmt` before/after execute, Load, reader Dispose, command Dispose, and same-command re-execution | no count change at command Dispose or reader-only release of the statement falsifies the source prediction | +| **P5** PostgreSQL prepared state | same-session `pg_prepared_statements` before `Prepare`, after, and after `command.Dispose()` | unchanged count across Dispose supports no server-statement release; a decrease refutes it | +| **G3** Own.NET end-to-end | production extractor → emitted OwnIR → solved summaries → core SARIF | any finding/anchor mismatch refutes the source-derived prediction below | + +`run.sh` no longer curls NuGet as a proxy for dependency availability: it tries `dotnet restore +--ignore-failed-sources`, allowing a warm package cache to work offline. Results are explicit: +`PASS`, `FAIL`, or `SKIP`. `run.sh all` is a convenient local run; **`run.sh all-required` converts +any SKIP into a non-zero failure**. The new +`.github/workflows/h28-command-falsifier.yml` runs `all-required` on this PR and preserves the +facts, summaries, SARIF and runtime logs as an artifact. This sandbox run remains pending until +that workflow supplies real G1–G3 results. --- -## 7. What current Own.NET does - -### 7.1 Extraction (source-derived — the extractor was not runnable here) +## 7. Current Own.NET behavior: predicted, not end-to-end confirmed -Line numbers are `frontend/roslyn/OwnSharp.Extractor/Program.cs` @ `e889f8b`. +`OWN001` means *"an owned resource is acquired but not released on every path … a possible +leak"* (`ownlang/diagnostics.py`, OWN001 catalogue entry). It does **not** assert an OS-handle +leak, nor does this investigation redefine the diagnostic as one. An error-severity finding is +not by itself proof that the provider's Dispose is operationally necessary. -1. **`command` becomes a tracked owned local.** `IsOwningFactory` (L4832) recognises the ADO.NET - tranche by method name **plus both resolved types implementing the `System.Data` interfaces**: - `CreateCommand` with `IDbConnection`→`IDbCommand` (L4880-4882), `ExecuteReader` - `IDbCommand`→`IDataReader` (L4877-4879), `BeginTransaction` (L4883-4885). The flow pass adds - the local to `candidates` (L7035-7036). -2. **No exemption applies.** `IsDisposeOptional` (L2593-2606) is a closed list — `Task`/`ValueTask`, - `DataTable`/`DataSet`/`DataView`, `StringWriter`/`StringReader`; `DbCommand` is absent. - `HasEmptyDisposeBody` cannot apply either: since the #238 soundness gate it is confined to - types implementing `IEnumerator`, which `DbCommand` does not. -3. **`reader`'s obligation is silently dropped** at `table.Load(reader)`: the argument-escape - rule (L7155-7162) untracks any candidate whose identifier is an `ArgumentSyntax` unless it is - a pool buffer, a bare-statement `consumedArg`, or an adopted ctor argument of a bounded - wrapper; then `tracked.ExceptWith(escapedLocals)` (L7182). `DataTable.Load` is external, so no - carve-out applies. This is precisely the row of - [#382](https://github.com/PhysShell/Own.NET/issues/382) reading *"anything else, and the - argument is a tracked local → an escape: the local is untracked"*. -4. `table` is not a candidate (dispose-optional), and `Close`/`Dispose`/`DisposeAsync` are - credited as releases (e.g. L3476, L4143) — which is what makes fixture variant F differ from B. +Source reading of `frontend/roslyn/OwnSharp.Extractor/Program.cs` at `e889f8b` predicts: -**Predicted verdict:** `OWN001` (error severity, non-zero exit) on **`command`**, silence on -**`reader`**. +1. `IsOwningFactory` (L4832, ADO.NET tranche L4877–4885) recognizes `IDbConnection.CreateCommand` + returning `IDbCommand` and `IDbCommand.ExecuteReader` returning `IDataReader`. The local-flow + pass adds these results to candidates (L7035–7036). +2. `IsDisposeOptional` (L2593–2606) exempts `DataTable`, not `DbCommand`. +3. The flow pass's argument escape adds a tracked local passed to an unrecognized external call + to `escapedLocals` (L7155–7162), then removes it from tracked locals (L7182). `DataTable.Load` + has no current summarized effect. This predicts `reader` is dropped at `table.Load(reader)`. +4. The predicted C# result is OWN001 on the undisposed `command`, silence on `reader` in B. + Variants A/D/G should be clean; E should report both command and reader. -### 7.2 Engine (executed) +The engine-only fixture `fx/CommandDispose.own` was run and reproduces its **manual reduction** +(three expected OWN001s: command in B and E; reader in E; A/D clean). This validates the core on +handwritten OwnIR-like operations **only**. It is not evidence that the production C# extractor +emits those operations. `scripts/verify_g3.py` now performs and asserts the missing production +C# → facts → summaries → SARIF path, writing each intermediate artifact under `evidence/`. +Its actual result is pending G3. -`fx/CommandDispose.own` is a hand reduction of the same family — the honest frame -`corpus/real-world/README.md` states: a model, not C# the checker read. - -``` -$ corpus/ownership-lab/h28-command/run.sh engine -…:42:3: error: [OWN001] 'command' is owned but not released at end of function … -…:69:32: error: [OWN001] 'command' is owned but not released at end of function … -…:69:7: error: [OWN001] 'reader' is owned but not released at end of function … -3 errors. # exit 1 -PREDICTION HOLDS: 2x OWN001 'command' (B, E) + 1x OWN001 'reader' (E), A/D clean. -``` -[`evidence/engine-CommandDispose.txt`] - -Given the obligation the core is correct: B reports the command, A and D are clean, and E (no -`Load`, so no escape) reports **both** — which separates the escape rule from the acquire rule -without reading the extractor. - -### 7.3 Where the model diverges from the runtime semantics - -* **The acquire rule is provider-blind.** `IsOwningFactory` matches `System.Data` *interface* - types precisely so it covers every provider — stated as a feature in its own comment. But §5.3 - shows `Dispose`'s effect is not uniform: real native release on Microsoft.Data.Sqlite, nothing - on the other three. `IsDisposeOptional` is the only channel for "disposing frees nothing", and - it is keyed on **namespace + type name** with no provider dimension and no implementation - input. Nothing in the model can carry "Dispose-effect: none" for Npgsql and "Dispose-effect: - native statements" for SQLite. -* **The escape drop is accidentally right, and only sometimes.** For a single-result-set reader, - dropping `reader` agrees with the witnessed `RELEASE_IF_LAST_RESULT_SET`. For a - **multi-result-set** reader `Load` leaves it **open** (§4.1) — there the drop is a false - negative. Own.NET is silent in both cases and cannot distinguish them, because the call fact - never reaches the core. -* **On Npgsql the finding is inverted.** With tracing enabled the object whose disposal is - load-bearing is the **reader** (§5.2) — the one Own.NET is silent about — while the - error-severity `OWN001` falls on the command, whose `Dispose` provably frees nothing (§5.1). -* **Coverage.** No committed `.facts.json` anywhere (main *or* the research branch) contains - `CreateCommand`, `DbCommand` or `DbDataReader`, and - `corpus/real-world/ado-executereader-leak/before.cs` deliberately makes the command a *borrowed - parameter* ("the only leak is `reader`"). The never-disposed-`CreateCommand()` shape has never - been through Own.NET end-to-end; `fx/CommandDispose.cs` is its first fixture. +The #382 connection is consequently **a concrete candidate witness, not yet experimentally +established**: issue #382 describes this argument-lowering loss, but until G3 reproduces the +predicted dropped-reader/command finding pair, this report does not claim a proven model gap. --- -## 8. Falsifiers +## 8. Review corrections and falsifiers -| if it seems that… | falsifier attempted | result | +| review concern | correction in this revision | status here | |---|---|---| -| the command **must** be disposed | find a provider where non-disposal is provably harmless | **found**: Npgsql (§5.1), also SqlClient and MySqlConnector (§5.3) | -| disposing the command is **optional** | find a provider/path leaving an observable resource | **found**: Microsoft.Data.Sqlite — native `sqlite3_stmt` SafeHandles on the command, unreleased by reader `Close`/`Dispose`, deterministically released only by `command.Dispose()` (§5.3) | -| the **reader owns the command** | prove or refute from implementation | **refuted as ownership**: `Cleanup` sets `Command.State = Idle` — it makes the command *reusable*. A partial cleanup duty over exactly one field (`CurrentActivity`, via `reader.Dispose` → `TraceCommandStop`) is not ownership, and `Close()` does not discharge it (§5.2) | -| `DataTable.Load` discharges the command | read `Load` | **refuted**: no reference to the command (§4.1) | -| `Load` always closes the reader | read the condition | **refuted as universal**: `!reader.NextResult()` — a multi-result-set reader stays **open** (§4.1) | -| undisposed commands accumulate SQLite statements | read `PrepareAndEnumerateStatements` | **refuted**: opens with `DisposePreparedStatements(disposing: false)` (§5.3) | -| non-disposal extends command lifetime via the connection | read `SqliteConnection._commands` | **refuted**: weak references (§5.3) | -| **Own.NET is simply wrong** | localise source → facts → verdict | **partly refuted**: extraction is faithful and the core is correct given the facts (§7.2). The defect is not in a stage — it is the missing provider dimension on the acquire plus the escape rule discarding the one call fact that decides the reader (§7.3) | -| the question is obsolete | — | **refuted**: the residual was recorded but never classified (§2), and the answer changes an engineering decision (§9) | - -**Not falsified but unproven at runtime:** §5.2's Activity consequence and §5.3's native -retention. Both are source-derived and marked PREDICTED; P4 and P6 exist to settle them (§6). +| P4 observed Activity after P2/P3 had executed extra SQL | P4 is a separate helper: one query, snapshots immediately after Load / reader Dispose / command Dispose. P1–P3 run without an ActivityListener. | implemented; .NET run pending | +| P6 forced GC before RSS and measured allocator-dependent memory | replaced by direct `sqlite3_next_stmt` live-handle enumeration through `SqliteConnection.Handle`; no RSS, no GC; includes same-command re-execution control | implemented; .NET run pending | +| `NpgsqlConnection.ExecuteScalarAsync` probably does not compile | query now uses `CreateCommand()` + `ExecuteScalar()`; G1 builds both harness and C# fixture | fixed in source; build pending | +| PostgreSQL session scope does not imply command cannot send DEALLOCATE | removed that inference; P5 directly tests the actual Npgsql command before/after Dispose | corrected; P5 pending | +| “Dispose releases nothing” ignores cache/state effects | narrowed to “no explicit native-handle or server `DEALLOCATE` release is visible in this Dispose body”; records the state transition/cache effect | corrected | +| OWN001 is a possible-leak diagnostic, not an OS-handle oracle | states its repository wording; no false-positive conclusion from source inspection alone | corrected | +| “finding is inverted” was too categorical without G3 | withdrawn; current wording is a source-derived prediction, pending real extraction and runtime tracing | corrected | +| NuGet curl blocks a warm package cache; optional SKIP can be green | restore is attempted with `--ignore-failed-sources`; `all-required` fails on any SKIP and is the PR workflow gate | implemented; workflow pending | + +The SQLite, Npgsql and BCL observations above were attacked with provider-source counterchecks; +none substitutes for G1–G3. If those gates disagree, the report and verdict must change. --- ## 9. Verdict -# H28-PROVIDER-SPECIFIC - -> **The absence of `Dispose()` on a `DbCommand` in the `DataTable.Load(reader)` family is not one -> fact. It is a real resource/lifetime defect on Microsoft.Data.Sqlite and releases nothing on -> Npgsql, Microsoft.Data.SqlClient and MySqlConnector. No provider-independent ownership rule can -> classify it, because the effect of `DbCommand.Dispose()` is entirely a provider implementation -> detail — the BCL declares no `Dispose` on `DbCommand` at all.** - -1. The old residual *"an object leak without a protocol consequence"* is **true for Npgsql, false - for Microsoft.Data.Sqlite**. It was written from a two-provider falsifier whose SQLite arm - never looked at the command. -2. Own.NET's `OWN001` on the undisposed command is **sound for Microsoft.Data.Sqlite** and - **unsupported for Npgsql**, where it is an error-severity, build-failing finding about an - object whose `Dispose` provably frees nothing. -3. That is **not** an argument for exempting `DbCommand`: the analyzer cannot tell the two apart - where the obligation is minted, and the SQLite case is a genuine native-handle deferral. - Suppressing `DbCommand` to remove the Npgsql false positive would delete a true positive - elsewhere. This is the decision the note exists to enable. -4. The sharper defect in this family is **not** the command. It is that `reader` — load-bearing on - Npgsql (§5.2) and a real leak on a multi-result-set reader (§4.1) — is dropped by the escape - rule with no callee fact recorded. On Npgsql with tracing on the verdict is **inverted**: - error on the harmless object, silence on the harmful one. - -**Pivot record.** The investigation moved from "is the command a leak?" to "what is the ownership -relation `connection → command → reader`?", because the command question resolved to a provider -table while the *relation* is where a reproducible, ownership-semantic, practically consequential -witness turned up (§5.2, §7.3). +# H28-INCONCLUSIVE + +Source inspection makes a provider-specific effect **plausible and testable**: Microsoft.Data.Sqlite +appears to keep native statement handles on `SqliteCommand`, while Npgsql's command Dispose body +shows command-state/cache work but no explicit native-handle or `DEALLOCATE` operation. The +Npgsql `Close`/`Dispose` Activity distinction is also a clear source-derived prediction. + +But the mandated differentiating runtime observations and current production extraction are not +executed yet. This report therefore does **not** decide whether an undisposed command in the +Npgsql target family is benign, a contract-level ownership defect, or a misleading OWN001; it +does **not** label OWN001 a false positive; and it does **not** claim a proven Own.NET model gap. +The one final verdict is **H28-INCONCLUSIVE** until G1, G2 and G3 pass or falsify the predictions. --- -## 10. Minimal next step - -A model gap is proved (§7.3), so — and only so — the smallest experiment that would close it. It -is **an instance of [#382](https://github.com/PhysShell/Own.NET/issues/382), not a parallel -architecture**: #382 already names this exact lowering and this exact loss (*"the lowering still -destroys two facts: that an interprocedural call happened, and which callee parameter received -which resource"*). The connection is demonstrable: the witnessed H-28 label -`DataTable.Load → RELEASE_IF_LAST_RESULT_SET` *is* a callee-parameter effect, which is the -vocabulary #382 proposes to stop guessing at in the frontend. `proven_call` (OwnIR v2, H1 — -`docs/notes/h1-proven-call.md`) is the landed precedent for the transport: the frontend emits a -call-shaped op, the core decides. - -| | | -|---|---| -| **Observed semantics** | `DataTable.Load(reader)` calls `reader.Close()` iff the first result set is the last, else the reader stays open; it never affects the command. `DbCommand.Dispose()`'s effect is provider-defined: nothing (Npgsql/SqlClient/MySqlConnector) or native statements + the reader (Microsoft.Data.Sqlite). | -| **Current representation** | The `Load` call is erased — `reader` joins `escapedLocals`, no `call` op (L7155-7182). The command's obligation is minted by `IsOwningFactory` (L4880) with no dispose-effect attribute; `IsDisposeOptional` (L2593) is the only "Dispose frees nothing" channel, keyed on namespace+type name. | -| **Missing fact** | (a) that an external call received the tracked reader, with the callee identity, so a witnessed-effect table can be consulted at all; (b) **conditionally** — the effect depends on `NextResult()`, which no static fact can settle. | -| **Smallest representation change** | #382's, restricted: emit a `call` op for an argument pass to an **external** callee instead of a bare escape, carrying `(callee, argument → parameter)`. No new lattice, no new diagnostic code, no provider special-casing. (b) then resolves to *may-release*, not *release* — the honest answer, needing no new vocabulary. | -| **Positive fixture** | `fx/CommandDispose.cs` variant **B** (single result set): `reader` carries a `call` fact to `DataTable.Load`, the core consults the witnessed label and reports `may-released`, leaving `OWN001` on `command` as the only finding. | -| **Negative / control fixture** | Variant **E** (no `Load`) must still report `OWN001` on **both** — proving the `call` fact did not become a blanket exemption. **A**/**D** must stay clean. | -| **Mutation / falsifier** | A multi-result-set reader passed to `Load`: `reader.IsClosed` is `false` afterwards (§4.1). Any representation recording `Load` as an unconditional release is falsified by it. Runtime probe **P4** falsifies §5.2 if `stopped=1` on variant B. | -| **Expected verdict delta** | B: `OWN001 command` unchanged, `reader` moves from *silently untracked* to *tracked-with-a-witnessed-may-release*. E: unchanged (2 findings). No finding is added or removed on this fixture — the delta is that the reader's disposition becomes a **recorded fact** instead of an erasure, which is what makes the multi-result-set false negative reachable later. | -| **Soundness risk** | Low and bounded, *provided* the label is entered as `RELEASE_IF_LAST_RESULT_SET` (conditional), never as `RELEASE`; unconditional would suppress a real leak on multi-result-set readers — a false negative worse than today's silence because it would look justified. Second risk: a `call` op for external callees widens what reaches the core, so #382's own gate applies unchanged (the H-28 `RECORD_AND_STOP` balance: ~1 true positive against ~110 sites each needing a proven effect) and must be re-checked before any default flips. | - -**Explicitly not proposed:** a TLA+ model, a P-037 change, a new summary lattice, provider-wide -Npgsql special cases, a universal ADO.NET model, a Roslyn frontend rewrite, or a new diagnostic -code. The provider dimension on `IsOwningFactory` (§7.3) is deliberately **left open**: it needs a -decision about where provider knowledge may live at all, which is larger than H-28 and should not -be settled by it. +## 10. Bounded next step — no architecture proposal + +Run the three kill-first gates in the new required workflow (or locally): + +```bash +corpus/ownership-lab/h28-command/run.sh all-required +``` + +| gate | must observe | kill / required response | +|---|---|---| +| **G1** compile + isolated P4 | build succeeds; direct snapshots show whether reader Dispose, not command Dispose, stops the Activity | compile failure means the harness is not usable; no lifecycle difference retracts the Activity claim | +| **G2** native SQLite statements | direct statement count changes at the operation predicted by source; same-command second execute does not accumulate | no statement-count effect at command Dispose retracts the claimed native-release consequence | +| **G3** production C# → OwnIR → summary → verdict | exact expected `OWN001` anchors from `verify_g3.py` | any mismatch corrects the report's Own.NET behavior; until it matches, do not claim a model gap or advance #382 from this witness | + +No new summary domain, provider registry, P-037 work, diagnostic, or analyzer rule is proposed. The +#382 relationship remains a follow-up candidate only if G3 proves the loss on this exact source. From b9a783981ea8a4afad2323b5223a142ba99b4f40 Mon Sep 17 00:00:00 2001 From: PhysShell <45852143+PhysShell@users.noreply.github.com> Date: Sat, 10 Oct 2026 11:34:28 +0000 Subject: [PATCH 3/7] docs(h28): record successful required gates Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com> --- corpus/ownership-lab/h28-command/README.txt | 30 +-- .../evidence/ci-run-38048287923.txt | 36 ++++ .../h28-command/fx/CommandDispose.cs | 45 ++-- .../h28-command/fx/CommandDispose.own | 12 +- docs/notes/h28-npgsql-command-resolution.md | 203 ++++++++++-------- 5 files changed, 194 insertions(+), 132 deletions(-) create mode 100644 corpus/ownership-lab/h28-command/evidence/ci-run-38048287923.txt diff --git a/corpus/ownership-lab/h28-command/README.txt b/corpus/ownership-lab/h28-command/README.txt index 287ad8ea..6b26a644 100644 --- a/corpus/ownership-lab/h28-command/README.txt +++ b/corpus/ownership-lab/h28-command/README.txt @@ -1,9 +1,11 @@ H-28-CMD: the DbCommand half of the `DataTable.Load(reader)` family. STATUS - Final verdict: H28-INCONCLUSIVE pending G1/G2/G3. Provider source suggests a provider-specific - difference, but the .NET runtime falsifier and production Roslyn -> OwnIR -> core path have not - run yet. Read docs/notes/h28-npgsql-command-resolution.md before interpreting any artifact. + Final verdict: H28-PROVIDER-SPECIFIC, narrowly bounded to Npgsql 10.0.3/PostgreSQL 16 and + Microsoft.Data.Sqlite 10.0.12. Required G1-G3 passed in PR workflow run #38048287923; its + `h28-command-falsifier` artifact contains runtime logs, OwnIR facts, summaries and SARIF. This + does not label OWN001 a false positive or establish a universal provider rule. Read + docs/notes/h28-npgsql-command-resolution.md before interpreting any artifact. The original H-28 (research/ownership-semantics-lab-v1@298b305, not on main) settled `DataTable.Load(reader)` for a one-result reader: it IS closed (`RELEASE_IF_LAST_RESULT_SET`, @@ -13,12 +15,12 @@ STATUS protocol consequence)" -- h28/scripts/h28_anchors_record.py, consequence_for_the_demanding_instance - This directory turns that residual into falsifiable source and runtime checks. It does not yet - call OWN001 a false positive, claim a native leak on Npgsql, or claim a proven model gap. + This directory records falsifiable source and runtime checks. It does not call OWN001 a false + positive, claim a native leak on Npgsql, or propose a universal rule from these provider runs. LAYOUT fx/CommandDispose.cs nine C# methods (A/B/C/D + controls), using abstract ADO.NET types. - The extraction expectations are PREDICTED until G3 runs. + G3 confirmed the expected finding anchors in workflow run #38048287923. fx/CommandDispose.own engine-only manual reduction. This is not C# extraction evidence. falsifier/Program.cs P1-P5 and G2/P6. P4 is isolated from P1-P3; P6 counts live SQLite statements via sqlite3_next_stmt, not RSS or forced GC. @@ -36,8 +38,8 @@ LAYOUT this net8.0 falsifier. Non-zero = drift. run.sh PASS/FAIL/SKIP driver. `all-required` fails on any SKIP. evidence/*.txt source excerpts with repository/ref/path/git blob sha/file sha256/ - line range. Engine output is saved. G3/runtime output is created - here by the required workflow if the gates run. + line range. Local engine output is saved. G3/runtime artifacts are + produced in CI and attached to workflow run #38048287923. .github/workflows/h28-command-falsifier.yml runs `all-required` on the PR and preserves results as an artifact. @@ -58,9 +60,11 @@ WHAT HAS RUN IN THE PRODUCING SANDBOX corpus cases; whole-tree Ruff; mypy (60 source files); 44 source excerpts + finalizer scan; Python/shell syntax checks, C# grammar parse (not compilation), and pgserver supervisor API/Unix-socket DSN preflight (not a .NET/provider falsifier). - BLOCKED: G1 runtime-project build, P4 Npgsql Activity probe, G2 native SQLite statement probe, - P5 Npgsql prepared-state probe, and G3 production C# extraction. No .NET SDK is present. - `all-required` ran and returned 1 on these SKIPs; see evidence/required-gates-local.txt. + PASS IN CI: G1 build/P4, G2 native SQLite statement checks, P5 PostgreSQL prepared-state check, + and G3 production C# extraction all passed in workflow run #38048287923. The exact + artifact is attached to that run; local status metadata is evidence/ci-run-38048287923.txt. + BLOCKED LOCALLY: this sandbox has no .NET SDK, so its `all-required` run returned 1 on G1/G2/G3 + SKIPs; see evidence/required-gates-local.txt. INCOMPLETE: `python tests/run_tests.py` passed its early analysis/codegen checks, then stopped at checkpoint validation because this checkout is shallow and `cargo` is absent. It is not counted as a green full-suite run. See evidence/local-validation.txt. @@ -68,5 +72,5 @@ WHAT HAS RUN IN THE PRODUCING SANDBOX SCOPE No production analyzer code, OwnIR vocabulary, ownership semantics, or diagnostics are changed. The normal real-world corpus suite does not glob `corpus/ownership-lab/`; these are experiment - fixtures. Any follow-up architecture work, including #382, waits for G3 to reproduce the - predicted extraction loss. + fixtures. G3 now confirms the predicted extraction loss on this exact fixture. Any architecture + follow-up, including #382, remains separate and is not proposed or implemented in this PR. diff --git a/corpus/ownership-lab/h28-command/evidence/ci-run-38048287923.txt b/corpus/ownership-lab/h28-command/evidence/ci-run-38048287923.txt new file mode 100644 index 00000000..010486cf --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/ci-run-38048287923.txt @@ -0,0 +1,36 @@ +# H-28 mandatory GitHub Actions gate record +# Captured from gh run view / GitHub Actions API; this is status metadata, not the per-stage log. +run: https://github.com/PhysShell/Own.NET/actions/runs/38048287923 +head: 008a21736e83139954d4ed1b34ab89fc8a194a00 (arena/e1a5511b-own-net; PR #399) +{ + "conclusion": "success", + "databaseId": 38048287923, + "event": "pull_request", + "headSha": "008a21736e83139954d4ed1b34ab89fc8a194a00", + "jobs": [ + { + "name": "H-28 required runtime + extractor gates", + "status": "completed", + "conclusion": "success", + "steps": [ + { + "name": "Install self-contained PostgreSQL 16 harness", + "conclusion": "success" + }, + { + "name": "Run mandatory G1-G3 gates (SKIP is failure)", + "conclusion": "success" + }, + { + "name": "Preserve source facts and runtime observations", + "conclusion": "success" + } + ] + } + ], + "status": "completed", + "url": "https://github.com/PhysShell/Own.NET/actions/runs/38048287923" +} +artifact: +{"expired":false,"name":"h28-command-falsifier","size_in_bytes":6235} +note: raw run-log and artifact downloads redirect to the Actions results blob service; this sandbox cannot access that host. diff --git a/corpus/ownership-lab/h28-command/fx/CommandDispose.cs b/corpus/ownership-lab/h28-command/fx/CommandDispose.cs index 10c3367e..a166708b 100644 --- a/corpus/ownership-lab/h28-command/fx/CommandDispose.cs +++ b/corpus/ownership-lab/h28-command/fx/CommandDispose.cs @@ -5,17 +5,18 @@ // IsOwningFactory matches on (frontend/roslyn/OwnSharp.Extractor/Program.cs, the ADO.NET // tranche: receiver implements IDbConnection, return implements IDbCommand). This makes it a // useful source-level fixture for the provider-agnostic acquisition rule; it does NOT run the -// same C# against each provider. Runtime/provider expectations remain predictions until G1/G2 run -// (see ../README.txt and docs/notes/h28-npgsql-command-resolution.md). +// same C# against each provider. G1/G2 runtime checks and G3 extraction have now run for the +// pinned providers; see ../README.txt and docs/notes/h28-npgsql-command-resolution.md. // -// The method name carries the expectation. Run with the flow-locals default: +// G3 checks the method-specific finding anchors. To reproduce the local production path: // dotnet ownsharp-extract.dll --flow-locals fx/CommandDispose.cs -o cmd.facts.json -// python -m ownlang ownir cmd.facts.json +// python -m ownlang summaries cmd.facts.json +// python -m ownlang ownir cmd.facts.json --format sarif --verbosity verbose // -// NOT EXECUTED IN THE SANDBOX THAT PRODUCED THIS COMMIT: no .NET SDK and no NuGet feed are -// reachable there (see docs/notes/h28-npgsql-command-resolution.md section 3). The -// expectations below are derived by source reading of the extractor at this commit, with -// line citations, and are marked PREDICTED until this file has been through a real build. +// The producing sandbox lacked the .NET SDK/NuGet feed, so initial expectations were source +// predictions. G1/G2/G3 have since passed in PR workflow run #38048287923; G3 confirmed the +// expected extraction anchors for this file. The attached workflow artifact holds the raw facts, +// summaries, SARIF and runtime logs (see docs/notes/h28-npgsql-command-resolution.md). using System.Data; using System.Data.Common; @@ -28,7 +29,7 @@ public static class CommandDispose // (Program.cs, escapedLocals: `idn.Parent is ArgumentSyntax && !poolBuffers && !consumedArg // && !IsAdoptedArgOfBoundedWrapper`). The command is NOT an argument anywhere, so it stays // tracked and unreleased. - // PREDICTED: OWN001 on `command`; SILENT on `reader`. + // G3 CONFIRMED: OWN001 on `command`; SILENT on `reader`. public static DataTable B_load_neither_disposed(DbConnection connection, string sql) { var command = connection.CreateCommand(); @@ -40,7 +41,7 @@ public static DataTable B_load_neither_disposed(DbConnection connection, string return table; } - // A: the fully-released control. PREDICTED: clean. + // A: the fully-released control. G3 CONFIRMED: clean. public static DataTable A_load_both_disposed(DbConnection connection, string sql) { using var command = connection.CreateCommand(); @@ -54,8 +55,8 @@ public static DataTable A_load_both_disposed(DbConnection connection, string sql // C: the reader explicitly released, the command not. In Npgsql 10.0.3 source, reader // Dispose/DisposeAsync call Command.TraceCommandStop while public Close uses the - // non-disposing close path. This predicts an Activity difference, not a runtime result; - // the isolated listener probe is G1/P4. PREDICTED: OWN001 on `command`. + // non-disposing close path. G1/P4 confirmed the isolated Activity snapshots; G3 CONFIRMED + // OWN001 on `command`. public static DataTable C_reader_disposed_command_not(DbConnection connection, string sql) { var command = connection.CreateCommand(); @@ -69,9 +70,8 @@ public static DataTable C_reader_disposed_command_not(DbConnection connection, s } // D: the command released after Load, the reader not (Load already closed it). For - // Microsoft.Data.Sqlite 10.0.12, source predicts that command.Dispose deterministically - // finalizes the command-held sqlite3_stmt handles. G2 measures this directly; it has not - // run here. PREDICTED: clean. + // Microsoft.Data.Sqlite 10.0.12, G2 observed the native statement count return to baseline + // at command.Dispose; G3 CONFIRMED this control is clean. public static DataTable D_command_disposed_after_load(DbConnection connection, string sql) { var command = connection.CreateCommand(); @@ -88,7 +88,7 @@ public static DataTable D_command_disposed_after_load(DbConnection connection, s // E: no DataTable.Load at all -- the reader obligation is never handed to an argument, so // it stays tracked. This is corpus/real-world/ado-executereader-leak with the command - // owned instead of borrowed. PREDICTED: OWN001 on `command` AND OWN001 on `reader`. + // owned instead of borrowed. G3 CONFIRMED: OWN001 on `command` AND OWN001 on `reader`. // If E reports only one of the two, the escape rule -- not the acquire rule -- is what // silenced the reader in B. public static int E_no_load_both_leak(DbConnection connection, string sql) @@ -105,7 +105,7 @@ public static int E_no_load_both_leak(DbConnection connection, string sql) // F: the reader released by an explicit Close() instead of an argument pass. // Program.cs credits Close/Dispose/DisposeAsync as a release, so this isolates // "released by a call the analyzer recognises" from "released by a callee it does not". - // PREDICTED: OWN001 on `command` only. + // G3 CONFIRMED: OWN001 on `command` only. public static int F_reader_closed_command_not(DbConnection connection, string sql) { var command = connection.CreateCommand(); @@ -120,7 +120,7 @@ public static int F_reader_closed_command_not(DbConnection connection, string sq // G: the source indicates reader cleanup sets `Command.State = CommandState.Idle`; this // predicts reuse is possible and that the command lifetime is not bounded by reader close. - // P2 measures same-command reuse separately. PREDICTED: clean. + // P2 emits a descriptive reuse observation; G3 CONFIRMED this control is clean. public static int G_command_reused_after_reader_closed(DbConnection connection, string sql) { using var command = connection.CreateCommand(); @@ -136,9 +136,9 @@ public static int G_command_reused_after_reader_closed(DbConnection connection, // H: the prepared-statement arm. Npgsql source routes prepared-statement management through // the connector's PreparedStatementManager, and its reviewed command Dispose body shows no // explicit native-handle release or DEALLOCATE. Connector/session ownership alone does not - // prove the command cannot issue DEALLOCATE. P5 directly compares pg_prepared_statements - // before Prepare, after Prepare, and after this command's Dispose. Until then the server-side - // effect is unresolved. PREDICTED by extractor source only: OWN001 on `command`. + // prove the command cannot issue DEALLOCATE. P5 measured the explicit statement count increase + // after Prepare and unchanged count after Dispose in the tested session. G3 CONFIRMED the + // extractor finding on `command`; neither result generalizes to every prepared resource. public static int H_prepared_command_not_disposed(DbConnection connection, string sql) { var command = connection.CreateCommand(); @@ -150,8 +150,7 @@ public static int H_prepared_command_not_disposed(DbConnection connection, strin // I: the dispose-optional control. DataTable IS IDisposable but Program.cs's // IsDisposeOptional exempts System.Data.DataTable/DataSet/DataView, so `table` must not - // become a candidate. Proves the OWN001 in B is about the command, not the table. - // PREDICTED: OWN001 on `command` only, never on `table`. + // become a candidate. G3 CONFIRMED: OWN001 on `command` only, never on `table`. public static DataTable I_table_is_dispose_optional(DbConnection connection, string sql) { var command = connection.CreateCommand(); diff --git a/corpus/ownership-lab/h28-command/fx/CommandDispose.own b/corpus/ownership-lab/h28-command/fx/CommandDispose.own index 02a532e9..e3f74547 100644 --- a/corpus/ownership-lab/h28-command/fx/CommandDispose.own +++ b/corpus/ownership-lab/h28-command/fx/CommandDispose.own @@ -7,9 +7,9 @@ // of the C#, not C# the checker read. It models the two obligations the extractor's // IsOwningFactory appears to mint for this family -- IDbConnection.CreateCommand() -> DbCommand // and IDbCommand.ExecuteReader() -> DbDataReader -- and it models the H-28 runtime fact that -// DataTable.Load(reader) closes the reader. The production extractor -> OwnIR path has NOT yet -// run here (G3 is pending). This hand-written reduction encodes the candidate callee effect -// because source inspection predicts the argument-escape rule may drop the call instead. +// DataTable.Load(reader) closes the reader. This hand-written reduction is NOT the G3 C# path; +// the production extractor -> OwnIR -> verdict ran separately in workflow #38048287923. This +// reduction encodes the witnessed callee effect explicitly so the core-only result stays distinct. module H28Cmd resource Command { @@ -36,7 +36,7 @@ resource Reader { // Sqlite AND Npgsql). The command is never released. // EXPECTED for this manual reduction: OWN001 on 'command'. This does not decide whether the // diagnostic's practical implication is misleading for a provider; provider effects are not -// encoded in this reduction, and the production C# extraction result is still pending G3. +// encoded in this reduction. G3 independently confirmed the C# extraction pattern. fn B_load_neither_disposed(conn: int) { let command = acquire Command(conn); let reader = acquire Reader(command); @@ -53,8 +53,8 @@ fn A_load_both_released(conn: int) { } // D -- control: the command released after Load. EXPECTED: clean in this manual reduction. -// Source predicts this is the arm that deterministically finalizes Microsoft.Data.Sqlite's -// command-held sqlite3_stmt handles; G2 must confirm it before treating that as a result. +// G2 confirmed this is the arm that returned Microsoft.Data.Sqlite's directly observed +// command-held sqlite3_stmt count to baseline. This manual reduction models only ownership ops. fn D_command_released_after_load(conn: int) { let command = acquire Command(conn); let reader = acquire Reader(command); diff --git a/docs/notes/h28-npgsql-command-resolution.md b/docs/notes/h28-npgsql-command-resolution.md index 763d36d0..c1e717e0 100644 --- a/docs/notes/h28-npgsql-command-resolution.md +++ b/docs/notes/h28-npgsql-command-resolution.md @@ -1,10 +1,12 @@ # H-28-CMD — resolving the undisposed `DbCommand` question -**Status: inconclusive pending three required gates. No production-code change.** -Current verdict: **H28-INCONCLUSIVE**. This is deliberately not the earlier -`H28-PROVIDER-SPECIFIC` verdict: source reading gives a provider-specific *prediction*, but the -real provider run and the production Roslyn → OwnIR → core run have not yet completed. -Artifacts and required reproductions: [`corpus/ownership-lab/h28-command/`](../../corpus/ownership-lab/h28-command/). +**Status: required gates passed in CI; no production-code change.** +Current verdict: **H28-PROVIDER-SPECIFIC**, narrowly bounded to the tested Npgsql 10.0.3 and +Microsoft.Data.Sqlite 10.0.12 paths. This does not call OWN001 a confirmed false positive or a +native-leak detector. The mandatory workflow run [#38048287923](https://github.com/PhysShell/Own.NET/actions/runs/38048287923) +passed `all-required` and uploaded the `h28-command-falsifier` artifact; its status record is +[`evidence/ci-run-38048287923.txt`](../../corpus/ownership-lab/h28-command/evidence/ci-run-38048287923.txt). +Artifacts and reproducible gates: [`corpus/ownership-lab/h28-command/`](../../corpus/ownership-lab/h28-command/). ```csharp var command = connection.CreateCommand(); @@ -43,9 +45,10 @@ here. **Scope correction.** The earlier committed version pivoted from this provider/lifetime question to a #382 argument-lowering model gap, treating a source-derived extractor prediction as if G3 -had proved it. That was premature. This revision returns to the original H-28 command question; -#382 stays a candidate follow-up only if real Roslyn → OwnIR → verdict evidence establishes that -specific loss. No architecture conclusion is drawn in advance. +had proved it. That was premature at the time. This revision returns to the original H-28 command +question; G3 now confirms the specific extraction loss on this fixture, which supports a bounded +follow-up candidate but does not justify a universal architecture conclusion. No #382 change is +made here. --- @@ -64,7 +67,7 @@ are recoverable from the committed generator `h28/scripts/h28_prereg.py`. | CA2000 (default options) does not report command or reader | `h28/analyzers/ca2000/{Repro2.cs,ca2000-warnings.txt}` | recorded | | the undisposed `DbCommand` consequence | `h28_anchors_record.py` → `consequence_for_the_demanding_instance` | recorded, **not classified** | | census gate → `RECORD_AND_STOP` | `h28/manual-read.json`, `h28/scripts/h28final.py`, `h28/h28-census-v1.json` | recovered | -| argument escape/drop rule | `h28/scripts/h28_prereg.py` → `necessary_condition_verified_by_code_reading`; current issue [#382](https://github.com/PhysShell/Own.NET/issues/382) | source observation; end-to-end witness pending | +| argument escape/drop rule | `h28/scripts/h28_prereg.py` → `necessary_condition_verified_by_code_reading`; current issue [#382](https://github.com/PhysShell/Own.NET/issues/382) | G3 reproduces the predicted drop on this fixture; broader architecture conclusions remain out of scope | Name collision: `H28` in `docs/notes/p022-bridge-verdict-checkpoint4b.md:216` is an unrelated bridge-verdict hypothesis. It is not this ownership-lab H-28. @@ -131,7 +134,7 @@ not reference or dispose the command. The original H-28 test measured only the o --- -## 5. Provider source observations (not yet runtime conclusions) +## 5. Provider source observations and bounded runtime results ### 5.1 Npgsql `Dispose`: narrow claim only @@ -155,9 +158,14 @@ The provider source has no `~NpgsqlCommand` declaration and its public construct full-file scan is reproducible from the pinned source]. Prepared statements are managed through the connector's `PreparedStatementManager` [`evidence/npgsql-10.0.3-NpgsqlConnector-PreparedStatementManager.txt`]. **Connection/session -ownership does not logically prevent a command from issuing `DEALLOCATE`**; the relevant -falsifier is instead P5, which directly compares `pg_prepared_statements` before and after -`NpgsqlCommand.Dispose()` in the same PostgreSQL session. That provider runtime test is pending. +ownership does not logically prevent a command from issuing `DEALLOCATE`.** P5 directly compared +`pg_prepared_statements` before `Prepare`, after `Prepare`, and after `NpgsqlCommand.Dispose()` in +the same temporary PostgreSQL 16 session. The P5 assertion passed in workflow run +[#38048287923](https://github.com/PhysShell/Own.NET/actions/runs/38048287923): the explicit prepared +statement count increased after Prepare and did not decrease after command Dispose. This result +is limited to that provider version and tested command; it neither says Dispose has no effect nor +proves behavior for every prepared statement or native resource. The full runtime output is in +the uploaded `h28-command-falsifier` artifact. ### 5.2 Npgsql reader `Close` versus `Dispose`: Activity hypothesis @@ -173,12 +181,13 @@ reader-dispose trace-stop path [`evidence/npgsql-10.0.3-NpgsqlDataReader-Dispose `evidence/npgsql-10.0.3-NpgsqlDataReader-Close-public.txt`, `evidence/npgsql-10.0.3-NpgsqlCommand-TraceCommandStop.txt`]. -This yields a **source-derived prediction, not yet a measured leak**: `DataTable.Load` calls -`reader.Close()`; if that closes the Npgsql reader without the `Dispose` path, an attached -listener may observe the command Activity remain started. The new P4 isolates one query per -variant and records immediately after `Load`, after `reader.Dispose()`, and after -`command.Dispose()`; no P2/P3 query is allowed between those snapshots. The Npgsql `ActivitySource` -is named `"Npgsql"`, which the listener must match. A failed P4 kills this hypothesis. +G1/P4 ran in the required workflow and its assertions passed. With the listener attached to the +Npgsql `ActivitySource`, the isolated one-query arm observed one started / zero stopped immediately +after `DataTable.Load`; explicit `reader.Dispose()` changed that to one started / one stopped. +In the command-only arm, `command.Dispose()` left the snapshot at one started / zero stopped. +The P4 helper contains no P2/P3 query between these lifecycle snapshots. This is evidence of an +unfinished tracing Activity under the tested conditions—not proof of an OS/native leak, nor a +claim about Npgsql without an ActivityListener. The exact run output is in the attached CI artifact. The same reader cleanup sets `Command.State = CommandState.Idle`, ends the connector user action, clears `CurrentReader` and returns a pooled `ColumnInfo[]` @@ -202,10 +211,9 @@ to its reader disposal; that clears the command's `DataReader` and disposes the `sqlite3_stmt` is an owning `SafeHandle`; its `ReleaseHandle()` calls `sqlite3_finalize` [`evidence/sqlitepclraw-sqlite3_stmt-SafeHandle.txt`], and the pinned BCL `SafeHandle` has a finalizer [`evidence/runtime-v8.0.20-SafeHandle-finalizer.txt`, -`evidence/runtime-v10.0.12-SafeHandle-finalizer.txt`]. Thus source -predicts that command disposal is the **deterministic release path** for those command-held -handles; without it, SafeHandle finalization is a later GC fallback. This matters to a resource-lifetime -analysis, but does not turn source reading into a runtime measurement. +`evidence/runtime-v10.0.12-SafeHandle-finalizer.txt`]. Source predicts that command disposal is the **deterministic release path** for those +command-held handles; without it, SafeHandle finalization is a later GC fallback. The runtime +measurement below tests the deterministic path directly and does not infer a leak from memory. G2 now observes the resource directly using public `SqliteConnection.Handle` [`evidence/mssqlite-SqliteConnection-Handle.txt`] and @@ -217,11 +225,7 @@ pointer-to-`SafeHandle` lookup by default, so the harness calls back to that handle [`evidence/sqlitepclraw-v2.1.12-enable-next-stmt.txt`, `evidence/sqlitepclraw-v2.1.12-find-stmt-enabled.txt`, `evidence/sqlitepclraw-sqlite3_stmt-SafeHandle.txt`]. It neither samples RSS nor forces GC. -Expected differences from the direct pre-query baseline: 0 at baseline; +1 after `ExecuteReader`; -still +1 after `DataTable.Load`/reader close and after `reader.Dispose`; back to 0 after -`command.Dispose`. A second execution on the **same command** must replace the old statement -(count stays at baseline + 1), not accumulate to baseline + 2. These are assertions in the -executable harness, not results yet. +G2's direct-count assertions passed in workflow run [#38048287923](https://github.com/PhysShell/Own.NET/actions/runs/38048287923): the count delta was +1 after `ExecuteReader`, stayed +1 after `DataTable.Load`/reader close and `reader.Dispose`, and returned to baseline after `command.Dispose`. A second execution on the **same command** stayed at baseline + 1 rather than accumulating a second statement, then final command disposal returned it to baseline. This establishes deterministic statement release for the tested SQLite provider path; it does not establish a permanent leak if the command is simply allowed to become unreachable, and no RSS or GC inference was used. Raw counts and logs are in the uploaded CI artifact. The historical side-claim that SqliteConnection strongly retains commands is not used: the pinned 10.0.12 source shows a `List>` @@ -237,11 +241,20 @@ context only, not the basis of this report's verdict. No universal conclusion is ## 6. Runtime and executable evidence status -**This sandbox has no `dotnet` executable**, so it cannot restore/build the falsifier or run the -production Roslyn extractor. Earlier network checks also found the NuGet hosts unreachable here. -The previous attempt to query PostgreSQL 16.2 via `pgserver` only established session-scoped -server catalog behavior; it **does not prove** what Npgsql `Command.Dispose()` does and is not -used as evidence for P5. The report withdraws that earlier inference. +**The producing sandbox has no `dotnet` executable**, so its local strict run could not build the +falsifier or execute the extractor; see `evidence/required-gates-local.txt`. The required PR +workflow then ran on commit `008a21736e83139954d4ed1b34ab89fc8a194a00` and completed successfully: +job `H-28 required runtime + extractor gates`, step `Run mandatory G1-G3 gates (SKIP is failure)` +passed in run [#38048287923](https://github.com/PhysShell/Own.NET/actions/runs/38048287923). Since +`all-required` returns non-zero on any SKIP, the compiled G1/P4, direct SQLite G2, PostgreSQL P5, +and production-extractor G3 assertions all ran and passed. The workflow uploaded the +`h28-command-falsifier` artifact containing raw observations, facts, summaries, SARIF and logs; +status metadata is saved at `evidence/ci-run-38048287923.txt`. The sandbox cannot download the raw +artifact from GitHub's Actions results store, so the exact trace files remain available from the +linked workflow run rather than copied into this checkout. + +A previous standalone `pgserver` catalog check established only session-scoped server behavior; +it did **not** prove what Npgsql `Command.Dispose()` does and is not used as evidence for P5. **Local validation actually run:** source derivation passed for all 44 excerpts plus the pinned Npgsql finalizer scan; `tests/test_corpus.py` passed 33/33; whole-tree `ruff check .`, `mypy` @@ -260,26 +273,25 @@ fetch/re-derivation transcript is `evidence/source-derivation-local.txt`. The executable under `falsifier/` is corrected and bounded: -| gate / probe | direct observation | falsifier | +| gate / probe | result in workflow run #38048287923 | falsifier / scope | |---|---|---| -| **G1** compile | `dotnet build` of the runtime harness; the build also compiles `fx/CommandDispose.cs` | a compile error means the harness is not runnable | -| **P1–P3** lifecycle controls | reader closed after `Load`; command reuse; next command on same connection | separates reader/command logical usability and protocol busy state | -| **P4** Npgsql Activity | one `ExecuteReader` + one `Load`; snapshots at the three lifecycle points; no interposed query | no started/stopped difference at the direct snapshots kills the Activity claim | -| **G2 / P6** SQLite native state | `sqlite3_next_stmt` before/after execute, Load, reader Dispose, command Dispose, and same-command re-execution | no count change at command Dispose or reader-only release of the statement falsifies the source prediction | -| **P5** PostgreSQL prepared state | same-session `pg_prepared_statements` before `Prepare`, after, and after `command.Dispose()` | unchanged count across Dispose supports no server-statement release; a decrease refutes it | -| **G3** Own.NET end-to-end | production extractor → emitted OwnIR → solved summaries → core SARIF | any finding/anchor mismatch refutes the source-derived prediction below | +| **G1** compile | runtime harness and linked C# fixture built successfully under `net8.0` | a compile error would have failed the required gate | +| **P1–P3** lifecycle controls | state/reuse/connection observations were emitted; they are descriptive controls, not pass/fail assertions | separate logical usability from resource-release claims | +| **P4** Npgsql Activity | assertions passed: after Load `started=1, stopped=0`; after reader Dispose `1,1`; command-only Dispose remains `1,0`; one query per arm | different snapshots or an interposed query would invalidate the tracing claim | +| **G2 / P6** SQLite native state | `sqlite3_next_stmt` delta assertions passed at baseline, ExecuteReader, Load, reader Dispose, command Dispose, and same-command re-execution | no native statement delta at command Dispose or accumulation on reuse would falsify the source prediction | +| **P5** PostgreSQL prepared state | same-session count increased after Prepare and was unchanged after command Dispose; assertion passed | a decrease would refute the bounded result for this explicit statement | +| **G3** Own.NET end-to-end | production extractor → OwnIR → summaries → SARIF matched the exact expected OWN001 anchors; assertion passed | any finding/anchor mismatch would falsify the source-derived extraction prediction | `run.sh` no longer curls NuGet as a proxy for dependency availability: it tries `dotnet restore ---ignore-failed-sources`, allowing a warm package cache to work offline. Results are explicit: -`PASS`, `FAIL`, or `SKIP`. `run.sh all` is a convenient local run; **`run.sh all-required` converts -any SKIP into a non-zero failure**. The new -`.github/workflows/h28-command-falsifier.yml` runs `all-required` on this PR and preserves the -facts, summaries, SARIF and runtime logs as an artifact. This sandbox run remains pending until -that workflow supplies real G1–G3 results. +--ignore-failed-sources -p:NuGetAudit=false`, allowing a warm package cache to work offline. Results +are explicit: `PASS`, `FAIL`, or `SKIP`. `run.sh all` permits environment SKIPs for local use; +**`run.sh all-required` converts any SKIP into a non-zero failure**. The PR workflow ran this strict +mode successfully and preserved the facts, summaries, SARIF and runtime logs in the +`h28-command-falsifier` artifact. --- -## 7. Current Own.NET behavior: predicted, not end-to-end confirmed +## 7. Own.NET behavior: G3 reproduced the predicted extraction pattern `OWN001` means *"an owned resource is acquired but not released on every path … a possible leak"* (`ownlang/diagnostics.py`, OWN001 catalogue entry). It does **not** assert an OS-handle @@ -298,16 +310,18 @@ Source reading of `frontend/roslyn/OwnSharp.Extractor/Program.cs` at `e889f8b` p 4. The predicted C# result is OWN001 on the undisposed `command`, silence on `reader` in B. Variants A/D/G should be clean; E should report both command and reader. -The engine-only fixture `fx/CommandDispose.own` was run and reproduces its **manual reduction** -(three expected OWN001s: command in B and E; reader in E; A/D clean). This validates the core on -handwritten OwnIR-like operations **only**. It is not evidence that the production C# extractor -emits those operations. `scripts/verify_g3.py` now performs and asserts the missing production -C# → facts → summaries → SARIF path, writing each intermediate artifact under `evidence/`. -Its actual result is pending G3. +The engine-only fixture `fx/CommandDispose.own` reproduces its **manual reduction** (three +expected OWN001s: command in B and E; reader in E; A/D clean). That is still only a core check on +handwritten operations. Separately, G3 ran the production C# → OwnIR → summaries → SARIF path and +passed its exact finding-anchor assertion in workflow run [#38048287923](https://github.com/PhysShell/Own.NET/actions/runs/38048287923). +The emitted facts, summaries, SARIF and log are in the attached `h28-command-falsifier` artifact. +The verified pattern is OWN001 on `command` and silence on `reader` in B; both findings in E; +A/D clean; and the predicted command finding in the other controls. -The #382 connection is consequently **a concrete candidate witness, not yet experimentally -established**: issue #382 describes this argument-lowering loss, but until G3 reproduces the -predicted dropped-reader/command finding pair, this report does not claim a proven model gap. +This confirms an extractor behavior on this fixture: passing the tracked `reader` to +`DataTable.Load` does not yield a corresponding release fact in the current C# → OwnIR path. It +supports #382 as a follow-up candidate, but does not establish that every argument escape is wrong, +prove an OS/native leak, or authorize architecture work in this PR. --- @@ -315,50 +329,59 @@ predicted dropped-reader/command finding pair, this report does not claim a prov | review concern | correction in this revision | status here | |---|---|---| -| P4 observed Activity after P2/P3 had executed extra SQL | P4 is a separate helper: one query, snapshots immediately after Load / reader Dispose / command Dispose. P1–P3 run without an ActivityListener. | implemented; .NET run pending | -| P6 forced GC before RSS and measured allocator-dependent memory | replaced by direct `sqlite3_next_stmt` live-handle enumeration through `SqliteConnection.Handle`; no RSS, no GC; includes same-command re-execution control | implemented; .NET run pending | -| `NpgsqlConnection.ExecuteScalarAsync` probably does not compile | query now uses `CreateCommand()` + `ExecuteScalar()`; G1 builds both harness and C# fixture | fixed in source; build pending | -| PostgreSQL session scope does not imply command cannot send DEALLOCATE | removed that inference; P5 directly tests the actual Npgsql command before/after Dispose | corrected; P5 pending | +| P4 observed Activity after P2/P3 had executed extra SQL | P4 is a separate helper: one query, snapshots immediately after Load / reader Dispose / command Dispose. P1–P3 run without an ActivityListener. | G1/P4 passed in workflow run #38048287923 | +| P6 forced GC before RSS and measured allocator-dependent memory | replaced by direct `sqlite3_next_stmt` live-handle enumeration through `SqliteConnection.Handle`; no RSS, no GC; includes same-command re-execution control | G2 assertions passed in workflow run #38048287923 | +| `NpgsqlConnection.ExecuteScalarAsync` probably does not compile | query now uses `CreateCommand()` + `ExecuteScalar()`; G1 builds both harness and C# fixture | G1 build passed in workflow run #38048287923 | +| PostgreSQL session scope does not imply command cannot send DEALLOCATE | removed that inference; P5 directly tests the actual Npgsql command before/after Dispose | P5 passed; tested statement count unchanged after Dispose | | “Dispose releases nothing” ignores cache/state effects | narrowed to “no explicit native-handle or server `DEALLOCATE` release is visible in this Dispose body”; records the state transition/cache effect | corrected | | OWN001 is a possible-leak diagnostic, not an OS-handle oracle | states its repository wording; no false-positive conclusion from source inspection alone | corrected | -| “finding is inverted” was too categorical without G3 | withdrawn; current wording is a source-derived prediction, pending real extraction and runtime tracing | corrected | -| NuGet curl blocks a warm package cache; optional SKIP can be green | restore is attempted with `--ignore-failed-sources`; `all-required` fails on any SKIP and is the PR workflow gate | implemented; workflow pending | +| “finding is inverted” was too categorical without G3 | replaced with a bounded statement: G3 confirms the command-finding / reader-silence pair, while P4 observes an unfinished Activity under its listener; neither proves a native leak or OWN001 false positive | G1/G2/G3 assertions passed; interpretation remains narrow | +| NuGet curl blocks a warm package cache; optional SKIP can be green | restore is attempted with `--ignore-failed-sources`; `all-required` fails on any SKIP and is the PR workflow gate | required workflow passed; artifact uploaded | -The SQLite, Npgsql and BCL observations above were attacked with provider-source counterchecks; -none substitutes for G1–G3. If those gates disagree, the report and verdict must change. +The SQLite, Npgsql and BCL source observations above were checked against the successful G1–G3 +run. The verdict is bounded to those tested provider versions and assertions; if a later run +contradicts them, the report and verdict must change. --- ## 9. Verdict -# H28-INCONCLUSIVE - -Source inspection makes a provider-specific effect **plausible and testable**: Microsoft.Data.Sqlite -appears to keep native statement handles on `SqliteCommand`, while Npgsql's command Dispose body -shows command-state/cache work but no explicit native-handle or `DEALLOCATE` operation. The -Npgsql `Close`/`Dispose` Activity distinction is also a clear source-derived prediction. - -But the mandated differentiating runtime observations and current production extraction are not -executed yet. This report therefore does **not** decide whether an undisposed command in the -Npgsql target family is benign, a contract-level ownership defect, or a misleading OWN001; it -does **not** label OWN001 a false positive; and it does **not** claim a proven Own.NET model gap. -The one final verdict is **H28-INCONCLUSIVE** until G1, G2 and G3 pass or falsify the predictions. +# H28-PROVIDER-SPECIFIC + +The required G1–G3 run passed for the tested Npgsql `10.0.3` / PostgreSQL 16 and +Microsoft.Data.Sqlite `10.0.12` paths (workflow run [#38048287923](https://github.com/PhysShell/Own.NET/actions/runs/38048287923); raw output in artifact `h28-command-falsifier`). For those tests: + +- **SQLite:** direct `sqlite3_next_stmt` observations show the command-held statement remains + after Load and reader disposal, then returns to baseline on command disposal; reusing the same + command replaces rather than accumulates the statement. This establishes a deterministic + native-statement release effect for this provider path. It does not prove a permanent leak if + the command later becomes unreachable and SafeHandle finalization occurs. +- **Npgsql:** `Dispose` changes command state and can cache/recycle the command. P5's explicit + server-prepared-statement count did not decrease after command disposal in the tested session; + this does not prove behavior for every prepared statement or any separate native handle. P4 + observed an Activity still started after Load and after command-only disposal, while reader + disposal stopped it. +- **Own.NET:** G3 reproduced the predicted diagnostic pair on the actual C# fixture: OWN001 on + `command`, but no reader finding after the `Load(reader)` argument pass. In the tested Npgsql + tracing arm that pair can be **potentially misleading**: the command diagnostic does not + identify the unfinished Activity, while the reader obligation is not reported. This is not a + claim that OWN001 is a confirmed false positive, that an OS/native leak exists on Npgsql, or + that every `DbCommand` behaves this way. + +Thus **H28-PROVIDER-SPECIFIC** is a bounded verdict about observed provider effects, not a +provider-wide analyzer rule or a resolution of OWN001's ownership-contract meaning. No diagnostic, +production semantics, or architecture was changed; PR #399 remains open. --- -## 10. Bounded next step — no architecture proposal - -Run the three kill-first gates in the new required workflow (or locally): +## 10. Scope boundary -```bash -corpus/ownership-lab/h28-command/run.sh all-required -``` - -| gate | must observe | kill / required response | -|---|---|---| -| **G1** compile + isolated P4 | build succeeds; direct snapshots show whether reader Dispose, not command Dispose, stops the Activity | compile failure means the harness is not usable; no lifecycle difference retracts the Activity claim | -| **G2** native SQLite statements | direct statement count changes at the operation predicted by source; same-command second execute does not accumulate | no statement-count effect at command Dispose retracts the claimed native-release consequence | -| **G3** production C# → OwnIR → summary → verdict | exact expected `OWN001` anchors from `verify_g3.py` | any mismatch corrects the report's Own.NET behavior; until it matches, do not claim a model gap or advance #382 from this witness | +G1–G3 are complete for this test correction; the workflow artifact at [run #38048287923](https://github.com/PhysShell/Own.NET/actions/runs/38048287923) +contains the detailed traces and G3 intermediates. The producing sandbox could not download the +artifact, so its status metadata is preserved locally and the full artifact remains available in +GitHub Actions. -No new summary domain, provider registry, P-037 work, diagnostic, or analyzer rule is proposed. The -#382 relationship remains a follow-up candidate only if G3 proves the loss on this exact source. +This PR stops at the validated fixture and report. It proposes no new summary domain, provider +registry, P-037 work, diagnostic, analyzer rule, or #382 architecture change. Any follow-up should +be a separate decision based on the exact bounded evidence above. PR #399 remains open and is not +to be merged as part of this work. From 26e32b95a354c394a5f763790c85f4db7937744e Mon Sep 17 00:00:00 2001 From: PhysShell <45852143+PhysShell@users.noreply.github.com> Date: Sat, 10 Oct 2026 18:06:03 +0500 Subject: [PATCH 4/7] test(h28): refresh CFG parity fixture --- tests/fixtures/cfg_parity.json | 182 +++++++++++++++++---------------- 1 file changed, 94 insertions(+), 88 deletions(-) diff --git a/tests/fixtures/cfg_parity.json b/tests/fixtures/cfg_parity.json index 354c5823..5f4682cd 100644 --- a/tests/fixtures/cfg_parity.json +++ b/tests/fixtures/cfg_parity.json @@ -1,6 +1,12 @@ { "comment": "GENERATED by tests/test_cfg_fixtures.py --write; do not edit. Python (ownlang) is authoritative; rust/crates/own-cfg replays every case and must match the canonical CFG JSON byte-for-byte and the (line, code) diagnostics exactly.", "cases": [ + { + "name": "corpus/ownership-lab/h28-command/fx/CommandDispose.own", + "source": "// H-28-CMD: the ENGINE-RUNNABLE half of the fixture.\n//\n// fx/CommandDispose.cs needs the Roslyn extractor, which needs a .NET SDK; this file needs\n// only `python -m ownlang`, so the engine arm of the finding is reproducible anywhere.\n//\n// Honest frame (the same one corpus/real-world/README.md states): this is a MANUAL REDUCTION\n// of the C#, not C# the checker read. It models the two obligations the extractor's\n// IsOwningFactory appears to mint for this family -- IDbConnection.CreateCommand() -> DbCommand\n// and IDbCommand.ExecuteReader() -> DbDataReader -- and it models the H-28 runtime fact that\n// DataTable.Load(reader) closes the reader. This hand-written reduction is NOT the G3 C# path;\n// the production extractor -> OwnIR -> verdict ran separately in workflow #38048287923. This\n// reduction encodes the witnessed callee effect explicitly so the core-only result stays distinct.\nmodule H28Cmd\n\nresource Command {\n acquire open\n release dispose\n kind \"disposable\"\n emit_type \"DbCommand\"\n emit_acquire \"{args}.CreateCommand()\"\n emit_release \"{0}.Dispose()\"\n}\n\nresource Reader {\n acquire open\n release dispose\n kind \"disposable\"\n emit_type \"DbDataReader\"\n emit_acquire \"{args}.ExecuteReader()\"\n emit_release \"{0}.Dispose()\"\n}\n\n// B -- the demanding instance (victor-wiki/DatabaseManager DbInterpreter.GetDataTableAsync:699).\n// table.Load(reader) closed the reader: witnessed RELEASE_IF_LAST_RESULT_SET by the H-28\n// runtime falsifier (corpus/ownership-lab/h28/falsifier/falsifier.out, F1 IsClosed=True on\n// Sqlite AND Npgsql). The command is never released.\n// EXPECTED for this manual reduction: OWN001 on 'command'. This does not decide whether the\n// diagnostic's practical implication is misleading for a provider; provider effects are not\n// encoded in this reduction. G3 independently confirmed the C# extraction pattern.\nfn B_load_neither_disposed(conn: int) {\n let command = acquire Command(conn);\n let reader = acquire Reader(command);\n release reader; // DataTable.Load(reader) -> reader.Close()\n // no `release command;`\n}\n\n// A -- control: both released. EXPECTED: clean.\nfn A_load_both_released(conn: int) {\n let command = acquire Command(conn);\n let reader = acquire Reader(command);\n release reader;\n release command;\n}\n\n// D -- control: the command released after Load. EXPECTED: clean in this manual reduction.\n// G2 confirmed this is the arm that returned Microsoft.Data.Sqlite's directly observed\n// command-held sqlite3_stmt count to baseline. This manual reduction models only ownership ops.\nfn D_command_released_after_load(conn: int) {\n let command = acquire Command(conn);\n let reader = acquire Reader(command);\n release reader;\n release command;\n}\n\n// E -- control with NO Load: the reader obligation is never handed to a callee, so it stays\n// tracked. EXPECTED: OWN001 on 'command' AND OWN001 on 'reader' -- two findings, which is how\n// you can tell \"the reader was released\" apart from \"the reader was untracked by the escape\n// rule\" without reading the extractor.\nfn E_no_load_both_leak(conn: int) {\n let command = acquire Command(conn);\n let reader = acquire Reader(command);\n}\n", + "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 41,\n \"op\": \"acquire\",\n \"resource\": \"Command\",\n \"sym\": 1\n },\n {\n \"line\": 42,\n \"op\": \"acquire\",\n \"resource\": \"Reader\",\n \"sym\": 2\n },\n {\n \"line\": 43,\n \"op\": \"release\",\n \"sym\": 2\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"B_load_neither_disposed\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 40,\n \"is_param_borrow\": false,\n \"kind\": \"plain\",\n \"name\": \"conn\",\n \"origin\": \"conn#40\",\n \"resource_kind\": null,\n \"type_name\": \"int\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 41,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"command\",\n \"origin\": \"command#41\",\n \"resource_kind\": \"disposable\",\n \"type_name\": \"Command\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 42,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"reader\",\n \"origin\": \"reader#42\",\n \"resource_kind\": \"disposable\",\n \"type_name\": \"Reader\"\n }\n ]\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 49,\n \"op\": \"acquire\",\n \"resource\": \"Command\",\n \"sym\": 1\n },\n {\n \"line\": 50,\n \"op\": \"acquire\",\n \"resource\": \"Reader\",\n \"sym\": 2\n },\n {\n \"line\": 51,\n \"op\": \"release\",\n \"sym\": 2\n },\n {\n \"line\": 52,\n \"op\": \"release\",\n \"sym\": 1\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"A_load_both_released\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 48,\n \"is_param_borrow\": false,\n \"kind\": \"plain\",\n \"name\": \"conn\",\n \"origin\": \"conn#48\",\n \"resource_kind\": null,\n \"type_name\": \"int\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 49,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"command\",\n \"origin\": \"command#49\",\n \"resource_kind\": \"disposable\",\n \"type_name\": \"Command\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 50,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"reader\",\n \"origin\": \"reader#50\",\n \"resource_kind\": \"disposable\",\n \"type_name\": \"Reader\"\n }\n ]\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 59,\n \"op\": \"acquire\",\n \"resource\": \"Command\",\n \"sym\": 1\n },\n {\n \"line\": 60,\n \"op\": \"acquire\",\n \"resource\": \"Reader\",\n \"sym\": 2\n },\n {\n \"line\": 61,\n \"op\": \"release\",\n \"sym\": 2\n },\n {\n \"line\": 62,\n \"op\": \"release\",\n \"sym\": 1\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"D_command_released_after_load\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 58,\n \"is_param_borrow\": false,\n \"kind\": \"plain\",\n \"name\": \"conn\",\n \"origin\": \"conn#58\",\n \"resource_kind\": null,\n \"type_name\": \"int\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 59,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"command\",\n \"origin\": \"command#59\",\n \"resource_kind\": \"disposable\",\n \"type_name\": \"Command\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 60,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"reader\",\n \"origin\": \"reader#60\",\n \"resource_kind\": \"disposable\",\n \"type_name\": \"Reader\"\n }\n ]\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 70,\n \"op\": \"acquire\",\n \"resource\": \"Command\",\n \"sym\": 1\n },\n {\n \"line\": 71,\n \"op\": \"acquire\",\n \"resource\": \"Reader\",\n \"sym\": 2\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"E_no_load_both_leak\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 69,\n \"is_param_borrow\": false,\n \"kind\": \"plain\",\n \"name\": \"conn\",\n \"origin\": \"conn#69\",\n \"resource_kind\": null,\n \"type_name\": \"int\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 70,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"command\",\n \"origin\": \"command#70\",\n \"resource_kind\": \"disposable\",\n \"type_name\": \"Command\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 71,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"reader\",\n \"origin\": \"reader#71\",\n \"resource_kind\": \"disposable\",\n \"type_name\": \"Reader\"\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", + "diags": [] + }, { "name": "corpus/real-world/ado-executereader-leak/case.own", "source": "// OwnLang model of the canonical ADO.NET reader leak (P1a, ADO.NET tranche). A DbDataReader\n// from DbCommand.ExecuteReader() is a fresh owned IDisposable the caller must dispose; here it\n// is acquired, read, and never released — the generic OWN001 leak. The command is a borrowed\n// parameter and the reader does not escape (only a count is returned), so it stays tracked.\n// See notes.md for the recognition rule (return type implements System.Data.IDataReader).\nmodule Corpus\nresource Reader {\n acquire open\n release dispose\n kind \"disposable\"\n emit_type \"DbDataReader\"\n emit_acquire \"{args}.ExecuteReader()\"\n emit_release \"{0}.Dispose()\"\n}\nfn Run(cmd: int) {\n let reader = acquire Reader(cmd); // var reader = cmd.ExecuteReader()\n // rows read via reader.Read(); no `release reader;` — never disposed (OWN001)\n}\n", @@ -367,6 +373,94 @@ "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 16,\n \"op\": \"acquire\",\n \"resource\": \"Subscription\",\n \"sym\": 1\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"CustomerViewModel\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 15,\n \"is_param_borrow\": false,\n \"kind\": \"plain\",\n \"name\": \"bus\",\n \"origin\": \"bus#15\",\n \"resource_kind\": null,\n \"type_name\": \"int\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 16,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"customerChanged\",\n \"origin\": \"customerChanged#16\",\n \"resource_kind\": \"subscription token\",\n \"type_name\": \"Subscription\"\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", "diags": [] }, + { + "name": "curated_buffer_policy", + "source": "module M\npolicy Fast {\n inline_bytes = 256;\n clear_on_release = true;\n fallback = pool;\n trace = debug;\n counters = true;\n}\nfn f(n: int) {\n let a = Buffer.scratch(64, policy = Fast, clear = true);\n let b = Buffer.stack(size, max = 1024);\n let c = Buffer.inline(32);\n let d = Buffer.pooled(n);\n let e = Buffer.native(n);\n release a; release b; release c; release d; release e;\n}\n", + "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"buffer\": {\n \"clear_on_release\": true,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": true,\n \"inline_bytes\": 256,\n \"line\": 10,\n \"mode\": \"scratch\",\n \"policy_name\": \"Fast\",\n \"sensitive\": false,\n \"size_const\": 64,\n \"size_var\": null,\n \"trace\": true\n },\n \"line\": 10,\n \"op\": \"acquire_buffer\",\n \"sym\": 1\n },\n {\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": true,\n \"fallback_pool\": false,\n \"inline_bytes\": 1024,\n \"line\": 11,\n \"mode\": \"stack\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"size\",\n \"trace\": true\n },\n \"line\": 11,\n \"op\": \"acquire_buffer\",\n \"sym\": 2\n },\n {\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": true,\n \"fallback_pool\": false,\n \"inline_bytes\": 32,\n \"line\": 12,\n \"mode\": \"inline\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": 32,\n \"size_var\": null,\n \"trace\": true\n },\n \"line\": 12,\n \"op\": \"acquire_buffer\",\n \"sym\": 3\n },\n {\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": true,\n \"inline_bytes\": 1024,\n \"line\": 13,\n \"mode\": \"pooled\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"n\",\n \"trace\": true\n },\n \"line\": 13,\n \"op\": \"acquire_buffer\",\n \"sym\": 4\n },\n {\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": false,\n \"inline_bytes\": 1024,\n \"line\": 14,\n \"mode\": \"native\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"n\",\n \"trace\": true\n },\n \"line\": 14,\n \"op\": \"acquire_buffer\",\n \"sym\": 5\n },\n {\n \"line\": 15,\n \"op\": \"release\",\n \"sym\": 1\n },\n {\n \"line\": 15,\n \"op\": \"release\",\n \"sym\": 2\n },\n {\n \"line\": 15,\n \"op\": \"release\",\n \"sym\": 3\n },\n {\n \"line\": 15,\n \"op\": \"release\",\n \"sym\": 4\n },\n {\n \"line\": 15,\n \"op\": \"release\",\n \"sym\": 5\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"f\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 9,\n \"is_param_borrow\": false,\n \"kind\": \"plain\",\n \"name\": \"n\",\n \"origin\": \"n#9\",\n \"resource_kind\": null,\n \"type_name\": \"int\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": {\n \"clear_on_release\": true,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": true,\n \"inline_bytes\": 256,\n \"line\": 10,\n \"mode\": \"scratch\",\n \"policy_name\": \"Fast\",\n \"sensitive\": false,\n \"size_const\": 64,\n \"size_var\": null,\n \"trace\": true\n },\n \"def_line\": 10,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"a\",\n \"origin\": \"a#10:11\",\n \"resource_kind\": null,\n \"type_name\": null\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": true,\n \"fallback_pool\": false,\n \"inline_bytes\": 1024,\n \"line\": 11,\n \"mode\": \"stack\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"size\",\n \"trace\": true\n },\n \"def_line\": 11,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"b\",\n \"origin\": \"b#11:11\",\n \"resource_kind\": null,\n \"type_name\": null\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": true,\n \"fallback_pool\": false,\n \"inline_bytes\": 32,\n \"line\": 12,\n \"mode\": \"inline\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": 32,\n \"size_var\": null,\n \"trace\": true\n },\n \"def_line\": 12,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"c\",\n \"origin\": \"c#12:11\",\n \"resource_kind\": null,\n \"type_name\": null\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": true,\n \"inline_bytes\": 1024,\n \"line\": 13,\n \"mode\": \"pooled\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"n\",\n \"trace\": true\n },\n \"def_line\": 13,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"d\",\n \"origin\": \"d#13:11\",\n \"resource_kind\": null,\n \"type_name\": null\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": false,\n \"inline_bytes\": 1024,\n \"line\": 14,\n \"mode\": \"native\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"n\",\n \"trace\": true\n },\n \"def_line\": 14,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"e\",\n \"origin\": \"e#14:11\",\n \"resource_kind\": null,\n \"type_name\": null\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", + "diags": [ + [ + 11, + "OWN030" + ] + ] + }, + { + "name": "curated_call_errors", + "source": "module M\nresource Conn { acquire open release close }\nextern fn Need(consume Conn);\nfn f() {\n let c = acquire Conn(1);\n Unknown(c);\n Need(c, c);\n Need(c);\n}\n", + "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 5,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 0\n },\n {\n \"args\": [\n {\n \"effect\": \"consume\",\n \"sym\": 0\n }\n ],\n \"callee\": \"Need\",\n \"line\": 8,\n \"op\": \"invoke\"\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"f\",\n \"params\": [],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 5,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"c\",\n \"origin\": \"c#5\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", + "diags": [ + [ + 6, + "OWN040" + ], + [ + 7, + "OWN041" + ] + ] + }, + { + "name": "curated_full_flow", + "source": "module M\nresource Conn { acquire open release close }\nresource Token { acquire mint release burn kind \"subscription token\" }\nextern fn Fill(borrow_mut Conn);\nextern fn Hash(borrow Conn);\nextern fn Store(consume Conn);\nfn f(n: int) {\n let c = acquire Conn(1);\n if (n) {\n borrow c as r { use r; }\n }\n let d = move c;\n Store(d);\n return;\n}\nfn g(n: int) {\n let b = Buffer.scratch(n);\n release b;\n}\n", + "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 8,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 1\n }\n ],\n \"label\": \"entry\",\n \"succ\": [\n 1,\n 2\n ]\n },\n {\n \"id\": 1,\n \"instrs\": [\n {\n \"binding\": 2,\n \"line\": 10,\n \"mut\": false,\n \"op\": \"borrow_start\",\n \"owner\": 1\n },\n {\n \"line\": 10,\n \"op\": \"use\",\n \"sym\": 2\n },\n {\n \"binding\": 2,\n \"line\": 10,\n \"mut\": false,\n \"op\": \"borrow_end\",\n \"owner\": 1\n }\n ],\n \"label\": \"then\",\n \"succ\": [\n 3\n ]\n },\n {\n \"id\": 2,\n \"instrs\": [],\n \"label\": \"else\",\n \"succ\": [\n 3\n ]\n },\n {\n \"id\": 3,\n \"instrs\": [\n {\n \"dst\": 3,\n \"line\": 12,\n \"op\": \"move_into\",\n \"src\": 1\n },\n {\n \"args\": [\n {\n \"effect\": \"consume\",\n \"sym\": 3\n }\n ],\n \"callee\": \"Store\",\n \"line\": 13,\n \"op\": \"invoke\"\n },\n {\n \"line\": 14,\n \"op\": \"return\",\n \"sym\": null\n }\n ],\n \"label\": \"merge\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"f\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 7,\n \"is_param_borrow\": false,\n \"kind\": \"plain\",\n \"name\": \"n\",\n \"origin\": \"n#7\",\n \"resource_kind\": null,\n \"type_name\": \"int\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 8,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"c\",\n \"origin\": \"c#8\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n },\n {\n \"borrow_is_mut\": false,\n \"buffer\": null,\n \"def_line\": 10,\n \"is_param_borrow\": false,\n \"kind\": \"borrow\",\n \"name\": \"r\",\n \"origin\": null,\n \"resource_kind\": null,\n \"type_name\": null\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 12,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"d\",\n \"origin\": \"c#8\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n }\n ]\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": true,\n \"inline_bytes\": 1024,\n \"line\": 17,\n \"mode\": \"scratch\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"n\",\n \"trace\": true\n },\n \"line\": 17,\n \"op\": \"acquire_buffer\",\n \"sym\": 1\n },\n {\n \"line\": 18,\n \"op\": \"release\",\n \"sym\": 1\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"g\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 16,\n \"is_param_borrow\": false,\n \"kind\": \"plain\",\n \"name\": \"n\",\n \"origin\": \"n#16\",\n \"resource_kind\": null,\n \"type_name\": \"int\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": true,\n \"inline_bytes\": 1024,\n \"line\": 17,\n \"mode\": \"scratch\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"n\",\n \"trace\": true\n },\n \"def_line\": 17,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"b\",\n \"origin\": \"b#17:13\",\n \"resource_kind\": null,\n \"type_name\": null\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", + "diags": [] + }, + { + "name": "curated_loops_and_borrows", + "source": "module M\nresource Conn { acquire open release close }\nextern fn Fill(borrow_mut Conn);\nfn f(n: int) {\n let c = acquire Conn(1);\n while (n) {\n borrow_mut c as m { Fill(m); }\n use c;\n }\n overspan c;\n release c;\n}\n", + "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 5,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 1\n }\n ],\n \"label\": \"entry\",\n \"succ\": [\n 1\n ]\n },\n {\n \"id\": 1,\n \"instrs\": [],\n \"label\": \"while.header\",\n \"succ\": [\n 2,\n 3\n ]\n },\n {\n \"id\": 2,\n \"instrs\": [\n {\n \"binding\": 2,\n \"line\": 7,\n \"mut\": true,\n \"op\": \"borrow_start\",\n \"owner\": 1\n },\n {\n \"args\": [\n {\n \"effect\": \"borrow_mut\",\n \"sym\": 2\n }\n ],\n \"callee\": \"Fill\",\n \"line\": 7,\n \"op\": \"invoke\"\n },\n {\n \"binding\": 2,\n \"line\": 7,\n \"mut\": true,\n \"op\": \"borrow_end\",\n \"owner\": 1\n },\n {\n \"line\": 8,\n \"op\": \"use\",\n \"sym\": 1\n }\n ],\n \"label\": \"while.body\",\n \"succ\": [\n 1\n ]\n },\n {\n \"id\": 3,\n \"instrs\": [\n {\n \"line\": 10,\n \"op\": \"overspan\",\n \"sym\": 1\n },\n {\n \"line\": 11,\n \"op\": \"release\",\n \"sym\": 1\n }\n ],\n \"label\": \"while.after\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"f\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 4,\n \"is_param_borrow\": false,\n \"kind\": \"plain\",\n \"name\": \"n\",\n \"origin\": \"n#4\",\n \"resource_kind\": null,\n \"type_name\": \"int\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 5,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"c\",\n \"origin\": \"c#5\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n },\n {\n \"borrow_is_mut\": true,\n \"buffer\": null,\n \"def_line\": 7,\n \"is_param_borrow\": false,\n \"kind\": \"borrow\",\n \"name\": \"m\",\n \"origin\": null,\n \"resource_kind\": null,\n \"type_name\": null\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", + "diags": [] + }, + { + "name": "curated_resolver_errors", + "source": "module M\nresource Conn { acquire open release close }\nfn f() {\n use undef;\n let a = acquire Missing(1);\n let b = acquire Conn(1);\n let b = acquire Conn(2);\n release b;\n let c = b;\n}\n", + "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 5,\n \"op\": \"acquire\",\n \"resource\": \"Missing\",\n \"sym\": 0\n },\n {\n \"line\": 6,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 1\n },\n {\n \"line\": 7,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 2\n },\n {\n \"line\": 8,\n \"op\": \"release\",\n \"sym\": 2\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"f\",\n \"params\": [],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 5,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"a\",\n \"origin\": \"a#5\",\n \"resource_kind\": null,\n \"type_name\": \"Missing\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 6,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"b\",\n \"origin\": \"b#6\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 7,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"b\",\n \"origin\": \"b#7\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", + "diags": [ + [ + 4, + "OWN030" + ], + [ + 5, + "OWN030" + ], + [ + 7, + "OWN031" + ], + [ + 9, + "OWN032" + ] + ] + }, + { + "name": "curated_return_variants", + "source": "module M\nresource Conn { acquire open release close }\nresource Other { acquire open release close }\nfn ret_owned() -> Conn { let c = acquire Conn(1); return c; }\nfn ret_borrow(x: &Conn) -> Conn { return x; }\nfn ret_plain() -> Conn { let n = 3; return n; }\nfn ret_wrong() -> Conn { let o = acquire Other(1); return o; }\nfn ret_void_val() { let c = acquire Conn(1); return c; }\nfn ret_missing() -> Conn { }\n", + "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 4,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 0\n },\n {\n \"line\": 4,\n \"op\": \"return\",\n \"sym\": 0\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": true,\n \"name\": \"ret_owned\",\n \"params\": [],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 4,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"c\",\n \"origin\": \"c#4\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n }\n ]\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 5,\n \"op\": \"return\",\n \"sym\": null\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": true,\n \"name\": \"ret_borrow\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": false,\n \"buffer\": null,\n \"def_line\": 5,\n \"is_param_borrow\": true,\n \"kind\": \"borrow\",\n \"name\": \"x\",\n \"origin\": \"x#5\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n }\n ]\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 6,\n \"op\": \"return\",\n \"sym\": null\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": true,\n \"name\": \"ret_plain\",\n \"params\": [],\n \"symbols\": []\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 7,\n \"op\": \"acquire\",\n \"resource\": \"Other\",\n \"sym\": 0\n },\n {\n \"line\": 7,\n \"op\": \"return\",\n \"sym\": 0\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": true,\n \"name\": \"ret_wrong\",\n \"params\": [],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 7,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"o\",\n \"origin\": \"o#7\",\n \"resource_kind\": null,\n \"type_name\": \"Other\"\n }\n ]\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 8,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 0\n },\n {\n \"line\": 8,\n \"op\": \"return\",\n \"sym\": null\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"ret_void_val\",\n \"params\": [],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 8,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"c\",\n \"origin\": \"c#8\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n }\n ]\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": true,\n \"name\": \"ret_missing\",\n \"params\": [],\n \"symbols\": []\n }\n ],\n \"ownlang_cfg_version\": 0\n}", + "diags": [ + [ + 5, + "OWN004" + ], + [ + 6, + "OWN035" + ], + [ + 7, + "OWN035" + ], + [ + 8, + "OWN035" + ], + [ + 9, + "OWN033" + ] + ] + }, { "name": "examples/bad_leak_branch.own", "source": "module LeakBranchDemo\n\nresource Conn {\n acquire open\n release close\n}\n\n// OWN001: released on the 'then' path only; leaks through 'else'.\nfn maybe(flag: int) {\n let c = acquire Conn(flag);\n if (flag) {\n release c;\n }\n}\n", @@ -491,94 +585,6 @@ "source": "module PoolDemo\n\nresource Buffer {\n acquire rent\n release give\n}\n\n// Canonical happy path: rent a buffer, look at it mutably then read it,\n// hand it back. Checker proves it is returned on the single path; codegen\n// hoists the release into a finally for exception-safety.\nfn process(size: int) {\n let buf = acquire Buffer(size);\n\n borrow_mut buf as bytes {\n use bytes;\n }\n\n borrow buf as bytes {\n use bytes;\n }\n\n release buf;\n}\n", "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 12,\n \"op\": \"acquire\",\n \"resource\": \"Buffer\",\n \"sym\": 1\n },\n {\n \"binding\": 2,\n \"line\": 14,\n \"mut\": true,\n \"op\": \"borrow_start\",\n \"owner\": 1\n },\n {\n \"line\": 15,\n \"op\": \"use\",\n \"sym\": 2\n },\n {\n \"binding\": 2,\n \"line\": 14,\n \"mut\": true,\n \"op\": \"borrow_end\",\n \"owner\": 1\n },\n {\n \"binding\": 3,\n \"line\": 18,\n \"mut\": false,\n \"op\": \"borrow_start\",\n \"owner\": 1\n },\n {\n \"line\": 19,\n \"op\": \"use\",\n \"sym\": 3\n },\n {\n \"binding\": 3,\n \"line\": 18,\n \"mut\": false,\n \"op\": \"borrow_end\",\n \"owner\": 1\n },\n {\n \"line\": 22,\n \"op\": \"release\",\n \"sym\": 1\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"process\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 11,\n \"is_param_borrow\": false,\n \"kind\": \"plain\",\n \"name\": \"size\",\n \"origin\": \"size#11\",\n \"resource_kind\": null,\n \"type_name\": \"int\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 12,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"buf\",\n \"origin\": \"buf#12\",\n \"resource_kind\": null,\n \"type_name\": \"Buffer\"\n },\n {\n \"borrow_is_mut\": true,\n \"buffer\": null,\n \"def_line\": 14,\n \"is_param_borrow\": false,\n \"kind\": \"borrow\",\n \"name\": \"bytes\",\n \"origin\": null,\n \"resource_kind\": null,\n \"type_name\": null\n },\n {\n \"borrow_is_mut\": false,\n \"buffer\": null,\n \"def_line\": 18,\n \"is_param_borrow\": false,\n \"kind\": \"borrow\",\n \"name\": \"bytes\",\n \"origin\": null,\n \"resource_kind\": null,\n \"type_name\": null\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", "diags": [] - }, - { - "name": "curated_full_flow", - "source": "module M\nresource Conn { acquire open release close }\nresource Token { acquire mint release burn kind \"subscription token\" }\nextern fn Fill(borrow_mut Conn);\nextern fn Hash(borrow Conn);\nextern fn Store(consume Conn);\nfn f(n: int) {\n let c = acquire Conn(1);\n if (n) {\n borrow c as r { use r; }\n }\n let d = move c;\n Store(d);\n return;\n}\nfn g(n: int) {\n let b = Buffer.scratch(n);\n release b;\n}\n", - "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 8,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 1\n }\n ],\n \"label\": \"entry\",\n \"succ\": [\n 1,\n 2\n ]\n },\n {\n \"id\": 1,\n \"instrs\": [\n {\n \"binding\": 2,\n \"line\": 10,\n \"mut\": false,\n \"op\": \"borrow_start\",\n \"owner\": 1\n },\n {\n \"line\": 10,\n \"op\": \"use\",\n \"sym\": 2\n },\n {\n \"binding\": 2,\n \"line\": 10,\n \"mut\": false,\n \"op\": \"borrow_end\",\n \"owner\": 1\n }\n ],\n \"label\": \"then\",\n \"succ\": [\n 3\n ]\n },\n {\n \"id\": 2,\n \"instrs\": [],\n \"label\": \"else\",\n \"succ\": [\n 3\n ]\n },\n {\n \"id\": 3,\n \"instrs\": [\n {\n \"dst\": 3,\n \"line\": 12,\n \"op\": \"move_into\",\n \"src\": 1\n },\n {\n \"args\": [\n {\n \"effect\": \"consume\",\n \"sym\": 3\n }\n ],\n \"callee\": \"Store\",\n \"line\": 13,\n \"op\": \"invoke\"\n },\n {\n \"line\": 14,\n \"op\": \"return\",\n \"sym\": null\n }\n ],\n \"label\": \"merge\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"f\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 7,\n \"is_param_borrow\": false,\n \"kind\": \"plain\",\n \"name\": \"n\",\n \"origin\": \"n#7\",\n \"resource_kind\": null,\n \"type_name\": \"int\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 8,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"c\",\n \"origin\": \"c#8\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n },\n {\n \"borrow_is_mut\": false,\n \"buffer\": null,\n \"def_line\": 10,\n \"is_param_borrow\": false,\n \"kind\": \"borrow\",\n \"name\": \"r\",\n \"origin\": null,\n \"resource_kind\": null,\n \"type_name\": null\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 12,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"d\",\n \"origin\": \"c#8\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n }\n ]\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": true,\n \"inline_bytes\": 1024,\n \"line\": 17,\n \"mode\": \"scratch\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"n\",\n \"trace\": true\n },\n \"line\": 17,\n \"op\": \"acquire_buffer\",\n \"sym\": 1\n },\n {\n \"line\": 18,\n \"op\": \"release\",\n \"sym\": 1\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"g\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 16,\n \"is_param_borrow\": false,\n \"kind\": \"plain\",\n \"name\": \"n\",\n \"origin\": \"n#16\",\n \"resource_kind\": null,\n \"type_name\": \"int\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": true,\n \"inline_bytes\": 1024,\n \"line\": 17,\n \"mode\": \"scratch\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"n\",\n \"trace\": true\n },\n \"def_line\": 17,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"b\",\n \"origin\": \"b#17:13\",\n \"resource_kind\": null,\n \"type_name\": null\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", - "diags": [] - }, - { - "name": "curated_buffer_policy", - "source": "module M\npolicy Fast {\n inline_bytes = 256;\n clear_on_release = true;\n fallback = pool;\n trace = debug;\n counters = true;\n}\nfn f(n: int) {\n let a = Buffer.scratch(64, policy = Fast, clear = true);\n let b = Buffer.stack(size, max = 1024);\n let c = Buffer.inline(32);\n let d = Buffer.pooled(n);\n let e = Buffer.native(n);\n release a; release b; release c; release d; release e;\n}\n", - "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"buffer\": {\n \"clear_on_release\": true,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": true,\n \"inline_bytes\": 256,\n \"line\": 10,\n \"mode\": \"scratch\",\n \"policy_name\": \"Fast\",\n \"sensitive\": false,\n \"size_const\": 64,\n \"size_var\": null,\n \"trace\": true\n },\n \"line\": 10,\n \"op\": \"acquire_buffer\",\n \"sym\": 1\n },\n {\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": true,\n \"fallback_pool\": false,\n \"inline_bytes\": 1024,\n \"line\": 11,\n \"mode\": \"stack\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"size\",\n \"trace\": true\n },\n \"line\": 11,\n \"op\": \"acquire_buffer\",\n \"sym\": 2\n },\n {\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": true,\n \"fallback_pool\": false,\n \"inline_bytes\": 32,\n \"line\": 12,\n \"mode\": \"inline\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": 32,\n \"size_var\": null,\n \"trace\": true\n },\n \"line\": 12,\n \"op\": \"acquire_buffer\",\n \"sym\": 3\n },\n {\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": true,\n \"inline_bytes\": 1024,\n \"line\": 13,\n \"mode\": \"pooled\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"n\",\n \"trace\": true\n },\n \"line\": 13,\n \"op\": \"acquire_buffer\",\n \"sym\": 4\n },\n {\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": false,\n \"inline_bytes\": 1024,\n \"line\": 14,\n \"mode\": \"native\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"n\",\n \"trace\": true\n },\n \"line\": 14,\n \"op\": \"acquire_buffer\",\n \"sym\": 5\n },\n {\n \"line\": 15,\n \"op\": \"release\",\n \"sym\": 1\n },\n {\n \"line\": 15,\n \"op\": \"release\",\n \"sym\": 2\n },\n {\n \"line\": 15,\n \"op\": \"release\",\n \"sym\": 3\n },\n {\n \"line\": 15,\n \"op\": \"release\",\n \"sym\": 4\n },\n {\n \"line\": 15,\n \"op\": \"release\",\n \"sym\": 5\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"f\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 9,\n \"is_param_borrow\": false,\n \"kind\": \"plain\",\n \"name\": \"n\",\n \"origin\": \"n#9\",\n \"resource_kind\": null,\n \"type_name\": \"int\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": {\n \"clear_on_release\": true,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": true,\n \"inline_bytes\": 256,\n \"line\": 10,\n \"mode\": \"scratch\",\n \"policy_name\": \"Fast\",\n \"sensitive\": false,\n \"size_const\": 64,\n \"size_var\": null,\n \"trace\": true\n },\n \"def_line\": 10,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"a\",\n \"origin\": \"a#10:11\",\n \"resource_kind\": null,\n \"type_name\": null\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": true,\n \"fallback_pool\": false,\n \"inline_bytes\": 1024,\n \"line\": 11,\n \"mode\": \"stack\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"size\",\n \"trace\": true\n },\n \"def_line\": 11,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"b\",\n \"origin\": \"b#11:11\",\n \"resource_kind\": null,\n \"type_name\": null\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": true,\n \"fallback_pool\": false,\n \"inline_bytes\": 32,\n \"line\": 12,\n \"mode\": \"inline\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": 32,\n \"size_var\": null,\n \"trace\": true\n },\n \"def_line\": 12,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"c\",\n \"origin\": \"c#12:11\",\n \"resource_kind\": null,\n \"type_name\": null\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": true,\n \"inline_bytes\": 1024,\n \"line\": 13,\n \"mode\": \"pooled\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"n\",\n \"trace\": true\n },\n \"def_line\": 13,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"d\",\n \"origin\": \"d#13:11\",\n \"resource_kind\": null,\n \"type_name\": null\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": false,\n \"inline_bytes\": 1024,\n \"line\": 14,\n \"mode\": \"native\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"n\",\n \"trace\": true\n },\n \"def_line\": 14,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"e\",\n \"origin\": \"e#14:11\",\n \"resource_kind\": null,\n \"type_name\": null\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", - "diags": [ - [ - 11, - "OWN030" - ] - ] - }, - { - "name": "curated_return_variants", - "source": "module M\nresource Conn { acquire open release close }\nresource Other { acquire open release close }\nfn ret_owned() -> Conn { let c = acquire Conn(1); return c; }\nfn ret_borrow(x: &Conn) -> Conn { return x; }\nfn ret_plain() -> Conn { let n = 3; return n; }\nfn ret_wrong() -> Conn { let o = acquire Other(1); return o; }\nfn ret_void_val() { let c = acquire Conn(1); return c; }\nfn ret_missing() -> Conn { }\n", - "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 4,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 0\n },\n {\n \"line\": 4,\n \"op\": \"return\",\n \"sym\": 0\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": true,\n \"name\": \"ret_owned\",\n \"params\": [],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 4,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"c\",\n \"origin\": \"c#4\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n }\n ]\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 5,\n \"op\": \"return\",\n \"sym\": null\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": true,\n \"name\": \"ret_borrow\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": false,\n \"buffer\": null,\n \"def_line\": 5,\n \"is_param_borrow\": true,\n \"kind\": \"borrow\",\n \"name\": \"x\",\n \"origin\": \"x#5\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n }\n ]\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 6,\n \"op\": \"return\",\n \"sym\": null\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": true,\n \"name\": \"ret_plain\",\n \"params\": [],\n \"symbols\": []\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 7,\n \"op\": \"acquire\",\n \"resource\": \"Other\",\n \"sym\": 0\n },\n {\n \"line\": 7,\n \"op\": \"return\",\n \"sym\": 0\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": true,\n \"name\": \"ret_wrong\",\n \"params\": [],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 7,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"o\",\n \"origin\": \"o#7\",\n \"resource_kind\": null,\n \"type_name\": \"Other\"\n }\n ]\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 8,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 0\n },\n {\n \"line\": 8,\n \"op\": \"return\",\n \"sym\": null\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"ret_void_val\",\n \"params\": [],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 8,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"c\",\n \"origin\": \"c#8\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n }\n ]\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": true,\n \"name\": \"ret_missing\",\n \"params\": [],\n \"symbols\": []\n }\n ],\n \"ownlang_cfg_version\": 0\n}", - "diags": [ - [ - 5, - "OWN004" - ], - [ - 6, - "OWN035" - ], - [ - 7, - "OWN035" - ], - [ - 8, - "OWN035" - ], - [ - 9, - "OWN033" - ] - ] - }, - { - "name": "curated_call_errors", - "source": "module M\nresource Conn { acquire open release close }\nextern fn Need(consume Conn);\nfn f() {\n let c = acquire Conn(1);\n Unknown(c);\n Need(c, c);\n Need(c);\n}\n", - "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 5,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 0\n },\n {\n \"args\": [\n {\n \"effect\": \"consume\",\n \"sym\": 0\n }\n ],\n \"callee\": \"Need\",\n \"line\": 8,\n \"op\": \"invoke\"\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"f\",\n \"params\": [],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 5,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"c\",\n \"origin\": \"c#5\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", - "diags": [ - [ - 6, - "OWN040" - ], - [ - 7, - "OWN041" - ] - ] - }, - { - "name": "curated_loops_and_borrows", - "source": "module M\nresource Conn { acquire open release close }\nextern fn Fill(borrow_mut Conn);\nfn f(n: int) {\n let c = acquire Conn(1);\n while (n) {\n borrow_mut c as m { Fill(m); }\n use c;\n }\n overspan c;\n release c;\n}\n", - "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 5,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 1\n }\n ],\n \"label\": \"entry\",\n \"succ\": [\n 1\n ]\n },\n {\n \"id\": 1,\n \"instrs\": [],\n \"label\": \"while.header\",\n \"succ\": [\n 2,\n 3\n ]\n },\n {\n \"id\": 2,\n \"instrs\": [\n {\n \"binding\": 2,\n \"line\": 7,\n \"mut\": true,\n \"op\": \"borrow_start\",\n \"owner\": 1\n },\n {\n \"args\": [\n {\n \"effect\": \"borrow_mut\",\n \"sym\": 2\n }\n ],\n \"callee\": \"Fill\",\n \"line\": 7,\n \"op\": \"invoke\"\n },\n {\n \"binding\": 2,\n \"line\": 7,\n \"mut\": true,\n \"op\": \"borrow_end\",\n \"owner\": 1\n },\n {\n \"line\": 8,\n \"op\": \"use\",\n \"sym\": 1\n }\n ],\n \"label\": \"while.body\",\n \"succ\": [\n 1\n ]\n },\n {\n \"id\": 3,\n \"instrs\": [\n {\n \"line\": 10,\n \"op\": \"overspan\",\n \"sym\": 1\n },\n {\n \"line\": 11,\n \"op\": \"release\",\n \"sym\": 1\n }\n ],\n \"label\": \"while.after\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"f\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 4,\n \"is_param_borrow\": false,\n \"kind\": \"plain\",\n \"name\": \"n\",\n \"origin\": \"n#4\",\n \"resource_kind\": null,\n \"type_name\": \"int\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 5,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"c\",\n \"origin\": \"c#5\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n },\n {\n \"borrow_is_mut\": true,\n \"buffer\": null,\n \"def_line\": 7,\n \"is_param_borrow\": false,\n \"kind\": \"borrow\",\n \"name\": \"m\",\n \"origin\": null,\n \"resource_kind\": null,\n \"type_name\": null\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", - "diags": [] - }, - { - "name": "curated_resolver_errors", - "source": "module M\nresource Conn { acquire open release close }\nfn f() {\n use undef;\n let a = acquire Missing(1);\n let b = acquire Conn(1);\n let b = acquire Conn(2);\n release b;\n let c = b;\n}\n", - "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 5,\n \"op\": \"acquire\",\n \"resource\": \"Missing\",\n \"sym\": 0\n },\n {\n \"line\": 6,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 1\n },\n {\n \"line\": 7,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 2\n },\n {\n \"line\": 8,\n \"op\": \"release\",\n \"sym\": 2\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"f\",\n \"params\": [],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 5,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"a\",\n \"origin\": \"a#5\",\n \"resource_kind\": null,\n \"type_name\": \"Missing\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 6,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"b\",\n \"origin\": \"b#6\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 7,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"b\",\n \"origin\": \"b#7\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", - "diags": [ - [ - 4, - "OWN030" - ], - [ - 5, - "OWN030" - ], - [ - 7, - "OWN031" - ], - [ - 9, - "OWN032" - ] - ] } ] } From f40d87599bff28638e97922260c965a7f750213e Mon Sep 17 00:00:00 2001 From: PhysShell <45852143+PhysShell@users.noreply.github.com> Date: Sat, 10 Oct 2026 18:06:09 +0500 Subject: [PATCH 5/7] test(h28): refresh diagnostics parity fixture --- tests/fixtures/diag_parity.json | 166 ++++++++++++++++++-------------- 1 file changed, 92 insertions(+), 74 deletions(-) diff --git a/tests/fixtures/diag_parity.json b/tests/fixtures/diag_parity.json index f3822cb4..79937058 100644 --- a/tests/fixtures/diag_parity.json +++ b/tests/fixtures/diag_parity.json @@ -1,6 +1,24 @@ { "comment": "GENERATED by tests/test_diag_fixtures.py --write; do not edit. Python (ownlang) is authoritative; rust/crates/own-analysis replays every case through the `check` surface and must match the ordered (line, code) verdict list exactly (issue #214, P-022 step 4).", "cases": [ + { + "name": "corpus/ownership-lab/h28-command/fx/CommandDispose.own", + "source": "// H-28-CMD: the ENGINE-RUNNABLE half of the fixture.\n//\n// fx/CommandDispose.cs needs the Roslyn extractor, which needs a .NET SDK; this file needs\n// only `python -m ownlang`, so the engine arm of the finding is reproducible anywhere.\n//\n// Honest frame (the same one corpus/real-world/README.md states): this is a MANUAL REDUCTION\n// of the C#, not C# the checker read. It models the two obligations the extractor's\n// IsOwningFactory appears to mint for this family -- IDbConnection.CreateCommand() -> DbCommand\n// and IDbCommand.ExecuteReader() -> DbDataReader -- and it models the H-28 runtime fact that\n// DataTable.Load(reader) closes the reader. This hand-written reduction is NOT the G3 C# path;\n// the production extractor -> OwnIR -> verdict ran separately in workflow #38048287923. This\n// reduction encodes the witnessed callee effect explicitly so the core-only result stays distinct.\nmodule H28Cmd\n\nresource Command {\n acquire open\n release dispose\n kind \"disposable\"\n emit_type \"DbCommand\"\n emit_acquire \"{args}.CreateCommand()\"\n emit_release \"{0}.Dispose()\"\n}\n\nresource Reader {\n acquire open\n release dispose\n kind \"disposable\"\n emit_type \"DbDataReader\"\n emit_acquire \"{args}.ExecuteReader()\"\n emit_release \"{0}.Dispose()\"\n}\n\n// B -- the demanding instance (victor-wiki/DatabaseManager DbInterpreter.GetDataTableAsync:699).\n// table.Load(reader) closed the reader: witnessed RELEASE_IF_LAST_RESULT_SET by the H-28\n// runtime falsifier (corpus/ownership-lab/h28/falsifier/falsifier.out, F1 IsClosed=True on\n// Sqlite AND Npgsql). The command is never released.\n// EXPECTED for this manual reduction: OWN001 on 'command'. This does not decide whether the\n// diagnostic's practical implication is misleading for a provider; provider effects are not\n// encoded in this reduction. G3 independently confirmed the C# extraction pattern.\nfn B_load_neither_disposed(conn: int) {\n let command = acquire Command(conn);\n let reader = acquire Reader(command);\n release reader; // DataTable.Load(reader) -> reader.Close()\n // no `release command;`\n}\n\n// A -- control: both released. EXPECTED: clean.\nfn A_load_both_released(conn: int) {\n let command = acquire Command(conn);\n let reader = acquire Reader(command);\n release reader;\n release command;\n}\n\n// D -- control: the command released after Load. EXPECTED: clean in this manual reduction.\n// G2 confirmed this is the arm that returned Microsoft.Data.Sqlite's directly observed\n// command-held sqlite3_stmt count to baseline. This manual reduction models only ownership ops.\nfn D_command_released_after_load(conn: int) {\n let command = acquire Command(conn);\n let reader = acquire Reader(command);\n release reader;\n release command;\n}\n\n// E -- control with NO Load: the reader obligation is never handed to a callee, so it stays\n// tracked. EXPECTED: OWN001 on 'command' AND OWN001 on 'reader' -- two findings, which is how\n// you can tell \"the reader was released\" apart from \"the reader was untracked by the escape\n// rule\" without reading the extractor.\nfn E_no_load_both_leak(conn: int) {\n let command = acquire Command(conn);\n let reader = acquire Reader(command);\n}\n", + "diags": [ + [ + 43, + "OWN001" + ], + [ + 71, + "OWN001" + ], + [ + 71, + "OWN001" + ] + ] + }, { "name": "corpus/real-world/ado-executereader-leak/case.own", "source": "// OwnLang model of the canonical ADO.NET reader leak (P1a, ADO.NET tranche). A DbDataReader\n// from DbCommand.ExecuteReader() is a fresh owned IDisposable the caller must dispose; here it\n// is acquired, read, and never released — the generic OWN001 leak. The command is a borrowed\n// parameter and the reader does not escape (only a count is returned), so it stays tracked.\n// See notes.md for the recognition rule (return type implements System.Data.IDataReader).\nmodule Corpus\nresource Reader {\n acquire open\n release dispose\n kind \"disposable\"\n emit_type \"DbDataReader\"\n emit_acquire \"{args}.ExecuteReader()\"\n emit_release \"{0}.Dispose()\"\n}\nfn Run(cmd: int) {\n let reader = acquire Reader(cmd); // var reader = cmd.ExecuteReader()\n // rows read via reader.Read(); no `release reader;` — never disposed (OWN001)\n}\n", @@ -615,6 +633,80 @@ ] ] }, + { + "name": "curated_buffer_policy", + "source": "module M\nfn f(n: int) {\n let a = Buffer.inline(999999);\n release a;\n}\n", + "diags": [ + [ + 3, + "OWN019" + ] + ] + }, + { + "name": "curated_double_release", + "source": "module M\nresource Conn { acquire open release close }\nresource Token { acquire mint release burn kind \"subscription token\" }\nextern fn Fill(borrow_mut Conn);\nextern fn Hash(borrow Conn);\nextern fn Store(consume Conn);\nfn f() {\n let c = acquire Conn(1);\n release c;\n release c;\n return;\n}\n", + "diags": [ + [ + 10, + "OWN003" + ] + ] + }, + { + "name": "curated_leak_and_release", + "source": "module M\nresource Conn { acquire open release close }\nresource Token { acquire mint release burn kind \"subscription token\" }\nextern fn Fill(borrow_mut Conn);\nextern fn Hash(borrow Conn);\nextern fn Store(consume Conn);\nfn leaks() {\n let c = acquire Conn(1);\n return;\n}\nfn clean() {\n let c = acquire Conn(1);\n release c;\n return;\n}\n", + "diags": [ + [ + 9, + "OWN001" + ] + ] + }, + { + "name": "curated_maybe_release_branch", + "source": "module M\nresource Conn { acquire open release close }\nresource Token { acquire mint release burn kind \"subscription token\" }\nextern fn Fill(borrow_mut Conn);\nextern fn Hash(borrow Conn);\nextern fn Store(consume Conn);\nfn f(n: int) {\n let c = acquire Conn(1);\n if (n) { release c; }\n Hash(c);\n return;\n}\n", + "diags": [ + [ + 10, + "OWN009" + ], + [ + 11, + "OWN001" + ] + ] + }, + { + "name": "curated_parse_error_is_own020", + "source": "module M\nfn f( {\n", + "diags": [ + [ + 2, + "OWN020" + ] + ] + }, + { + "name": "curated_use_after_move", + "source": "module M\nresource Conn { acquire open release close }\nresource Token { acquire mint release burn kind \"subscription token\" }\nextern fn Fill(borrow_mut Conn);\nextern fn Hash(borrow Conn);\nextern fn Store(consume Conn);\nfn f() {\n let c = acquire Conn(1);\n let d = move c;\n Hash(c);\n Store(d);\n return;\n}\n", + "diags": [ + [ + 10, + "OWN005" + ] + ] + }, + { + "name": "curated_use_after_release", + "source": "module M\nresource Conn { acquire open release close }\nresource Token { acquire mint release burn kind \"subscription token\" }\nextern fn Fill(borrow_mut Conn);\nextern fn Hash(borrow Conn);\nextern fn Store(consume Conn);\nfn f() {\n let c = acquire Conn(1);\n release c;\n Hash(c);\n return;\n}\n", + "diags": [ + [ + 10, + "OWN002" + ] + ] + }, { "name": "examples/bad_leak_branch.own", "source": "module LeakBranchDemo\n\nresource Conn {\n acquire open\n release close\n}\n\n// OWN001: released on the 'then' path only; leaks through 'else'.\nfn maybe(flag: int) {\n let c = acquire Conn(flag);\n if (flag) {\n release c;\n }\n}\n", @@ -788,80 +880,6 @@ "name": "examples/ok_pool.own", "source": "module PoolDemo\n\nresource Buffer {\n acquire rent\n release give\n}\n\n// Canonical happy path: rent a buffer, look at it mutably then read it,\n// hand it back. Checker proves it is returned on the single path; codegen\n// hoists the release into a finally for exception-safety.\nfn process(size: int) {\n let buf = acquire Buffer(size);\n\n borrow_mut buf as bytes {\n use bytes;\n }\n\n borrow buf as bytes {\n use bytes;\n }\n\n release buf;\n}\n", "diags": [] - }, - { - "name": "curated_leak_and_release", - "source": "module M\nresource Conn { acquire open release close }\nresource Token { acquire mint release burn kind \"subscription token\" }\nextern fn Fill(borrow_mut Conn);\nextern fn Hash(borrow Conn);\nextern fn Store(consume Conn);\nfn leaks() {\n let c = acquire Conn(1);\n return;\n}\nfn clean() {\n let c = acquire Conn(1);\n release c;\n return;\n}\n", - "diags": [ - [ - 9, - "OWN001" - ] - ] - }, - { - "name": "curated_use_after_release", - "source": "module M\nresource Conn { acquire open release close }\nresource Token { acquire mint release burn kind \"subscription token\" }\nextern fn Fill(borrow_mut Conn);\nextern fn Hash(borrow Conn);\nextern fn Store(consume Conn);\nfn f() {\n let c = acquire Conn(1);\n release c;\n Hash(c);\n return;\n}\n", - "diags": [ - [ - 10, - "OWN002" - ] - ] - }, - { - "name": "curated_double_release", - "source": "module M\nresource Conn { acquire open release close }\nresource Token { acquire mint release burn kind \"subscription token\" }\nextern fn Fill(borrow_mut Conn);\nextern fn Hash(borrow Conn);\nextern fn Store(consume Conn);\nfn f() {\n let c = acquire Conn(1);\n release c;\n release c;\n return;\n}\n", - "diags": [ - [ - 10, - "OWN003" - ] - ] - }, - { - "name": "curated_use_after_move", - "source": "module M\nresource Conn { acquire open release close }\nresource Token { acquire mint release burn kind \"subscription token\" }\nextern fn Fill(borrow_mut Conn);\nextern fn Hash(borrow Conn);\nextern fn Store(consume Conn);\nfn f() {\n let c = acquire Conn(1);\n let d = move c;\n Hash(c);\n Store(d);\n return;\n}\n", - "diags": [ - [ - 10, - "OWN005" - ] - ] - }, - { - "name": "curated_maybe_release_branch", - "source": "module M\nresource Conn { acquire open release close }\nresource Token { acquire mint release burn kind \"subscription token\" }\nextern fn Fill(borrow_mut Conn);\nextern fn Hash(borrow Conn);\nextern fn Store(consume Conn);\nfn f(n: int) {\n let c = acquire Conn(1);\n if (n) { release c; }\n Hash(c);\n return;\n}\n", - "diags": [ - [ - 10, - "OWN009" - ], - [ - 11, - "OWN001" - ] - ] - }, - { - "name": "curated_buffer_policy", - "source": "module M\nfn f(n: int) {\n let a = Buffer.inline(999999);\n release a;\n}\n", - "diags": [ - [ - 3, - "OWN019" - ] - ] - }, - { - "name": "curated_parse_error_is_own020", - "source": "module M\nfn f( {\n", - "diags": [ - [ - 2, - "OWN020" - ] - ] } ] } From ab458f7c254fcfc3443c6fcd8eb0e0a84e56d1d0 Mon Sep 17 00:00:00 2001 From: PhysShell <45852143+PhysShell@users.noreply.github.com> Date: Sat, 10 Oct 2026 18:21:20 +0500 Subject: [PATCH 6/7] test(h28): preserve parity generator case order --- tests/fixtures/cfg_parity.json | 176 ++++++++++++++++----------------- 1 file changed, 88 insertions(+), 88 deletions(-) diff --git a/tests/fixtures/cfg_parity.json b/tests/fixtures/cfg_parity.json index 5f4682cd..49394d1d 100644 --- a/tests/fixtures/cfg_parity.json +++ b/tests/fixtures/cfg_parity.json @@ -373,94 +373,6 @@ "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 16,\n \"op\": \"acquire\",\n \"resource\": \"Subscription\",\n \"sym\": 1\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"CustomerViewModel\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 15,\n \"is_param_borrow\": false,\n \"kind\": \"plain\",\n \"name\": \"bus\",\n \"origin\": \"bus#15\",\n \"resource_kind\": null,\n \"type_name\": \"int\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 16,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"customerChanged\",\n \"origin\": \"customerChanged#16\",\n \"resource_kind\": \"subscription token\",\n \"type_name\": \"Subscription\"\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", "diags": [] }, - { - "name": "curated_buffer_policy", - "source": "module M\npolicy Fast {\n inline_bytes = 256;\n clear_on_release = true;\n fallback = pool;\n trace = debug;\n counters = true;\n}\nfn f(n: int) {\n let a = Buffer.scratch(64, policy = Fast, clear = true);\n let b = Buffer.stack(size, max = 1024);\n let c = Buffer.inline(32);\n let d = Buffer.pooled(n);\n let e = Buffer.native(n);\n release a; release b; release c; release d; release e;\n}\n", - "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"buffer\": {\n \"clear_on_release\": true,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": true,\n \"inline_bytes\": 256,\n \"line\": 10,\n \"mode\": \"scratch\",\n \"policy_name\": \"Fast\",\n \"sensitive\": false,\n \"size_const\": 64,\n \"size_var\": null,\n \"trace\": true\n },\n \"line\": 10,\n \"op\": \"acquire_buffer\",\n \"sym\": 1\n },\n {\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": true,\n \"fallback_pool\": false,\n \"inline_bytes\": 1024,\n \"line\": 11,\n \"mode\": \"stack\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"size\",\n \"trace\": true\n },\n \"line\": 11,\n \"op\": \"acquire_buffer\",\n \"sym\": 2\n },\n {\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": true,\n \"fallback_pool\": false,\n \"inline_bytes\": 32,\n \"line\": 12,\n \"mode\": \"inline\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": 32,\n \"size_var\": null,\n \"trace\": true\n },\n \"line\": 12,\n \"op\": \"acquire_buffer\",\n \"sym\": 3\n },\n {\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": true,\n \"inline_bytes\": 1024,\n \"line\": 13,\n \"mode\": \"pooled\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"n\",\n \"trace\": true\n },\n \"line\": 13,\n \"op\": \"acquire_buffer\",\n \"sym\": 4\n },\n {\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": false,\n \"inline_bytes\": 1024,\n \"line\": 14,\n \"mode\": \"native\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"n\",\n \"trace\": true\n },\n \"line\": 14,\n \"op\": \"acquire_buffer\",\n \"sym\": 5\n },\n {\n \"line\": 15,\n \"op\": \"release\",\n \"sym\": 1\n },\n {\n \"line\": 15,\n \"op\": \"release\",\n \"sym\": 2\n },\n {\n \"line\": 15,\n \"op\": \"release\",\n \"sym\": 3\n },\n {\n \"line\": 15,\n \"op\": \"release\",\n \"sym\": 4\n },\n {\n \"line\": 15,\n \"op\": \"release\",\n \"sym\": 5\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"f\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 9,\n \"is_param_borrow\": false,\n \"kind\": \"plain\",\n \"name\": \"n\",\n \"origin\": \"n#9\",\n \"resource_kind\": null,\n \"type_name\": \"int\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": {\n \"clear_on_release\": true,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": true,\n \"inline_bytes\": 256,\n \"line\": 10,\n \"mode\": \"scratch\",\n \"policy_name\": \"Fast\",\n \"sensitive\": false,\n \"size_const\": 64,\n \"size_var\": null,\n \"trace\": true\n },\n \"def_line\": 10,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"a\",\n \"origin\": \"a#10:11\",\n \"resource_kind\": null,\n \"type_name\": null\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": true,\n \"fallback_pool\": false,\n \"inline_bytes\": 1024,\n \"line\": 11,\n \"mode\": \"stack\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"size\",\n \"trace\": true\n },\n \"def_line\": 11,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"b\",\n \"origin\": \"b#11:11\",\n \"resource_kind\": null,\n \"type_name\": null\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": true,\n \"fallback_pool\": false,\n \"inline_bytes\": 32,\n \"line\": 12,\n \"mode\": \"inline\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": 32,\n \"size_var\": null,\n \"trace\": true\n },\n \"def_line\": 12,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"c\",\n \"origin\": \"c#12:11\",\n \"resource_kind\": null,\n \"type_name\": null\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": true,\n \"inline_bytes\": 1024,\n \"line\": 13,\n \"mode\": \"pooled\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"n\",\n \"trace\": true\n },\n \"def_line\": 13,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"d\",\n \"origin\": \"d#13:11\",\n \"resource_kind\": null,\n \"type_name\": null\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": false,\n \"inline_bytes\": 1024,\n \"line\": 14,\n \"mode\": \"native\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"n\",\n \"trace\": true\n },\n \"def_line\": 14,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"e\",\n \"origin\": \"e#14:11\",\n \"resource_kind\": null,\n \"type_name\": null\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", - "diags": [ - [ - 11, - "OWN030" - ] - ] - }, - { - "name": "curated_call_errors", - "source": "module M\nresource Conn { acquire open release close }\nextern fn Need(consume Conn);\nfn f() {\n let c = acquire Conn(1);\n Unknown(c);\n Need(c, c);\n Need(c);\n}\n", - "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 5,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 0\n },\n {\n \"args\": [\n {\n \"effect\": \"consume\",\n \"sym\": 0\n }\n ],\n \"callee\": \"Need\",\n \"line\": 8,\n \"op\": \"invoke\"\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"f\",\n \"params\": [],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 5,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"c\",\n \"origin\": \"c#5\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", - "diags": [ - [ - 6, - "OWN040" - ], - [ - 7, - "OWN041" - ] - ] - }, - { - "name": "curated_full_flow", - "source": "module M\nresource Conn { acquire open release close }\nresource Token { acquire mint release burn kind \"subscription token\" }\nextern fn Fill(borrow_mut Conn);\nextern fn Hash(borrow Conn);\nextern fn Store(consume Conn);\nfn f(n: int) {\n let c = acquire Conn(1);\n if (n) {\n borrow c as r { use r; }\n }\n let d = move c;\n Store(d);\n return;\n}\nfn g(n: int) {\n let b = Buffer.scratch(n);\n release b;\n}\n", - "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 8,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 1\n }\n ],\n \"label\": \"entry\",\n \"succ\": [\n 1,\n 2\n ]\n },\n {\n \"id\": 1,\n \"instrs\": [\n {\n \"binding\": 2,\n \"line\": 10,\n \"mut\": false,\n \"op\": \"borrow_start\",\n \"owner\": 1\n },\n {\n \"line\": 10,\n \"op\": \"use\",\n \"sym\": 2\n },\n {\n \"binding\": 2,\n \"line\": 10,\n \"mut\": false,\n \"op\": \"borrow_end\",\n \"owner\": 1\n }\n ],\n \"label\": \"then\",\n \"succ\": [\n 3\n ]\n },\n {\n \"id\": 2,\n \"instrs\": [],\n \"label\": \"else\",\n \"succ\": [\n 3\n ]\n },\n {\n \"id\": 3,\n \"instrs\": [\n {\n \"dst\": 3,\n \"line\": 12,\n \"op\": \"move_into\",\n \"src\": 1\n },\n {\n \"args\": [\n {\n \"effect\": \"consume\",\n \"sym\": 3\n }\n ],\n \"callee\": \"Store\",\n \"line\": 13,\n \"op\": \"invoke\"\n },\n {\n \"line\": 14,\n \"op\": \"return\",\n \"sym\": null\n }\n ],\n \"label\": \"merge\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"f\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 7,\n \"is_param_borrow\": false,\n \"kind\": \"plain\",\n \"name\": \"n\",\n \"origin\": \"n#7\",\n \"resource_kind\": null,\n \"type_name\": \"int\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 8,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"c\",\n \"origin\": \"c#8\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n },\n {\n \"borrow_is_mut\": false,\n \"buffer\": null,\n \"def_line\": 10,\n \"is_param_borrow\": false,\n \"kind\": \"borrow\",\n \"name\": \"r\",\n \"origin\": null,\n \"resource_kind\": null,\n \"type_name\": null\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 12,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"d\",\n \"origin\": \"c#8\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n }\n ]\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": true,\n \"inline_bytes\": 1024,\n \"line\": 17,\n \"mode\": \"scratch\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"n\",\n \"trace\": true\n },\n \"line\": 17,\n \"op\": \"acquire_buffer\",\n \"sym\": 1\n },\n {\n \"line\": 18,\n \"op\": \"release\",\n \"sym\": 1\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"g\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 16,\n \"is_param_borrow\": false,\n \"kind\": \"plain\",\n \"name\": \"n\",\n \"origin\": \"n#16\",\n \"resource_kind\": null,\n \"type_name\": \"int\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": true,\n \"inline_bytes\": 1024,\n \"line\": 17,\n \"mode\": \"scratch\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"n\",\n \"trace\": true\n },\n \"def_line\": 17,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"b\",\n \"origin\": \"b#17:13\",\n \"resource_kind\": null,\n \"type_name\": null\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", - "diags": [] - }, - { - "name": "curated_loops_and_borrows", - "source": "module M\nresource Conn { acquire open release close }\nextern fn Fill(borrow_mut Conn);\nfn f(n: int) {\n let c = acquire Conn(1);\n while (n) {\n borrow_mut c as m { Fill(m); }\n use c;\n }\n overspan c;\n release c;\n}\n", - "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 5,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 1\n }\n ],\n \"label\": \"entry\",\n \"succ\": [\n 1\n ]\n },\n {\n \"id\": 1,\n \"instrs\": [],\n \"label\": \"while.header\",\n \"succ\": [\n 2,\n 3\n ]\n },\n {\n \"id\": 2,\n \"instrs\": [\n {\n \"binding\": 2,\n \"line\": 7,\n \"mut\": true,\n \"op\": \"borrow_start\",\n \"owner\": 1\n },\n {\n \"args\": [\n {\n \"effect\": \"borrow_mut\",\n \"sym\": 2\n }\n ],\n \"callee\": \"Fill\",\n \"line\": 7,\n \"op\": \"invoke\"\n },\n {\n \"binding\": 2,\n \"line\": 7,\n \"mut\": true,\n \"op\": \"borrow_end\",\n \"owner\": 1\n },\n {\n \"line\": 8,\n \"op\": \"use\",\n \"sym\": 1\n }\n ],\n \"label\": \"while.body\",\n \"succ\": [\n 1\n ]\n },\n {\n \"id\": 3,\n \"instrs\": [\n {\n \"line\": 10,\n \"op\": \"overspan\",\n \"sym\": 1\n },\n {\n \"line\": 11,\n \"op\": \"release\",\n \"sym\": 1\n }\n ],\n \"label\": \"while.after\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"f\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 4,\n \"is_param_borrow\": false,\n \"kind\": \"plain\",\n \"name\": \"n\",\n \"origin\": \"n#4\",\n \"resource_kind\": null,\n \"type_name\": \"int\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 5,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"c\",\n \"origin\": \"c#5\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n },\n {\n \"borrow_is_mut\": true,\n \"buffer\": null,\n \"def_line\": 7,\n \"is_param_borrow\": false,\n \"kind\": \"borrow\",\n \"name\": \"m\",\n \"origin\": null,\n \"resource_kind\": null,\n \"type_name\": null\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", - "diags": [] - }, - { - "name": "curated_resolver_errors", - "source": "module M\nresource Conn { acquire open release close }\nfn f() {\n use undef;\n let a = acquire Missing(1);\n let b = acquire Conn(1);\n let b = acquire Conn(2);\n release b;\n let c = b;\n}\n", - "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 5,\n \"op\": \"acquire\",\n \"resource\": \"Missing\",\n \"sym\": 0\n },\n {\n \"line\": 6,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 1\n },\n {\n \"line\": 7,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 2\n },\n {\n \"line\": 8,\n \"op\": \"release\",\n \"sym\": 2\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"f\",\n \"params\": [],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 5,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"a\",\n \"origin\": \"a#5\",\n \"resource_kind\": null,\n \"type_name\": \"Missing\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 6,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"b\",\n \"origin\": \"b#6\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 7,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"b\",\n \"origin\": \"b#7\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", - "diags": [ - [ - 4, - "OWN030" - ], - [ - 5, - "OWN030" - ], - [ - 7, - "OWN031" - ], - [ - 9, - "OWN032" - ] - ] - }, - { - "name": "curated_return_variants", - "source": "module M\nresource Conn { acquire open release close }\nresource Other { acquire open release close }\nfn ret_owned() -> Conn { let c = acquire Conn(1); return c; }\nfn ret_borrow(x: &Conn) -> Conn { return x; }\nfn ret_plain() -> Conn { let n = 3; return n; }\nfn ret_wrong() -> Conn { let o = acquire Other(1); return o; }\nfn ret_void_val() { let c = acquire Conn(1); return c; }\nfn ret_missing() -> Conn { }\n", - "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 4,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 0\n },\n {\n \"line\": 4,\n \"op\": \"return\",\n \"sym\": 0\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": true,\n \"name\": \"ret_owned\",\n \"params\": [],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 4,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"c\",\n \"origin\": \"c#4\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n }\n ]\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 5,\n \"op\": \"return\",\n \"sym\": null\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": true,\n \"name\": \"ret_borrow\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": false,\n \"buffer\": null,\n \"def_line\": 5,\n \"is_param_borrow\": true,\n \"kind\": \"borrow\",\n \"name\": \"x\",\n \"origin\": \"x#5\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n }\n ]\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 6,\n \"op\": \"return\",\n \"sym\": null\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": true,\n \"name\": \"ret_plain\",\n \"params\": [],\n \"symbols\": []\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 7,\n \"op\": \"acquire\",\n \"resource\": \"Other\",\n \"sym\": 0\n },\n {\n \"line\": 7,\n \"op\": \"return\",\n \"sym\": 0\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": true,\n \"name\": \"ret_wrong\",\n \"params\": [],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 7,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"o\",\n \"origin\": \"o#7\",\n \"resource_kind\": null,\n \"type_name\": \"Other\"\n }\n ]\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 8,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 0\n },\n {\n \"line\": 8,\n \"op\": \"return\",\n \"sym\": null\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"ret_void_val\",\n \"params\": [],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 8,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"c\",\n \"origin\": \"c#8\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n }\n ]\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": true,\n \"name\": \"ret_missing\",\n \"params\": [],\n \"symbols\": []\n }\n ],\n \"ownlang_cfg_version\": 0\n}", - "diags": [ - [ - 5, - "OWN004" - ], - [ - 6, - "OWN035" - ], - [ - 7, - "OWN035" - ], - [ - 8, - "OWN035" - ], - [ - 9, - "OWN033" - ] - ] - }, { "name": "examples/bad_leak_branch.own", "source": "module LeakBranchDemo\n\nresource Conn {\n acquire open\n release close\n}\n\n// OWN001: released on the 'then' path only; leaks through 'else'.\nfn maybe(flag: int) {\n let c = acquire Conn(flag);\n if (flag) {\n release c;\n }\n}\n", @@ -585,6 +497,94 @@ "source": "module PoolDemo\n\nresource Buffer {\n acquire rent\n release give\n}\n\n// Canonical happy path: rent a buffer, look at it mutably then read it,\n// hand it back. Checker proves it is returned on the single path; codegen\n// hoists the release into a finally for exception-safety.\nfn process(size: int) {\n let buf = acquire Buffer(size);\n\n borrow_mut buf as bytes {\n use bytes;\n }\n\n borrow buf as bytes {\n use bytes;\n }\n\n release buf;\n}\n", "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 12,\n \"op\": \"acquire\",\n \"resource\": \"Buffer\",\n \"sym\": 1\n },\n {\n \"binding\": 2,\n \"line\": 14,\n \"mut\": true,\n \"op\": \"borrow_start\",\n \"owner\": 1\n },\n {\n \"line\": 15,\n \"op\": \"use\",\n \"sym\": 2\n },\n {\n \"binding\": 2,\n \"line\": 14,\n \"mut\": true,\n \"op\": \"borrow_end\",\n \"owner\": 1\n },\n {\n \"binding\": 3,\n \"line\": 18,\n \"mut\": false,\n \"op\": \"borrow_start\",\n \"owner\": 1\n },\n {\n \"line\": 19,\n \"op\": \"use\",\n \"sym\": 3\n },\n {\n \"binding\": 3,\n \"line\": 18,\n \"mut\": false,\n \"op\": \"borrow_end\",\n \"owner\": 1\n },\n {\n \"line\": 22,\n \"op\": \"release\",\n \"sym\": 1\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"process\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 11,\n \"is_param_borrow\": false,\n \"kind\": \"plain\",\n \"name\": \"size\",\n \"origin\": \"size#11\",\n \"resource_kind\": null,\n \"type_name\": \"int\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 12,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"buf\",\n \"origin\": \"buf#12\",\n \"resource_kind\": null,\n \"type_name\": \"Buffer\"\n },\n {\n \"borrow_is_mut\": true,\n \"buffer\": null,\n \"def_line\": 14,\n \"is_param_borrow\": false,\n \"kind\": \"borrow\",\n \"name\": \"bytes\",\n \"origin\": null,\n \"resource_kind\": null,\n \"type_name\": null\n },\n {\n \"borrow_is_mut\": false,\n \"buffer\": null,\n \"def_line\": 18,\n \"is_param_borrow\": false,\n \"kind\": \"borrow\",\n \"name\": \"bytes\",\n \"origin\": null,\n \"resource_kind\": null,\n \"type_name\": null\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", "diags": [] + }, + { + "name": "curated_full_flow", + "source": "module M\nresource Conn { acquire open release close }\nresource Token { acquire mint release burn kind \"subscription token\" }\nextern fn Fill(borrow_mut Conn);\nextern fn Hash(borrow Conn);\nextern fn Store(consume Conn);\nfn f(n: int) {\n let c = acquire Conn(1);\n if (n) {\n borrow c as r { use r; }\n }\n let d = move c;\n Store(d);\n return;\n}\nfn g(n: int) {\n let b = Buffer.scratch(n);\n release b;\n}\n", + "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 8,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 1\n }\n ],\n \"label\": \"entry\",\n \"succ\": [\n 1,\n 2\n ]\n },\n {\n \"id\": 1,\n \"instrs\": [\n {\n \"binding\": 2,\n \"line\": 10,\n \"mut\": false,\n \"op\": \"borrow_start\",\n \"owner\": 1\n },\n {\n \"line\": 10,\n \"op\": \"use\",\n \"sym\": 2\n },\n {\n \"binding\": 2,\n \"line\": 10,\n \"mut\": false,\n \"op\": \"borrow_end\",\n \"owner\": 1\n }\n ],\n \"label\": \"then\",\n \"succ\": [\n 3\n ]\n },\n {\n \"id\": 2,\n \"instrs\": [],\n \"label\": \"else\",\n \"succ\": [\n 3\n ]\n },\n {\n \"id\": 3,\n \"instrs\": [\n {\n \"dst\": 3,\n \"line\": 12,\n \"op\": \"move_into\",\n \"src\": 1\n },\n {\n \"args\": [\n {\n \"effect\": \"consume\",\n \"sym\": 3\n }\n ],\n \"callee\": \"Store\",\n \"line\": 13,\n \"op\": \"invoke\"\n },\n {\n \"line\": 14,\n \"op\": \"return\",\n \"sym\": null\n }\n ],\n \"label\": \"merge\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"f\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 7,\n \"is_param_borrow\": false,\n \"kind\": \"plain\",\n \"name\": \"n\",\n \"origin\": \"n#7\",\n \"resource_kind\": null,\n \"type_name\": \"int\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 8,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"c\",\n \"origin\": \"c#8\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n },\n {\n \"borrow_is_mut\": false,\n \"buffer\": null,\n \"def_line\": 10,\n \"is_param_borrow\": false,\n \"kind\": \"borrow\",\n \"name\": \"r\",\n \"origin\": null,\n \"resource_kind\": null,\n \"type_name\": null\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 12,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"d\",\n \"origin\": \"c#8\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n }\n ]\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": true,\n \"inline_bytes\": 1024,\n \"line\": 17,\n \"mode\": \"scratch\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"n\",\n \"trace\": true\n },\n \"line\": 17,\n \"op\": \"acquire_buffer\",\n \"sym\": 1\n },\n {\n \"line\": 18,\n \"op\": \"release\",\n \"sym\": 1\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"g\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 16,\n \"is_param_borrow\": false,\n \"kind\": \"plain\",\n \"name\": \"n\",\n \"origin\": \"n#16\",\n \"resource_kind\": null,\n \"type_name\": \"int\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": true,\n \"inline_bytes\": 1024,\n \"line\": 17,\n \"mode\": \"scratch\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"n\",\n \"trace\": true\n },\n \"def_line\": 17,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"b\",\n \"origin\": \"b#17:13\",\n \"resource_kind\": null,\n \"type_name\": null\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", + "diags": [] + }, + { + "name": "curated_buffer_policy", + "source": "module M\npolicy Fast {\n inline_bytes = 256;\n clear_on_release = true;\n fallback = pool;\n trace = debug;\n counters = true;\n}\nfn f(n: int) {\n let a = Buffer.scratch(64, policy = Fast, clear = true);\n let b = Buffer.stack(size, max = 1024);\n let c = Buffer.inline(32);\n let d = Buffer.pooled(n);\n let e = Buffer.native(n);\n release a; release b; release c; release d; release e;\n}\n", + "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"buffer\": {\n \"clear_on_release\": true,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": true,\n \"inline_bytes\": 256,\n \"line\": 10,\n \"mode\": \"scratch\",\n \"policy_name\": \"Fast\",\n \"sensitive\": false,\n \"size_const\": 64,\n \"size_var\": null,\n \"trace\": true\n },\n \"line\": 10,\n \"op\": \"acquire_buffer\",\n \"sym\": 1\n },\n {\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": true,\n \"fallback_pool\": false,\n \"inline_bytes\": 1024,\n \"line\": 11,\n \"mode\": \"stack\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"size\",\n \"trace\": true\n },\n \"line\": 11,\n \"op\": \"acquire_buffer\",\n \"sym\": 2\n },\n {\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": true,\n \"fallback_pool\": false,\n \"inline_bytes\": 32,\n \"line\": 12,\n \"mode\": \"inline\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": 32,\n \"size_var\": null,\n \"trace\": true\n },\n \"line\": 12,\n \"op\": \"acquire_buffer\",\n \"sym\": 3\n },\n {\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": true,\n \"inline_bytes\": 1024,\n \"line\": 13,\n \"mode\": \"pooled\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"n\",\n \"trace\": true\n },\n \"line\": 13,\n \"op\": \"acquire_buffer\",\n \"sym\": 4\n },\n {\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": false,\n \"inline_bytes\": 1024,\n \"line\": 14,\n \"mode\": \"native\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"n\",\n \"trace\": true\n },\n \"line\": 14,\n \"op\": \"acquire_buffer\",\n \"sym\": 5\n },\n {\n \"line\": 15,\n \"op\": \"release\",\n \"sym\": 1\n },\n {\n \"line\": 15,\n \"op\": \"release\",\n \"sym\": 2\n },\n {\n \"line\": 15,\n \"op\": \"release\",\n \"sym\": 3\n },\n {\n \"line\": 15,\n \"op\": \"release\",\n \"sym\": 4\n },\n {\n \"line\": 15,\n \"op\": \"release\",\n \"sym\": 5\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"f\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 9,\n \"is_param_borrow\": false,\n \"kind\": \"plain\",\n \"name\": \"n\",\n \"origin\": \"n#9\",\n \"resource_kind\": null,\n \"type_name\": \"int\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": {\n \"clear_on_release\": true,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": true,\n \"inline_bytes\": 256,\n \"line\": 10,\n \"mode\": \"scratch\",\n \"policy_name\": \"Fast\",\n \"sensitive\": false,\n \"size_const\": 64,\n \"size_var\": null,\n \"trace\": true\n },\n \"def_line\": 10,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"a\",\n \"origin\": \"a#10:11\",\n \"resource_kind\": null,\n \"type_name\": null\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": true,\n \"fallback_pool\": false,\n \"inline_bytes\": 1024,\n \"line\": 11,\n \"mode\": \"stack\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"size\",\n \"trace\": true\n },\n \"def_line\": 11,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"b\",\n \"origin\": \"b#11:11\",\n \"resource_kind\": null,\n \"type_name\": null\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": true,\n \"fallback_pool\": false,\n \"inline_bytes\": 32,\n \"line\": 12,\n \"mode\": \"inline\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": 32,\n \"size_var\": null,\n \"trace\": true\n },\n \"def_line\": 12,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"c\",\n \"origin\": \"c#12:11\",\n \"resource_kind\": null,\n \"type_name\": null\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": true,\n \"inline_bytes\": 1024,\n \"line\": 13,\n \"mode\": \"pooled\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"n\",\n \"trace\": true\n },\n \"def_line\": 13,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"d\",\n \"origin\": \"d#13:11\",\n \"resource_kind\": null,\n \"type_name\": null\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": {\n \"clear_on_release\": false,\n \"counters\": true,\n \"elem\": \"byte\",\n \"fallback_forbidden\": false,\n \"fallback_pool\": false,\n \"inline_bytes\": 1024,\n \"line\": 14,\n \"mode\": \"native\",\n \"policy_name\": null,\n \"sensitive\": false,\n \"size_const\": null,\n \"size_var\": \"n\",\n \"trace\": true\n },\n \"def_line\": 14,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"e\",\n \"origin\": \"e#14:11\",\n \"resource_kind\": null,\n \"type_name\": null\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", + "diags": [ + [ + 11, + "OWN030" + ] + ] + }, + { + "name": "curated_return_variants", + "source": "module M\nresource Conn { acquire open release close }\nresource Other { acquire open release close }\nfn ret_owned() -> Conn { let c = acquire Conn(1); return c; }\nfn ret_borrow(x: &Conn) -> Conn { return x; }\nfn ret_plain() -> Conn { let n = 3; return n; }\nfn ret_wrong() -> Conn { let o = acquire Other(1); return o; }\nfn ret_void_val() { let c = acquire Conn(1); return c; }\nfn ret_missing() -> Conn { }\n", + "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 4,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 0\n },\n {\n \"line\": 4,\n \"op\": \"return\",\n \"sym\": 0\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": true,\n \"name\": \"ret_owned\",\n \"params\": [],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 4,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"c\",\n \"origin\": \"c#4\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n }\n ]\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 5,\n \"op\": \"return\",\n \"sym\": null\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": true,\n \"name\": \"ret_borrow\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": false,\n \"buffer\": null,\n \"def_line\": 5,\n \"is_param_borrow\": true,\n \"kind\": \"borrow\",\n \"name\": \"x\",\n \"origin\": \"x#5\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n }\n ]\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 6,\n \"op\": \"return\",\n \"sym\": null\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": true,\n \"name\": \"ret_plain\",\n \"params\": [],\n \"symbols\": []\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 7,\n \"op\": \"acquire\",\n \"resource\": \"Other\",\n \"sym\": 0\n },\n {\n \"line\": 7,\n \"op\": \"return\",\n \"sym\": 0\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": true,\n \"name\": \"ret_wrong\",\n \"params\": [],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 7,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"o\",\n \"origin\": \"o#7\",\n \"resource_kind\": null,\n \"type_name\": \"Other\"\n }\n ]\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 8,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 0\n },\n {\n \"line\": 8,\n \"op\": \"return\",\n \"sym\": null\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"ret_void_val\",\n \"params\": [],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 8,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"c\",\n \"origin\": \"c#8\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n }\n ]\n },\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": true,\n \"name\": \"ret_missing\",\n \"params\": [],\n \"symbols\": []\n }\n ],\n \"ownlang_cfg_version\": 0\n}", + "diags": [ + [ + 5, + "OWN004" + ], + [ + 6, + "OWN035" + ], + [ + 7, + "OWN035" + ], + [ + 8, + "OWN035" + ], + [ + 9, + "OWN033" + ] + ] + }, + { + "name": "curated_call_errors", + "source": "module M\nresource Conn { acquire open release close }\nextern fn Need(consume Conn);\nfn f() {\n let c = acquire Conn(1);\n Unknown(c);\n Need(c, c);\n Need(c);\n}\n", + "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 5,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 0\n },\n {\n \"args\": [\n {\n \"effect\": \"consume\",\n \"sym\": 0\n }\n ],\n \"callee\": \"Need\",\n \"line\": 8,\n \"op\": \"invoke\"\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"f\",\n \"params\": [],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 5,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"c\",\n \"origin\": \"c#5\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", + "diags": [ + [ + 6, + "OWN040" + ], + [ + 7, + "OWN041" + ] + ] + }, + { + "name": "curated_loops_and_borrows", + "source": "module M\nresource Conn { acquire open release close }\nextern fn Fill(borrow_mut Conn);\nfn f(n: int) {\n let c = acquire Conn(1);\n while (n) {\n borrow_mut c as m { Fill(m); }\n use c;\n }\n overspan c;\n release c;\n}\n", + "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 5,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 1\n }\n ],\n \"label\": \"entry\",\n \"succ\": [\n 1\n ]\n },\n {\n \"id\": 1,\n \"instrs\": [],\n \"label\": \"while.header\",\n \"succ\": [\n 2,\n 3\n ]\n },\n {\n \"id\": 2,\n \"instrs\": [\n {\n \"binding\": 2,\n \"line\": 7,\n \"mut\": true,\n \"op\": \"borrow_start\",\n \"owner\": 1\n },\n {\n \"args\": [\n {\n \"effect\": \"borrow_mut\",\n \"sym\": 2\n }\n ],\n \"callee\": \"Fill\",\n \"line\": 7,\n \"op\": \"invoke\"\n },\n {\n \"binding\": 2,\n \"line\": 7,\n \"mut\": true,\n \"op\": \"borrow_end\",\n \"owner\": 1\n },\n {\n \"line\": 8,\n \"op\": \"use\",\n \"sym\": 1\n }\n ],\n \"label\": \"while.body\",\n \"succ\": [\n 1\n ]\n },\n {\n \"id\": 3,\n \"instrs\": [\n {\n \"line\": 10,\n \"op\": \"overspan\",\n \"sym\": 1\n },\n {\n \"line\": 11,\n \"op\": \"release\",\n \"sym\": 1\n }\n ],\n \"label\": \"while.after\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"f\",\n \"params\": [\n 0\n ],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 4,\n \"is_param_borrow\": false,\n \"kind\": \"plain\",\n \"name\": \"n\",\n \"origin\": \"n#4\",\n \"resource_kind\": null,\n \"type_name\": \"int\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 5,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"c\",\n \"origin\": \"c#5\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n },\n {\n \"borrow_is_mut\": true,\n \"buffer\": null,\n \"def_line\": 7,\n \"is_param_borrow\": false,\n \"kind\": \"borrow\",\n \"name\": \"m\",\n \"origin\": null,\n \"resource_kind\": null,\n \"type_name\": null\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", + "diags": [] + }, + { + "name": "curated_resolver_errors", + "source": "module M\nresource Conn { acquire open release close }\nfn f() {\n use undef;\n let a = acquire Missing(1);\n let b = acquire Conn(1);\n let b = acquire Conn(2);\n release b;\n let c = b;\n}\n", + "cfg": "{\n \"functions\": [\n {\n \"blocks\": [\n {\n \"id\": 0,\n \"instrs\": [\n {\n \"line\": 5,\n \"op\": \"acquire\",\n \"resource\": \"Missing\",\n \"sym\": 0\n },\n {\n \"line\": 6,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 1\n },\n {\n \"line\": 7,\n \"op\": \"acquire\",\n \"resource\": \"Conn\",\n \"sym\": 2\n },\n {\n \"line\": 8,\n \"op\": \"release\",\n \"sym\": 2\n }\n ],\n \"label\": \"entry\",\n \"succ\": []\n }\n ],\n \"entry\": 0,\n \"has_return_type\": false,\n \"name\": \"f\",\n \"params\": [],\n \"symbols\": [\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 5,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"a\",\n \"origin\": \"a#5\",\n \"resource_kind\": null,\n \"type_name\": \"Missing\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 6,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"b\",\n \"origin\": \"b#6\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n },\n {\n \"borrow_is_mut\": null,\n \"buffer\": null,\n \"def_line\": 7,\n \"is_param_borrow\": false,\n \"kind\": \"owned\",\n \"name\": \"b\",\n \"origin\": \"b#7\",\n \"resource_kind\": null,\n \"type_name\": \"Conn\"\n }\n ]\n }\n ],\n \"ownlang_cfg_version\": 0\n}", + "diags": [ + [ + 4, + "OWN030" + ], + [ + 5, + "OWN030" + ], + [ + 7, + "OWN031" + ], + [ + 9, + "OWN032" + ] + ] } ] } From 7de5016b087d50faa9122c80e68fca6bd593459e Mon Sep 17 00:00:00 2001 From: PhysShell <45852143+PhysShell@users.noreply.github.com> Date: Sat, 10 Oct 2026 18:21:24 +0500 Subject: [PATCH 7/7] test(h28): preserve parity generator case order --- tests/fixtures/diag_parity.json | 148 ++++++++++++++++---------------- 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/tests/fixtures/diag_parity.json b/tests/fixtures/diag_parity.json index 79937058..d98c3cd7 100644 --- a/tests/fixtures/diag_parity.json +++ b/tests/fixtures/diag_parity.json @@ -633,80 +633,6 @@ ] ] }, - { - "name": "curated_buffer_policy", - "source": "module M\nfn f(n: int) {\n let a = Buffer.inline(999999);\n release a;\n}\n", - "diags": [ - [ - 3, - "OWN019" - ] - ] - }, - { - "name": "curated_double_release", - "source": "module M\nresource Conn { acquire open release close }\nresource Token { acquire mint release burn kind \"subscription token\" }\nextern fn Fill(borrow_mut Conn);\nextern fn Hash(borrow Conn);\nextern fn Store(consume Conn);\nfn f() {\n let c = acquire Conn(1);\n release c;\n release c;\n return;\n}\n", - "diags": [ - [ - 10, - "OWN003" - ] - ] - }, - { - "name": "curated_leak_and_release", - "source": "module M\nresource Conn { acquire open release close }\nresource Token { acquire mint release burn kind \"subscription token\" }\nextern fn Fill(borrow_mut Conn);\nextern fn Hash(borrow Conn);\nextern fn Store(consume Conn);\nfn leaks() {\n let c = acquire Conn(1);\n return;\n}\nfn clean() {\n let c = acquire Conn(1);\n release c;\n return;\n}\n", - "diags": [ - [ - 9, - "OWN001" - ] - ] - }, - { - "name": "curated_maybe_release_branch", - "source": "module M\nresource Conn { acquire open release close }\nresource Token { acquire mint release burn kind \"subscription token\" }\nextern fn Fill(borrow_mut Conn);\nextern fn Hash(borrow Conn);\nextern fn Store(consume Conn);\nfn f(n: int) {\n let c = acquire Conn(1);\n if (n) { release c; }\n Hash(c);\n return;\n}\n", - "diags": [ - [ - 10, - "OWN009" - ], - [ - 11, - "OWN001" - ] - ] - }, - { - "name": "curated_parse_error_is_own020", - "source": "module M\nfn f( {\n", - "diags": [ - [ - 2, - "OWN020" - ] - ] - }, - { - "name": "curated_use_after_move", - "source": "module M\nresource Conn { acquire open release close }\nresource Token { acquire mint release burn kind \"subscription token\" }\nextern fn Fill(borrow_mut Conn);\nextern fn Hash(borrow Conn);\nextern fn Store(consume Conn);\nfn f() {\n let c = acquire Conn(1);\n let d = move c;\n Hash(c);\n Store(d);\n return;\n}\n", - "diags": [ - [ - 10, - "OWN005" - ] - ] - }, - { - "name": "curated_use_after_release", - "source": "module M\nresource Conn { acquire open release close }\nresource Token { acquire mint release burn kind \"subscription token\" }\nextern fn Fill(borrow_mut Conn);\nextern fn Hash(borrow Conn);\nextern fn Store(consume Conn);\nfn f() {\n let c = acquire Conn(1);\n release c;\n Hash(c);\n return;\n}\n", - "diags": [ - [ - 10, - "OWN002" - ] - ] - }, { "name": "examples/bad_leak_branch.own", "source": "module LeakBranchDemo\n\nresource Conn {\n acquire open\n release close\n}\n\n// OWN001: released on the 'then' path only; leaks through 'else'.\nfn maybe(flag: int) {\n let c = acquire Conn(flag);\n if (flag) {\n release c;\n }\n}\n", @@ -880,6 +806,80 @@ "name": "examples/ok_pool.own", "source": "module PoolDemo\n\nresource Buffer {\n acquire rent\n release give\n}\n\n// Canonical happy path: rent a buffer, look at it mutably then read it,\n// hand it back. Checker proves it is returned on the single path; codegen\n// hoists the release into a finally for exception-safety.\nfn process(size: int) {\n let buf = acquire Buffer(size);\n\n borrow_mut buf as bytes {\n use bytes;\n }\n\n borrow buf as bytes {\n use bytes;\n }\n\n release buf;\n}\n", "diags": [] + }, + { + "name": "curated_leak_and_release", + "source": "module M\nresource Conn { acquire open release close }\nresource Token { acquire mint release burn kind \"subscription token\" }\nextern fn Fill(borrow_mut Conn);\nextern fn Hash(borrow Conn);\nextern fn Store(consume Conn);\nfn leaks() {\n let c = acquire Conn(1);\n return;\n}\nfn clean() {\n let c = acquire Conn(1);\n release c;\n return;\n}\n", + "diags": [ + [ + 9, + "OWN001" + ] + ] + }, + { + "name": "curated_use_after_release", + "source": "module M\nresource Conn { acquire open release close }\nresource Token { acquire mint release burn kind \"subscription token\" }\nextern fn Fill(borrow_mut Conn);\nextern fn Hash(borrow Conn);\nextern fn Store(consume Conn);\nfn f() {\n let c = acquire Conn(1);\n release c;\n Hash(c);\n return;\n}\n", + "diags": [ + [ + 10, + "OWN002" + ] + ] + }, + { + "name": "curated_double_release", + "source": "module M\nresource Conn { acquire open release close }\nresource Token { acquire mint release burn kind \"subscription token\" }\nextern fn Fill(borrow_mut Conn);\nextern fn Hash(borrow Conn);\nextern fn Store(consume Conn);\nfn f() {\n let c = acquire Conn(1);\n release c;\n release c;\n return;\n}\n", + "diags": [ + [ + 10, + "OWN003" + ] + ] + }, + { + "name": "curated_use_after_move", + "source": "module M\nresource Conn { acquire open release close }\nresource Token { acquire mint release burn kind \"subscription token\" }\nextern fn Fill(borrow_mut Conn);\nextern fn Hash(borrow Conn);\nextern fn Store(consume Conn);\nfn f() {\n let c = acquire Conn(1);\n let d = move c;\n Hash(c);\n Store(d);\n return;\n}\n", + "diags": [ + [ + 10, + "OWN005" + ] + ] + }, + { + "name": "curated_maybe_release_branch", + "source": "module M\nresource Conn { acquire open release close }\nresource Token { acquire mint release burn kind \"subscription token\" }\nextern fn Fill(borrow_mut Conn);\nextern fn Hash(borrow Conn);\nextern fn Store(consume Conn);\nfn f(n: int) {\n let c = acquire Conn(1);\n if (n) { release c; }\n Hash(c);\n return;\n}\n", + "diags": [ + [ + 10, + "OWN009" + ], + [ + 11, + "OWN001" + ] + ] + }, + { + "name": "curated_buffer_policy", + "source": "module M\nfn f(n: int) {\n let a = Buffer.inline(999999);\n release a;\n}\n", + "diags": [ + [ + 3, + "OWN019" + ] + ] + }, + { + "name": "curated_parse_error_is_own020", + "source": "module M\nfn f( {\n", + "diags": [ + [ + 2, + "OWN020" + ] + ] } ] }