From 28ef07fcad73dbaae10111f54a54b70b9947581e Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Fri, 18 Sep 2026 13:12:57 +0200 Subject: [PATCH 1/3] Reapply "UnboundList: More compact string representation" This reverts commit 3fedfbb5fb8df33cd5affe6fbc651e6155eb650b. --- shared/util/codeql/util/UnboundList.qll | 47 ++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/shared/util/codeql/util/UnboundList.qll b/shared/util/codeql/util/UnboundList.qll index 622895a6bd01..a08f76f9c362 100644 --- a/shared/util/codeql/util/UnboundList.qll +++ b/shared/util/codeql/util/UnboundList.qll @@ -10,6 +10,7 @@ overlay[local?] module; private import Location +private import Strings /** Provides the input to `Make`. */ signature module InputSig { @@ -52,7 +53,43 @@ module Make Input> { /** Gets the rank of element `e`, which is used internally in the string encoding. */ int getRank(Element e) { e = DenseRank::denseRank(result) } - private string encode(Element e) { result = getRank(e).toString() } + /** Gets the ASCII printable excluding `.` with zero-based index `code`. */ + pragma[nomagic] + private string interpretAsciiCode(int code) { + exists(int dot, int c | + c = code + 1 and + // `.` is used as element separator, so cannot be used to encode elements + dot = asciiPrintable(".") and + if c < dot then c = asciiPrintable(result) else c + 1 = asciiPrintable(result) + ) + } + + private int asciiCodes() { result = strictcount(interpretAsciiCode(_)) } + + /** + * Gets the `i`th digit (modulo `asciiCodes()`) in a base-`asciiCodes()` integer + * representation of `getRank(e)`. + */ + private int getAsciiCodePart(Element e, int i) { + result = getRank(e) and + i = 0 + or + exists(int mid | + mid = getAsciiCodePart(e, i - 1) and + result = mid / asciiCodes() and + result > 0 + ) + } + + pragma[nomagic] + private string encode(Element e) { + result = + strictconcat(string s, int i | + s = interpretAsciiCode(getAsciiCodePart(e, i) % asciiCodes()) + | + s order by i + ) + } bindingset[s] private Element decode(string s) { encode(result) = s } @@ -88,7 +125,7 @@ module Make Input> { // Same as // `result = count(this.indexOf("."))` // but performs better because it doesn't use an aggregate - result = this.regexpReplaceAll("[0-9]+", "").length() + result = this.regexpReplaceAll("[^\\.]+", "").length() } /** Gets the list obtained by appending `suffix` onto this list. */ @@ -123,7 +160,7 @@ module Make Input> { // `regexpCapture` will then always join in both groups, only to afterwards filter // based on the requested group (the group number is not part of the binding set // of `regexpCapture`) - elem = this.regexpCapture("^([0-9]+)\\..*$", 1) and + elem = this.regexpCapture("^([^\\.]+)\\..*$", 1) and e = decode(elem) and suffix = this.suffix(elem.length() + 1) ) @@ -133,7 +170,7 @@ module Make Input> { bindingset[this] predicate isSnoc(UnboundList prefix, Element e) { // same remark as above about not using multiple capture groups - prefix = this.regexpCapture("^(|.+\\.)[0-9]+\\.$", 1) and + prefix = this.regexpCapture("^(|.+\\.)[^\\.]+\\.$", 1) and e = decode(this.substring(prefix.stringLength(), this.stringLength() - 1)) } @@ -148,7 +185,7 @@ module Make Input> { */ bindingset[this] UnboundList getProperPrefix(int i) { - exists(string regexp, int occurrenceOffset | regexp = "[0-9]+\\." | + exists(string regexp, int occurrenceOffset | regexp = "[^\\.]+\\." | exists(this.regexpFind(regexp, i, occurrenceOffset)) and result = this.prefix(occurrenceOffset) ) From 4bfcf489058f43c7f46d1ce61984d6f944c889a4 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Fri, 18 Sep 2026 13:13:13 +0200 Subject: [PATCH 2/3] Fix bad join --- shared/util/codeql/util/UnboundList.qll | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shared/util/codeql/util/UnboundList.qll b/shared/util/codeql/util/UnboundList.qll index a08f76f9c362..0e9241b8d6e6 100644 --- a/shared/util/codeql/util/UnboundList.qll +++ b/shared/util/codeql/util/UnboundList.qll @@ -220,6 +220,8 @@ module Make Input> { UnboundList nil() { result.isEmpty() } /** Gets the singleton list `e`. */ + bindingset[e] + pragma[inline_late] UnboundList singleton(Element e) { result = encode(e) + "." } /** From 7e40d3a0c15d8485d09dadbc49b919b1a38ed7c5 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 21 Sep 2026 10:29:55 +0200 Subject: [PATCH 3/3] UnboundList: Use simpler alphabet for encoding --- shared/util/codeql/util/UnboundList.qll | 36 +++++++++---------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/shared/util/codeql/util/UnboundList.qll b/shared/util/codeql/util/UnboundList.qll index 0e9241b8d6e6..efdcf967ef88 100644 --- a/shared/util/codeql/util/UnboundList.qll +++ b/shared/util/codeql/util/UnboundList.qll @@ -53,30 +53,25 @@ module Make Input> { /** Gets the rank of element `e`, which is used internally in the string encoding. */ int getRank(Element e) { e = DenseRank::denseRank(result) } - /** Gets the ASCII printable excluding `.` with zero-based index `code`. */ + /** Gets the character that `code` represents when encoding elements. */ pragma[nomagic] - private string interpretAsciiCode(int code) { - exists(int dot, int c | - c = code + 1 and - // `.` is used as element separator, so cannot be used to encode elements - dot = asciiPrintable(".") and - if c < dot then c = asciiPrintable(result) else c + 1 = asciiPrintable(result) - ) + private string interpretCode(int code) { + result = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".charAt(code) } - private int asciiCodes() { result = strictcount(interpretAsciiCode(_)) } + private int codes() { result = strictcount(interpretCode(_)) } /** - * Gets the `i`th digit (modulo `asciiCodes()`) in a base-`asciiCodes()` integer + * Gets the `i`th code (modulo `codes()`) in a base-`codes()` integer * representation of `getRank(e)`. */ - private int getAsciiCodePart(Element e, int i) { + private int getCodePart(Element e, int i) { result = getRank(e) and i = 0 or exists(int mid | - mid = getAsciiCodePart(e, i - 1) and - result = mid / asciiCodes() and + mid = getCodePart(e, i - 1) and + result = mid / codes() and result > 0 ) } @@ -84,14 +79,9 @@ module Make Input> { pragma[nomagic] private string encode(Element e) { result = - strictconcat(string s, int i | - s = interpretAsciiCode(getAsciiCodePart(e, i) % asciiCodes()) - | - s order by i - ) + strictconcat(string s, int i | s = interpretCode(getCodePart(e, i) % codes()) | s order by i) } - bindingset[s] private Element decode(string s) { encode(result) = s } /** @@ -125,7 +115,7 @@ module Make Input> { // Same as // `result = count(this.indexOf("."))` // but performs better because it doesn't use an aggregate - result = this.regexpReplaceAll("[^\\.]+", "").length() + result = this.regexpReplaceAll("[a-zA-Z0-9]+", "").length() } /** Gets the list obtained by appending `suffix` onto this list. */ @@ -160,7 +150,7 @@ module Make Input> { // `regexpCapture` will then always join in both groups, only to afterwards filter // based on the requested group (the group number is not part of the binding set // of `regexpCapture`) - elem = this.regexpCapture("^([^\\.]+)\\..*$", 1) and + elem = this.regexpCapture("^([a-zA-Z0-9]+)\\..*$", 1) and e = decode(elem) and suffix = this.suffix(elem.length() + 1) ) @@ -170,7 +160,7 @@ module Make Input> { bindingset[this] predicate isSnoc(UnboundList prefix, Element e) { // same remark as above about not using multiple capture groups - prefix = this.regexpCapture("^(|.+\\.)[^\\.]+\\.$", 1) and + prefix = this.regexpCapture("^(|.+\\.)[a-zA-Z0-9]+\\.$", 1) and e = decode(this.substring(prefix.stringLength(), this.stringLength() - 1)) } @@ -185,7 +175,7 @@ module Make Input> { */ bindingset[this] UnboundList getProperPrefix(int i) { - exists(string regexp, int occurrenceOffset | regexp = "[^\\.]+\\." | + exists(string regexp, int occurrenceOffset | regexp = "[a-zA-Z0-9]+\\." | exists(this.regexpFind(regexp, i, occurrenceOffset)) and result = this.prefix(occurrenceOffset) )