diff --git a/rust/ql/lib/change-notes/2026-02-05-neutral-models.md b/rust/ql/lib/change-notes/2026-02-05-neutral-models.md new file mode 100644 index 00000000000..da232f093ff --- /dev/null +++ b/rust/ql/lib/change-notes/2026-02-05-neutral-models.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Added support for neutral models (`extensible: neutralModel`) to control where generated source, sink and flow summary models apply. diff --git a/rust/ql/lib/codeql/rust/dataflow/internal/ModelsAsData.qll b/rust/ql/lib/codeql/rust/dataflow/internal/ModelsAsData.qll index ce80664b322..5eb05e84833 100644 --- a/rust/ql/lib/codeql/rust/dataflow/internal/ModelsAsData.qll +++ b/rust/ql/lib/codeql/rust/dataflow/internal/ModelsAsData.qll @@ -90,9 +90,9 @@ extensible predicate summaryModel( ); /** - * Holds if a neutral model of kind `kind` exists for the function with canonical path `path`. The - * only effect of a neutral model is to prevent generated and inherited models of the corresponding - * `kind` (`source`, `sink` or `summary`) from being applied. + * Holds if a neutral model exists for the function with canonical path `path`. The only + * effect of a neutral model is to prevent generated and inherited models of the corresponding + * `kind` (`source`, `sink` or `summary`) from being applied to that function. */ extensible predicate neutralModel( string path, string kind, string provenance, QlBuiltins::ExtensionId madId @@ -148,18 +148,16 @@ private predicate summaryModelRelevant( summaryModel(f, input, output, kind, provenance, isInherited, madId) and // Only apply generated or inherited models to functions in library code and // when no strictly better model (or neutral model) exists - ( - if provenance.isGenerated() or isInherited = true - then - not f.fromSource() and - not exists(Provenance other | summaryModel(f, _, _, _, other, false, _) | - provenance.isGenerated() and other.isManual() - or - provenance = other and isInherited = true - ) and - not neutralModel(f.getCanonicalPath(), "summary", _, _) - else any() - ) + if provenance.isGenerated() or isInherited = true + then + not f.fromSource() and + not exists(Provenance other | summaryModel(f, _, _, _, other, false, _) | + provenance.isGenerated() and other.isManual() + or + provenance = other and isInherited = true + ) and + not neutralModel(f.getCanonicalPath(), "summary", _, _) + else any() } private class SummarizedCallableFromModel extends SummarizedCallable::Range { diff --git a/rust/ql/test/library-tests/dataflow/models/main.rs b/rust/ql/test/library-tests/dataflow/models/main.rs index a8461fe3e00..34b7b9188f2 100644 --- a/rust/ql/test/library-tests/dataflow/models/main.rs +++ b/rust/ql/test/library-tests/dataflow/models/main.rs @@ -430,8 +430,8 @@ pub fn neutral_manual_sink(i: i64) {} fn test_neutrals() { // neutral models should cause corresponding generated models to be ignored. - // Thus, the `neutral_generated_*` source/sink, which have both a - // generated and a neutral model, should not have flow. + // Thus, the `neutral_generated_source` and `neutral_generated_sink`, which + // have both a generated and a neutral model, should not have flow. sink(generated_source(1)); // $ hasValueFlow=1 sink(neutral_generated_source(2));