From 78a4f9c562ab3fce2362d1e29dab4ad5a1fe4595 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Fri, 18 Sep 2026 10:52:22 +0200 Subject: [PATCH 1/3] Shared: Add `InverseAppend` modules for effecient prefix matching --- shared/util/codeql/util/Strings.qll | 191 ++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) diff --git a/shared/util/codeql/util/Strings.qll b/shared/util/codeql/util/Strings.qll index b032bb73672b..de4139550dbe 100644 --- a/shared/util/codeql/util/Strings.qll +++ b/shared/util/codeql/util/Strings.qll @@ -91,3 +91,194 @@ int asciiPrintable(string char) { .charAt(_) ) } + +/** + * Escapes all characters in `s` that have special meaning in regular expressions. + */ +bindingset[s] +string regexpEscape(string s) { + result = s.regexpReplaceAll("([\\\\.*+?\\[^\\]$(){}=!<>|:\\-])", "\\\\$1") +} + +/** Provides the input to `InverseAppend`. */ +signature module InverseAppendInputSig { + /** A prefix matching result. */ + bindingset[this] + class Result; + + /** + * Holds if `prefix` is a prefix candidate with resulting value `res`. + */ + predicate prefixCandidate(string prefix, Result res); +} + +/** Provides the `inverseAppend` predicate. */ +module InverseAppend { + /** + * Holds if `s = prefix + suffix` with resulting value `res`. + * + * The predicate avoids unnecessary result fan-out by first constraining the + * prefix using a regular expression match. + */ + bindingset[s] + predicate inverseAppend(string s, string prefix, string suffix, Input::Result res) { + exists(string regexp | + regexp = + "(" + + strictconcat(string prefixCand | + Input::prefixCandidate(prefixCand, _) + | + regexpEscape(prefixCand), "|" + ) + ").*" and + prefix = s.regexpCapture(regexp, 1) and + s = prefix + suffix and + Input::prefixCandidate(prefix, res) + ) + } +} + +/** Provides the input to `InverseAppend1`. */ +signature module InverseAppend1InputSig { + /** A prefix matching context. */ + bindingset[this] + class C; + + /** A prefix matching result. */ + bindingset[this] + class Result; + + /** + * Holds if `prefix` is a prefix candidate in the context `c` + * with resulting value `res`. + */ + predicate prefixCandidate(string prefix, C c, Result res); +} + +/** Provides the `inverseAppend` predicate. */ +module InverseAppend1 { + /** + * Holds if `s = prefix + suffix` in the context `c` with resulting + * value `res`. + * + * The predicate avoids unnecessary result fan-out by first constraining the + * prefix using a regular expression match. + */ + bindingset[s] + predicate inverseAppend(string s, string prefix, string suffix, Input::C c, Input::Result res) { + exists(string regexp | + regexp = + "(" + + strictconcat(string prefixCand | + Input::prefixCandidate(prefixCand, c, _) + | + regexpEscape(prefixCand), "|" + ) + ").*" and + prefix = s.regexpCapture(regexp, 1) and + s = prefix + suffix and + Input::prefixCandidate(prefix, c, res) + ) + } +} + +/** Provides the input to `InverseAppend2`. */ +signature module InverseAppend2InputSig { + /** A prefix matching context. */ + bindingset[this] + class C1; + + /** A prefix matching context. */ + bindingset[this] + class C2; + + /** A prefix matching result. */ + bindingset[this] + class Result; + + /** + * Holds if `prefix` is a prefix candidate in the context `(c1, c2)` + * with resulting value `res`. + */ + predicate prefixCandidate(string prefix, C1 c1, C2 c2, Result res); +} + +/** Provides the `inverseAppend` predicate. */ +module InverseAppend2 { + /** + * Holds if `s = prefix + suffix` in the context `(c1, c2)` with resulting + * value `res`. + * + * The predicate avoids unnecessary result fan-out by first constraining the + * prefix using a regular expression match. + */ + bindingset[s] + predicate inverseAppend( + string s, string prefix, string suffix, Input::C1 c1, Input::C2 c2, Input::Result res + ) { + exists(string regexp | + regexp = + "(" + + strictconcat(string prefixCand | + Input::prefixCandidate(prefixCand, c1, c2, _) + | + regexpEscape(prefixCand), "|" + ) + ").*" and + prefix = s.regexpCapture(regexp, 1) and + s = prefix + suffix and + Input::prefixCandidate(prefix, c1, c2, res) + ) + } +} + +/** Provides the input to `InverseAppend3`. */ +signature module InverseAppend3InputSig { + /** A prefix matching context. */ + bindingset[this] + class C1; + + /** A prefix matching context. */ + bindingset[this] + class C2; + + /** A prefix matching context. */ + bindingset[this] + class C3; + + /** A prefix matching result. */ + bindingset[this] + class Result; + + /** + * Holds if `prefix` is a prefix candidate in the context `(c1, c2, c3)` + * with resulting value `res`. + */ + predicate prefixCandidate(string prefix, C1 c1, C2 c2, C3 c3, Result res); +} + +/** Provides the `inverseAppend` predicate. */ +module InverseAppend3 { + /** + * Holds if `s = prefix + suffix` in the context `(c1, c2, c3)` with resulting + * value `res`. + * + * The predicate avoids unnecessary result fan-out by first constraining the + * prefix using a regular expression match. + */ + bindingset[s] + predicate inverseAppend( + string s, string prefix, string suffix, Input::C1 c1, Input::C2 c2, Input::C3 c3, + Input::Result res + ) { + exists(string regexp | + regexp = + "(" + + strictconcat(string prefixCand | + Input::prefixCandidate(prefixCand, c1, c2, c3, _) + | + regexpEscape(prefixCand), "|" + ) + ").*" and + prefix = s.regexpCapture(regexp, 1) and + s = prefix + suffix and + Input::prefixCandidate(prefix, c1, c2, c3, res) + ) + } +} From 3720a8cfa98bc2392fa210b7057b96a4e3f32841 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Fri, 18 Sep 2026 10:53:18 +0200 Subject: [PATCH 2/3] Type inference: Performance tweaks --- .../typeinference/internal/TypeInference.qll | 107 ++++++++++++++---- 1 file changed, 86 insertions(+), 21 deletions(-) diff --git a/shared/typeinference/codeql/typeinference/internal/TypeInference.qll b/shared/typeinference/codeql/typeinference/internal/TypeInference.qll index 399e5d818c87..a475a19096d7 100644 --- a/shared/typeinference/codeql/typeinference/internal/TypeInference.qll +++ b/shared/typeinference/codeql/typeinference/internal/TypeInference.qll @@ -70,6 +70,7 @@ overlay[local?] module; private import codeql.util.Location +private import codeql.util.Strings /** Provides the input to `Make1`. */ signature module InputSig1 { @@ -1261,30 +1262,58 @@ module Make1 Input1> { module MatchingWithEnvironment { private import Input + private Type getTypeArgumentNonPseudo(Access a, int pos, TypePath path) { + result = a.getTypeArgument(pos, path) and + not result instanceof PseudoType + } + /** * Gets the type of the type argument at `path` in `a` that corresponds to * the type parameter `tp` in `target`, if any. * * Note that this predicate crucially does not depend on type inference, - * and hence can appear in negated position, e.g., as in - * `directTypeMatch`. + * and hence can appear in negated position, e.g., as in `directTypeMatch`. */ bindingset[a, target] pragma[inline_late] Type getTypeArgument(Access a, Declaration target, TypeParameter tp, TypePath path) { exists(int pos | - result = a.getTypeArgument(pos, path) and - tp = target.getTypeParameter(pos) and - not result instanceof PseudoType + result = getTypeArgumentNonPseudo(a, pos, path) and + tp = target.getTypeParameter(pos) + ) + } + + bindingset[a, target] + pragma[inline_late] + private predicate hasNotTypeArgument0(Access a, Declaration target, TypeParameter tp) { + exists(int pos | + tp = target.getTypeParameter(pragma[only_bind_into](pos)) and + not exists(getTypeArgumentNonPseudo(a, pos, _)) ) } + bindingset[target, tp] + pragma[inline_late] + private predicate hasNotTypeArgument1(Declaration target, TypeParameter tp) { + not tp = target.getTypeParameter(_) + } + + /** + * A join-order optimized version of `not exists(getTypeArgument(a, target, tp, _)`. + */ + pragma[inline] + private predicate hasNotTypeArgument(Access a, Declaration target, TypeParameter tp) { + hasNotTypeArgument0(a, target, tp) + or + hasNotTypeArgument1(target, tp) + } + pragma[nomagic] private predicate directTypeMatch0( Access a, DeclarationPosition dpos, AccessEnvironment e, Declaration target, TypePath pathToTypeParam, TypeParameter tp ) { - not exists(getTypeArgument(a, target, tp, _)) and + hasNotTypeArgument(a, target, tp) and tp = target.getDeclaredType(dpos, pathToTypeParam) and target = a.getTarget(e) } @@ -1359,12 +1388,18 @@ module Make1 Input1> { t = a.getInferredType(e, apos, TypePath::nil()) } + private predicate relevantAccessTarget( + Access a, AccessPosition apos, AccessEnvironment e, Declaration target + ) { + exists(Type t | + accessTargetsWithArgRootType(a, e, target, apos, t) and + argRootTypeSatisfiesTargetTypeCand(t, target, apos, _, _) + ) + } + private newtype TRelevantAccess = MkRelevantAccess(Access a, AccessPosition apos, AccessEnvironment e) { - exists(Declaration target, Type t | - accessTargetsWithArgRootType(a, e, target, apos, t) and - argRootTypeSatisfiesTargetTypeCand(t, target, apos, _, _) - ) + relevantAccessTarget(a, apos, e, _) } private class RelevantAccess extends MkRelevantAccess { @@ -1374,7 +1409,12 @@ module Make1 Input1> { RelevantAccess() { this = MkRelevantAccess(a, apos, e) } - RelevantTarget getTarget() { result = MkRelevantTarget(a.getTarget(e), apos) } + RelevantTarget getTarget() { + exists(Declaration target | + relevantAccessTarget(a, apos, e, target) and + result = MkRelevantTarget(target, apos) + ) + } pragma[nomagic] Type getTypeAt(TypePath path) { result = a.getInferredType(e, apos, path) } @@ -1395,6 +1435,18 @@ module Make1 Input1> { private module SatisfiesParameterConstraint = SatisfiesConstraint; + private module InverseAppend2Input implements InverseAppend2InputSig { + class C1 = Declaration; + + class C2 = AccessPosition; + + class Result = TypeParameter; + + predicate prefixCandidate(string prefix, C1 c1, C2 c2, Result res) { + argRootTypeSatisfiesTargetTypeCand(_, c1, c2, res, prefix) + } + } + /** * Holds if the (transitive) base type `t` at `path` of `a` in environment `e` * for some `AccessPosition` matches the type parameter `tp`, which is used in @@ -1431,13 +1483,12 @@ module Make1 Input1> { predicate baseTypeMatch( Access a, AccessEnvironment e, Declaration target, TypePath path, Type t, TypeParameter tp ) { - exists(AccessPosition apos, TypePath pathToTp | - argRootTypeSatisfiesTargetTypeCand(_, target, pragma[only_bind_into](apos), tp, pathToTp) and + exists(AccessPosition apos, TypePath pathFull | SatisfiesParameterConstraint::satisfiesConstraint(MkRelevantAccess(a, pragma[only_bind_into](apos), e), - MkRelevantTarget(target, pragma[only_bind_into](apos)), pathToTp.appendInverse(path), - t) and - not exists(getTypeArgument(a, target, tp, _)) + MkRelevantTarget(target, pragma[only_bind_into](apos)), pathFull, t) and + InverseAppend2::inverseAppend(pathFull, _, path, target, apos, tp) and + hasNotTypeArgument(a, target, tp) ) } } @@ -1591,15 +1642,29 @@ module Make1 Input1> { constrainedTp != tp } + private module InverseAppend3Input implements InverseAppend3InputSig { + class C1 = Declaration; + + class C2 = TypeParameter; + + class C3 = TypeMention; + + class Result = TypeParameter; + + predicate prefixCandidate(string prefix, C1 c1, C2 c2, C3 c3, Result res) { + typeParameterConstraintHasTypeParameter(c1, c2, c3, prefix, res) + } + } + pragma[nomagic] private predicate typeConstraintBaseTypeMatch( Access a, AccessEnvironment e, Declaration target, TypePath path, Type t, TypeParameter tp ) { - not exists(getTypeArgument(a, target, tp, _)) and - exists(TypeMention constraint, TypeParameter constrainedTp, TypePath pathToTp | - typeParameterConstraintHasTypeParameter(target, constrainedTp, constraint, pathToTp, tp) and - AccessConstraint::satisfiesConstraint(a, e, target, constrainedTp, constraint, - pathToTp.appendInverse(path), t) + hasNotTypeArgument(a, target, tp) and + exists(TypeParameter constrainedTp, TypeMention constraint, TypePath pathFull | + AccessConstraint::satisfiesConstraint(a, e, target, constrainedTp, constraint, pathFull, t) and + InverseAppend3::inverseAppend(pathFull, _, path, target, + constrainedTp, constraint, tp) ) } From 80db2b229618a63e8360cf60968404bccf5c74dd Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Fri, 18 Sep 2026 15:00:21 +0200 Subject: [PATCH 3/3] Type inference: Revert inverse append changes --- .../typeinference/internal/TypeInference.qll | 42 +--- shared/util/codeql/util/Strings.qll | 191 ------------------ 2 files changed, 8 insertions(+), 225 deletions(-) diff --git a/shared/typeinference/codeql/typeinference/internal/TypeInference.qll b/shared/typeinference/codeql/typeinference/internal/TypeInference.qll index a475a19096d7..8331144faa80 100644 --- a/shared/typeinference/codeql/typeinference/internal/TypeInference.qll +++ b/shared/typeinference/codeql/typeinference/internal/TypeInference.qll @@ -70,7 +70,6 @@ overlay[local?] module; private import codeql.util.Location -private import codeql.util.Strings /** Provides the input to `Make1`. */ signature module InputSig1 { @@ -1435,18 +1434,6 @@ module Make1 Input1> { private module SatisfiesParameterConstraint = SatisfiesConstraint; - private module InverseAppend2Input implements InverseAppend2InputSig { - class C1 = Declaration; - - class C2 = AccessPosition; - - class Result = TypeParameter; - - predicate prefixCandidate(string prefix, C1 c1, C2 c2, Result res) { - argRootTypeSatisfiesTargetTypeCand(_, c1, c2, res, prefix) - } - } - /** * Holds if the (transitive) base type `t` at `path` of `a` in environment `e` * for some `AccessPosition` matches the type parameter `tp`, which is used in @@ -1483,11 +1470,12 @@ module Make1 Input1> { predicate baseTypeMatch( Access a, AccessEnvironment e, Declaration target, TypePath path, Type t, TypeParameter tp ) { - exists(AccessPosition apos, TypePath pathFull | + exists(AccessPosition apos, TypePath pathToTp | + argRootTypeSatisfiesTargetTypeCand(_, target, pragma[only_bind_into](apos), tp, pathToTp) and SatisfiesParameterConstraint::satisfiesConstraint(MkRelevantAccess(a, pragma[only_bind_into](apos), e), - MkRelevantTarget(target, pragma[only_bind_into](apos)), pathFull, t) and - InverseAppend2::inverseAppend(pathFull, _, path, target, apos, tp) and + MkRelevantTarget(target, pragma[only_bind_into](apos)), pathToTp.appendInverse(path), + t) and hasNotTypeArgument(a, target, tp) ) } @@ -1642,29 +1630,15 @@ module Make1 Input1> { constrainedTp != tp } - private module InverseAppend3Input implements InverseAppend3InputSig { - class C1 = Declaration; - - class C2 = TypeParameter; - - class C3 = TypeMention; - - class Result = TypeParameter; - - predicate prefixCandidate(string prefix, C1 c1, C2 c2, C3 c3, Result res) { - typeParameterConstraintHasTypeParameter(c1, c2, c3, prefix, res) - } - } - pragma[nomagic] private predicate typeConstraintBaseTypeMatch( Access a, AccessEnvironment e, Declaration target, TypePath path, Type t, TypeParameter tp ) { hasNotTypeArgument(a, target, tp) and - exists(TypeParameter constrainedTp, TypeMention constraint, TypePath pathFull | - AccessConstraint::satisfiesConstraint(a, e, target, constrainedTp, constraint, pathFull, t) and - InverseAppend3::inverseAppend(pathFull, _, path, target, - constrainedTp, constraint, tp) + exists(TypeMention constraint, TypeParameter constrainedTp, TypePath pathToTp | + typeParameterConstraintHasTypeParameter(target, constrainedTp, constraint, pathToTp, tp) and + AccessConstraint::satisfiesConstraint(a, e, target, constrainedTp, constraint, + pathToTp.appendInverse(path), t) ) } diff --git a/shared/util/codeql/util/Strings.qll b/shared/util/codeql/util/Strings.qll index de4139550dbe..b032bb73672b 100644 --- a/shared/util/codeql/util/Strings.qll +++ b/shared/util/codeql/util/Strings.qll @@ -91,194 +91,3 @@ int asciiPrintable(string char) { .charAt(_) ) } - -/** - * Escapes all characters in `s` that have special meaning in regular expressions. - */ -bindingset[s] -string regexpEscape(string s) { - result = s.regexpReplaceAll("([\\\\.*+?\\[^\\]$(){}=!<>|:\\-])", "\\\\$1") -} - -/** Provides the input to `InverseAppend`. */ -signature module InverseAppendInputSig { - /** A prefix matching result. */ - bindingset[this] - class Result; - - /** - * Holds if `prefix` is a prefix candidate with resulting value `res`. - */ - predicate prefixCandidate(string prefix, Result res); -} - -/** Provides the `inverseAppend` predicate. */ -module InverseAppend { - /** - * Holds if `s = prefix + suffix` with resulting value `res`. - * - * The predicate avoids unnecessary result fan-out by first constraining the - * prefix using a regular expression match. - */ - bindingset[s] - predicate inverseAppend(string s, string prefix, string suffix, Input::Result res) { - exists(string regexp | - regexp = - "(" + - strictconcat(string prefixCand | - Input::prefixCandidate(prefixCand, _) - | - regexpEscape(prefixCand), "|" - ) + ").*" and - prefix = s.regexpCapture(regexp, 1) and - s = prefix + suffix and - Input::prefixCandidate(prefix, res) - ) - } -} - -/** Provides the input to `InverseAppend1`. */ -signature module InverseAppend1InputSig { - /** A prefix matching context. */ - bindingset[this] - class C; - - /** A prefix matching result. */ - bindingset[this] - class Result; - - /** - * Holds if `prefix` is a prefix candidate in the context `c` - * with resulting value `res`. - */ - predicate prefixCandidate(string prefix, C c, Result res); -} - -/** Provides the `inverseAppend` predicate. */ -module InverseAppend1 { - /** - * Holds if `s = prefix + suffix` in the context `c` with resulting - * value `res`. - * - * The predicate avoids unnecessary result fan-out by first constraining the - * prefix using a regular expression match. - */ - bindingset[s] - predicate inverseAppend(string s, string prefix, string suffix, Input::C c, Input::Result res) { - exists(string regexp | - regexp = - "(" + - strictconcat(string prefixCand | - Input::prefixCandidate(prefixCand, c, _) - | - regexpEscape(prefixCand), "|" - ) + ").*" and - prefix = s.regexpCapture(regexp, 1) and - s = prefix + suffix and - Input::prefixCandidate(prefix, c, res) - ) - } -} - -/** Provides the input to `InverseAppend2`. */ -signature module InverseAppend2InputSig { - /** A prefix matching context. */ - bindingset[this] - class C1; - - /** A prefix matching context. */ - bindingset[this] - class C2; - - /** A prefix matching result. */ - bindingset[this] - class Result; - - /** - * Holds if `prefix` is a prefix candidate in the context `(c1, c2)` - * with resulting value `res`. - */ - predicate prefixCandidate(string prefix, C1 c1, C2 c2, Result res); -} - -/** Provides the `inverseAppend` predicate. */ -module InverseAppend2 { - /** - * Holds if `s = prefix + suffix` in the context `(c1, c2)` with resulting - * value `res`. - * - * The predicate avoids unnecessary result fan-out by first constraining the - * prefix using a regular expression match. - */ - bindingset[s] - predicate inverseAppend( - string s, string prefix, string suffix, Input::C1 c1, Input::C2 c2, Input::Result res - ) { - exists(string regexp | - regexp = - "(" + - strictconcat(string prefixCand | - Input::prefixCandidate(prefixCand, c1, c2, _) - | - regexpEscape(prefixCand), "|" - ) + ").*" and - prefix = s.regexpCapture(regexp, 1) and - s = prefix + suffix and - Input::prefixCandidate(prefix, c1, c2, res) - ) - } -} - -/** Provides the input to `InverseAppend3`. */ -signature module InverseAppend3InputSig { - /** A prefix matching context. */ - bindingset[this] - class C1; - - /** A prefix matching context. */ - bindingset[this] - class C2; - - /** A prefix matching context. */ - bindingset[this] - class C3; - - /** A prefix matching result. */ - bindingset[this] - class Result; - - /** - * Holds if `prefix` is a prefix candidate in the context `(c1, c2, c3)` - * with resulting value `res`. - */ - predicate prefixCandidate(string prefix, C1 c1, C2 c2, C3 c3, Result res); -} - -/** Provides the `inverseAppend` predicate. */ -module InverseAppend3 { - /** - * Holds if `s = prefix + suffix` in the context `(c1, c2, c3)` with resulting - * value `res`. - * - * The predicate avoids unnecessary result fan-out by first constraining the - * prefix using a regular expression match. - */ - bindingset[s] - predicate inverseAppend( - string s, string prefix, string suffix, Input::C1 c1, Input::C2 c2, Input::C3 c3, - Input::Result res - ) { - exists(string regexp | - regexp = - "(" + - strictconcat(string prefixCand | - Input::prefixCandidate(prefixCand, c1, c2, c3, _) - | - regexpEscape(prefixCand), "|" - ) + ").*" and - prefix = s.regexpCapture(regexp, 1) and - s = prefix + suffix and - Input::prefixCandidate(prefix, c1, c2, c3, res) - ) - } -}