diff --git a/shared/util/codeql/util/UnboundList.qll b/shared/util/codeql/util/UnboundList.qll index 622895a6bd01..efdcf967ef88 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,9 +53,35 @@ 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 character that `code` represents when encoding elements. */ + pragma[nomagic] + private string interpretCode(int code) { + result = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".charAt(code) + } + + private int codes() { result = strictcount(interpretCode(_)) } + + /** + * Gets the `i`th code (modulo `codes()`) in a base-`codes()` integer + * representation of `getRank(e)`. + */ + private int getCodePart(Element e, int i) { + result = getRank(e) and + i = 0 + or + exists(int mid | + mid = getCodePart(e, i - 1) and + result = mid / codes() and + result > 0 + ) + } + + pragma[nomagic] + private string encode(Element e) { + result = + 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 } /** @@ -88,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("[0-9]+", "").length() + result = this.regexpReplaceAll("[a-zA-Z0-9]+", "").length() } /** Gets the list obtained by appending `suffix` onto this list. */ @@ -123,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("^([0-9]+)\\..*$", 1) and + elem = this.regexpCapture("^([a-zA-Z0-9]+)\\..*$", 1) and e = decode(elem) and suffix = this.suffix(elem.length() + 1) ) @@ -133,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("^(|.+\\.)[0-9]+\\.$", 1) and + prefix = this.regexpCapture("^(|.+\\.)[a-zA-Z0-9]+\\.$", 1) and e = decode(this.substring(prefix.stringLength(), this.stringLength() - 1)) } @@ -148,7 +175,7 @@ module Make Input> { */ bindingset[this] UnboundList getProperPrefix(int i) { - exists(string regexp, int occurrenceOffset | regexp = "[0-9]+\\." | + exists(string regexp, int occurrenceOffset | regexp = "[a-zA-Z0-9]+\\." | exists(this.regexpFind(regexp, i, occurrenceOffset)) and result = this.prefix(occurrenceOffset) ) @@ -183,6 +210,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) + "." } /**