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." + } +}