Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 9 additions & 112 deletions shared/typeinference/codeql/typeinference/internal/TypeInference.qll
Original file line number Diff line number Diff line change
Expand Up @@ -224,25 +224,17 @@ signature module InputSig1<LocationSig Location> {

module Make1<LocationSig Location, InputSig1<Location> Input1> {
private import Input1
private import codeql.util.UnboundList as UnboundListImpl

private module TypeParameter {
private import codeql.util.DenseRank
private module UnboundListInput implements UnboundListImpl::InputSig<Location> {
class Element = TypeParameter;

private module DenseRankInput implements DenseRankInputSig {
class Ranked = TypeParameter;
predicate getId = getTypeParameterId/1;

predicate getRank = getTypeParameterId/1;
}

int getRank(TypeParameter tp) { tp = DenseRank<DenseRankInput>::denseRank(result) }

string encode(TypeParameter tp) { result = getRank(tp).toString() }

bindingset[s]
TypeParameter decode(string s) { encode(result) = s }
predicate getLengthLimit = getTypePathLimit/0;
}

final private class String = string;
private import UnboundListImpl::Make<Location, UnboundListInput>

/**
* A path into a type.
Expand Down Expand Up @@ -274,101 +266,10 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
* implementation uses unique type parameter identifiers, in order to not mix
* up type parameters from different types.
*/
class TypePath extends String {
bindingset[this]
TypePath() { exists(this) }

bindingset[this]
private TypeParameter getTypeParameter(int i) {
result = TypeParameter::decode(this.splitAt(".", i))
}

/** Gets a textual representation of this type path. */
bindingset[this]
string toString() {
result =
concat(int i, TypeParameter tp |
tp = this.getTypeParameter(i)
|
tp.toString(), "." order by i
)
}

/** Holds if this type path is empty. */
predicate isEmpty() { this = "" }

/** Gets the length of this path. */
bindingset[this]
pragma[inline_late]
int length() {
// Same as
// `result = count(this.indexOf("."))`
// but performs better because it doesn't use an aggregate
result = this.regexpReplaceAll("[0-9]+", "").length()
}

/** Gets the path obtained by appending `suffix` onto this path. */
bindingset[this, suffix]
TypePath append(TypePath suffix) {
result = this + suffix and
(
not exists(getTypePathLimit())
or
result.length() <= getTypePathLimit()
)
}

/**
* Gets the path obtained by appending `suffix` onto this path.
*
* Unlike `append`, this predicate has `result` in the binding set,
* so there is no need to check the length of `result`.
*/
bindingset[this, result]
TypePath appendInverse(TypePath suffix) { suffix = result.stripPrefix(this) }

/** Gets the path obtained by removing `prefix` from this path. */
bindingset[this, prefix]
TypePath stripPrefix(TypePath prefix) { this = prefix + result }

/** Holds if this path starts with `tp`, followed by `suffix`. */
bindingset[this]
predicate isCons(TypeParameter tp, TypePath suffix) {
exists(string regexp | regexp = "([0-9]+)\\.(.*)" |
tp = TypeParameter::decode(this.regexpCapture(regexp, 1)) and
suffix = this.regexpCapture(regexp, 2)
)
}

/** Holds if this path starts with `prefix`, followed by `tp`. */
bindingset[this]
predicate isSnoc(TypePath prefix, TypeParameter tp) {
exists(string regexp | regexp = "(|.+\\.)([0-9]+)\\." |
prefix = this.regexpCapture(regexp, 1) and
tp = TypeParameter::decode(this.regexpCapture(regexp, 2))
)
}

/** Gets the head of this path, if any. */
bindingset[this]
TypeParameter getHead() { result = this.getTypeParameter(0) }
}
class TypePath = UnboundList;

/** Provides predicates for constructing `TypePath`s. */
module TypePath {
/** Gets the empty type path. */
TypePath nil() { result.isEmpty() }

/** Gets the singleton type path `tp`. */
TypePath singleton(TypeParameter tp) { result = TypeParameter::encode(tp) + "." }

/**
* Gets the type path obtained by appending the singleton type path `tp`
* onto `suffix`.
*/
bindingset[suffix]
TypePath cons(TypeParameter tp, TypePath suffix) { result = singleton(tp).append(suffix) }
}
module TypePath = UnboundList;

/**
* A class that has a type tree associated with it.
Expand Down Expand Up @@ -600,11 +501,7 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {

private TypeParameter getNthTypeParameter(TypeAbstraction abs, int i) {
result =
rank[i + 1](TypeParameter tp |
tp = abs.getATypeParameter()
|
tp order by TypeParameter::getRank(tp)
)
rank[i + 1](TypeParameter tp | tp = abs.getATypeParameter() | tp order by getRank(tp))
}

/**
Expand Down
153 changes: 153 additions & 0 deletions shared/util/codeql/util/UnboundList.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/**
* Provides logic for representing unbound lists.
*
* The lists are represented internally as strings, and generally it should
* be preferred to instead use a `newtype` representation, but in certain
* cases where this is not feasible (typically because of performance) unbound
* lists can be used.
*/
overlay[local?]
module;

private import Location

/** Provides the input to `Make`. */
signature module InputSig<LocationSig Location> {
/** An element. */
class Element {
/** Gets a textual representation of this element. */
string toString();

/** Gets the location of this element. */
Location getLocation();
}

/** Gets a unique ID used to identify element `e` amongst all elements. */
int getId(Element e);

/**
* Gets a textual representation for element `e`, which will be used in the
* `toString` representation for unbound lists.
*/
default string getElementString(Element e) { result = e.toString() }

/** Gets an optional length limit for unbound lists. */
default int getLengthLimit() { none() }
}

final private class String = string;

/** Provides the `UnboundList` implementation. */
module Make<LocationSig Location, InputSig<Location> Input> {
private import Input
private import codeql.util.DenseRank

// Use dense ranking to assign compact IDs to elements
private module DenseRankInput implements DenseRankInputSig {
class Ranked = Element;

predicate getRank = getId/1;
}

/** Gets the rank of element `e`, which is used internally in the string encoding. */
int getRank(Element e) { e = DenseRank<DenseRankInput>::denseRank(result) }

private string encode(Element e) { result = getRank(e).toString() }

bindingset[s]
private Element decode(string s) { encode(result) = s }

/**
* An unbound list encoded as a string.
*/
class UnboundList extends String {
bindingset[this]
UnboundList() { exists(this) }

/** Gets the `i`th element in this list. */
bindingset[this]
private Element getElement(int i) { result = decode(this.splitAt(".", i)) }

/** Gets a textual representation of this list. */
bindingset[this]
string toString() {
result =
concat(int i, Element e | e = this.getElement(i) | getElementString(e), "." order by i)
}

/** Holds if this list is empty. */
predicate isEmpty() { this = "" }

/** Gets the length of this list. */
bindingset[this]
pragma[inline_late]
int length() {
// Same as
// `result = count(this.indexOf("."))`
// but performs better because it doesn't use an aggregate
result = this.regexpReplaceAll("[0-9]+", "").length()
}

/** Gets the list obtained by appending `suffix` onto this list. */
bindingset[this, suffix]
UnboundList append(UnboundList suffix) {
result = this + suffix and
(
not exists(getLengthLimit())
or
result.length() <= getLengthLimit()
)
}

/**
* Gets the list obtained by appending `suffix` onto this list.
*
* Unlike `append`, this predicate has `result` in the binding set,
* so there is no need to check the length of `result`.
*/
bindingset[this, result]
UnboundList appendInverse(UnboundList suffix) { suffix = result.stripPrefix(this) }

/** Gets the list obtained by removing `prefix` from this list. */
bindingset[this, prefix]
UnboundList stripPrefix(UnboundList prefix) { this = prefix + result }

/** Holds if this list starts with `e`, followed by `suffix`. */
bindingset[this]
predicate isCons(Element e, UnboundList suffix) {
exists(string regexp | regexp = "([0-9]+)\\.(.*)" |
e = decode(this.regexpCapture(regexp, 1)) and
suffix = this.regexpCapture(regexp, 2)
)
}

/** Holds if this list starts with `prefix`, followed by `e`. */
bindingset[this]
predicate isSnoc(UnboundList prefix, Element e) {
exists(string regexp | regexp = "(|.+\\.)([0-9]+)\\." |
prefix = this.regexpCapture(regexp, 1) and
e = decode(this.regexpCapture(regexp, 2))
)
}

/** Gets the head of this list, if any. */
bindingset[this]
Element getHead() { result = this.getElement(0) }
}

/** Provides predicates for constructing `UnboundList`s. */
module UnboundList {
/** Gets the empty list. */
UnboundList nil() { result.isEmpty() }

/** Gets the singleton list `e`. */
UnboundList singleton(Element e) { result = encode(e) + "." }

/**
* Gets the list obtained by appending the singleton list `e`
* onto `suffix`.
*/
bindingset[suffix]
UnboundList cons(Element e, UnboundList suffix) { result = singleton(e).append(suffix) }
}
}
Loading