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 new file mode 100644 index 00000000..6b26a644 --- /dev/null +++ b/corpus/ownership-lab/h28-command/README.txt @@ -0,0 +1,76 @@ +H-28-CMD: the DbCommand half of the `DataTable.Load(reader)` family. + +STATUS + 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`, + 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 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. + 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. + 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 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. 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. + +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 + + 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). + 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. + +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. 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/MANIFEST.txt b/corpus/ownership-lab/h28-command/evidence/MANIFEST.txt new file mode 100644 index 00000000..b4ac588b --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/MANIFEST.txt @@ -0,0 +1,47 @@ +# 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 +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-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 +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@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/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/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 new file mode 100644 index 00000000..f1d98186 --- /dev/null +++ b/corpus/ownership-lab/h28-command/evidence/engine-CommandDispose.txt @@ -0,0 +1,17 @@ +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 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 new file mode 100644 index 00000000..62dee430 --- /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 : v10.0.12 +# path : src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs +# blob sha : 01b3eed85551a660e1502d0c09b02361b9242bd0 +# file sha256: c5d427a5d70161528f63b9f789af93b0b24af3d7118dbfd92912db808ac1f5fb +# file bytes : 21934 +# marker : 'protected override void Dispose(bool disposing)' +# region : lines 216..227 +# derived by: scripts/derive_source_evidence.py + 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 new file mode 100644 index 00000000..70f59eb5 --- /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 : v10.0.12 +# path : src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs +# blob sha : 01b3eed85551a660e1502d0c09b02361b9242bd0 +# file sha256: c5d427a5d70161528f63b9f789af93b0b24af3d7118dbfd92912db808ac1f5fb +# file bytes : 21934 +# marker : 'private void DisposePreparedStatements(bool disposing = true)' +# region : lines 520..541 +# derived by: scripts/derive_source_evidence.py + 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 new file mode 100644 index 00000000..e3064b41 --- /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 : v10.0.12 +# path : src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs +# blob sha : 01b3eed85551a660e1502d0c09b02361b9242bd0 +# file sha256: c5d427a5d70161528f63b9f789af93b0b24af3d7118dbfd92912db808ac1f5fb +# file bytes : 21934 +# marker : 'private IEnumerable<(sqlite3_stmt Statement, int ParamCount)> PrepareAndEnumerateStatements()' +# region : lines 464..469 +# derived by: scripts/derive_source_evidence.py + 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 new file mode 100644 index 00000000..c1d3e2ce --- /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 : v10.0.12 +# path : src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs +# blob sha : 01b3eed85551a660e1502d0c09b02361b9242bd0 +# file sha256: c5d427a5d70161528f63b9f789af93b0b24af3d7118dbfd92912db808ac1f5fb +# file bytes : 21934 +# marker : '_preparedStatements =' +# region : lines 30..31 +# derived by: scripts/derive_source_evidence.py + 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 new file mode 100644 index 00000000..898130e6 --- /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 : v10.0.12 +# path : src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs +# blob sha : 3062c0f923c5358326da200bc01e7448bb756477 +# file sha256: 0fd8dd0cd1ca7e6f9ad7fa1301cfd77a743c33be3276550fad94b20557b096b6 +# file bytes : 38343 +# marker : 'List> _commands' +# region : lines 31..32 +# derived by: scripts/derive_source_evidence.py + 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 new file mode 100644 index 00000000..498fafe9 --- /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 : v10.0.12 +# path : src/Microsoft.Data.Sqlite.Core/SqliteDataReader.cs +# blob sha : 2537670988b0bef85011b4f42a4fb2e77e572f44 +# file sha256: 0712b68bd0b036886b3ea5c5209ba257a4d6b3ba9ac51655dbb2c4c374946968 +# file bytes : 33555 +# marker : 'public override void Close()' +# region : lines 227..268 +# derived by: scripts/derive_source_evidence.py + 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 new file mode 100644 index 00000000..2ac2cfb0 --- /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..244600c0 --- /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..e66e99fd --- /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..4af6b495 --- /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..82e088ac --- /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..6d4bcc15 --- /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..94d58083 --- /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..70af8a08 --- /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-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 new file mode 100644 index 00000000..a9012137 --- /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..868a33c8 --- /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..2a7e9416 --- /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..e6ef527c --- /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..68444c97 --- /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-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 new file mode 100644 index 00000000..c16f9051 --- /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..bf532c95 --- /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/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 new file mode 100644 index 00000000..8cc28bab --- /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..9d8883fe --- /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-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 new file mode 100644 index 00000000..d7edfa71 --- /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..b7c569cf --- /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/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 new file mode 100644 index 00000000..07226e4b --- /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..7c6c4b56 --- /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 : v2.1.12 +# path : src/SQLitePCLRaw.core/handles.cs +# blob sha : 0973a6de5a4e18374193dce87e4ded3b7d736fbf +# file sha256: 9afd074db410ba78fdeb89540fc05a42c52d64a924a041b907f64198830e6c0a +# file bytes : 11066 +# marker : 'public class sqlite3_stmt : SafeHandle' +# region : lines 195..220 +# derived by: scripts/derive_source_evidence.py + 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 new file mode 100644 index 00000000..3dc395f9 --- /dev/null +++ b/corpus/ownership-lab/h28-command/falsifier/Program.cs @@ -0,0 +1,415 @@ +// H-28-CMD falsifier: the provider effect of not disposing a DbCommand. +// +// 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. +// +// 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. +// +// 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. +// +// 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 +{ + // 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 ActivityListener NewNpgsqlListener() => new() + { + 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() + { + Interlocked.Exchange(ref _started, 0); + Interlocked.Exchange(ref _stopped, 0); + } + + 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(); + DbDataReader reader = null; + bool commandDisposed = false, readerDisposed = false; + try + { + command.CommandText = sql; + reader = command.ExecuteReader(); + var table = new DataTable(); + 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}"); + } + } + finally + { + if (reader is not null && !readerDisposed) + reader.Dispose(); + if (!commandDisposed) + command.Dispose(); + } + return output; + } + + // 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 = 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(); + } + 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) + { + count++; + statement = SQLitePCL.raw.sqlite3_next_stmt(db, statement); + } + return count; + } + + 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)"); + + 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})"); + + 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) + { + reader.Dispose(); + int afterReaderDispose = LiveSqliteStatements(connection); + output.Add($"{name} P6 after reader.Dispose={afterReaderDispose} " + + $"(delta={afterReaderDispose - baseline})"); + } + if (disposeCommand) + { + command.Dispose(); + int afterCommandDispose = LiveSqliteStatements(connection); + output.Add($"{name} P6 after command.Dispose={afterCommandDispose} " + + $"(delta={afterCommandDispose - baseline})"); + } + + // 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) + { + 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})"); + } + else + { + // Unmeasured containment cleanup: preserve the measured before/after stages above. + reader.Dispose(); + command.Dispose(); + } + return output; + } + + // 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 output = new List(); + int Count() + { + using var countCommand = connection.CreateCommand(); + countCommand.CommandText = "select count(*) from pg_prepared_statements"; + return Convert.ToInt32(countCommand.ExecuteScalar()); + } + + 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), + ("C_reader_only", false, true), + ("D_command_only", true, false), + ("A_both", true, true), + }; + + static int _failures; + static void Check(bool condition, string message) + { + Console.WriteLine($"{(condition ? "PASS" : "FAIL")} {message}"); + if (!condition) _failures++; + } + + static void Main(string[] args) + { + 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}"); + + // 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"); + + if (mode == "sqlite-only") + { + Console.WriteLine($"\n{(_failures == 0 ? "PASS" : "FAIL")} G2 SQLite-only; failures={_failures}"); + Environment.ExitCode = _failures == 0 ? 0 : 1; + return; + } + + if (string.IsNullOrWhiteSpace(pgDsn)) + { + Console.Error.WriteLine("FAIL G1: H28_PG_DSN was not supplied; no PostgreSQL runtime was tested."); + Environment.ExitCode = 2; + return; + } + + 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()) + { + ActivitySource.AddActivityListener(listener); + foreach (var v in Variants) + { + 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## 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 new file mode 100644 index 00000000..3425a39f --- /dev/null +++ b/corpus/ownership-lab/h28-command/falsifier/h28cmd.csproj @@ -0,0 +1,25 @@ + + + + Exe + net8.0 + disable + enable + h28cmd-falsifier + H28Cmd + true + + + + + + + + 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 new file mode 100644 index 00000000..a166708b --- /dev/null +++ b/corpus/ownership-lab/h28-command/fx/CommandDispose.cs @@ -0,0 +1,162 @@ +// 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). This makes it a +// useful source-level fixture for the provider-agnostic acquisition rule; it does NOT run the +// 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. +// +// 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 summaries cmd.facts.json +// python -m ownlang ownir cmd.facts.json --format sarif --verbosity verbose +// +// 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; + +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. + // G3 CONFIRMED: 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. G3 CONFIRMED: 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. In Npgsql 10.0.3 source, reader + // Dispose/DisposeAsync call Command.TraceCommandStop while public Close uses the + // 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(); + 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 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(); + 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. 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) + { + 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". + // G3 CONFIRMED: 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 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 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(); + 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. 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 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(); + 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. 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(); + 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..e3f74547 --- /dev/null +++ b/corpus/ownership-lab/h28-command/fx/CommandDispose.own @@ -0,0 +1,72 @@ +// 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 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. 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 { + 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 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. G3 independently confirmed the C# extraction pattern. +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 in this manual reduction. +// 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); + 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..1ec1d1c5 --- /dev/null +++ b/corpus/ownership-lab/h28-command/run.sh @@ -0,0 +1,185 @@ +#!/usr/bin/env bash +# H-28-CMD gates. Results are explicitly PASS / FAIL / SKIP. +# +# 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. +# +# 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)" +ROOT="$(cd "$HERE/../../.." && pwd)" +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 "=== 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 + rc=$? + { + 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" + n_cmd=$(grep -c "'command' is owned but not released" "$out") + n_reader=$(grep -c "'reader' is owned but not released" "$out") + 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 "FAIL ENGINE: rc=$rc command=$n_cmd reader=$n_reader; expected rc=1 command=2 reader=1." + return 1 +} + +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 + 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 + + 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 + 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 + 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 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 ;; + 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 new file mode 100755 index 00000000..86dc2063 --- /dev/null +++ b/corpus/ownership-lab/h28-command/scripts/derive_source_evidence.py @@ -0,0 +1,440 @@ +#!/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 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 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 declared source excerpt or scan 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 re +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-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", + "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-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", + "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", "v10.0.12", + "src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs", + "protected override void Dispose(bool disposing)", 12, + ), + ( + "mssqlite-SqliteCommand-DisposePreparedStatements.txt", + "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", "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", "v10.0.12", + "src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs", + "_preparedStatements =", 2, + ), + ( + "mssqlite-SqliteDataReader-Close-Dispose.txt", + "dotnet/efcore", "v10.0.12", + "src/Microsoft.Data.Sqlite.Core/SqliteDataReader.cs", + "public override void Close()", 42, + ), + ( + "mssqlite-SqliteConnection-commands-weakrefs.txt", + "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", "v2.1.12", + "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, + ), +] + +# (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") + 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 + 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 + + +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})") + + 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 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)} 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", + file=sys.stderr) + return 1 if failed else 0 + + +if __name__ == "__main__": + sys.exit(main()) 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 new file mode 100644 index 00000000..c1e717e0 --- /dev/null +++ b/docs/notes/h28-npgsql-command-resolution.md @@ -0,0 +1,387 @@ +# H-28-CMD — resolving the undisposed `DbCommand` question + +**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(); +var reader = command.ExecuteReader(); +var table = new DataTable(); +table.Load(reader); // neither command nor reader is explicitly disposed +``` + +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. 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"*. + +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)**. + +— `research/ownership-semantics-lab-v1@298b305`, +`corpus/ownership-lab/h28/scripts/h28_anchors_record.py`, field +`consequence_for_the_demanding_instance`. + +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 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. + +--- + +## 2. Recovered prior evidence + +**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 | `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) | 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. + +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 and source references + +| component | version / ref | evidence | +|---|---|---| +| 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 BCL contract + +| 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()` | 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`]. + +**Current `System.Data` implementation, not a universal interface guarantee:** +`DataTable.Load` delegates to `FillFromReader`, then: + +```csharp +if (!reader.IsClosed && !reader.NextResult()) +{ + reader.Close(); +} +``` + +— `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. Provider source observations and bounded runtime results + +### 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`.** 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 + +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`]. + +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[]` +[`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`]. 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 +`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. +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>` +[`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 and executable evidence status + +**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` +(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 | result in workflow run #38048287923 | falsifier / scope | +|---|---|---| +| **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 -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. 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 +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. + +Source reading of `frontend/roslyn/OwnSharp.Extractor/Program.cs` at `e889f8b` predicts: + +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. + +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. + +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. + +--- + +## 8. Review corrections and falsifiers + +| 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. | 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 | 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 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-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. Scope boundary + +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. + +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. diff --git a/tests/fixtures/cfg_parity.json b/tests/fixtures/cfg_parity.json index 354c5823..49394d1d 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", diff --git a/tests/fixtures/diag_parity.json b/tests/fixtures/diag_parity.json index f3822cb4..d98c3cd7 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",