Releases: nrodear/StaticCodeAnalyser
Release list
v0.9.8 — Phase 1-4 + Hardening v3/v4 + FP-Reduction Sprint
Release 0.9.8 — Phase 1-4 + Hardening v3/v4 + FP-Reduction Sprint
Released: 2026-06-10
🇩🇪 Deutsche Version
This release closes the 0.9.8 development cycle that began on
2026-06-02. Three layers of work landed on top of v0.9.7:
- Scanner-Qualität Phases 1-4 (since v0.9.7) — 13 quick-wins,
confidence audit, A.5{$IFDEF}-Awareness, SCA165 UnusedSuppression,
Golden-Corpus regression suite, SARIFpartialFingerprintsfor
baseline diffs. - Hardening v3 + v4 (2026-06-07/08) — stack-overflow protection
for deep ASTs (8 iterative-DFS detector refactors + 32 MB stack
PE-patch), AST-Destroy reentrancy double-free fix, DFM
resource-wrapper ($FF $0A $00) support, IDE-plugin Win32-OOM fix
via FixHint memoize-cache, scan.log phase-tracking + skip-log. - FP-Reduction Sprint (2026-06-09) — self-scan FPs in four style
detectors reduced by 78% (67 → 15). Side-fix:FreeAndNil(Self.Field)
withSelf.-qualifier is now recognised as freeing.
Full real-world verification: 24 repos in
D:\git-sca-realworld\ scan in 411 s with 0 crashes and produce
1.055.283 findings at profile strict.
TL;DR Highlights (delta over the original Phase 1-only plan)
- DFM Resource-Wrapper-Format
$FF $0A $00now parses correctly —
GExperts jumps from 0 to 1.084 DFM-findings, JVCL DFM-coverage roughly
doubles. The previous code mis-classified these as text and silently
emitted nothing. - No more
EInvalidPointer/SCA006 mid-scan crash on second file
via the AST-Destroy reentrancy bug fix (5ecd498). - No more Win32
EOutOfMemoryin the IDE plugin's
HighlightAllFindingsInFileat ≥100k findings —TFixHintResolver
now caches by(Kind, Severity). - scan.log Phase-Tracking — every
Analyseabbruch:finding now
reveals the last successful phase + current file in the log,
replacing the old black box. - scan.log skip-log — ignored / excluded files appear with a reason
instead of disappearing silently. - 4 style-detector FP-classes killed without weakening real-bug
detection:SCA017 DebugOutput(string-literal contents),SCA070 CommentedOutCode(inline-doc + doc-block start),SCA019 TodoComment
(detector-source mentions),SCA005 FormatMismatch(empty const
resolve). - FieldLeak-Detector —
FreeAndNil(Self.Field)withSelf.-
qualifier is now recognised (USepa.pas repro). - Two new configuration knobs —
[Detectors] MaxLineLength=N(default 120) and
[Detectors] MaxCaseBranches=N(default 10). - Anfänger-Build-Guide —
HowTo_Build{,_de}.mdwalks Delphi
newcomers from IDE installation to first IDE-plugin scan.
Earlier in the 0.9.8 cycle (the Phase 1 deliverables — still in this release)
13 commits since v0.9.7. Phase 1 of
Konzept_ScannerQualitaet.md is
complete (6/6 quick-wins); Phase 4 has begun with the A.3-Minimal
cross-unit visibility check. A multi-persona review (Architecture +
Security + Performance) hardened the code along the way.
Highlights
--time-detectorsMarkdown report — per-detector cumulative
wall-time + call count for data-driven optimisation.- Test-fixture auto-detection — findings from
uTest*.pas/
*Sample.pas/*Demo.pasand test/samples/demos/resources
directories are filtered out in thedefaultandselftest-quiet
profiles. Self-test report is now clean of intentional-fixture noise. - Unused-suppression tracking (SCA165) —
// noinspection Xmarkers
that never suppressed a finding are themselves flagged. Suppression
hygiene now visible. - Golden-corpus FP-regression suite — 5 historical FP reproducers
per Round-1..13 fix, PowerShell runner, CI-ready exit code. Every
future detector change is regression-guarded. - SARIF + Baseline contextHash — SHA256 over the ±3-line snippet
(whitespace-normalised) added to every finding. Baseline matches via
contextHash or legacy fingerprint — survives method renames and
small refactors. Old baselines remain valid (no migration needed). - Confidence audit (35 kinds →
fcMedium) — heuristic / metric /
style / DFM-schema / no-data-flow-security kinds tagged. With
--min-confidence highthe report shows only structural bugs without
losing detector coverage. - A.3-Minimal: SCA052 cross-unit reactivated —
gSymbolRefIndexis
now consulted forfkUnusedPublicMember. Methods called via
obj.Method(args)from another unit are no longer flagged as "dead
public API". - Security hardening —
noinspection Allexcludes critical kinds,
marker parsing is string-context-aware, test-fixture filter is
repo-root-anchored, baseline JSON has entry + length caps. - Performance —
gFileTextCacheis mtime-aware and lives across
the post-scan phase; saves ~191k redundant file reads + UTF-8
validations on a real-world scan.uVisibilityChecktree-walks cached
once per unit.
Phase 1 quick-wins (6/6)
C.4 Per-detector performance profile
--time-detectors writes a Markdown table with per-detector
cumulative wall-time, call count, average. Lets you pick optimisation
targets based on data, not gut feeling.
analyser.exe --path D:\repo --full --time-detectors > perf.mdA.2 Test-fixture auto-detection
TDetectorUtils.IsTestFixturePath returns true for paths matching:
- basenames:
uTest*.pas,*_Test.pas,*_Tests.pas,*Sample.pas,
*Demo.pas,*Sample_*.pas,*_Demo_*.pas,*Sample.dfm,
*Demo.dfm,MeineUnit.pas(and a few SCA-internal demo files) - repo-root-relative directory components:
test,tests,unittest,unittests,samples,demos,
resources
Auto-on for --profile default and selftest-quiet, off for strict.
Manual override via --hide-test-fixtures / --show-test-fixtures.
C.3 Unused-suppression tracking (SCA165)
For every // noinspection X marker we record Consumed = false
initially. When ApplyToFindings suppresses a finding via the marker,
Consumed := true. After the suppression pass, any marker still at
Consumed = false emits an fkUnusedSuppression finding on the marker
line, with a quick-fix hint to remove the marker.
C.1 Golden-corpus regression tests
tests/golden-corpus/fp-reproducers/ holds 5 historical false-positive
reproducer .pas snippets (one per Round-1..13 fix). expected.json
maps each file to a must_not_flag list of rule IDs. The PowerShell
runner tools/check-golden-corpus.ps1 scans the corpus and exits 0/1
based on whether any previously-fixed FP class fires again.
powershell -ExecutionPolicy Bypass -File tools\check-golden-corpus.ps1C.2 SARIF partialFingerprints.contextHash/v1
New unit uFindingFingerprint:
contextHash/v1 = "v1:" + SHA256( Normalize( ±3 lines around finding ) )
Normalize collapses whitespace runs to a single space, tabs → space,
empty lines dropped, CRLF / LF normalised. The hash is stable against
re-indent, trailing-whitespace cleanup and small line-drift refactors.
uBaseline.Apply matches contextHash or legacy fingerprint —
old baselines without contextHash remain valid (backward-compat),
new baselines survive small refactors.
A.1 Confidence-tagging audit
KindDefaultConfidence in uSCAConsts tags ~35 detector kinds as
fcMedium (pattern-match, metrics, style, DFM heuristics, security
without data-flow). All structural-bug detectors stay fcHigh.
TLeakFinding.SetKind reads from this map. Override pattern (e.g.
uCommandInjection := fcLow) is preserved via the new
SetKind(K, AConfidence) overload. Per-kind justifications in
docs/ConfidenceAudit.md.
Phase 4 partial — A.3 cross-unit visibility (begun)
Minimal step
uVisibilityCheck for fkUnusedPublicMember (SCA052) now consults
gSymbolRefIndex.HasExternalRefs before emitting. If any external unit
references the member via Obj.Member(args), no finding is emitted.
Audit (spot-check 18 known cross-unit methods)
- ✅ 8/18 (44 %) A.3 takes effect — pattern:
instance.Method(args) - ❌ 10/18 (56 %) A.3 inactive — 3 known index limitations:
- nkRef / value-use without parens (
F.SeverityText) - class-function-call
TClass.ClassMethod(args)— AST does not
classify asObj.Member - bare calls (
Fingerprint(F)) — deliberate index simplification
- nkRef / value-use without parens (
All three are documented in Konzept_ScannerQualitaet.md §A.3+ as the
prioritised follow-up sprint.
Library FP class (not addressable by the index)
Real-world scan shows the top SCA052 hot-spots are vendor libraries
(mormot.*, MVCFramework, LoggerPro) where the consumers live in
other repos that the index cannot see. Recommendation: path-override
default for vendor/ directories.
Multi-persona review
After Phase 1 + A.3-Minimal a three-persona review (Architecture +
Security + Performance) was run on the branch. 9 of 10 findings were
implemented in commit 120894a:
| # | Area | Fix |
|---|---|---|
| 1 | Perf | Baseline.Apply skips ContextHash for legacy baselines |
| 2 | Sec | noinspection All excludes security-critical kinds |
| 3 | Sec | ParseMarkerLine uses ScanCodeLine (string-context-aware) |
| 4 | Sec | IsTestFixturePath repo-root-anchored via BaseDir |
| 5 | Perf | gFileTextCache lives through post-scan + leak fix |
| 6 | Sec | Baseline JSON 1 M entry / 256 char cap |
| 8 | Perf | Suppression uses AcquireLines (cache hits) |
| 9 | Perf | uVisibilityCheck tree-walks cached per unit |
| 10 | API | SetKind(K, AConfidence) overload |
Finding #7 (DefaultConfidence into TFindingKindMeta record) deferred
as invasive (touches ~160 KIND_META entries) and would require an
explicit drift-test design.
TFileTextCache was subsequently made mtime-aware (commit 1e7e193) to
plug a stale-cache issue exposed by the regression suite after the
post-scan cache change.
Fil...
v0.9.7 - Audit-Nachzug + Round 11 Regex-Cache + Detector-Checkliste-Konformitaet
Highlights
Detector-Audit gegen Todo_neuerdetector.md-Checkliste - alle 156 Detektoren systematisch geprueft. Vier echte Gaps geschlossen:
fmCommandInjection(SCA163, Errors-Sektion)fmInsecureCryptoAlgorithm(SCA162, Warnings-Sektion)fmUnusedRoutine(SCA164, Hints-Sektion)fmNoSonarMarker(SCA065, Hints-Sektion)
Pro Filter alle 5 Touch-Points abgedeckt:
- TFilterMode-Enum-Eintrag
Matches()-case-BranchKindSearchKeywords-Eintrag (Freitext-Suche)- IDE-Combo (uIDEAnalyserForm)
- Standalone-Combo (uMainForm)
Andere Audit-"Lücken" sind Architektur-by-design:
- ~20 DFM-Detektoren via
TDfmAnalysisRunner-Adapter registriert - ~14 Multi-Kind-Container (CodeSmells2/ConcurrencyExt/PerfHotspots/RestHttpSecurity) emittieren mehrere existing Kinds
Performance: Round 11 Regex-Cache
7 weitere Detektoren auf Module-Level-Lazy-Cache umgestellt (zusaetzlich zu Round 9 mit 4 Detektoren):
- uIntegerOverflow, uBoolAlwaysTrue, uFloatEquality
- uSetLengthAppendInLoop, uPublicMemberWithoutDoc, uHardcodedString
- uConcurrencyExt (3 von 5 konstante Patterns)
Kumulativ Round 9 + 11: ~25 von ~30 moeglichen Regex-Compilations pro File eliminiert. Marginal in Prozenten (Delphi 12 TRegEx hat internen Pattern-Cache), aber sauber im Code.
Inhalt des ZIP
StaticCodeAnalyser.exe- Standalone Win64 (10.5 MB unkomprimiert)rules/sca-rules.json+ SchemaREADME.md+CHANGELOG.md
Run
StaticCodeAnalyser.exe --path <dir> --full --profile selftest-quiet `
--report-html sca.html --report-sarif sca.sarif --quietVollstaendiger Changelog: v0.9.6...v0.9.7
v0.9.6 - Self-Test FP-Reduction (43% Noise weniger) + Perf-Optimierungen
Highlights
Dogfooding-Workflow eingefuehrt: analyser.exe --path . --full --profile selftest-quiet --report-html sca-selftest.html triagiert das eigene Repo. Siehe HowTo_DetectorSelftest.md.
False-Positive-Reduction: 12 200 -> 6 917 Findings (-43%), Errors 37 -> 5 (-86%)
Acht Triage-Runden gegen das eigene Repo, jeder Detector-Fix mit struktureller Begruendung dokumentiert in Todo_FalsePositiveReduction.md Section D.
Detector-Fixes (16 Detektoren plus Parser)
- uHardcodedSecret (SCA004): Meta-Felder (
SourceToken/PasswordChar/TokenRef) skippen - uRestHttpSecurity (SCA116): TLS-Pattern auf string-stripped Code (kein Self-Match in Fix-Templates)
- uSqlDangerousStatement (SCA058):
:=im Literal -> Pascal-Code-Zitat, kein SQL - uFormatMismatch (SCA005):
IsInsideStringLiteral-Guard - uUseAfterFree (SCA134): Sibling-Free-Guard fuer if/else-Pattern
- uFieldName (SCA103): Default-Visibility ist
published(TForm/TFrame/TDataModule) - eliminierte ~3000 FPs - uRedundantJump (SCA080): Look-Ahead - nur flaggen wenn nach
end;Block-Terminator - eliminierte ~900 FPs - uUnusedRoutine (SCA164):
MaxLineOfstattNextStartAfterfuer Self-Call-Range - uPublicField (SCA089): Multi-line Method-Header-Continuation + Section-Boundaries
- uGroupedDeclaration (SCA076): ParenDepth-Filter - Param-Listen idiomatisch, nicht geflaggt
- uCommentedOutCode (SCA070): Backtick-Code-Spans aus Doc-Kommentaren strippen
- uCanBeClassMethod (SCA148): Event-Handler-Signature (
Sender: TObject) -> Instance-Method bleiben - uLeakDetector2 (SCA001): Acquire/Release/Dispose/Return/Recycle-Pool-Pattern erkennen - eliminierte 65 FPs
- uExceptionTooGeneral (SCA078): Legit Top-Level-Handler (Log+Exit/Halt/Raise/Result-Assign)
- uUnusedLocal (SCA053): Source-Verify gegen Parser-Cleanup-Edge-Case
- uFreeWithoutNil (SCA139): Nur Felder flaggen - Locals fallen aus dem Scope - eliminierte 111 FPs
- uTodoComment (SCA019): Skip Pfad-Refs (
todo-sonar.md) - uMethodName (SCA106): DFM-Event-Handler-Skip
- uTypeName (SCA104): Exception-Klassen (
E*/*Error/*Exception) - uLockWithoutTryFinally (SCA109): Method-Header + Cache-Calls mit Args ausschliessen
- Parser uParser2.ParseForStmt: Loop-Variable in
for k := 1 to N dowird jetzt in TypeRef gejoint (war vorher aus AST verworfen)
Neue Infrastruktur
- Profile-Negation-Syntax:
[""*"", ""!Kind1"", ""-Kind2""]inrules/sca-rules.json - Bundled Profile
selftest-quiet: 11 Style-Detektoren ausgeblendet fuer fokussiertes Dogfooding - CLI-Flag
--report-html: Self-contained HTML Code-Review-Report (filter/sort/snippets) - HowTo_DetectorSelftest.md: Workflow-Dokumentation
Performance
Round 9 (commit 81749a0): 4 Hot-Path-Detektoren (uRestHttpSecurity, uPerfHotspots, uLockWithoutTryFinally, uUseAfterFree) compileren ihre Regex-Patterns nur noch EINMAL pro Prozess statt pro File. Gemessener Effekt: 3.0s -> 2.92s (-2.7% auf bereits sehr flotter Baseline).
Tests
Alle 1696 DUnit-Tests gruen. Zwei Tests an neues Verhalten angepasst:
uTestGroupedDeclaration.ParameterGrouped_Reported->_NotReporteduTestFreeWithoutNil.FreeWithoutNil_ReportedSRC von Local auf Field umgestellt
Version
Alle internen Version-Konstanten (uSCAConsts, uConsoleRunner, uIDEExpert, sca-rules.json, dproj VerInfo) jetzt einheitlich auf 0.9.6. Vorher waren drei verschiedene Versionen verstreut (0.9.1 / 0.9.3 / 0.9.5).
Vollstaendiger Changelog: v0.9.5...v0.9.6
v0.9.5 - Branding + Standalone-UI refresh + scan perf wins
Highlights
Branding (canonical Embarcadero pattern)
- Standalone EXE: app icon via
<Icon_MainIcon>+ multi-ressca.ico - IDE-Plugin: Splash/About bitmap + Float-mode caption icon + Tools-menu entry with 16x16 icon via
INTAServices.AddImages - BRCC32 16-bit-era constraints documented (24-bit BMPs, no 256x256 sub-icons, no PNG-in-ICO)
Standalone UI refresh (Tier 1+2)
- Hamburger menu (Cancel/Export/Settings/Ignore) replaces scattered toolbar buttons
- Segoe UI + IDE_BG_CHROME theming aligned with IDE-Plugin look
- BtnBranch always visible, hamburger sits to its right
New detectors
- SCA162 InsecureCryptoAlgorithm (MD5/SHA1/DES/RC4)
- SCA163 CommandInjection (ShellExecute/CreateProcess/WinExec + string-concat)
- SCA164 UnusedRoutine
- TThreadDestroyWithoutTerminate: type-resolution via constructor-call fallback
- DetectorReview filter mode (DEBUG-only, INI-gated)
Scan performance
RunAllDetectorspost-filter O(n^2) -> O(n) via PrevCount tracking + lazyTStopwatchTAstNode.FindAlllazy per-node cache: eliminates ~140 redundant tree-walks per file (54x nkMethod + 26x nkAssign + 22x nkCall + 11x nkClass over ~150 detectors). API unchanged, snapshot-copy preserves caller ownership.
IDE-Plugin
- Tools-menu entry with icon (canonical
INTAServices.AddImages+TAction+TMenuItem) - Hamburger menu entry to clear all hover-annotation markers
FP-Fixes & polish
- InsecureCrypto: "des" as German word no longer matches the DES cipher
- FloatEquality:
if Value = nilno longer flagged (NEVER_FLOAT_TOKENS) - 195 missing DE translations backfilled + CI lock
- Hint cleanup: H2443/H2219/H2077
Full changelog: v0.9.3...v0.9.5
v0.9.3 - Docked-mode theme refresh + encoding + multi-finding hint
Release 0.9.3 — 2026-05-27
A polish release focused on IDE-plugin theme reliability in docked-mode,
file encoding correctness, and annotation-overlay usability when
multiple findings sit on the same line. No new detectors (count stays at
161); the changes target reliability, visual consistency, and false-positive
reduction.
Highlights
- Docked-mode IDE-theme refresh now works for the entire plugin, not
just the grid. Tiles, toolbar panels, help-panel caption and before/after
memos now follow IDE theme switches reliably — previously only the grid
picked up the new theme while everything else stayed in the old colors. - IDE plugin announces itself in the start sequence — splash-screen
entry + About-Box registration via the standard ToolsAPI splash/about
services. - File encoding detection — BOM-sniff → strict-UTF-8-validation →
Windows-1252 fallback. Fixes mojibake on ANSI files with German umlauts
in the unit header (the old code forced UTF-8 with lenient byte
substitution). - Multi-finding annotation hint shows full descriptions per finding,
not just titles. When two or three findings share the same line, the
overlay no longer collapses them to a title list. - FloatEquality false-positive on string comparisons fixed —
aValue = ''(string) is no longer reported as a float-equality bug,
even when another function in the same file hasaValue: Doubleas
a parameter. - Visible per-tile border in the Sonar stats row using
clBtnShadow
(wascl3DDkShadow— collapsed onto the chrome background in many
IDE themes). - ~80× faster theme switch via cold/warm Apply split (per-Descendant
ApplyTheme only on first apply; subsequent switches reuse style hooks).
🎨 IDE-plugin theme reliability (NEW)
The IDE plugin can run floating (its own window) or docked (as a
tab inside the IDE main window or a side bar). Theme handling between
the two modes diverged significantly in the VCL/ToolsAPI stack, and
0.9.2 only worked correctly when floating.
Symptoms before
Switch IDE → Options → Theme: only the result grid picked up the new
colors. Tiles, toolbar background, path/filter panels, the help-panel
caption, and before/after memos remained in the previous theme until
the IDE was restarted.
Diagnosis
Three independent root causes:
IOTAIDEThemingServices.ApplyThemedoesn't propagate transitively
through aTFrameto its descendants in Delphi 12. Only controls the
caller passes directly get their style hooks updated.Vcl.Themes.StyleServices(global) stays stale in docked mode —
Theming.ApplyTheme(IDE-Main-Form)doesn't update the VCL-global
StyleServices, so custom paint code readingclBtnFacevia the
global service gets the previous theme's RGB.- System-color identifiers (
TPanel.Color := clBtnFace) are resolved
at paint time against the stale VCL-global, not against the IDE's
theming service.
Solution: per-Descendant 5-step apply pipeline
uIDETheme.TIDETheme.Apply now walks the entire control tree under the
frame and, per node:
- Apply IDE-style-hook via
IOTAIDEThemingServices.ApplyTheme. - Preserve
StyleElements - [seClient]across the apply (snapshot +
restore). Lets controls keep their explicitColorinstead of being
overpainted by the style hook. - Resolve
ColorandFont.Coloragainst the IDE-theme's
StyleServicesand write the concrete RGB back. The original
identifier (clBtnFace,clWindow, ...) is cached per control so
subsequent theme switches still resolve correctly. - Bind to the IDE style by setting
TControl.StyleName := IdeStyle.Name
(Delphi 10.4+ per-control routing). This is what fixesTButton,
TStringGridheader/border,TComboBox,TProgressBar,
TStatusBar— they go through VCL-StyleHooks that previously read
from the stale VCL-global. Invalidate(with explicitRepaintonTCustomGriddescendants
that have their own paint cache).
The key win: this avoids the 2-second IDE hang that
TStyleManager.SetStyle would cause — SetStyle broadcasts
CM_STYLECHANGED over every form in Screen.Forms[] (50+ forms ×
1000+ controls per form). Per-control StyleName gives the same
visual outcome for the plugin's own controls without the broadcast.
See Konzept_DockedThemeRefresh.md for
the full diagnostic trail.
🛠 IDE plugin start-sequence integration
The plugin now registers itself in the IDE start-up sequence with the
standard ToolsAPI hooks:
- Splash-screen entry during IDE boot (the box that lists "Bundled
in this product..." — your plugin shows up there alongside
GExperts/CnPack/etc.). - About-Box entry — Help → About... → "Static Code Analyser" with
version + description.
Visible confirmation that the plugin loaded correctly without opening
the tool window.
🧰 Visual polish
Per-tile border (Sonar stats row)
The 9 stat tiles (Errors / Warnings / Hints / Read errors / Bugs /
Security / Duplicates / Cyclomatic / Quality) now have a visible 1 px
border drawn in clBtnShadow (theme-aware via
ActiveStyleServices.GetSystemColor). Previously cl3DDkShadow was
used, which collapses onto clBtnFace (the tile background) in many
themes — the border was technically drawn but invisible.
"Quality" caption
FTileScore caption shortened from "Code Quality" to "Quality" (tile
label + hover tooltip title). The status-bar score line keeps the long
form — different context, more room.
Stats row 2 px taller
STATS_PANEL_HEIGHT 51 → 53 gives glyph + count + caption more vertical
breathing room. Tiles inherit the panel height via alLeft.
Toolbar simplification
The IDE-plugin toolbar dropped from 4 rows to 3 (Path / Filter+Search /
Stats). Less-used actions (Branch, Save, Quit, Settings, Ignore,
Export, Profile) moved into a hamburger menu. The Analyse button and
filter/severity combos stay always-visible. Responsive layout still
toggles labels and combo widths on width thresholds.
🔧 Infrastructure fixes
Encoding detection (file cache)
uFileTextCache previously called LoadFromFile(file, TEncoding.UTF8)
which is lenient — invalid UTF-8 bytes get silently replaced with
U+FFFD (replacement character), and the explicit-fallback except
branch never fired. Result: ANSI files with German umlauts (ä, ö,
ü in unit headers, copyright comments) came through with mojibake.
The new LoadFileSmart helper does three-stage detection:
- BOM sniff (UTF-8 / UTF-16 LE / UTF-16 BE) — if present, use
that encoding and skip the BOM bytes. - No BOM, all bytes form well-formed UTF-8 (RFC 3629 validation)
— use UTF-8. - Otherwise —
TEncoding.Default(Windows-1252 on a typical
Windows install).
Both TFileTextCache.GetLines (cached path) and AcquireLines
(fallback path) go through LoadFileSmart now.
Multi-finding annotation overlay
When two or more findings sit on the same source line, the editor
annotation overlay used to collapse them into a summary with bullet
list of titles only — descriptions were dropped. The bullet list now
includes each finding's full description indented under the title:
• PointerArithmeticOnString [Warning]
String + integer addition treated as pointer arithmetic ...
• UseAfterFree [Error]
Variable accessed after FreeAndNil ...
Title / severity / stripe-color still come from the strongest finding.
Fix-hint remains suppressed in multi-mark (showing only one fix would
be misleading).
FloatEquality detector — false-positive on string compares
The lexical detector strips strings and comments before scanning for
<var> = <token> patterns. Empty string literals ('') were replaced
with two spaces, which let the regex bridge over them and pick up the
next token. Concrete failure mode:
if aValue = '' then Exit; // aValue: string…was reported as Float equality (aValue = then) is unreliable —
because aValue: Double existed as a parameter in another function in
the same file (file-wide FloatVars), and '' → spaces → regex skipped to "then". The detector also called the Pascal keyword then the RHS.
Fix: stripped string content now uses ~ (not \w, not \s) as the
marker. The regex \s*[\w.]+ fails at the first ~, so cross-string
matches no longer happen. New regression test:
StringEqualityWithFloatVarNameElsewhere_NotReported.
⚡ Theme-switch performance
The Apply pipeline keeps a per-control snapshot of the original color
identifiers (FOrigColors: TDictionary<Pointer, TControlColors>). On
first apply per control the original clBtnFace / clWindow value is
stored; on every subsequent theme switch the cache returns the
identifier and resolution runs against the fresh IDE-style — fixing
the "second-switch bug" where Color had already been written as
concrete RGB and lost the clSystemColor bit.
Combined with the cold/warm split (per-Descendant ApplyTheme only
once; later switches skip the style-hook registration), full theme
switch time dropped from ~2 s of broadcast-driven repaints to under
~25 ms.
🧱 Code structure
uIDETheme.TIDEThemeImpl.ApplyRecursiverefactored — was an 80-line
procedure with four mixed concerns, now four named helpers
(ApplyStyleHookPreserveSeClient,ResolveDescendantColors,
BindToIdeStyle,TriggerRepaint) plus a 15-line walker.TIDEThemeImpl.RebuildCachereads from
IOTAIDEThemingServices.StyleServicesinstead of the VCL-global —
matches the rest of the pipeline and avoids stale docked-mode colors
inFrameBg/FrameFg.EngineSCA/folder renamed toSCA.Engine/(preparation for a later
project-split into Engine / Standalone+CLI / IDE-Plugin / Tests, see
Konzept_ProjektAufteilung.md).
Compatibility
- **Delphi 12 At...
v0.9.2 - More detectors implemented + Standalone progress + Configurable IDE shortcuts
Release 0.9.2 — 2026-05-22
v0.9.1 was already published earlier (2026-05-16, "SonarQube integration").
The work in this release tag bumps to 0.9.2 so the previous tag stays
intact in the history.
Highlights
- 9 new mORMot-cluster detectors (SCA153-161): UnpairedLock,
MoveSizeOfPointer, WithMultipleTargets, GetMemWithoutFreeMem,
SetLengthAppendInLoop, PointerArithmeticOnString, EmptyOnHandler,
StringFromPointer, PointerSubtraction. - Standalone GUI finally has a progress bar + Cancel button +
MAX_SCAN_FILES scan-runaway protection (was IDE-only before). - IDE-plugin keyboard shortcuts are configurable (cnpack-style:
press the key combo, store in INI). Master toggle to disable all
shortcuts at once. Settings dialog now scrollable. - Detector count: 161 kinds (Sonar-style finding categories),
delivered by ~130 pipeline classes — some classes emit multiple
kinds (e.g.uVisibilityCheck→ 4,uDfmAnalysisRunner→ 22 DFM). - Translation completeness: 161 / 161 finding-hint descriptions
covered in both DE translation stores (GDeMap runtime fallback- i18n/de.po dxgettext path), all 82 combo-labels translated.
What's new
Detectors
SCA138-152 — Sonar-50 expansion to cover the remaining
Maintainability + Code-Smell slots:
| ID | Name | Type |
|---|---|---|
| 138 | GodClass | Code Smell |
| 139 | FreeWithoutNil | Bug |
| 140 | MultipleExit | Code Smell |
| 141 | LargeClass | Code Smell |
| 142 | UnsortedUses | Code Smell |
| 143 | MissingUnitHeader | Code Smell |
| 144 | FloatEquality | Bug |
| 145 | ExceptInDestructor | Bug |
| 146 | BooleanParam | Code Smell |
| 147 | UnusedPrivateMethod | Code Smell |
| 148 | CanBeClassMethod | Code Smell |
| 149 | MissingOverride | Bug |
| 150 | BoolAlwaysTrue | Bug |
| 151 | ConstantReturn | Code Smell |
| 152 | HardcodedString | Code Smell |
SCA153-161 — mORMot-cluster: patterns recurring in large
low-level Delphi codebases (threading primitives, raw heap
allocation, dynamic-array growth, byte-level buffer ops, PChar
arithmetic, multi-target with blocks, typed exception handlers,
string casts from raw pointers, Win64 pointer subtraction).
| ID | Name | Type | Severity |
|---|---|---|---|
| 153 | UnpairedLock | Bug | Warning |
| 154 | MoveSizeOfPointer | Bug | Warning |
| 155 | WithMultipleTargets | Code Smell | Hint |
| 156 | GetMemWithoutFreeMem | Bug | Warning |
| 157 | SetLengthAppendInLoop | Code Smell | Warning |
| 158 | PointerArithmeticOnString | Bug | Warning |
| 159 | EmptyOnHandler | Bug | Warning |
| 160 | StringFromPointer | Bug | Warning |
| 161 | PointerSubtraction | Bug | Warning |
UI / UX
- Standalone progress feedback:
TProgressBar(Marquee during
the directory scan, Position-bar with %-readout during the file
phase) + aCancelbutton +MAX_SCAN_FILES=20000guard. Wired
into all three analysis entry points: full-folder scan, branch-
changes-only scan, single-file analysis. - IDE-plugin configurable shortcuts: Tools → Options → Static
Code Analyser → Hotkeys section. Click into the edit field, press
the desired key combo (e.g.Ctrl+Alt+Afor silent analysis,
Ctrl+Alt+↑/↓for finding navigation). Stored inanalyser.ini,
master "Enable all keyboard shortcuts" toggle gates all of them. - Settings dialog as TScrollBox: groups (Silent → Rule-Set →
Detectors → Hotkeys) stack vertically with auto-scroll, no more
per-group height-engineering. - Use-cases section in README (EN+DE): 19-row capability matrix
showing what each deployment mode (IDE plugin / Standalone GUI /
CLI) supports, plus 4 role profiles.
Build / Infra
- Central output directory:
..\Output\Test\<Platform> <Config>
in all three .dproj — no more per-project Win32/Win64 build
artefacts polluting the source tree. Added to.gitignore. paths.optset.xmlshared search path for the IDE plugin —
one Form-sources directory per line with explanatory comments.- TestInsight CI: GitHub Actions workflow runs the full DUnitX
test suite on push (Win32 + Win64).
Tests
- uTestSuppressionCompleteness: three-test fixture that
guarantees every one of the 161 detector kinds is reachable via
the// noinspection <Name>suppression marker. - 36 new DUnitX fixtures for the new SCA138-161 detectors
(~5 tests each: positive cases + negative cases + Kind/Severity
round-trip).
Translations
- 161 / 161 hint descriptions present in both
uLocalization.pas:GDeMap(runtime fallback) andi18n/de.po
(dxgettext path). - 82 / 82 UI combo-labels translated.
Todo_TexteTranslate.md(new): per-detector translation
checklist with audit shell snippets.
Bug fixes
IDE-plugin progress bar
- Scan→File-phase transition no longer waits for the 100ms
throttle. First File-Phase callback always forces the
pbstMarquee → pbstNormalstyle switch. - RunCurrent (single-file analysis) now toggles
pbstMarquee
for the duration. Previously the bar stayed idle.
Detectors (round 1)
Eight SCA138-152 detectors had AST-shape assumptions that didn't
match the actual parser output:
- uBooleanParam:
IfStmtcondition is flat text in
IfNode.TypeRef, not in children. Added word-boundary scan. - uCanBeClassMethod: method body lives in an
nkBlockwrapper
child, not direct children. AddednkBlockto body check, plus
cross-decl lookup so interface-only;virtualmarkers are seen. - uConstantReturn:
IsFunctionMethodwas looking for:in
TypeRef; parser uses'function:Integer'format.ExtractRhs
was reading children; RHS lives innkAssign.TypeRef. - uFloatEquality: rejected
Ratio = 0.5because0.5has a
dot — was meant to filter qualified names. Now also accepts
numeric literals (first char is a digit). - uLargeClass: measured span between method headers only;
600-line method bodies were invisible. AddedDeepMaxLine
recursive descendant scan. - uExceptInDestructor: raises in the try-body of
try/except
were flagged even though theexceptcatches them. Added
nkTryExceptspecial case. - uFreeWithoutNil: identity check
S = FreeCallbetween an
nkAssignand annkCallcould never match. Switched to
Line-number comparison. - uGodClass:
;abstractmarker was being looked for on
nkClass.TypeRef, but the parser only stores class-level
modifiers there for inheritance lists. Detect "framework class"
by checking all methods have;abstractin their TypeRef instead.
Build
- Forward-declaration for
IsShortcutsMasterEnabledin
uIDEAnalyserForm.pas— was called ~1700 lines before its
definition, caused E2003 in the IDE build. - dpk + dproj sync for 15 implicit-import warnings (W1033):
detector units must be listed in the packagecontainsclause. - Hint cleanup: removed unused locals in uAbstractNotImpl
(Other), uUnusedPrivateMethod (Sections,Mthods), dropped
deadFoundwrite in uFreeWithoutNil.
Breaking changes
None. All existing rule names, KIND_META entries, and suppression
markers remain stable.
Compatibility
- Delphi 12 Athens (RAD Studio 23) - target
- Sonar: External-Issues JSON format unchanged
- SARIF: 2.1.0 output unchanged
analyser.inischema: backward compatible; new keys for
shortcut config and master-toggle (default = enabled)
Files added in this release
Todo_TexteTranslate.md— generic translation checklistpaths.optset.xml— IDE-plugin shared search pathRELEASE_NOTES.md/RELEASE_NOTES_de.md— this file- 9 new
uXxx.pasdetector units (SCA153-161) - 9 new
uTestXxx.pastest fixtures uTestSuppressionCompleteness.pas— completeness guard
Acknowledgements
Detectors SCA153-161 came out of an audit of the
mORMot2 source tree —
patterns that recur in large low-level Delphi codebases were
extracted into reusable lexical detectors.
Release 0.9.2 — 2026-05-22
v0.9.1 wurde bereits am 2026-05-16 als "SonarQube integration" veröffentlicht.
Die Arbeiten in diesem Release-Tag bumpen daher auf 0.9.2, damit der
ältere Tag in der Historie unverändert bleibt.
Highlights
- 9 neue mORMot-Cluster-Detektoren (SCA153-161): UnpairedLock,
MoveSizeOfPointer, WithMultipleTargets, GetMemWithoutFreeMem,
SetLengthAppendInLoop, PointerArithmeticOnString, EmptyOnHandler,
StringFromPointer, PointerSubtraction. - Standalone-GUI hat endlich Progressbar + Cancel-Button +
MAX_SCAN_FILES-Schutz vor Scan-Runaway (vorher nur im IDE-Plugin). - IDE-Plugin Tastenkürzel sind konfigurierbar (cnpack-Stil:
Tasten drücken, in INI speichern). Master-Toggle, um alle
Kürzel auf einen Schlag abzuschalten. Einstellungs-Dialog
scrollbar. - Detektor-Anzahl: 161 Kinds (Sonar-Befund-Kategorien),
geliefert von ~130 Pipeline-Klassen — einige Klassen
emittieren mehrere Kinds (z. B.uVisibilityCheck→ 4,
uDfmAnalysisRunner→ 22 DFM). - Übersetzungs-Vollständigkeit: 161 / 161 Hint-Descriptions
in beiden DE-Stores (GDeMap-Runtime-Fallback + i18n/de.po
dxgettext-Pfad) abgedeckt, alle 82 Combo-Labels übersetzt.
Was ist neu
Detektoren
SCA138-152 — Sonar-50-Erweiterung für die verbleibenden
Maintainability + Code-Smell-Slots:
| ID | Name | Type |
|---|---|---|
| 138 | GodClass | Code Smell |
| 139 | FreeWithoutNil | Bug |
| 140 | MultipleExit | Code Smell |
| 141 | LargeClass | Code Smell |
| 142 | UnsortedUses | Code Smell |
| 143 | MissingUnitHeader | Code Smell |
| 144 | FloatEquality | Bug |
| 145 | ExceptInDestructor | Bug |
| 146 | BooleanParam | Code Smell |
| 147 | UnusedPrivateMethod | Code Smell |
| 148 | CanBeClassMethod | Code Smell |
| 149 | MissingOverride | Bug |
| 150 | BoolAlwaysTrue | Bug |
| 151 | ConstantReturn | Code Smell |
| 152 | HardcodedString | Code Smell |
v0.9.0 - Silent-Mode + Tools>Options + Rule-Set profiles
v0.9.0 — Silent-Mode, Tools>Options Page, Rule-Set Profiles, IDE-Plugin polish
Workflow-focused release. Three big themes:
- Silent-Mode — single-file analysis directly from the editor
right-click +Ctrl+Alt+Ahotkey. No dock window opens. Findings
appear as gutter markers + hover overlays in the code editor. - Rule-Set profiles —
ide-fast,default,strict,security,
bugs-only,code-quality,dfm-onlyshipped as a JSON catalog
(rules/sca-rules.json) plus CLI flags--profile/--min-severity
for CI integration. - Tools > Options page under Third Party > Static Code Analyser
in the Delphi IDE — Silent-Mode toggle, profile dropdowns,
detector toggles. All persisted inanalyser.ini.
🤫 Silent-Mode (NEW)
A new analysis path that bypasses the dock window entirely:
- Editor right-click → "Analyse current file (silent)" — analyses
the currently active.pasand renders findings inline. Ctrl+Alt+Ahotkey — same action, editor-wide. Registered as
a properIOTAKeyboardBinding, works without first opening the
popup.- Inline rendering — gutter markers +
[✕]-dismissible hover
overlays per finding. Markers survive tab switches via multi-file
marker storage; they auto-clear on file save (line numbers stale
after edits). - Disable toggle —
Tools > Options > Third Party > Static Code Analyser > Silent Mode. INI:[Silent] Enabled=0/1.
Implementation notes:
- Editor-popup integration uses the OnPopup-chain pattern
(GExperts/CnPack-style): each popup-show clears our previous item,
lets the IDE rebuild the menu, then appends a fresh item. Permanent
inserts clash with the IDE's action-manager rebuild. IOTAKeyboardBindingregistersCtrl+Alt+Aeditor-wide so the
hotkey works even without a prior right-click. Diagnostic logging
on registration conflict (other plugins competing for the same
shortcut).
🎚 Rule-Set profiles (NEW)
Seven bundled profiles cover the common analysis use-cases:
| Profile | Use-case |
|---|---|
ide-fast |
Default for IDE-plugin Silent-Mode — fast subset for live feedback |
default |
Standalone GUI default — full detector set |
strict |
CI gate — everything on, including stylistic detectors |
security |
OWASP / vulnerability focus |
bugs-only |
Just Bug type findings (no smells, no style) |
code-quality |
Smells + cyclomatic + duplication |
dfm-only |
Just the 20 DFM detectors |
Source: rules/sca-rules.json — single source of truth. The Delphi
IDE plugin and the CLI both read the same catalog.
CLI flags for CI:
analyser.d12.exe path/to/repo --profile strict --min-severity warning --format sarifINI keys for persistent configuration:
[Rules]
Profile=strict # standalone + CLI
IdeProfile=ide-fast # IDE plugin (dock window + Silent-Mode)
MinSeverity=warning⚙ Tools > Options page (NEW)
The Delphi IDE plugin now contributes a configuration page under
Tools > Options > Third Party > Static Code Analyser:
- Silent Mode — enable/disable the editor right-click + hotkey
- Rule-Set — Profile (CLI/Form), Min-Severity, IDE Profile
- Detectors — UsesCheck, IncludeTests, AutoDiscoverClasses
All values persisted in the same analyser.ini the dock window uses,
so the Options page is just an alternative UI — no duplicate settings
store. Page open is fast (~5 ms): TMemIniFile + pre-warmed
TRuleCatalog.
The dock-window Profile combo also acts as a live override that wins
over the INI for Silent-Mode runs — you can switch between profiles
in the dock and have the next Silent-Mode trigger pick it up without
hitting Save.
✨ IDE-plugin polish
- Annotation overlay —
[✕]-button per hover overlay to dismiss
individual stale findings (in addition to the file-scoped reset on
save). After-code section in the Help-panel comes with a✓
marker prefix. - Multi-file marker storage —
TFindingHighlighterkeys marks by
NormalizePath(filename)so switching editor tabs no longer
clears markers of background files. - Auto-clear on save —
IOTAModuleNotifierattached per marked
file;AfterSaveclears its marks because line numbers are no
longer reliable after edits. - Performance —
TIniFile→TMemIniFile(~25 syscalls → 1)
inuRepoSettings.TRuleCatalog.ProfileNamespre-warmed at
plugin load so the first Tools>Options page render doesn't pay
the ~20-50 ms JSON-parse cost.
🌍 Internationalisation (i18n)
All new IDE-plugin captions and the standalone form's UI strings
are wrapped with _() and have German translations in
uLocalization.BuildDeMap. 18 new DE mappings cover the Tools>Options
page, editor popup, View menu, and BtnBranch hint. Wording
cleanups for existing entries:
- "Silent-Modus" / "Silent-Analyse" (unified, was mixed with
"Stille Analyse") - "Markierungen" (was "Streifen")
- "Regelsatz" (was "Regel-Set")
- "auch DUnit/DUnitX-Test-Units analysieren" (was "mit analysieren")
DFM-level hardcoded hints (e.g. BtnBranch.Hint) moved to runtime
assignment in FormCreate so the translation actually applies.
🖥 Standalone form
UI parity with the IDE plugin: toolbar with toolbar buttons, stat
tiles row above the grid, hint panel right of the grid for
Before/After code examples, three-panel status bar. Profile +
Min-Severity combos in the toolbar populated from
TRuleCatalog.ProfileNames. The standalone EXE no longer pops a
black console window on launch (was {$APPTYPE CONSOLE} from the
old DPR template; now GUI subsystem with AttachConsole for the
optional CLI path).
🐛 Fixes
- Silent-Mode
AutoDiscoverCustomClasses— the global flag is
now mirrored fromSettings.AutoDiscoverClassesbefore every
Silent run. Previously the detection ran with whatever state the
last dock-window run left behind, orFalseon cold IDE start.
DiscoveredClasses/DiscoveredStaticClassesare cleared per
Silent run so leftover hits from a different project don't bleed
into the current analysis. FEditorEventsObjMemoryLeak false-positive —// noinspection MemoryLeakmarker added directly above the.Createline in
uIDELineHighlighter. The detector flagged this as a leak, but
TFindingEditorEventsinheritsTInterfacedObjectand the
refcount is held byFEditorEvents : INTACodeEditorEvents. No
explicitFreeneeded.uDfmRepoIndex/uFormBinderH2077 hints — dead-store
nil-assignments before re-assignment removed.
📦 Migration
- No INI migration needed — new keys are read with sane defaults
([Silent] Enabled=1,[Rules] IdeProfile=ide-fast, etc.). - Rule profiles: existing setups with no
[Rules] Profile=get
default(the full detector set, same behaviour as v0.8.0). - Plugin file rename: package is now
StaticCodeAnalyser.IDE.d12.bpl(wasStaticCodeAnalyserIDE.bpl).
Remove the old BPL fromTools > Configure > Component > Install Packagesand add the new one. docs/APP.pngremoved — replaced bydocs/sca-demo.gifas the
README hero. If you embeddeddocs/APP.pngsomewhere else, you'll
need to update the path.Co-Authored-Bypolicy: commits in this release deliberately
strip theCo-Authored-By: Claudefooter per maintainer preference.
v0.8.0 - DFM Scanner + Headless CLI + Rule Catalog + SARIF + Win64
v0.8.0 — DFM Scanner + Headless CLI + Rule Catalog + SARIF + Win64
Major-feature release that bundles four years' worth of structural
work into one tagged version:
- DFM scanner with 20 form-file detectors (Pascal-AST coupled
via FormBinder + repo-wide form index) — total detector count
grows from 21 to 41. - Headless CLI mode that turns the tool into a CI/CD building
block — console mode + detector rule catalog as single source of
truth + SARIF v2.1.0 export for GitHub Code Scanning. - IDE plugin polish — Sonar-style stat tiles, three-tier
responsive docked-mode layout (hamburger menu absorbs actions
under 500 px), single-file live watch, hover-hint overlay,
editor-as-text DFM viewer via Close-and-Reopen. - Win64 readiness — the standalone EXE now compiles cleanly
for both Win32 and Win64 (the IDE plugin remains Win32-only
because the RAD Studio 12 IDE itself is 32-bit).
📐 DFM scanner (NEW)
20 dedicated detectors for .dfm form files, grouped into six
clusters. Pascal-AST coupling means findings like
"Btn1Click referenced in DFM doesn't exist in published section"
or "SQL property built from Edit1.Text in companion .pas" are
caught.
Detectors (cluster overview)
| Cluster | Detectors |
|---|---|
| Dead-Wiring | DfmDeadEvent, DfmOrphanHandler, DfmEmptyBoundEvent |
| Data-Access | DfmSchemaMismatch, DfmCircularDataSource, DfmFieldTypeMismatch, DfmRequiredFieldUnbound, DfmRequiredFieldNotVisible |
| Security | DfmHardcodedDbCreds, DfmSqlFromUserInput |
| Layering | DfmDbInUiForm, DfmCrossFormCoupling, DfmLayerViolation, DfmForbiddenClass |
| UI/UX | DfmDuplicateBinding, DfmTabOrderConflict, DfmGodHandler, DfmActionMismatch |
| Naming | DfmDefaultName, DfmHardcodedCaption |
Full descriptions in DETECTORS.md.
Infrastructure
- DFM lexer + parser + component graph in
sources/Parsing/uDfmLexer.pas,uDfmParser.pas,
uComponentGraph.pas— own parsing front-end, independent of
ToolsAPI / IDE. - Binary-DFM reader (
uDfmBinaryReader) — TPF0-prefix
detection + text conversion via the RTL. Binary-saved forms
used to be silently skipped; now they get analysed. - Typed property accessors on
TPropValue/
TComponentNode—GetBoolean / GetInteger / GetString / GetIdent / SetPropertyContains, default-aware (VCL defaults
for properties not serialised in the DFM). - FormBinder + Repo-wide form index (
uFormBinder,
uDfmRepoIndex) — couples DFM graph to Pascal AST,
BindWithParentswalks the class inheritance chain so
inherited members are visible to detectors. - Frame resolver (
uDfmFrameResolver) —
ResolveFrameGraph/EnumerateFrameComponentsload a
frame's components on demand for cross-frame analyses. - VCS-diff aware —
.dfmchanges trigger analysis of the
companion.pasautomatically (and vice versa). - HTML report — file dropdown groups
.pas+.dfmwith
the same basename under one heading.
🖥️ IDE plugin polish (NEW since v0.7.2)
- Dockable tool window with three-tier responsive layout
(Narrow/Medium/Full breakpoints at 500/700 px). In narrow mode
the toolbar collapses to ▶ Analyse / 📄 File / SearchEdit /
☰ Hamburger — all hidden actions remain reachable through the
hamburger menu. - Single-file live watch ("📄 Current file") — file gets
re-analysed on every save (300 ms debounced) and every edit
(1000 ms debounced) in a background thread..dfmsaves now
also trigger re-analysis of the companion.pas. - DFM finding opens DFM as text in the Code Editor via the
Close-and-Reopen pattern (DFMCheck/GExperts-style). When the
companion.pasis modified, falls back to opening the.pas
and showing a status-bar hint. - Hover-hint overlay for finding lines with multi-line wrap.
- Sonar-style stat tiles above the grid (Errors / Warnings /
Hints / Bugs / Vulnerabilities / Code Quality score). - Theme tracking — follows the active Delphi IDE theme via
IOTAIDEThemingServices+INTAIDEThemingServicesNotifier
(light/dark/Mountain Mist/Carbon). - Claude AI prompt copy — click a finding to copy a
ready-made Markdown prompt with code context to the clipboard.
🛠️ Headless CLI + Rule Catalog + SARIF
Highlights
-
Headless CLI mode —
analyser.d12.exeis now console-aware
({$APPTYPE CONSOLE}) and accepts switches:analyser.exe --path D:\repo --full --report-sarif sca.sarif analyser.exe --path D:\repo --branch --quiet analyser.exe --file MyUnit.pas analyser.exe --help / --versionWithout switch arguments the VCL GUI still launches as before.
Exit-code convention follows the SCA-tool standard:0 = clean 1 = hints only 2 = warnings present 3 = errors present 4 = read errors (parser/IO) 99 = tool error (bad args, missing path, ...) -
Rule catalog (
rules/sca-rules.json) — single source of truth
for all 22 Pascal-AST detector rules (SCA001–SCA022). Per rule:
stable ID, name, short/full description, default severity, type,
tags, CWE, OWASP refs, config key, detector unit, bad/good examples.
LoaderuRuleCatalogwith lookup-by-kind and lookup-by-ID. JSON
schema (rules/sca-rules.schema.json) for editor autocomplete and
CI validation. The 20 new DFM detectors ship viaKIND_METAin
uSCAConstsbut are not yet in the JSON catalog — that integration
is the first follow-up item after this release. -
SARIF v2.1.0 export (
--report-sarif <file>) — the OASIS
standard for static-analysis output. Natively consumed by
GitHub Code Scanning, Azure DevOps, Visual Studio Code
(with the SARIF extension) and SonarCloud. Findings appear
directly in PRs as inline annotations; the GitHub Security tab
tracks history per branch.- Full
tool.driver.rules[]from the rule catalog (every finding
has a rule ID + helpUri pointing atdocs/rules.md) partialFingerprints.primaryLocationLineHash(SHA256 over rule ID- path + line + message) for cross-commit deduplication in GitHub
- Repo-relative paths with forward slashes (SARIF/GitHub convention)
properties.tagsincludes catalog tags + CWE + OWASP for filtering
- Full
CI integration
-
GitHub Actions workflow (
.github/workflows/sca.yml) as a template:- Job
sca: full project scan on push + PR, SARIF upload via
github/codeql-action/upload-sarif@v3 - Job
sca-pr-changes: branch diff only (--branch), fail-on-error
for PRs
- Job
-
Distribution pattern: the workflow downloads a release asset
(analyser-windows.zip) containinganalyser.d12.exeand the
rules/folder. Upload once per release with:Compress-Archive -Path bin\analyser.d12.exe,rules -DestinationPath dist\analyser-windows.zip gh release upload v0.8.0 dist\analyser-windows.zip
Engine
-
uConsoleRunner(Console/uConsoleRunner.pas, NEW) — argument
parser supporting both--key=valueand--key valuesyntax, run
dispatcher (Branch/SingleFile/Full), exit-code mapping, stderr
logging. Fully testable through theParseArgs(Args: array of string)
API without process I/O. -
uRuleCatalog(Common/uRuleCatalog.pas, NEW) — lazy JSON
loader with file lookup order (ExeDir/rules/, ../rules/, .../rules/),
fallback mode if catalog JSON is missing (minimal metadata from the
KIND_METAenum), lookup methods + ForEach iterator. -
uExportSARIF(Output/uExportSARIF.pas, NEW) — SARIF writer
viaSystem.JSON, no external deps.WriteFileAPI +ToJsonString
API (for tests).partialFingerprintsviaTHashSHA2.GetHashString. -
DPR dispatch (
analyser.d12.dpr) — checks viaIsCliMode
whether any param starts with-or/. If yes:
Halt(uConsoleRunner.RunFromCmdLine)without spawning a VCL form.
Otherwise:Application.Runas before. The duplicate
Application.CreateFormbug from v0.7.x is fixed in passing.
Tests
-
TTestRuleCatalogwith 6 tests inuTestRuleCatalog:
EveryFindingKindHasRule (every TFindingKind must have a catalog entry),
RuleIDsFollowConvention (SCA\d{3}), RuleIDsAreUnique,
KindNameMatchesCatalog, ToolInfoIsPopulated, GetRuleByIDRoundtrip. -
TTestExportSARIFwith 10 tests inuTestExportSARIF:
SchemaUriPresent, VersionIs210, ToolDriverHasNameAndVersion,
RulesArrayContainsAllKinds, ResultHasRuleIdAndLevel,
ResultLocationHasFileAndLine, RelativePathsAreUsedWhenBaseDirSet,
FingerprintHashIsStable, SeverityMapsCorrectly,
EmptyFindingsListProducesEmptyResults.
Docs
-
docs/rules.md— consolidated Markdown reference for all 22
rules with anchor links per rule ID. Referenced by SARIFhelpUri
(docs/rules.md#sca001); GitHub shows it on the "More info" click
in PR annotations. -
tools/gen-rules-docs.py(Python 3.7+) — generates per-rule
Markdown pages (docs/rules/SCA001.md, ...) from the catalog JSON.
Has a--checkmode for CI (fails if docs are out of sync).
Currently optional — the singledocs/rules.mdcovers SARIF
helpUri as well.
Known limitations
-
Per-rule HTML documentation is not yet shipped — SARIF
helpUripoints at anchors in the consolidateddocs/rules.md
rather than separate pages. With Python available,
tools/gen-rules-docs.pygenerates separate files per rule. -
Quality-gate flags (
--max-errors N --max-warnings N) are not
yet implemented — pipeline failure today is only via the generic
exit code (3 = errors). For threshold-based fails, check via an
ifstep in.github/workflows/sca.ymluntil then. -
Custom-rule engine (YAML) is on the roadmap as a v0.9.0
item — YAML parser,TCustomRuleDetector,--custom-rulesflag.
Today users are limited to the hardcoded detectors.
🏗️ Win64 readiness
...
v0.7.2
v0.7.2 — Responsive-Layout-Refactor + Detector-Accuracy
Patch-Release ueber v0.7.1. Schwerpunkt: Architektur-Cleanup der Plugin-
UI (zentraler 3-Stufen-Layout-Controller statt verstreuter Visibility-
Toggler), Font-skalierte Component-Hoehen, drei FormatMismatch-Detector-
Fixes nach Code-Reviews realer mORMot-2.4-Befunde, sowie Polish am
In-Editor Hover-Hint Overlay.
Highlights
-
Zentraler
TResponsiveLayoutController(NEU, ersetzt
TResponsiveVisibilityController) — eine Klasse, eine ClientWidth-
Quelle (Frame.OnResize), eine Sichtbarkeitstabelle. Vorher 5 verteilte
Controller-Instanzen ueber 4 Panels mit chained OnResize-Hooks; jetzt
deklarative Stage-Registrierung an einer Stelle:FResp := TResponsiveLayoutController.Create(Self, Self, BREAKPOINT_MEDIUM, BREAKPOINT_FULL); FResp.RegisterCtrl(FBtnCancel, usFull); // nur FULL FResp.RegisterCtrl(FLblFilter, usMedium); // ab MEDIUM FResp.RegisterCtrl(FBtnHamburger, usNarrow, usMedium); // inverseHat
AfterApply-Callback fuer Folge-Anpassungen (Sub-Panel-Width-Sync,
SearchEdit-MinWidth-Sync). Ergebnis: 5 Controller-Instanzen + chained-
OnResize-Race-Conditions weg, +80 Zeilen Code geloescht, alle Sichtbar-
keitsregeln auf einem Blick lesbar. -
TToolbarSizing-Helper (NEU) — loest die VCL-Quirk dassTComboBox
dieAlign.Heightignoriert (rendert immer aufItemHeight + ~6 px).
Drei statische Methoden:HeightForFont(Font)— leitet Soll-Hoehe ausAbs(Font.Height) + 11
ab. DPI-aware ueberFont.Height(Pixel, kein extraScaleWnoetig).
Bei Segoe UI 8pt = 22 px, 9pt = 23 px, 10pt = 24 px.Apply(Ctrl, AHeight)— setztConstraints.MinHeight = MaxHeight
(VCL respektiert das auch bei aktivem Align), bei TComboBox zusaetz-
lichItemHeight := AHeight - 6. Resultat: Buttons/Edits/Combos
rendern uniform.ApplyIconButton(Ctrl, AWidth, AHeight)— zusaetzlich Width-
Constraints fuer pixel-genaue Icon-Buttons (Browse "...",
Hamburger ☰, Branch-Changes ⎇ alle uniform 32 px).
-
Floated-Min-Width = 500 px (NEU) —
Frame.Constraints.MinWidth+
Propagation auf das IDE-Host-Form viaGetParentFormin
FrameCreated. Verhindert pathologisch schmale Floats - das floated
Window kann nicht mehr unter den MEDIUM-Stufen-Threshold geschrumpft
werden. -
In-Editor Hover-Hint Overlay (verfeinert) — Theme-adaptiv (Light/
Dark folgt aktivem IDE-Theme viaINTACodeEditorServices.Options. BackgroundColor), mehrzeiliger Wrap-Text, Severity-getoenter Title-
Bar mit Akzent-Stripe links + Type-Badge rechts. Klick auf eine
Befundzeile markiert ALLE Befunde derselben Datei (Multi-Marker via
TDictionary<Integer, TFindingMark>). Komplett i18n via_().
Engine
-
FormatMismatch: drei zusammenhaengende False-Positive-Fixes nach
Code-Reviews realer mORMot-2.4-Befunde:-
Bare-
%-Counting fuer mORMot-Funktionen (FormatUtf8,
FormatString,StringFormatUtf8): diese nutzen%allein als
Platzhalter (kein Type-Letter wie%s/%d). NeueIsBareStyle-
Check + zweite Counting-Strategie viaCountPlaceholders(ABareStyle).
Hardcoded-ListeBARE_STYLE_FUNCS- bei weiteren mORMot-Idiom-
Funktionen dort ergaenzen. -
%%ist KEIN Escape im Bare-Style - verifiziert in mORMot-
Source (mormot.core.text.pas:9616 TFormatUtf8.Parse): jedes%
konsumiert ein Argument,%%= zwei aufeinanderfolgende Args ohne
Trenner (mORMot nutzt das z.B. um Where-Clauses zu kettenkonkate-
nieren). Standard-RTL-Formatbleibt streng - dort ist%%weiter-
hin Escape. -
String-Literal-Konkatenation
'a' + 'b'wird vor dem Counting
gemerged - typisch fuer mehrzeilige SQL-Strings:FormatUtf8('SELECT ... ' + 'WHERE Id=%', [id]) // OK, 1+1 match
Helper
ReadStringLiteral/SkipSpaces, Loop bis kein+ '...'
mehr folgt. Nicht-statische Fortsetzungen (+ IntToStr(x)) brechen
den Merge ab und der Detector liefert nur das gemergete Prefix
(konservativ, kein False Positive).
Effekt auf reale mORMot-2.4-Findings:
File v0.7.1 v0.7.2 dmvc-ai/.../api.impl.pas:62False Positive (0 vs 1) OK (1 vs 1) dmvc-ai/.../api.impl.pas:71False Positive (1 vs 2) OK (2 vs 2) dmvc-ai/.../api.impl.pas:126False Positive (1 vs 2) OK (2 vs 2) mormot.orm.rest.pas:1780True Bug, Count falsch True Bug, Count korrekt (9 vs 8) -
IDE-Plugin
TResponsiveVisibilityControllerentfernt (deprecated in v0.7.1,
geloescht in v0.7.2) -TResponsiveLayoutControllerersetzt vollstaendig.- 3-Stufen-Layout stabilisiert: NARROW (
<500), MEDIUM (500..849),
FULL (>=850). Zwei Stufen-Wechsel statt frueher einer - Uebergang
vom Hamburger-Pattern zum vollen UI ist smoother (kein abrupter
Sprung in voller Toolbar). - Hamburger-Menu neu strukturiert: Analyse Branch-Changes, Cancel
Analysis, Export..., Settings..., Ignore list... (Browse "..." und
▶ Analyse + 📄 File sind im Toolbar IMMER sichtbar, nicht im Menu).
HamburgerMenuPopupsynct Enabled-State von Cancel + Branch-Changes
Items mit den zugehoerigen Buttons - kein Doppelclick-Race wenn
Analyse laeuft. - Branch-Changes-Button als Icon-Glyph ⎇ (32 px) statt Caption-Button
(104 px). Caption "Branch-Changes" + Volltext bleiben im Tooltip und
im Hamburger-Menu erreichbar. TAnalyserDockableForm.FrameCreatedpropagiert die Frame-MinWidth/
MinHeight auf das Host-Form viaGetParentForm- das IDE-Floating-
Container respektiert die Mindestbreite jetzt auch beim Resize.- Tile-Reihe: alle 9 Tiles uniform 55 px breit (statt frueher 65/72/72
pro Severity-/Type-/Detector-Tile). Konsistente Sonar-Style-Reihe.
Stage-Registrierung: 4 essentials immer, +5 ab MEDIUM (Read errors,
Bugs, Security, Duplicates, Cyclomatic). - Toolbar-Padding reduziert (
TB_PADDING_TB2→1) - Toolbar-Reihen
nehmen pro Zeile 2 px weniger Hoehe ein.
Tests
TTestFormatMismatchBareStylemit 6 neuen Tests in
uTestFormatMismatch:FormatUtf8_TwoBarePercents_TwoArgs_NoFinding(Original-Bug-Case)FormatUtf8_OneBarePercent_TwoArgs_ReportsErrorFormatString_BarePercentSeparator_NoFindingFormatUtf8_DoublePercent_ConsumesTwoArgs(verifiziert kein Escape)FormatUtf8_ConcatenatedLiteral_AllPlaceholdersCountedFormatUtf8_ConcatenatedLiteral_MismatchAcrossSplit_ReportsErrorStandardFormat_PercentUnderscore_StillReportsMismatch(RTL-Format-
Regression)
i18n
- Hover-Overlay: alle Anzeige-Strings ueber
_()lokalisiert -
Severity-Texte, Badge-Beschriftungen, Tile-Hints. DE-Dict-Eintraege
fuerBug,Code Smell,Vulnerability,Security Hotspot,
Code Duplication,Read Error. TLeakFinding.SeverityText/TypeText: Source-Strings auf
Englisch (Konvention vonuLocalization), DE-Mapping ueber
Dictionary. Fallsanalyser.ini [UI]/Language=de, wird in der UI
automatischFehler/Warnung/Hinweisangezeigt.
Bekannte Einschraenkungen
%%-Heuristik im Bare-Style ist konservativ: jedes%zaehlt
unabhaengig vom Kontext. Bei sehr exotischen mORMot-Patterns (z.B.
FormatUtf8('%' + Var, [x])) kann der Detector daneben liegen wenn
Varzur Compile-Zeit ein%enthaelt - aber das ist a) Bare-Style-
konform (mORMot wuerde es auch falsch interpretieren) und b) schwer
statisch zu erkennen.- Bare-Style-Funktionsliste ist hardcoded auf
formatutf8/formatstring/stringformatutf8. Falls weitere mORMot-
Helpers (StringFormatBuffer,FormatShort,FormatToShort) auf-
tauchen, inBARE_STYLE_FUNCSergaenzen. Optional spaeter via
[Detectors] BareFormatFunctions=...konfigurierbar machen.
Upgrade von v0.7.1
- Keine Source-Aenderungen im User-Code noetig. Alle Detector-Fixes
sind reine Engine-Verbesserungen. - IDE-Plugin Layout identisch zu v0.7.1 (3-Stufen NARROW/MEDIUM/FULL),
nur intern auf den zentralen Controller umgebaut. Keine User-
sichtbaren Aenderungen. analyser.ini-Konfiguration unveraendert.[Detectors] FormatFunctionsaus v0.7.1 funktioniert weiterhin - die Bare-Style-
Erkennung erfolgt orthogonal ueber den Funktionsnamen.- HTML-/JSON-/CSV-Exporte unveraendert.
- Bestehende Suppressions,
ignore.txt-Eintraege, Custom-LeakyClasses,
Severity-Konfiguration bleiben gueltig.
v0.7.1 - Pre-Release
v0.7.1 — Pre-Release
Patch-Release ueber v0.7.0 mit Schwerpunkt auf Code-Metriken (neuer
Detector), responsive Docked-Mode-UI fuer das IDE-Plugin, AI-Prompt-
Verbesserungen und durchgaengiger DPI-Skalierung.
Highlights
- Cyclomatic Complexity (McCabe) Detector (NEU, Phase 1 der Code-
Metriken-Roadmap) —TCyclomaticComplexityDetectormisst pro
Methode die McCabe-Komplexitaet: 1 Base +if+case-Arm +
for/while/repeat+on-Handler +and/or/xorBinaryOps.
elsezaehlt nicht (binary branch). Default-Schwelle: 10
(Sonar/Checkstyle/PMD-Standard) via[Detectors] CyclomaticMax.
Findings alsfkCyclomaticComplexitymit Refactor-Hint in
uFixHint(Before/After-Beispiel mitCanProcess/ProcessOrder-
Methoden-Split). - Docked-Mode UI (NEU, IDE-Plugin) — der Plugin-Frame erkennt
schmale Container (< 700 px ClientWidth, typisch fuer gedockte
IDE-Tool-Panels) und reduziert sich auf das Wesentliche:- Stats-Tiles schrumpfen auf 4 essenzielle (Errors / Warnings /
Hints / Code Quality) - Action-Buttons (Start / Current / Branch-Changes) wandern ins
Hamburger-Menu (☰) - Settings / Ignore-Liste werden ebenfalls per Hamburger erreichbar
- Filter-Combo-Labels verschwinden (Combos selbsterklaerend)
- SearchEdit-MinWidth schrumpft von 120 auf 60 px im Docked
- Sub-Panel-Container (Severity-/Type-Combo) passen ihre Width an
Label-Visibility an - Floated: alles wie gewohnt - voller Funktionsumfang sichtbar
- Stats-Tiles schrumpfen auf 4 essenzielle (Errors / Warnings /
- AI-Prompt-Rewrite (
uClaudePrompt) — Role-Priming am Anfang
("senior Delphi developer reviewing static-analysis output"),
strukturierte Antwort-Vorgabe (Cause / Fix / Verify), Vorher/Nachher
als "Reference pattern (NOT the user's code)" markiert, Source-
Marker>>>deutlich auffaelliger, False-Positive-Pfad mit
// noinspection-Vorschlag. Komplett ueber_(...)lokalisiert -
DE-User bekommt DE-Prompt -> DE AI-Antwort. - DPI-Scaling durchgehend in der IDE-Plugin-UI - alle hardcoded
Pixel-Widths/Heights laufen jetzt durchScaleW(...)(Frame) bzw.
ScaleByPPI(...)(Stats-Tiles). Plugin sieht auf 200%-DPI nicht
mehr halb so gross aus. Auch der Docked-Threshold 700 wird skaliert.
IDE-Plugin
- Cyclomatic-Stats-Tile zwischen "Duplicates" und "Code Quality"
(Magenta-Branch-Glyph, ICON_SMELL-Farbton). Zaehlt
fkCyclomaticComplexity-Findings. - Klickbare Stats-Tiles - Hover zeigt Multi-Line-Tooltip mit
Erklaerung, Klick filtert das Grid auf die jeweilige Severity bzw.
Type-Bucket. Code-Quality-Tile resetet alle Filter. - Filter-Combo bekommt "Cyclomatic Complexity"-Eintrag.
TResponsiveVisibilityController(inuIDEStatsTiles) -
generischer Layout-Controller, hookt Parent.OnResize chained ohne
bestehende Handler zu zerstoeren, AInverse-Flag fuer "show-only-
when-narrow"-Controls (Hamburger).TAnalyserFrame.Resize-Override (statt nur OnResize-Event) -
garantiert Responsive-Trigger auch bei Float->Dock-Transitions, wo
die IDE-Dock-Logik den OnResize-Event manchmal verschluckt.- Magic-Numbers konsolidiert in
BTN_W_*/LBL_W_*/CMB_W_*/
STATS_*/TB_*-Konstanten. 14 verstreute Pixel-Werte vorher,
jetzt benannte Const-Sektion. BuildHamburgerMenuals eigene Methode extrahiert (war ~25
Inline-Zeilen).
Engine
- Cyclomatic-Detector-Implementation liest
nkIfStmt.TypeRef
fuer Boolean-Op-Counts (Parser baut keine Expression-AST, nur
Cond-Text auf if-Knoten).while/repeat/case-Conditions liegen
gar nicht vor - akzeptabler Trade-off vs. Parser-Erweiterung.
Tests
uTestAnalyserChecks(7500 LOC) zerlegt in 16 Per-Detector-
Test-Units:uTestFindingHelper(sharedTFindingHelper), plus
je eine Unit pro Detector-Themengruppe (uTestLeakDetector,
uTestSQLInjection,uTestHardcodedSecret, ...). DUnitX
initialization-Block geloescht; Runner nutzt RTTI
(UseRTTI := True) und findet[TestFixture]-Klassen automatisch
sobald die Unit referenziert ist.TTestCyclomaticComplexitymit 10 neuen Tests in
uTestCodeMetrics(Trivial, SingleIf, ElseDoesNotCount,
BooleanAndOr, ManyIfs, CaseArms, ForWhileRepeat, OnHandler,
TryFinallyNotCounted, TwoMethodsOneOver).
i18n
- ~40 neue Dict-Eintraege in
uLocalization: Tile-Hints
(Multi-Line, EN-source -> DE-dict), Hamburger-Menu-Items,
Cyclomatic-Tile-Caption + FilterCombo-Eintrag, AI-Prompt-Headings- Strukturierte-Antwort-Bestandteile, Watch-Status, Cyclomatic-
FixHint.
- Strukturierte-Antwort-Bestandteile, Watch-Status, Cyclomatic-
- uIDEWatchMode Status-Strings durch
Format(_(...), [...])
ersetzt - vorher hardcoded English ohne_(...)-Wrap. - Stale Dict-Eintraege gefixt:
Settings: ... next click of Branch-Changes->... the next analysis run(Source hatte ich
geaendert, Dict zeigte alte Variante). Spelling-Drift fix:
Analyzing:(US) ->Analysing:(BR), matched jetzt den
existierenden Dict-Key.
Configuration
[Detectors] CyclomaticMax=10(NEU, Default 10) - INI-Doc-
Block ergaenzt mit Erklaerung was zaehlt.
Bekannte Einschraenkungen
- Single-File-Live-Watch ohne Re-Entrancy-Guard - unveraendert
zu v0.7.0. Bei langsamen Workers + aktivem Tippen kann der Backlog
wachsen. Phase 2 (TODO.md Single-File-Live-Watch). - Docked-Mode UI ist Phase 1 der Roadmap - weitere Phasen in
TODO.md(Layout-Architektur via TFlowPanel, Two-Mode-UI, Polish).
Upgrade von v0.7.0
[Detectors] CyclomaticMax=10ist neu im Default-INI-Template,
aber bestehendeanalyser.iniohne diesen Eintrag bekommt einfach
den Default. Kein Migrationspfad noetig.- IDE-Plugin Layout veraendert sich beim Resize/Dock - Floated-
Mode visuell wie v0.7.0, Docked-Mode neu (Hamburger + reduzierte
Toolbar). Keine User-Aktion noetig. - Bestehende Suppressions,
ignore.txt-Eintraege, Custom-LeakyClasses,
Severity-Konfiguration bleiben unveraendert gueltig. - HTML-Exporte unveraendert.