From 651770b4126e6b8fce4ecd0cfee6245ec4897225 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 15 Jun 2026 10:26:47 +0200 Subject: [PATCH 1/3] Java: Fix performance issue in type flow library --- .../semmle/code/java/dataflow/TypeFlow.qll | 31 ++++++++++++++++ .../security/ListOfConstantsSanitizer.qll | 2 ++ shared/typeflow/codeql/typeflow/TypeFlow.qll | 6 ++++ .../codeql/typeflow/UniversalFlow.qll | 35 +++++++++++++++++-- .../codeql/typeflow/internal/TypeFlowImpl.qll | 2 ++ 5 files changed, 73 insertions(+), 3 deletions(-) diff --git a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll index 2c04a6413eb7..6627b3ed0041 100644 --- a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll +++ b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll @@ -72,6 +72,35 @@ module FlowStepsInput implements UniversalFlow::UniversalFlowInput { } } + private class FlowNodeElement extends Element { + FlowNodeElement() { + this instanceof Field or + this instanceof Expr or + this instanceof Method + } + } + + private predicate id(FlowNodeElement x, FlowNodeElement y) { x = y } + + private predicate idOf(FlowNodeElement x, int y) = equivalenceRelation(id/2)(x, y) + + int getFlowNodeId(FlowNode n) { + n = + rank[result](FlowNode n0, int a, int b | + a = 0 and + idOf(any(n0.asField()), b) + or + // no case for `n0.asSsa()`; here we rely on the built-in location-based ranking + a = 1 and + idOf(any(n0.asExpr()), b) + or + a = 2 and + idOf(any(n0.asMethod()), b) + | + n0 order by a, b + ) + } + private SrcCallable viableCallable_v1(Call c) { result = viableImpl_v1(c) or @@ -165,6 +194,8 @@ private module Input implements TypeFlowInput { class TypeFlowNode = FlowNode; + predicate getTypeFlowNodeId = FlowStepsInput::getFlowNodeId/1; + predicate isExcludedFromNullAnalysis = FlowStepsInput::isExcludedFromNullAnalysis/1; class Type = RefType; diff --git a/java/ql/lib/semmle/code/java/security/ListOfConstantsSanitizer.qll b/java/ql/lib/semmle/code/java/security/ListOfConstantsSanitizer.qll index c8d52f4191cc..b00c5c99405a 100644 --- a/java/ql/lib/semmle/code/java/security/ListOfConstantsSanitizer.qll +++ b/java/ql/lib/semmle/code/java/security/ListOfConstantsSanitizer.qll @@ -170,6 +170,8 @@ private class EmptyCollectionConstructor extends Constructor { private module CollectionFlowStepsInput implements UniversalFlow::UniversalFlowInput { import FlowStepsInput + predicate getFlowNodeId = FlowStepsInput::getFlowNodeId/1; + /** * Holds if `n2` is a collection/array/constant whose value(s) are * determined completely from the range of `n1` nodes. diff --git a/shared/typeflow/codeql/typeflow/TypeFlow.qll b/shared/typeflow/codeql/typeflow/TypeFlow.qll index 52a911974091..87840fb4d64e 100644 --- a/shared/typeflow/codeql/typeflow/TypeFlow.qll +++ b/shared/typeflow/codeql/typeflow/TypeFlow.qll @@ -29,6 +29,12 @@ signature module TypeFlowInput { Location getLocation(); } + /** + * Gets an identifier for node `n`, if any. When not implemented for a given node, + * the library will use location-based ranking. + */ + default int getTypeFlowNodeId(TypeFlowNode n) { none() } + /** * Holds if data can flow from `n1` to `n2` in one step. * diff --git a/shared/typeflow/codeql/typeflow/UniversalFlow.qll b/shared/typeflow/codeql/typeflow/UniversalFlow.qll index e5f07690a183..9387cc063123 100644 --- a/shared/typeflow/codeql/typeflow/UniversalFlow.qll +++ b/shared/typeflow/codeql/typeflow/UniversalFlow.qll @@ -45,6 +45,12 @@ signature module UniversalFlowInput { Location getLocation(); } + /** + * Gets an identifier for node `n`, if any. When not implemented for a given node, + * the library will use location-based ranking. + */ + default int getFlowNodeId(FlowNode n) { none() } + /** * Holds if data can flow from `n1` to `n2` in one step. * @@ -149,17 +155,40 @@ module Make I> { private module RankEdge implements RankedEdge { private import E + private int getFlowNodeIdByLoc(FlowNode n) { + n = + rank[result](FlowNode n0, string filePath, int startline, int startcolumn | + not exists(getFlowNodeId(n0)) and + n0.getLocation().hasLocationInfo(filePath, startline, startcolumn, _, _) + | + n0 order by filePath, startline, startcolumn + ) + } + + private int getFlowNodeIdExt(FlowNode n) { + n = + rank[result](FlowNode n0, int a, int b | + a = 0 and + b = getFlowNodeId(n0) + or + a = 1 and + b = getFlowNodeIdByLoc(n0) + | + n0 order by a, b + ) + } + /** * Holds if `r` is a ranking of the incoming edges `(n1,n2)` to `n2`. The used * ordering is not necessarily total, so the ranking may have gaps. */ private predicate edgeRank1(int r, FlowNode n1, Node n2) { n1 = - rank[r](FlowNode n, int startline, int startcolumn | + rank[r](FlowNode n, int id | edge(n, n2) and - n.getLocation().hasLocationInfo(_, startline, startcolumn, _, _) + id = getFlowNodeIdExt(n) | - n order by startline, startcolumn + n order by id ) } diff --git a/shared/typeflow/codeql/typeflow/internal/TypeFlowImpl.qll b/shared/typeflow/codeql/typeflow/internal/TypeFlowImpl.qll index 437e1ab31992..71b530b143e6 100644 --- a/shared/typeflow/codeql/typeflow/internal/TypeFlowImpl.qll +++ b/shared/typeflow/codeql/typeflow/internal/TypeFlowImpl.qll @@ -12,6 +12,8 @@ module TypeFlow I> { private module UfInput implements UniversalFlow::UniversalFlowInput { class FlowNode = TypeFlowNode; + predicate getFlowNodeId = I::getTypeFlowNodeId/1; + predicate step = I::step/2; predicate isNullValue = I::isNullValue/1; From 568de02e98e0ac000febf5e02fd3a6968aa53710 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 15 Jun 2026 10:58:48 +0200 Subject: [PATCH 2/3] Update shared/typeflow/codeql/typeflow/UniversalFlow.qll Co-authored-by: Anders Schack-Mulligen --- shared/typeflow/codeql/typeflow/UniversalFlow.qll | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/shared/typeflow/codeql/typeflow/UniversalFlow.qll b/shared/typeflow/codeql/typeflow/UniversalFlow.qll index 9387cc063123..ddf01e7dd8bf 100644 --- a/shared/typeflow/codeql/typeflow/UniversalFlow.qll +++ b/shared/typeflow/codeql/typeflow/UniversalFlow.qll @@ -155,9 +155,12 @@ module Make I> { private module RankEdge implements RankedEdge { private import E + private predicate needsNodeId(FlowNode n) { edge(n, _) } + private int getFlowNodeIdByLoc(FlowNode n) { n = rank[result](FlowNode n0, string filePath, int startline, int startcolumn | + needsNodeId(n0) and not exists(getFlowNodeId(n0)) and n0.getLocation().hasLocationInfo(filePath, startline, startcolumn, _, _) | @@ -168,6 +171,7 @@ module Make I> { private int getFlowNodeIdExt(FlowNode n) { n = rank[result](FlowNode n0, int a, int b | + needsNodeId(n0) and a = 0 and b = getFlowNodeId(n0) or From 686e98c6ffe6d22bf0defbf6c0016ed2dbbe1c40 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 15 Jun 2026 11:37:14 +0200 Subject: [PATCH 3/3] Update java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll Co-authored-by: Anders Schack-Mulligen --- java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll index 6627b3ed0041..e11013f1232d 100644 --- a/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll +++ b/java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll @@ -88,14 +88,14 @@ module FlowStepsInput implements UniversalFlow::UniversalFlowInput { n = rank[result](FlowNode n0, int a, int b | a = 0 and - idOf(any(n0.asField()), b) + idOf(n0.asField(), b) or // no case for `n0.asSsa()`; here we rely on the built-in location-based ranking a = 1 and - idOf(any(n0.asExpr()), b) + idOf(n0.asExpr(), b) or a = 2 and - idOf(any(n0.asMethod()), b) + idOf(n0.asMethod(), b) | n0 order by a, b )