From fdccc166bb1e52dd89bd5ba95acda3fad774242c Mon Sep 17 00:00:00 2001 From: "Aaron K. Clark" Date: Mon, 25 May 2026 22:48:30 -0500 Subject: [PATCH] =?UTF-8?q?feat(decompiler):=20Rec=2031=20#31-2=20?= =?UTF-8?q?=E2=80=94=20cppRaiiAudit=20per-file=20gate=20for=20RAII=20Stage?= =?UTF-8?q?=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `gradle cppRaiiAudit` as a new verification task modeled on ignoreAudit / objectInputStreamAudit. The task walks the protected file set and forbids raw `new (...)` or `new [...]` allocations — the RAII idiom we want is `std::make_unique(...)` or `std::make_shared(...)` (or stack / value allocation), so smart pointers never produce raw pointers to leak across an API boundary. Stage 1 protected set: - Ghidra/Features/Decompiler/src/decompile/cpp/address.cc - Ghidra/Features/Decompiler/src/decompile/cpp/space.cc - Ghidra/Features/Decompiler/src/decompile/cpp/rangeutil.cc In-tree state: all three files were already raw-`new`-free (the audit's mention of `range.cc` maps to `rangeutil.cc` in the current tree; the other two never had raw owning allocations). This PR therefore lays the gate to fail CI on any future regression that reintroduces a raw owning pointer in the protected set — it doesn't itself migrate any code. Wiring: - gradle/cppRaiiAudit.gradle — new task with PROTECTED_FILES allowlist - build.gradle — apply line alongside other audits - .github/workflows/build-ghidra.yml — `Audit C++ RAII protected files` step Subsequent stages (#31-3, #31-4, ...) extend PROTECTED_FILES as more files are RAII-converted. The tree-wide "no raw new in cpp/" lint described as #31-10 in RAII_MIGRATION.md is the broader form of this same gate. Closes #31-2 of Rec 31. Docs updated: - docs/decompiler/RAII_MIGRATION.md — sequencing table now carries Status column with shipped/open per row. - SprintPlanning.md — Sprint 6 #31-2 row marked shipped. Proudly Made in Nebraska. Go Big Red! 🌽 https://xkcd.com/2347/ Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/build-ghidra.yml | 8 +++ SprintPlanning.md | 2 +- build.gradle | 3 + docs/decompiler/RAII_MIGRATION.md | 24 ++++---- gradle/cppRaiiAudit.gradle | 95 ++++++++++++++++++++++++++++++ 5 files changed, 119 insertions(+), 13 deletions(-) create mode 100644 gradle/cppRaiiAudit.gradle diff --git a/.github/workflows/build-ghidra.yml b/.github/workflows/build-ghidra.yml index f247bd22297..dc6b3b63a70 100644 --- a/.github/workflows/build-ghidra.yml +++ b/.github/workflows/build-ghidra.yml @@ -102,6 +102,14 @@ jobs: # CI on any regression that reintroduces unfiltered deserialization. run: gradle objectInputStreamAudit + - name: Audit C++ RAII protected files (Rec 31 #31-2) + # Forbid raw `new` allocations in decompile/cpp/address.cc, + # space.cc, rangeutil.cc. Already RAII-clean (zero raw new); + # this gate fails CI on any regression that reintroduces a + # raw owning pointer in the protected set. Set extends as + # later RAII stages (#31-3+) convert more files. + run: gradle cppRaiiAudit + - name: i18n lint (Docking) # Localization PoC: fail the build if a hardcoded UI literal # reappears in Ghidra/Framework/Docking outside the diff --git a/SprintPlanning.md b/SprintPlanning.md index 62599fba46a..687b6c34d9d 100644 --- a/SprintPlanning.md +++ b/SprintPlanning.md @@ -144,7 +144,7 @@ first implementation tier. - [x] ~~**Rec 32 PR #32-2:** bump `-std=c++11` → `-std=c++14` in `decompile/cpp/Makefile`.~~ Shipped: [PR #310](https://github.com/CryptoJones/GayHydra/pull/310). - [x] ~~**Rec 32 PR #32-3:** bump to `-std=c++20`.~~ Shipped: rolled in with [PR #314](https://github.com/CryptoJones/GayHydra/pull/314); same three sites as #32-2 (`buildNatives.gradle` Gcc/Clang blocks, `decompile/cpp/Makefile`, `decompile/cpp/fuzz/Makefile.fuzz`), flag-only change. Toolchain floor recorded in `docs/decompiler/CPP20_ADOPTION.md` (gcc ≥10, clang ≥12, MSVC 2019 16.10+/2022). -- [ ] **Rec 31 PR #31-2:** RAII Stage 1 — convert `address.cc`, `space.cc`, `range.cc` to `unique_ptr`. CI lint: no raw `new` in these files. +- [x] ~~**Rec 31 PR #31-2:** RAII Stage 1 — convert `address.cc`, `space.cc`, `range.cc` to `unique_ptr`. CI lint: no raw `new` in these files.~~ Shipped: the three foundation files were already raw-`new`-free in tree (only `new` mentions are in comments); the `gradle cppRaiiAudit` per-file gate was added to fail CI on any regression. Tree path uses `rangeutil.cc` (the file the audit named as `range.cc`). - [ ] **Rec 31 PR #31-3 + Rec 32 PR #32-4:** RAII Stage 2 (`marshal.cc`, `xml.cc`) paired with `std::span` adoption in their parameter pairs. Joint review. --- diff --git a/build.gradle b/build.gradle index 85be837f3c4..b752222e894 100644 --- a/build.gradle +++ b/build.gradle @@ -51,6 +51,9 @@ apply from: 'gradle/ignoreAudit.gradle' // Raw ObjectInputStream enforcement gate. See docs/security/JAVA_DESERIALIZATION_AUDIT.md (Rec 19 #19-6). apply from: 'gradle/objectInputStreamAudit.gradle' +// C++ RAII audit — protected-file gate. See docs/decompiler/CPP20_ADOPTION.md (Rec 31 #31-2). +apply from: 'gradle/cppRaiiAudit.gradle' + /*************************************************************************************** * Print current Java and Gradle version and make sure the correct version of gradle is being used diff --git a/docs/decompiler/RAII_MIGRATION.md b/docs/decompiler/RAII_MIGRATION.md index 3c4104bf3d3..a60fcd89e54 100644 --- a/docs/decompiler/RAII_MIGRATION.md +++ b/docs/decompiler/RAII_MIGRATION.md @@ -171,18 +171,18 @@ can land at C++11 and C++20 later. ## Sequencing -| PR | Scope | -|---|---| -| #31-1 (this PR) | This plan | -| #31-2 | Stage 1 — `address.cc`, `space.cc`, `range.cc` | -| #31-3 | Stage 2 — `marshal.cc`, `xml.cc` | -| #31-4 | Stage 3 — `database.cc`, `comment.cc`, `cover.cc` | -| #31-5 | Stage 4 — `type.cc`, `userop.cc` | -| #31-6 | Stage 5 — pcode core | -| #31-7 | Stage 6 — analysis passes | -| #31-8 | Stage 7 — Sleigh runtime (parallel to #31-7) | -| #31-9 | Stage 8 — mop-up | -| #31-10 | CI lint enforcing "no raw new in cpp/" | +| PR | Scope | Status | +|---|---|---| +| #31-1 | This plan | shipped | +| #31-2 | Stage 1 — `address.cc`, `space.cc`, `rangeutil.cc` (audit's "range.cc" target) | shipped: the three files were already raw-`new`-free in tree; the `gradle cppRaiiAudit` per-file gate (see [`gradle/cppRaiiAudit.gradle`](../../gradle/cppRaiiAudit.gradle)) was added to fail CI on any regression. Wired into `.github/workflows/build-ghidra.yml` alongside `ignoreAudit` and `objectInputStreamAudit`. | +| #31-3 | Stage 2 — `marshal.cc`, `xml.cc` | open | +| #31-4 | Stage 3 — `database.cc`, `comment.cc`, `cover.cc` | open | +| #31-5 | Stage 4 — `type.cc`, `userop.cc` | open | +| #31-6 | Stage 5 — pcode core | open | +| #31-7 | Stage 6 — analysis passes | open | +| #31-8 | Stage 7 — Sleigh runtime (parallel to #31-7) | open | +| #31-9 | Stage 8 — mop-up | open | +| #31-10 | Tree-wide CI lint enforcing "no raw new in cpp/" | open — `cppRaiiAudit` foundation laid in #31-2 (per-file gate); #31-10 generalises to all `decompile/cpp/**.cc` | Each stage is reviewed by at least one decompiler maintainer (see [MAINTAINERS.md](../../MAINTAINERS.md)). diff --git a/gradle/cppRaiiAudit.gradle b/gradle/cppRaiiAudit.gradle new file mode 100644 index 00000000000..94eaf3058ea --- /dev/null +++ b/gradle/cppRaiiAudit.gradle @@ -0,0 +1,95 @@ +/* ### + * C++ RAII audit task — protected-file gate. + * + * Addresses Rec 31 #31-2 of the 2026-05-21 audit. See + * docs/decompiler/RAII_MIGRATION.md for the migration plan and + * docs/decompiler/CPP20_ADOPTION.md for the language-stage context. + * + * Walks a fixed list of decompiler C++ files asserting they contain + * no raw `new (...)` or `new [...]` allocations. + * The RAII idiom we want is `std::make_unique(...)` (or `std::make_shared`, + * or stack/value allocation). Smart-pointer factories never produce raw + * pointers to leak across the API boundary, so the audit forbids the + * raw form anywhere in the protected set. + * + * The protected set is intentionally small. Sprint 6 #31-2 brings the + * first three foundation files under the gate; later stages (#31-3, + * #31-4, ...) extend the set as more files are RAII-converted. The + * gate is per-file allowlist — anything not on the list is unaffected. + * The tree-wide "no raw new in cpp/" lint (#31-10 in RAII_MIGRATION.md) + * is a later, broader form of this same idea. + * + * Apply this from the root build.gradle: + * apply from: 'gradle/cppRaiiAudit.gradle' + * + * The task is named `cppRaiiAudit` at the root and is invoked as a + * dedicated CI step in `.github/workflows/build-ghidra.yml`. + */ + +import java.util.regex.Pattern + +// `new` keyword followed by an identifier and a `(`, `[`, or `<` +// opener. The `\b` word boundary keeps us from matching `renewable` +// or similar; the lookahead enforces an allocation site, not a +// general `new` mention (e.g., in comments / strings that survive +// the trim filter below). +final Pattern RAW_NEW_PATTERN = Pattern.compile( + '\\bnew\\s+[A-Za-z_][A-Za-z0-9_:]*\\s*[(\\[<]') + +// Files under the RAII gate. Path-suffix match keeps the rule +// independent of the repo's root layout. Sprint 6 #31-2 starts with +// the address / space / rangeutil triangle that the audit named as +// foundation files; subsequent stages extend the set. +final Set PROTECTED_FILES = [ + 'Ghidra/Features/Decompiler/src/decompile/cpp/address.cc', + 'Ghidra/Features/Decompiler/src/decompile/cpp/space.cc', + 'Ghidra/Features/Decompiler/src/decompile/cpp/rangeutil.cc', +] as Set + +task cppRaiiAudit { + description = 'Verify protected decompiler C++ files contain no raw `new` allocations.' + group = 'verification' + + doLast { + List violations = [] + List missing = [] + PROTECTED_FILES.each { String rel -> + File f = new File(rootDir, rel) + if (!f.exists()) { + missing.add(rel) + return + } + int lineNum = 0 + f.eachLine('UTF-8') { String line -> + lineNum++ + // Skip pure-comment lines — both `//` line-comments + // and `*` continuation lines inside `/* ... */` blocks. + // Same simple filter as objectInputStreamAudit; string- + // literal false positives can be fixed by the author. + String trimmed = line.trim() + if (trimmed.startsWith('//') || trimmed.startsWith('*')) { + return + } + def m = RAW_NEW_PATTERN.matcher(line) + if (m.find()) { + violations.add("${rel}:${lineNum} raw `new` allocation " + + "(use std::make_unique or std::make_shared)") + } + } + } + + if (!missing.isEmpty()) { + logger.warn 'cppRaiiAudit: protected files missing from tree (audit list out of date):' + missing.each { logger.warn " ${it}" } + } + if (!violations.isEmpty()) { + logger.error '== C++ RAII audit failures ==' + violations.each { logger.error " ${it}" } + throw new GradleException( + "${violations.size()} raw `new` allocation(s) in RAII-protected files. " + + "See docs/decompiler/RAII_MIGRATION.md (Rec 31 #31-2) — protected files " + + "must construct heap-owned objects via std::make_unique / std::make_shared.") + } + logger.lifecycle "cppRaiiAudit: ${PROTECTED_FILES.size()} protected file(s) clean." + } +}