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) )