Commit 5f1e5b9
Make the ABAP confidence grade mean something, using what the callers pass
I set out to add interprocedural taint and measured first. The engine seeds every
inbound parameter of every FORM, METHOD and FUNCTION as tainted, so across every
ABAP fixture in this repository:
sink-carrying findings: 11 {'confirmed': 11}
Eleven of eleven confirmed, none tentative. A grade with one value is decoration,
and the label it prints — "tainted input reaches this sink" — was going on a
subroutine whose only caller passes the string literal 'SFLIGHT'.
Safe to change, because the two grades behave identically downstream:
fair_adapter._is_unevidenced treats only `pattern-only` as unevidenced, and
release_gate's default policy blocks on `confirmed` and `tentative` alike.
Nothing leaves FAIR pricing and nothing stops holding a build. Only the claim
changes, and the finding is still reported either way — the downgrade-never-hide
contract in `_refine` is untouched.
modules/abap_callgraph.py binds actuals to formals across PERFORM, CALL FUNCTION,
CALL METHOD and obj->meth( ), and answers whether anything visible hands a
parameter something the caller controls. Across the fixtures: 12 confirmed and 2
tentative where it was 11 and 0, and four traces that now open with the call
rather than stopping at the procedure header — a developer is pointed at
`PERFORM run_query USING p_carr` instead of being told `iv_carrid` is tainted and
left to find the caller themselves.
WHAT IS NOT CLAIMED. This is not interprocedural data-flow analysis, and
docs/CVA_MERGE_PLAN.md's "not claim interprocedural analysis" still holds. One
artefact, no fixpoint, and any non-literal actual is treated as tainted rather
than proven to be. Only a FORM whose visible callers ALL pass literals is ever
cleared: a METHOD or a FUNCTION keeps the conservative seed however clean its
visible callers look, because a public method is called by whatever imports the
class and a remote-enabled function module by another system. "No caller in this
file" is their ordinary state and, for the RFC case, the dangerous one — so it is
never read as a clean bill, for a FORM either.
Two defects found while building it, both of the kind where a string invents
structure:
* the formals list captured type names, so `TYPE REF TO cl_thing` yielded
`cl_thing` as a parameter and every later positional bind landed on the wrong
argument. A keyword list cannot fix this — `TYPE string` would then swallow
the next parameter — so the type name is consumed positionally.
* parsing arguments off raw text let a literal containing an equals sign
contribute a formal named from inside the string, losing the real one. Call
structure is now read off the masked text, where literal content is blanked
and every offset is preserved; the CALL FUNCTION callee, which IS a literal,
is read from the raw text at the masked match's own span.
evidence_for is memoised — the analyzer re-walks a scope per sink and the graph
cannot change after parsing. With it, the graph's cost is no longer measurable
above the noise on a synthetic 4,000-line program with 2,000 findings, which is
already past the size of real ABAP objects.
38 tests. Seven mutations run against them; all seven fail. Two escaped the first
draft — a METHOD fed only literals and a FORM with no visible caller were both
missing from the fixture, which is exactly the pair the kind guard exists for.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>1 parent 871d21b commit 5f1e5b9
5 files changed
Lines changed: 909 additions & 10 deletions
File tree
- modules
- tests
- fixtures/abap
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
717 | 717 | | |
718 | 718 | | |
719 | 719 | | |
720 | | - | |
| 720 | + | |
721 | 721 | | |
722 | 722 | | |
723 | 723 | | |
| |||
0 commit comments