Skip to content

Integrate all three PRs: package layout + buffer policies + codegen fixes & fuzzer#3

Merged
PhysShell merged 9 commits into
mainfrom
claude/codegen-bugs-tests-670240
Jun 14, 2026
Merged

Integrate all three PRs: package layout + buffer policies + codegen fixes & fuzzer#3
PhysShell merged 9 commits into
mainfrom
claude/codegen-bugs-tests-670240

Conversation

@PhysShell

@PhysShell PhysShell commented Jun 14, 2026

Copy link
Copy Markdown
Owner

Single integration of the three open PRs onto one linear history off main,
resolving the conflicts that came from all three independently restructuring
the repo and rewriting codegen.py.

What's included (in order):

  1. PR Remove unnecessary .txt extensions from example and source files #1 — package layout & housekeeping: flat modules → ownlang/ package,
    examples → examples/, .gitignore, README quickstart fixes, and the
    return-under-borrow soundness fix.
  2. PR Add buffer storage policies with mandatory logging #2 — buffer storage policies (ported to the package layout): the
    stackalloc/scratch/pool/native/inline feature, buffers.py/report.py,
    trace/counter hooks, and the report CLI. PR Add buffer storage policies with mandatory logging #2 was authored with package
    imports but committed flat, so it never actually ran; re-homed into
    ownlang/ it does. Its own suite (95 analysis cases + buffer/report smokes)
    is green.
  3. Two codegen/analysis fixes on top of PR Add buffer storage policies with mandatory logging #2:
  4. PR Integrate all three PRs: package layout + buffer policies + codegen fixes & fuzzer #3 — codegen guards: test_codegen.py (asserts on the generated C#)
    and test_codegen_props.py (fuzzes thousands of random clean programs
    against an AST-derived oracle), wired into run_tests.py, plus a GitHub
    Actions workflow (3.11/3.12/3.13 + a 50k-draw fuzz pass).

Architecture note: no divergence. All three sit on the same pipeline
(lexer → parser → AST → CFG → loans/permissions dataflow → two-mode codegen).
The analysis core is structurally identical; PR #2 adds one IR node
(AcquireBuffer) and buffer-escape diagnostics in-place, and reworks the
codegen hoist — which is exactly where the churn and the leak lived.

Verification (local + CI): analysis 95/95, codegen 32/32, golden/buffer/
escape/branchy/nesting/ordering/helper PASS, codegen-content 21/21, property
fuzz 0 failures (3k in-suite, 50k extended).

Supersedes #1 and #2.

claude added 5 commits June 13, 2026 16:58
Rename the four example/source files to their real extensions so they
match the names already used in the README and file headers:
  Program.cs.txt          -> Program.cs
  buffer.own.txt          -> buffer.own
  ok_pool.own.txt         -> ok_pool.own
  bad_leak_branch.own.txt -> bad_leak_branch.own
The modules used relative imports (.parser, .cfg, ...) and run_tests.py
imported the 'ownlang' package, but everything sat flat in the repo
root, so 'python tests/run_tests.py' failed with ModuleNotFoundError.

Move files into the layout already documented in the README:
  ownlang/    <- the 8 .py modules + new __init__.py
  tests/      <- run_tests.py
  examples/                 <- ok_pool.own, bad_leak_branch.own
  examples/golden_arraypool <- buffer.own, Program.cs

Now passing: 42/42 analysis, 11/11 codegen, golden PASS; and the
documented CLI (python -m ownlang check/emit/cfg) works.
Ignore __pycache__/ and *.pyc (generated when running the test suite)
plus the .NET bin/ and obj/ output from the golden_arraypool demo, so
build artifacts stay out of the repo.
README's lead commands pointed at examples/ok_extern_calls.own and
examples/bad_maybe_release.own, which never existed as files (they were
only inline test cases). Add them as standalone .own files, verified
with the checker here:
  ok_extern_calls.own   -> checks clean (extern borrow/borrow_mut/consume)
  bad_maybe_release.own -> fires OWN009 + OWN001, cfg/emit behave as documented

For demo.csproj (referenced but never shipped, and unverifiable without a
.NET SDK in this sandbox) fix the README instead of adding an untested
project file: show how to wrap Program.cs in a console project, and drop
the demo.csproj entry from the structure tree.
Two blocking issues from code review, both reproduced before fixing:

1. Soundness hole: returning an owner while it is borrowed checked clean.
   'return b' is an escape/consume of an owned resource, so under a live
   loan it must fail like move/consume do. The Return handler validated
   moved/released state but never the active loans on the returned owner.
   Now it fires OWN007 (mirrors move_while_borrowed/consume_while_borrowed).
   Added regression test return_while_borrowed; updated the OWN007 row in
   the README code table to list return alongside move/consume.

2. README quickstart was broken: 'cd ownlang' steps into the package dir,
   after which 'python -m ownlang' fails (No module named ownlang) and the
   examples/ paths no longer resolve. Replaced with a note to run from the
   repo root.

Suite: 43/43 analysis (was 42 + new case), 11/11 codegen, golden PASS.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@PhysShell PhysShell changed the base branch from main to claude/untitled-tyoeaq June 14, 2026 07:57
claude added 4 commits June 14, 2026 09:01
PR #2 was authored with package-style relative imports (from . import ...)
but committed as flat files, so it never actually ran. Re-home it onto the
package layout established by PR #1: modules into ownlang/, buffers.py and
report.py alongside, the test runner + its buffer_scratch_program.cs.txt
fixture into tests/, example programs into examples/. Merge .gitignore and
splice PR #1's README quickstart fix into PR #2's README. No logic changes.
The straight-line hoist treated only acquire/buffer lets as releasable
scopes, so a top-level `release` of an owned *parameter* (which opens no
scope) fell through the "stray release, skip" branch and was dropped --
generating a method that never releases the resource (a leak). Any release
reaching that loop is by construction unconsumed by a scope, so emit it
faithfully at its source site. Found by the property fuzzer (release-count
invariant: source=1, emitted=0).
PR #2's rewritten Return handling set the owner to ESCAPED without checking
for a live loan, so `borrow b as s { return b; }` returned a resource out
from under an outstanding borrow with no diagnostic. PR #1 caught this on the
original analysis; re-apply it, adapted to PR #2's buffer-aware Return block:
returning an owner needs Own permission, so a live loan is OWN007 (as for move).
Stack my codegen guards on top of the buffer feature. test_codegen.py
asserts on the generated C# (release placement/count, declaration order,
escape handling, span-typed borrowed params); test_codegen_props.py fuzzes
thousands of random clean programs against an AST-derived release-accounting
oracle (this is what caught the dropped owned-parameter release). Both are
wired into run_tests.py and a GitHub Actions workflow runs the suite on
3.11/3.12/3.13 plus an extended 50k-draw fuzz pass.
@PhysShell PhysShell force-pushed the claude/codegen-bugs-tests-670240 branch from 4fb725f to d0d8662 Compare June 14, 2026 09:22
@PhysShell PhysShell changed the title Fix three codegen miscompiles in the try/finally hoist path; add codegen tests Integrate all three PRs: package layout + buffer policies + codegen fixes & fuzzer Jun 14, 2026
@PhysShell PhysShell changed the base branch from claude/untitled-tyoeaq to main June 14, 2026 09:22
@PhysShell PhysShell merged commit 01da03d into main Jun 14, 2026
4 checks passed
PhysShell pushed a commit that referenced this pull request Jun 18, 2026
… re-validation

Leak #4 in the SystemEvents fixture: a FileStream that IS disposed, but the
Dispose() sits inside the try after a may-throw call (WriteByte), so it's
skipped on the exceptional path. This is CodeQL's cs/dispose-not-called-on-throw
(cs/local-not-disposed also models it). Own.NET used to miss it — disposed
somewhere looked balanced — until the exception-edge model (77b2edd) inserted a
throw edge before each may-throw statement in a try.

Re-running the oracle on this fixture should land #4 in "Agree" with CodeQL,
joining #2/#3, with #1 (subscription) staying Own.NET-only. Bumps the oracle
sentinel to trigger the push-run.
PhysShell pushed a commit that referenced this pull request Jun 18, 2026
…oracle fixture

The exception-edge try-lowering injects an exceptional exit (a bare return while
the local is live) before each may-throw statement. A local never disposed then
leaks on BOTH that exit and the normal fall-through, so the core emits OWN001
once per exit. Every flow-local diagnostic remaps to the acquire line, so the two
collapse to byte-identical findings. The first oracle run surfaced this as
Program.cs:54 appearing twice in "Agree".

- ownir.py: drop byte-identical findings (same file/line/code/component/event/
  handler/message/kind/severity) before sorting. Native-OwnLang leaks at distinct
  lines stay distinct — the key includes line; only the bridge's line-collapse
  makes them identical, so deduping is exactly right.
- test_ownir.py + flow_leak_two_exits fixture: a two-leaking-exits flow body
  (acquire; if(*){return}; use) must yield exactly one OWN001 (TryNeverDisposed
  'tfLeak'@105). Without the dedup it returns two.

Also tighten the SystemEvents fixture's dispose-on-throw case (#4): the three
tools anchor the same leak at different points (Own.NET acquire / CodeQL Dispose /
Infer# last-access), so a spread-out method puts them >3 lines apart and the
oracle's ±3 window splits one leak into own-only + oracle-only. Keep the try a
one-liner adjacent to the acquire so the anchors fall inside the window -> #4
joins #2/#3 in "Agree" across all three. Bumps the oracle sentinel.
PhysShell pushed a commit that referenced this pull request Jun 18, 2026
Layer a region-escape path onto the OwnIR bridge: a new `capture`
subscription fact (a tokenless strong `event += h` whose source provably
outlives the subscriber) lowers to the lifetime engine's `subscribe self
to <source>` and surfaces as OWN014 — the WPF "escape to App" — instead of
the bespoke OWN001 token tier. WPF subscriptions become a profile of the
general owner/release-region model (ROADMAP Milestone 2).

- lifetimes.py: stamp `subject` (source#line) on OWN014 so the bridge maps
  it back to the C# subscription (reuses _handle_of; invisible to render).
- ownir.py: lower `capture` facts (to_module + to_own) to lifetime decls +
  a source param carrying its region + `subscribe`; map OWN014 back to a
  region-escape Finding. A `static` source is a process-lived (longest)
  region -> OWN014; an injected/unknown source stays conservatively silent
  (the region model is precise where the token tier only warns).
- Additive + zero-regression: the current Roslyn extractor never emits
  `capture`, so existing OWN001/timer/field/pool/DI paths are untouched
  (58/58 bridge checks, ruff, mypy --strict all green). Exercised by a
  hand-written fixture until the extractor emits the fact (P-004 WPF005).

New: tests/fixtures/ownir/capture.facts.json + test_ownir assertions.
Docs: P-004 (WPF005 bridge+core done), lifetimes.md (slice #3 IR bridge),
ROADMAP (Milestone 2 in progress).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED
PhysShell pushed a commit that referenced this pull request Jun 18, 2026
Complete the WPF region-escape path end to end (P-004 WPF005). The Roslyn
extractor now lowers a static-source `event += handler` (a process-lived
static event, or a static field/property receiver) to a tokenless `capture`
OwnIR fact instead of a token `subscription`, so a real SystemEvents-style
leak surfaces as OWN014 (the subscriber is promoted to process lifetime) —
the WPF escape as a profile of the general owner/release-region model.

Extractor (Program.cs): one-line reroute — `source == "static"` ? "capture".
  Injected/unknown sources stay token `subscription` (OWN001, severity-tiered);
  timers stay `timer`. No existing sample is a non-timer static source, so the
  existing wpf-extractor assertions are unchanged.
Bridge (ownir.py): a `capture` with a matching `-=` (`released: true`) is
  mitigated -> silent (the source no longer holds self on close), mirroring a
  released token subscription. Pinned by a released-capture fixture case.
Sample: StaticEventEscapeViewModel.cs (instance handler on Calc.GlobalPing ->
  OWN014; an unsubscribed variant stays silent), wired into CI wpf-extractor
  with OWN014 + silence assertions. The facts->core effect is verified locally
  (simulated extractor output replays the exact CI greps); the C# extraction
  is CI-only (no local dotnet).
Corpus: corpus/wpf/systemevents-region-escape — the SystemEvents leak through
  the region model (OWN014), paralleling the token-model view in
  corpus/real-world/screentogif-systemevents-leak.
Oracle: OWN014 joins OWN_LEAK so cross-tool leak accounting still counts these.
Docs: P-004 (WPF005 end-to-end), lifetimes.md (slice #3), ROADMAP (Milestone
  2), README (P-001 extractor now emits OWN014 for static events).

Local: ruff, mypy --strict, full suite (ownir 59/59, wpf 4/4), oracle 19/19,
miner 8/8 all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED
PhysShell pushed a commit that referenced this pull request Jun 22, 2026
…eturn only (Codex)

Codex P2: treating a rented field passed to ANY field-stored `new X(field)` as released
wrongly suppresses a real leak for NON-owning wrappers (`_view = new ReadOnlyMemory<byte>(_buf)`
and other cached views), where the array is never returned. Soundly distinguishing an owning
guard (that Returns the buffer) from a non-owning view is not worth it for the single
SharedArrayPoolBuffer FP, so the guard-transfer is removed entirely.

Fix #3 keeps only the sound, high-value part: a pooled FIELD is released if `pool.Return(field)`
appears anywhere in the class (cross-member ctor-rent + Dispose-return — BufferedReadStream).
SharedArrayPoolBuffer's indirect release via its LifetimeGuard is left as an honest known
limitation. Sample/CI drop the PoolFieldTransferredToGuard case accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED
PhysShell added a commit that referenced this pull request Jun 22, 2026
…tion

fix(extractor): null-conditional dispose + cross-member pool release (mined ImageSharp FP #2,#3/4)
PhysShell pushed a commit that referenced this pull request Jun 27, 2026
)

Two cross-reference fixes flagged by Codex:

- The cleanup-rule cross-walk was off: OWNTS004 is AbortController, which maps to
  EFF005 — not EFF004 (timers = OWNTS002). State the accurate one-to-one:
  OWNTS002/003/004 ↔ EFF004/EFF003/EFF005. Fixed in P-017's reference note and in
  P-020's open-question #3.
- Stop marketing the OWNTS001 cleanup rule (→ OWN001) with the request-storm /
  Cloudflare story. That shape is EFF001/002 (new dependency-stability analysis the
  core lacks), per P-020's honest split — the conflation P-020 explicitly avoids.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnGqHwVb2H1jZ71f498MFa
PhysShell pushed a commit that referenced this pull request Jun 27, 2026
…restore scaffolding

Live protobuf oracle re-run confirms PropertyReturnsOwnedMember clears the
CommandLineOptions XsltMessageEncountered self-cycle FP at the source: own-only 0,
the finding absent from both own-only and the baselined section. Delete its baseline
entry (protobuf baseline 5 -> 4 findings) and document the fix landed (root-cause #3
PARTLY FIXED). Restore the dev scaffolding: oracle-target.txt back to the
systemevents-console fixture, remove the branch from the oracle.yml push filter.

Newtonsoft serializer.Error stays baselined as an honest 'may outlive' warning (its
source escapes and the handler is a parameter's delegate — non-leak is unprovable
without lifetime modelling).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019846YSZ35c7CdkWQ1qX5gm
PhysShell pushed a commit that referenced this pull request Jul 3, 2026
Both reviewers converged; all corrections are factual/design refinements
(no forks):

- Crate DAG: own-codegen is a SIBLING of own-diagnostics (both consume
  own-cfg/own-analysis), not chained through it. Codegen is
  verdict-independent (AST/CFG-driven, matching Python codegen.generate),
  so the old own-diagnostics -> own-codegen edge would have forced
  diagnostics to re-export solver internals. Added a fitness function
  locking codegen !-> diagnostics (and !-> analysis for now). (Codex P2,
  CodeRabbit #1)
- Oracle: do NOT reuse scripts/oracle_compare.py as the parity oracle —
  it is cross-tool fuzzy (leak-only, +-N line tolerance, coarse severity)
  and would mask off-by-one/label/subject/exit-status divergences. Spec a
  new exact harness over status+stdout+stderr+SARIF/JSON, with an
  exit/crash gate first, exact set-equality incl. evidence label text,
  and intra-tie ordering. (Codex P1, CodeRabbit #4)
- CFG seam does not exist yet: python cfg prints human text, not JSON.
  Add+freeze a canonical cfg --format json before the ratchet uses that
  seam; added as migration step 0. (Codex P2)
- State: arena+CoW likely wins this procedural workload; bench largest
  real function, wall-clock + RSS. Prior art: clippy lint-pass registry,
  prusti-viper encoding boundary. Repo-layout revisit trigger. (CodeRabbit
  #2/#3/#5/#6)
PhysShell pushed a commit that referenced this pull request Jul 4, 2026
Closes 3 of 4 open questions with sourced findings; #1 and #3 retain a
5-minute empirical residual (run on real Linux), not a design blocker.

- gVisor x toolchain: LIKELY OK (277/351 syscalls; runtimes regression-
  tested). Residual: run gate.toml under runsc, watch for "Bad system call".
  Known gaps flagged (io_uring off, iptables partial, no KVM-in-sandbox).
- Kata 2026: CLOSED — ~100-300ms boot, ~130-200MB/pod, needs KVM, no native
  egress, guest-kernel patch-currency required. Its edge is containerd/K8s
  drop-in; with no K8s here it adds overhead without advantage over direct
  Firecracker (L1) or Sandlock (L2). Fills the deep-research Kata gap.
- QUIC/HTTP3 egress: CLOSED default — blanket UDP block suffices; CLI agent
  tools are TCP-first and fall back cleanly. Don't build MITM-CA/L7 proxy
  until a hard QUIC dependency appears. Residual: confirm nothing hangs.
- TCO self-host vs managed: CLOSED — both cheap in $ at N=1; the fork is
  purpose/trust/offline, not cost. For a for-the-soul self-use project,
  build self-host; the L2 MVP is days not weeks. E2B is benchmark, not target.

Also fixes the §6 trigger-table (an earlier note blockquote split the table
mid-rows).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LMA4JwvgPBCZuEbAiG5LJL
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants