diff --git a/repo-tests/codeql.txt b/repo-tests/codeql.txt
index 869ad93732c..56db311862a 100644
--- a/repo-tests/codeql.txt
+++ b/repo-tests/codeql.txt
@@ -1 +1 @@
-a2371370ff8260e789342e0ac759bc67ed401702
+6c2713dd8bf76ae1207e3123900a04d6f89b5162
diff --git a/repo-tests/codeql/cpp/ql/lib/experimental/semmle/code/cpp/security/PrivateCleartextWrite.qll b/repo-tests/codeql/cpp/ql/lib/experimental/semmle/code/cpp/security/PrivateCleartextWrite.qll
index 922dadaa20e..5438722fd08 100644
--- a/repo-tests/codeql/cpp/ql/lib/experimental/semmle/code/cpp/security/PrivateCleartextWrite.qll
+++ b/repo-tests/codeql/cpp/ql/lib/experimental/semmle/code/cpp/security/PrivateCleartextWrite.qll
@@ -52,11 +52,8 @@ module PrivateCleartextWrite {
class WriteSink extends Sink {
WriteSink() {
- exists(FileWrite f, BufferWrite b |
- this.asExpr() = f.getASource()
- or
- this.asExpr() = b.getAChild()
- )
+ this.asExpr() = any(FileWrite f).getASource() or
+ this.asExpr() = any(BufferWrite b).getAChild()
}
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/experimental/semmle/code/cpp/security/PrivateData.qll b/repo-tests/codeql/cpp/ql/lib/experimental/semmle/code/cpp/security/PrivateData.qll
index 621e8aad707..ec37e8ce86c 100644
--- a/repo-tests/codeql/cpp/ql/lib/experimental/semmle/code/cpp/security/PrivateData.qll
+++ b/repo-tests/codeql/cpp/ql/lib/experimental/semmle/code/cpp/security/PrivateData.qll
@@ -13,26 +13,25 @@ import cpp
/** A string for `match` that identifies strings that look like they represent private data. */
private string privateNames() {
- // Inspired by the list on https://cwe.mitre.org/data/definitions/359.html
- // Government identifiers, such as Social Security Numbers
- result = "%social%security%number%" or
- // Contact information, such as home addresses and telephone numbers
- result = "%postcode%" or
- result = "%zipcode%" or
- // result = "%telephone%" or
- // Geographic location - where the user is (or was)
- result = "%latitude%" or
- result = "%longitude%" or
- // Financial data - such as credit card numbers, salary, bank accounts, and debts
- result = "%creditcard%" or
- result = "%salary%" or
- result = "%bankaccount%" or
- // Communications - e-mail addresses, private e-mail messages, SMS text messages, chat logs, etc.
- // result = "%email%" or
- // result = "%mobile%" or
- result = "%employer%" or
- // Health - medical conditions, insurance status, prescription records
- result = "%medical%"
+ result =
+ [
+ // Inspired by the list on https://cwe.mitre.org/data/definitions/359.html
+ // Government identifiers, such as Social Security Numbers
+ "%social%security%number%",
+ // Contact information, such as home addresses and telephone numbers
+ "%postcode%", "%zipcode%",
+ // result = "%telephone%" or
+ // Geographic location - where the user is (or was)
+ "%latitude%", "%longitude%",
+ // Financial data - such as credit card numbers, salary, bank accounts, and debts
+ "%creditcard%", "%salary%", "%bankaccount%",
+ // Communications - e-mail addresses, private e-mail messages, SMS text messages, chat logs, etc.
+ // result = "%email%" or
+ // result = "%mobile%" or
+ "%employer%",
+ // Health - medical conditions, insurance status, prescription records
+ "%medical%"
+ ]
}
/** An expression that might contain private data. */
diff --git a/repo-tests/codeql/cpp/ql/lib/external/ExternalArtifact.qll b/repo-tests/codeql/cpp/ql/lib/external/ExternalArtifact.qll
index abbc96a7b47..1034f1c9ecc 100644
--- a/repo-tests/codeql/cpp/ql/lib/external/ExternalArtifact.qll
+++ b/repo-tests/codeql/cpp/ql/lib/external/ExternalArtifact.qll
@@ -15,7 +15,7 @@ class ExternalData extends @externalDataElement {
* Gets the path of the file this data was loaded from, with its
* extension replaced by `.ql`.
*/
- string getQueryPath() { result = getDataPath().regexpReplaceAll("\\.[^.]*$", ".ql") }
+ string getQueryPath() { result = this.getDataPath().regexpReplaceAll("\\.[^.]*$", ".ql") }
/** Gets the number of fields in this data item. */
int getNumFields() { result = 1 + max(int i | externalData(this, _, i, _) | i) }
@@ -24,22 +24,22 @@ class ExternalData extends @externalDataElement {
string getField(int i) { externalData(this, _, i, result) }
/** Gets the integer value of the `i`th field of this data item. */
- int getFieldAsInt(int i) { result = getField(i).toInt() }
+ int getFieldAsInt(int i) { result = this.getField(i).toInt() }
/** Gets the floating-point value of the `i`th field of this data item. */
- float getFieldAsFloat(int i) { result = getField(i).toFloat() }
+ float getFieldAsFloat(int i) { result = this.getField(i).toFloat() }
/** Gets the value of the `i`th field of this data item, interpreted as a date. */
- date getFieldAsDate(int i) { result = getField(i).toDate() }
+ date getFieldAsDate(int i) { result = this.getField(i).toDate() }
/** Gets a textual representation of this data item. */
- string toString() { result = getQueryPath() + ": " + buildTupleString(0) }
+ string toString() { result = this.getQueryPath() + ": " + this.buildTupleString(0) }
/** Gets a textual representation of this data item, starting with the `n`th field. */
private string buildTupleString(int n) {
- n = getNumFields() - 1 and result = getField(n)
+ n = this.getNumFields() - 1 and result = this.getField(n)
or
- n < getNumFields() - 1 and result = getField(n) + "," + buildTupleString(n + 1)
+ n < this.getNumFields() - 1 and result = this.getField(n) + "," + this.buildTupleString(n + 1)
}
}
@@ -53,8 +53,8 @@ class DefectExternalData extends ExternalData {
}
/** Gets the URL associated with this data item. */
- string getURL() { result = getField(0) }
+ string getURL() { result = this.getField(0) }
/** Gets the message associated with this data item. */
- string getMessage() { result = getField(1) }
+ string getMessage() { result = this.getField(1) }
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Class.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Class.qll
index 987ec7ffa3d..4fe1b07e32a 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Class.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Class.qll
@@ -237,7 +237,7 @@ class Class extends UserType {
exists(ClassDerivation cd | cd.getBaseClass() = base |
result =
this.accessOfBaseMemberMulti(cd.getDerivedClass(),
- fieldInBase.accessInDirectDerived(cd.getASpecifier().(AccessSpecifier)))
+ fieldInBase.accessInDirectDerived(cd.getASpecifier()))
)
}
@@ -261,21 +261,20 @@ class Class extends UserType {
* includes the case of `base` = `this`.
*/
AccessSpecifier accessOfBaseMember(Declaration member) {
- result =
- this.accessOfBaseMember(member.getDeclaringType(), member.getASpecifier().(AccessSpecifier))
+ result = this.accessOfBaseMember(member.getDeclaringType(), member.getASpecifier())
}
/**
* DEPRECATED: name changed to `hasImplicitCopyConstructor` to reflect that
* `= default` members are no longer included.
*/
- deprecated predicate hasGeneratedCopyConstructor() { hasImplicitCopyConstructor() }
+ deprecated predicate hasGeneratedCopyConstructor() { this.hasImplicitCopyConstructor() }
/**
* DEPRECATED: name changed to `hasImplicitCopyAssignmentOperator` to
* reflect that `= default` members are no longer included.
*/
- deprecated predicate hasGeneratedCopyAssignmentOperator() { hasImplicitCopyConstructor() }
+ deprecated predicate hasGeneratedCopyAssignmentOperator() { this.hasImplicitCopyConstructor() }
/**
* Holds if this class, struct or union has an implicitly-declared copy
@@ -319,7 +318,7 @@ class Class extends UserType {
exists(Type t | t = this.getAFieldSubobjectType().getUnspecifiedType() |
// Note: Overload resolution is not implemented -- all copy
// constructors are considered equal.
- this.cannotAccessCopyConstructorOnAny(t.(Class))
+ this.cannotAccessCopyConstructorOnAny(t)
)
or
// - T has direct or virtual base class that cannot be copied (has deleted,
@@ -392,7 +391,7 @@ class Class extends UserType {
exists(Type t | t = this.getAFieldSubobjectType().getUnspecifiedType() |
// Note: Overload resolution is not implemented -- all copy assignment
// operators are considered equal.
- this.cannotAccessCopyAssignmentOperatorOnAny(t.(Class))
+ this.cannotAccessCopyAssignmentOperatorOnAny(t)
)
or
exists(Class c | c = this.getADirectOrVirtualBase() |
@@ -487,7 +486,7 @@ class Class extends UserType {
exists(ClassDerivation cd |
// Add the offset of the direct base class and the offset of `baseClass`
// within that direct base class.
- cd = getADerivation() and
+ cd = this.getADerivation() and
result = cd.getBaseClass().getANonVirtualBaseClassByteOffset(baseClass) + cd.getByteOffset()
)
}
@@ -502,12 +501,12 @@ class Class extends UserType {
*/
int getABaseClassByteOffset(Class baseClass) {
// Handle the non-virtual case.
- result = getANonVirtualBaseClassByteOffset(baseClass)
+ result = this.getANonVirtualBaseClassByteOffset(baseClass)
or
exists(Class virtualBaseClass, int virtualBaseOffset, int offsetFromVirtualBase |
// Look for the base class as a non-virtual base of a direct or indirect
// virtual base, adding the two offsets.
- getVirtualBaseClassByteOffset(virtualBaseClass) = virtualBaseOffset and
+ this.getVirtualBaseClassByteOffset(virtualBaseClass) = virtualBaseOffset and
offsetFromVirtualBase = virtualBaseClass.getANonVirtualBaseClassByteOffset(baseClass) and
result = virtualBaseOffset + offsetFromVirtualBase
)
@@ -623,11 +622,11 @@ class Class extends UserType {
* inherits one).
*/
predicate isPolymorphic() {
- exists(MemberFunction f | f.getDeclaringType() = getABaseClass*() and f.isVirtual())
+ exists(MemberFunction f | f.getDeclaringType() = this.getABaseClass*() and f.isVirtual())
}
override predicate involvesTemplateParameter() {
- getATemplateArgument().(Type).involvesTemplateParameter()
+ this.getATemplateArgument().(Type).involvesTemplateParameter()
}
/** Holds if this class, struct or union was declared 'final'. */
@@ -765,7 +764,7 @@ class ClassDerivation extends Locatable, @derivation {
* };
* ```
*/
- Class getBaseClass() { result = getBaseType().getUnderlyingType() }
+ Class getBaseClass() { result = this.getBaseType().getUnderlyingType() }
override string getAPrimaryQlClass() { result = "ClassDerivation" }
@@ -818,7 +817,7 @@ class ClassDerivation extends Locatable, @derivation {
predicate hasSpecifier(string s) { this.getASpecifier().hasName(s) }
/** Holds if the derivation is for a virtual base class. */
- predicate isVirtual() { hasSpecifier("virtual") }
+ predicate isVirtual() { this.hasSpecifier("virtual") }
/** Gets the location of the derivation. */
override Location getLocation() { derivations(underlyingElement(this), _, _, _, result) }
@@ -846,7 +845,7 @@ class ClassDerivation extends Locatable, @derivation {
* ```
*/
class LocalClass extends Class {
- LocalClass() { isLocal() }
+ LocalClass() { this.isLocal() }
override string getAPrimaryQlClass() { not this instanceof LocalStruct and result = "LocalClass" }
@@ -989,9 +988,9 @@ class ClassTemplateSpecialization extends Class {
TemplateClass getPrimaryTemplate() {
// Ignoring template arguments, the primary template has the same name
// as each of its specializations.
- result.getSimpleName() = getSimpleName() and
+ result.getSimpleName() = this.getSimpleName() and
// It is in the same namespace as its specializations.
- result.getNamespace() = getNamespace() and
+ result.getNamespace() = this.getNamespace() and
// It is distinguished by the fact that each of its template arguments
// is a distinct template parameter.
count(TemplateParameter tp | tp = result.getATemplateArgument()) =
@@ -1108,7 +1107,7 @@ deprecated class Interface extends Class {
* ```
*/
class VirtualClassDerivation extends ClassDerivation {
- VirtualClassDerivation() { hasSpecifier("virtual") }
+ VirtualClassDerivation() { this.hasSpecifier("virtual") }
override string getAPrimaryQlClass() { result = "VirtualClassDerivation" }
}
@@ -1136,7 +1135,7 @@ class VirtualBaseClass extends Class {
VirtualClassDerivation getAVirtualDerivation() { result.getBaseClass() = this }
/** A class/struct that is derived from this one using virtual inheritance. */
- Class getAVirtuallyDerivedClass() { result = getAVirtualDerivation().getDerivedClass() }
+ Class getAVirtuallyDerivedClass() { result = this.getAVirtualDerivation().getDerivedClass() }
}
/**
@@ -1155,7 +1154,7 @@ class ProxyClass extends UserType {
override string getAPrimaryQlClass() { result = "ProxyClass" }
/** Gets the location of the proxy class. */
- override Location getLocation() { result = getTemplateParameter().getDefinitionLocation() }
+ override Location getLocation() { result = this.getTemplateParameter().getDefinitionLocation() }
/** Gets the template parameter for which this is the proxy class. */
TemplateParameter getTemplateParameter() {
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Declaration.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Declaration.qll
index b1422aa6342..8def15e8c13 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Declaration.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Declaration.qll
@@ -184,7 +184,7 @@ class Declaration extends Locatable, @declaration {
predicate hasDefinition() { exists(this.getDefinition()) }
/** DEPRECATED: Use `hasDefinition` instead. */
- predicate isDefined() { hasDefinition() }
+ predicate isDefined() { this.hasDefinition() }
/** Gets the preferred location of this declaration, if any. */
override Location getLocation() { none() }
@@ -209,7 +209,7 @@ class Declaration extends Locatable, @declaration {
predicate isStatic() { this.hasSpecifier("static") }
/** Holds if this declaration is a member of a class/struct/union. */
- predicate isMember() { hasDeclaringType() }
+ predicate isMember() { this.hasDeclaringType() }
/** Holds if this declaration is a member of a class/struct/union. */
predicate hasDeclaringType() { exists(this.getDeclaringType()) }
@@ -226,14 +226,14 @@ class Declaration extends Locatable, @declaration {
* When called on a template, this will return a template parameter type for
* both typed and non-typed parameters.
*/
- final Locatable getATemplateArgument() { result = getTemplateArgument(_) }
+ final Locatable getATemplateArgument() { result = this.getTemplateArgument(_) }
/**
* Gets a template argument used to instantiate this declaration from a template.
* When called on a template, this will return a non-typed template
* parameter value.
*/
- final Locatable getATemplateArgumentKind() { result = getTemplateArgumentKind(_) }
+ final Locatable getATemplateArgumentKind() { result = this.getTemplateArgumentKind(_) }
/**
* Gets the `i`th template argument used to instantiate this declaration from a
@@ -252,9 +252,9 @@ class Declaration extends Locatable, @declaration {
* `getTemplateArgument(1)` return `1`.
*/
final Locatable getTemplateArgument(int index) {
- if exists(getTemplateArgumentValue(index))
- then result = getTemplateArgumentValue(index)
- else result = getTemplateArgumentType(index)
+ if exists(this.getTemplateArgumentValue(index))
+ then result = this.getTemplateArgumentValue(index)
+ else result = this.getTemplateArgumentType(index)
}
/**
@@ -275,13 +275,13 @@ class Declaration extends Locatable, @declaration {
* `getTemplateArgumentKind(0)`.
*/
final Locatable getTemplateArgumentKind(int index) {
- exists(getTemplateArgumentValue(index)) and
- result = getTemplateArgumentType(index)
+ exists(this.getTemplateArgumentValue(index)) and
+ result = this.getTemplateArgumentType(index)
}
/** Gets the number of template arguments for this declaration. */
final int getNumberOfTemplateArguments() {
- result = count(int i | exists(getTemplateArgument(i)))
+ result = count(int i | exists(this.getTemplateArgument(i)))
}
private Type getTemplateArgumentType(int index) {
@@ -327,9 +327,9 @@ class DeclarationEntry extends Locatable, TDeclarationEntry {
* available), or the name declared by this entry otherwise.
*/
string getCanonicalName() {
- if getDeclaration().hasDefinition()
- then result = getDeclaration().getDefinition().getName()
- else result = getName()
+ if this.getDeclaration().hasDefinition()
+ then result = this.getDeclaration().getDefinition().getName()
+ else result = this.getName()
}
/**
@@ -370,18 +370,18 @@ class DeclarationEntry extends Locatable, TDeclarationEntry {
/**
* Holds if this declaration entry has a specifier with the given name.
*/
- predicate hasSpecifier(string specifier) { getASpecifier() = specifier }
+ predicate hasSpecifier(string specifier) { this.getASpecifier() = specifier }
/** Holds if this declaration entry is a definition. */
predicate isDefinition() { none() } // overridden in subclasses
override string toString() {
- if isDefinition()
- then result = "definition of " + getName()
+ if this.isDefinition()
+ then result = "definition of " + this.getName()
else
- if getName() = getCanonicalName()
- then result = "declaration of " + getName()
- else result = "declaration of " + getCanonicalName() + " as " + getName()
+ if this.getName() = this.getCanonicalName()
+ then result = "declaration of " + this.getName()
+ else result = "declaration of " + this.getCanonicalName() + " as " + this.getName()
}
}
@@ -490,8 +490,7 @@ class AccessHolder extends Declaration, TAccessHolder {
*/
pragma[inline]
predicate canAccessMember(Declaration member, Class derived) {
- this.couldAccessMember(member.getDeclaringType(), member.getASpecifier().(AccessSpecifier),
- derived)
+ this.couldAccessMember(member.getDeclaringType(), member.getASpecifier(), derived)
}
/**
@@ -580,7 +579,7 @@ private class DirectAccessHolder extends Element {
// transitive closure with a restricted base case.
this.thisCanAccessClassStep(base, derived)
or
- exists(Class between | thisCanAccessClassTrans(base, between) |
+ exists(Class between | this.thisCanAccessClassTrans(base, between) |
isDirectPublicBaseOf(between, derived) or
this.thisCanAccessClassStep(between, derived)
)
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Element.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Element.qll
index 1f547adccaa..9273d1b31bf 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Element.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Element.qll
@@ -61,7 +61,7 @@ class ElementBase extends @element {
/**
* Gets a comma-separated list of the names of the primary CodeQL classes to which this element belongs.
*/
- final string getPrimaryQlClasses() { result = concat(getAPrimaryQlClass(), ",") }
+ final string getPrimaryQlClasses() { result = concat(this.getAPrimaryQlClass(), ",") }
/**
* Gets the name of a primary CodeQL class to which this element belongs.
@@ -206,9 +206,9 @@ class Element extends ElementBase {
/** Gets the closest `Element` enclosing this one. */
cached
Element getEnclosingElement() {
- result = getEnclosingElementPref()
+ result = this.getEnclosingElementPref()
or
- not exists(getEnclosingElementPref()) and
+ not exists(this.getEnclosingElementPref()) and
(
this = result.(Class).getAMember()
or
@@ -281,7 +281,7 @@ private predicate isFromUninstantiatedTemplateRec(Element e, Element template) {
* ```
*/
class StaticAssert extends Locatable, @static_assert {
- override string toString() { result = "static_assert(..., \"" + getMessage() + "\")" }
+ override string toString() { result = "static_assert(..., \"" + this.getMessage() + "\")" }
/**
* Gets the expression which this static assertion ensures is true.
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Enum.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Enum.qll
index 9cddeb78f9b..38263dacf7a 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Enum.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Enum.qll
@@ -85,7 +85,7 @@ class Enum extends UserType, IntegralOrEnumType {
* ```
*/
class LocalEnum extends Enum {
- LocalEnum() { isLocal() }
+ LocalEnum() { this.isLocal() }
override string getAPrimaryQlClass() { result = "LocalEnum" }
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/File.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/File.qll
index f486dd8d3c5..3b72533b4f4 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/File.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/File.qll
@@ -52,7 +52,7 @@ class Container extends Locatable, @container {
*/
string getRelativePath() {
exists(string absPath, string pref |
- absPath = getAbsolutePath() and sourceLocationPrefix(pref)
+ absPath = this.getAbsolutePath() and sourceLocationPrefix(pref)
|
absPath = pref and result = ""
or
@@ -79,7 +79,7 @@ class Container extends Locatable, @container {
*
*/
string getBaseName() {
- result = getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1)
+ result = this.getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1)
}
/**
@@ -105,7 +105,9 @@ class Container extends Locatable, @container {
*
| "/tmp/x.tar.gz" | "gz" |
*
*/
- string getExtension() { result = getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3) }
+ string getExtension() {
+ result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3)
+ }
/**
* Gets the stem of this container, that is, the prefix of its base name up to
@@ -124,7 +126,9 @@ class Container extends Locatable, @container {
* | "/tmp/x.tar.gz" | "x.tar" |
*
*/
- string getStem() { result = getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1) }
+ string getStem() {
+ result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1)
+ }
/** Gets the parent container of this file or folder, if any. */
Container getParentContainer() {
@@ -135,20 +139,20 @@ class Container extends Locatable, @container {
Container getAChildContainer() { this = result.getParentContainer() }
/** Gets a file in this container. */
- File getAFile() { result = getAChildContainer() }
+ File getAFile() { result = this.getAChildContainer() }
/** Gets the file in this container that has the given `baseName`, if any. */
File getFile(string baseName) {
- result = getAFile() and
+ result = this.getAFile() and
result.getBaseName() = baseName
}
/** Gets a sub-folder in this container. */
- Folder getAFolder() { result = getAChildContainer() }
+ Folder getAFolder() { result = this.getAChildContainer() }
/** Gets the sub-folder in this container that has the given `baseName`, if any. */
Folder getFolder(string baseName) {
- result = getAFolder() and
+ result = this.getAFolder() and
result.getBaseName() = baseName
}
@@ -157,7 +161,7 @@ class Container extends Locatable, @container {
*
* This is the absolute path of the container.
*/
- override string toString() { result = getAbsolutePath() }
+ override string toString() { result = this.getAbsolutePath() }
}
/**
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Function.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Function.qll
index 6cae134645f..0f1de2b512c 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Function.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Function.qll
@@ -43,26 +43,26 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
*/
string getFullSignature() {
exists(string name, string templateArgs, string args |
- result = name + templateArgs + args + " -> " + getType().toString() and
- name = getQualifiedName() and
+ result = name + templateArgs + args + " -> " + this.getType().toString() and
+ name = this.getQualifiedName() and
(
- if exists(getATemplateArgument())
+ if exists(this.getATemplateArgument())
then
templateArgs =
"<" +
concat(int i |
- exists(getTemplateArgument(i))
+ exists(this.getTemplateArgument(i))
|
- getTemplateArgument(i).toString(), ", " order by i
+ this.getTemplateArgument(i).toString(), ", " order by i
) + ">"
else templateArgs = ""
) and
args =
"(" +
concat(int i |
- exists(getParameter(i))
+ exists(this.getParameter(i))
|
- getParameter(i).getType().toString(), ", " order by i
+ this.getParameter(i).getType().toString(), ", " order by i
) + ")"
)
}
@@ -70,7 +70,7 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
/** Gets a specifier of this function. */
override Specifier getASpecifier() {
funspecifiers(underlyingElement(this), unresolveElement(result)) or
- result.hasName(getADeclarationEntry().getASpecifier())
+ result.hasName(this.getADeclarationEntry().getASpecifier())
}
/** Gets an attribute of this function. */
@@ -149,7 +149,7 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
* Holds if this function is declared with `__attribute__((naked))` or
* `__declspec(naked)`.
*/
- predicate isNaked() { getAnAttribute().hasName("naked") }
+ predicate isNaked() { this.getAnAttribute().hasName("naked") }
/**
* Holds if this function has a trailing return type.
@@ -172,7 +172,7 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
* Gets the return type of this function after specifiers have been deeply
* stripped and typedefs have been resolved.
*/
- Type getUnspecifiedType() { result = getType().getUnspecifiedType() }
+ Type getUnspecifiedType() { result = this.getType().getUnspecifiedType() }
/**
* Gets the nth parameter of this function. There is no result for the
@@ -206,7 +206,7 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
int getEffectiveNumberOfParameters() {
// This method is overridden in `MemberFunction`, where the result is
// adjusted to account for the implicit `this` parameter.
- result = getNumberOfParameters()
+ result = this.getNumberOfParameters()
}
/**
@@ -216,7 +216,7 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
* return `int p1, int p2`.
*/
string getParameterString() {
- result = concat(int i | | min(getParameter(i).getTypedName()), ", " order by i)
+ result = concat(int i | | min(this.getParameter(i).getTypedName()), ", " order by i)
}
/** Gets a call to this function. */
@@ -229,7 +229,7 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
*/
override FunctionDeclarationEntry getADeclarationEntry() {
if fun_decls(_, underlyingElement(this), _, _, _)
- then declEntry(result)
+ then this.declEntry(result)
else
exists(Function f |
this.isConstructedFrom(f) and
@@ -250,7 +250,7 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
* Gets the location of a `FunctionDeclarationEntry` corresponding to this
* declaration.
*/
- override Location getADeclarationLocation() { result = getADeclarationEntry().getLocation() }
+ override Location getADeclarationLocation() { result = this.getADeclarationEntry().getLocation() }
/** Holds if this Function is a Template specialization. */
predicate isSpecialization() {
@@ -265,14 +265,14 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
* definition, if any.
*/
override FunctionDeclarationEntry getDefinition() {
- result = getADeclarationEntry() and
+ result = this.getADeclarationEntry() and
result.isDefinition()
}
/** Gets the location of the definition, if any. */
override Location getDefinitionLocation() {
- if exists(getDefinition())
- then result = getDefinition().getLocation()
+ if exists(this.getDefinition())
+ then result = this.getDefinition().getLocation()
else exists(Function f | this.isConstructedFrom(f) and result = f.getDefinition().getLocation())
}
@@ -281,7 +281,7 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
* definition, if possible.)
*/
override Location getLocation() {
- if exists(getDefinition())
+ if exists(this.getDefinition())
then result = this.getDefinitionLocation()
else result = this.getADeclarationLocation()
}
@@ -299,7 +299,7 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
BlockStmt getBlock() { result.getParentScope() = this }
/** Holds if this function has an entry point. */
- predicate hasEntryPoint() { exists(getEntryPoint()) }
+ predicate hasEntryPoint() { exists(this.getEntryPoint()) }
/**
* Gets the first node in this function's control flow graph.
@@ -392,7 +392,7 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
* Holds if this function has C linkage, as specified by one of its
* declaration entries. For example: `extern "C" void foo();`.
*/
- predicate hasCLinkage() { getADeclarationEntry().hasCLinkage() }
+ predicate hasCLinkage() { this.getADeclarationEntry().hasCLinkage() }
/**
* Holds if this function is constructed from `f` as a result
@@ -409,27 +409,27 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
* several functions that are not linked together have been compiled. An
* example would be a project with many 'main' functions.
*/
- predicate isMultiplyDefined() { strictcount(getFile()) > 1 }
+ predicate isMultiplyDefined() { strictcount(this.getFile()) > 1 }
/** Holds if this function is a varargs function. */
- predicate isVarargs() { hasSpecifier("varargs") }
+ predicate isVarargs() { this.hasSpecifier("varargs") }
/** Gets a type that is specified to be thrown by the function. */
- Type getAThrownType() { result = getADeclarationEntry().getAThrownType() }
+ Type getAThrownType() { result = this.getADeclarationEntry().getAThrownType() }
/**
* Gets the `i`th type specified to be thrown by the function.
*/
- Type getThrownType(int i) { result = getADeclarationEntry().getThrownType(i) }
+ Type getThrownType(int i) { result = this.getADeclarationEntry().getThrownType(i) }
/** Holds if the function has an exception specification. */
- predicate hasExceptionSpecification() { getADeclarationEntry().hasExceptionSpecification() }
+ predicate hasExceptionSpecification() { this.getADeclarationEntry().hasExceptionSpecification() }
/** Holds if this function has a `throw()` exception specification. */
- predicate isNoThrow() { getADeclarationEntry().isNoThrow() }
+ predicate isNoThrow() { this.getADeclarationEntry().isNoThrow() }
/** Holds if this function has a `noexcept` exception specification. */
- predicate isNoExcept() { getADeclarationEntry().isNoExcept() }
+ predicate isNoExcept() { this.getADeclarationEntry().isNoExcept() }
/**
* Gets a function that overloads this one.
@@ -539,7 +539,7 @@ private predicate candGetAnOverloadNonMember(string name, Namespace namespace, F
*/
class FunctionDeclarationEntry extends DeclarationEntry, @fun_decl {
/** Gets the function which is being declared or defined. */
- override Function getDeclaration() { result = getFunction() }
+ override Function getDeclaration() { result = this.getFunction() }
override string getAPrimaryQlClass() { result = "FunctionDeclarationEntry" }
@@ -586,7 +586,7 @@ class FunctionDeclarationEntry extends DeclarationEntry, @fun_decl {
* case, catch) plus the number of branching expressions (`?`, `&&`,
* `||`) plus one.
*/
- int getCyclomaticComplexity() { result = 1 + cyclomaticComplexityBranches(getBlock()) }
+ int getCyclomaticComplexity() { result = 1 + cyclomaticComplexityBranches(this.getBlock()) }
/**
* If this is a function definition, get the block containing the
@@ -594,7 +594,7 @@ class FunctionDeclarationEntry extends DeclarationEntry, @fun_decl {
*/
BlockStmt getBlock() {
this.isDefinition() and
- result = getFunction().getBlock() and
+ result = this.getFunction().getBlock() and
result.getFile() = this.getFile()
}
@@ -604,7 +604,7 @@ class FunctionDeclarationEntry extends DeclarationEntry, @fun_decl {
*/
pragma[noopt]
int getNumberOfLines() {
- exists(BlockStmt b, Location l, int start, int end, int diff | b = getBlock() |
+ exists(BlockStmt b, Location l, int start, int end, int diff | b = this.getBlock() |
l = b.getLocation() and
start = l.getStartLine() and
end = l.getEndLine() and
@@ -618,7 +618,7 @@ class FunctionDeclarationEntry extends DeclarationEntry, @fun_decl {
* declaration.
*/
ParameterDeclarationEntry getAParameterDeclarationEntry() {
- result = getParameterDeclarationEntry(_)
+ result = this.getParameterDeclarationEntry(_)
}
/**
@@ -639,7 +639,8 @@ class FunctionDeclarationEntry extends DeclarationEntry, @fun_decl {
* return 'int p1, int p2'.
*/
string getParameterString() {
- result = concat(int i | | min(getParameterDeclarationEntry(i).getTypedName()), ", " order by i)
+ result =
+ concat(int i | | min(this.getParameterDeclarationEntry(i).getTypedName()), ", " order by i)
}
/**
@@ -647,10 +648,10 @@ class FunctionDeclarationEntry extends DeclarationEntry, @fun_decl {
*
* `extern "C" void foo();`
*/
- predicate hasCLinkage() { getASpecifier() = "c_linkage" }
+ predicate hasCLinkage() { this.getASpecifier() = "c_linkage" }
/** Holds if this declaration entry has a void parameter list. */
- predicate hasVoidParamList() { getASpecifier() = "void_param_list" }
+ predicate hasVoidParamList() { this.getASpecifier() = "void_param_list" }
/** Holds if this declaration is also a definition of its function. */
override predicate isDefinition() { fun_def(underlyingElement(this)) }
@@ -665,7 +666,7 @@ class FunctionDeclarationEntry extends DeclarationEntry, @fun_decl {
predicate isImplicit() { fun_implicit(underlyingElement(this)) }
/** Gets a type that is specified to be thrown by the declared function. */
- Type getAThrownType() { result = getThrownType(_) }
+ Type getAThrownType() { result = this.getThrownType(_) }
/**
* Gets the `i`th type specified to be thrown by the declared function
@@ -690,8 +691,8 @@ class FunctionDeclarationEntry extends DeclarationEntry, @fun_decl {
predicate hasExceptionSpecification() {
fun_decl_throws(underlyingElement(this), _, _) or
fun_decl_noexcept(underlyingElement(this), _) or
- isNoThrow() or
- isNoExcept()
+ this.isNoThrow() or
+ this.isNoExcept()
}
/**
@@ -763,7 +764,7 @@ class Operator extends Function {
*/
class TemplateFunction extends Function {
TemplateFunction() {
- is_function_template(underlyingElement(this)) and exists(getATemplateArgument())
+ is_function_template(underlyingElement(this)) and exists(this.getATemplateArgument())
}
override string getAPrimaryQlClass() { result = "TemplateFunction" }
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Include.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Include.qll
index f21edb2651d..9a120b1013c 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Include.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Include.qll
@@ -23,7 +23,7 @@ class Include extends PreprocessorDirective, @ppd_include {
* Gets the token which occurs after `#include`, for example `"filename"`
* or ``.
*/
- string getIncludeText() { result = getHead() }
+ string getIncludeText() { result = this.getHead() }
/** Gets the file directly included by this `#include`. */
File getIncludedFile() { includes(underlyingElement(this), unresolveElement(result)) }
@@ -53,7 +53,7 @@ class Include extends PreprocessorDirective, @ppd_include {
* ```
*/
class IncludeNext extends Include, @ppd_include_next {
- override string toString() { result = "#include_next " + getIncludeText() }
+ override string toString() { result = "#include_next " + this.getIncludeText() }
}
/**
@@ -65,5 +65,5 @@ class IncludeNext extends Include, @ppd_include_next {
* ```
*/
class Import extends Include, @ppd_objc_import {
- override string toString() { result = "#import " + getIncludeText() }
+ override string toString() { result = "#import " + this.getIncludeText() }
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Initializer.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Initializer.qll
index 64607af3393..62af72c1803 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Initializer.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Initializer.qll
@@ -34,8 +34,8 @@ class Initializer extends ControlFlowNode, @initialiser {
override predicate fromSource() { not this.getLocation() instanceof UnknownLocation }
override string toString() {
- if exists(getDeclaration())
- then result = "initializer for " + max(getDeclaration().getName())
+ if exists(this.getDeclaration())
+ then result = "initializer for " + max(this.getDeclaration().getName())
else result = "initializer"
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Location.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Location.qll
index 15ae2121255..92b358d474c 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Location.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Location.qll
@@ -79,8 +79,8 @@ class Location extends @location {
/** Holds if location `l` is completely contained within this one. */
predicate subsumes(Location l) {
- exists(File f | f = getFile() |
- exists(int thisStart, int thisEnd | charLoc(f, thisStart, thisEnd) |
+ exists(File f | f = this.getFile() |
+ exists(int thisStart, int thisEnd | this.charLoc(f, thisStart, thisEnd) |
exists(int lStart, int lEnd | l.charLoc(f, lStart, lEnd) |
thisStart <= lStart and lEnd <= thisEnd
)
@@ -97,10 +97,10 @@ class Location extends @location {
* see `subsumes`.
*/
predicate charLoc(File f, int start, int end) {
- f = getFile() and
+ f = this.getFile() and
exists(int maxCols | maxCols = maxCols(f) |
- start = getStartLine() * maxCols + getStartColumn() and
- end = getEndLine() * maxCols + getEndColumn()
+ start = this.getStartLine() * maxCols + this.getStartColumn() and
+ end = this.getEndLine() * maxCols + this.getEndColumn()
)
}
}
@@ -144,7 +144,7 @@ class Locatable extends Element { }
* expressions, one for statements and one for other program elements.
*/
class UnknownLocation extends Location {
- UnknownLocation() { getFile().getAbsolutePath() = "" }
+ UnknownLocation() { this.getFile().getAbsolutePath() = "" }
}
/**
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Macro.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Macro.qll
index aa4b8d41999..6d61ae7be7c 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Macro.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Macro.qll
@@ -44,10 +44,10 @@ class Macro extends PreprocessorDirective, @ppd_define {
* Gets the name of the macro. For example, `MAX` in
* `#define MAX(x,y) (((x)>(y))?(x):(y))`.
*/
- string getName() { result = getHead().splitAt("(", 0) }
+ string getName() { result = this.getHead().splitAt("(", 0) }
/** Holds if the macro has name `name`. */
- predicate hasName(string name) { getName() = name }
+ predicate hasName(string name) { this.getName() = name }
}
/**
@@ -130,7 +130,7 @@ class MacroAccess extends Locatable, @macroinvocation {
override string toString() { result = this.getMacro().getHead() }
/** Gets the name of the accessed macro. */
- string getMacroName() { result = getMacro().getName() }
+ string getMacroName() { result = this.getMacro().getName() }
}
/**
@@ -197,8 +197,8 @@ class MacroInvocation extends MacroAccess {
* expression. In other cases, it may have multiple results or no results.
*/
Expr getExpr() {
- result = getAnExpandedElement() and
- not result.getParent() = getAnExpandedElement() and
+ result = this.getAnExpandedElement() and
+ not result.getParent() = this.getAnExpandedElement() and
not result instanceof Conversion
}
@@ -208,8 +208,8 @@ class MacroInvocation extends MacroAccess {
* element is not a statement (for example if it is an expression).
*/
Stmt getStmt() {
- result = getAnExpandedElement() and
- not result.getParent() = getAnExpandedElement()
+ result = this.getAnExpandedElement() and
+ not result.getParent() = this.getAnExpandedElement()
}
/**
@@ -278,7 +278,7 @@ deprecated class MacroInvocationExpr extends Expr {
MacroInvocation getInvocation() { result.getExpr() = this }
/** Gets the name of the invoked macro. */
- string getMacroName() { result = getInvocation().getMacroName() }
+ string getMacroName() { result = this.getInvocation().getMacroName() }
}
/**
@@ -298,7 +298,7 @@ deprecated class MacroInvocationStmt extends Stmt {
MacroInvocation getInvocation() { result.getStmt() = this }
/** Gets the name of the invoked macro. */
- string getMacroName() { result = getInvocation().getMacroName() }
+ string getMacroName() { result = this.getInvocation().getMacroName() }
}
/** Holds if `l` is the location of a macro. */
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/MemberFunction.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/MemberFunction.qll
index 63c1406d8a5..03b1704549f 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/MemberFunction.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/MemberFunction.qll
@@ -36,7 +36,9 @@ class MemberFunction extends Function {
* `this` parameter.
*/
override int getEffectiveNumberOfParameters() {
- if isStatic() then result = getNumberOfParameters() else result = getNumberOfParameters() + 1
+ if this.isStatic()
+ then result = this.getNumberOfParameters()
+ else result = this.getNumberOfParameters() + 1
}
/** Holds if this member is private. */
@@ -49,13 +51,13 @@ class MemberFunction extends Function {
predicate isPublic() { this.hasSpecifier("public") }
/** Holds if this declaration has the lvalue ref-qualifier */
- predicate isLValueRefQualified() { hasSpecifier("&") }
+ predicate isLValueRefQualified() { this.hasSpecifier("&") }
/** Holds if this declaration has the rvalue ref-qualifier */
- predicate isRValueRefQualified() { hasSpecifier("&&") }
+ predicate isRValueRefQualified() { this.hasSpecifier("&&") }
/** Holds if this declaration has a ref-qualifier */
- predicate isRefQualified() { isLValueRefQualified() or isRValueRefQualified() }
+ predicate isRefQualified() { this.isLValueRefQualified() or this.isRValueRefQualified() }
/** Holds if this function overrides that function. */
predicate overrides(MemberFunction that) {
@@ -73,10 +75,10 @@ class MemberFunction extends Function {
* class body.
*/
FunctionDeclarationEntry getClassBodyDeclarationEntry() {
- if strictcount(getADeclarationEntry()) = 1
- then result = getDefinition()
+ if strictcount(this.getADeclarationEntry()) = 1
+ then result = this.getDefinition()
else (
- result = getADeclarationEntry() and result != getDefinition()
+ result = this.getADeclarationEntry() and result != this.getDefinition()
)
}
@@ -198,7 +200,7 @@ class Constructor extends MemberFunction {
* compiler-generated action which initializes a base class or member
* variable.
*/
- ConstructorInit getAnInitializer() { result = getInitializer(_) }
+ ConstructorInit getAnInitializer() { result = this.getInitializer(_) }
/**
* Gets an entry in the constructor's initializer list, or a
@@ -220,8 +222,8 @@ class ImplicitConversionFunction extends MemberFunction {
functions(underlyingElement(this), _, 4)
or
// ConversionConstructor (deprecated)
- strictcount(Parameter p | p = getAParameter() and not p.hasInitializer()) = 1 and
- not hasSpecifier("explicit")
+ strictcount(Parameter p | p = this.getAParameter() and not p.hasInitializer()) = 1 and
+ not this.hasSpecifier("explicit")
}
/** Gets the type this `ImplicitConversionFunction` takes as input. */
@@ -248,8 +250,8 @@ class ImplicitConversionFunction extends MemberFunction {
*/
deprecated class ConversionConstructor extends Constructor, ImplicitConversionFunction {
ConversionConstructor() {
- strictcount(Parameter p | p = getAParameter() and not p.hasInitializer()) = 1 and
- not hasSpecifier("explicit")
+ strictcount(Parameter p | p = this.getAParameter() and not p.hasInitializer()) = 1 and
+ not this.hasSpecifier("explicit")
}
override string getAPrimaryQlClass() {
@@ -301,15 +303,15 @@ class CopyConstructor extends Constructor {
hasCopySignature(this) and
(
// The rest of the parameters all have default values
- forall(int i | i > 0 and exists(getParameter(i)) | getParameter(i).hasInitializer())
+ forall(int i | i > 0 and exists(this.getParameter(i)) | this.getParameter(i).hasInitializer())
or
// or this is a template class, in which case the default values have
// not been extracted even if they exist. In that case, we assume that
// there are default values present since that is the most common case
// in real-world code.
- getDeclaringType() instanceof TemplateClass
+ this.getDeclaringType() instanceof TemplateClass
) and
- not exists(getATemplateArgument())
+ not exists(this.getATemplateArgument())
}
override string getAPrimaryQlClass() { result = "CopyConstructor" }
@@ -325,8 +327,8 @@ class CopyConstructor extends Constructor {
// type-checked for each template instantiation; if an argument in an
// instantiation fails to type-check then the corresponding parameter has
// no default argument in the instantiation.
- getDeclaringType() instanceof TemplateClass and
- getNumberOfParameters() > 1
+ this.getDeclaringType() instanceof TemplateClass and
+ this.getNumberOfParameters() > 1
}
}
@@ -358,15 +360,15 @@ class MoveConstructor extends Constructor {
hasMoveSignature(this) and
(
// The rest of the parameters all have default values
- forall(int i | i > 0 and exists(getParameter(i)) | getParameter(i).hasInitializer())
+ forall(int i | i > 0 and exists(this.getParameter(i)) | this.getParameter(i).hasInitializer())
or
// or this is a template class, in which case the default values have
// not been extracted even if they exist. In that case, we assume that
// there are default values present since that is the most common case
// in real-world code.
- getDeclaringType() instanceof TemplateClass
+ this.getDeclaringType() instanceof TemplateClass
) and
- not exists(getATemplateArgument())
+ not exists(this.getATemplateArgument())
}
override string getAPrimaryQlClass() { result = "MoveConstructor" }
@@ -382,8 +384,8 @@ class MoveConstructor extends Constructor {
// type-checked for each template instantiation; if an argument in an
// instantiation fails to type-check then the corresponding parameter has
// no default argument in the instantiation.
- getDeclaringType() instanceof TemplateClass and
- getNumberOfParameters() > 1
+ this.getDeclaringType() instanceof TemplateClass and
+ this.getNumberOfParameters() > 1
}
}
@@ -426,7 +428,7 @@ class Destructor extends MemberFunction {
* Gets a compiler-generated action which destructs a base class or member
* variable.
*/
- DestructorDestruction getADestruction() { result = getDestruction(_) }
+ DestructorDestruction getADestruction() { result = this.getDestruction(_) }
/**
* Gets a compiler-generated action which destructs a base class or member
@@ -475,16 +477,16 @@ class ConversionOperator extends MemberFunction, ImplicitConversionFunction {
*/
class CopyAssignmentOperator extends Operator {
CopyAssignmentOperator() {
- hasName("operator=") and
+ this.hasName("operator=") and
(
hasCopySignature(this)
or
// Unlike CopyConstructor, this member allows a non-reference
// parameter.
- getParameter(0).getUnspecifiedType() = getDeclaringType()
+ this.getParameter(0).getUnspecifiedType() = this.getDeclaringType()
) and
not exists(this.getParameter(1)) and
- not exists(getATemplateArgument())
+ not exists(this.getATemplateArgument())
}
override string getAPrimaryQlClass() { result = "CopyAssignmentOperator" }
@@ -507,10 +509,10 @@ class CopyAssignmentOperator extends Operator {
*/
class MoveAssignmentOperator extends Operator {
MoveAssignmentOperator() {
- hasName("operator=") and
+ this.hasName("operator=") and
hasMoveSignature(this) and
not exists(this.getParameter(1)) and
- not exists(getATemplateArgument())
+ not exists(this.getATemplateArgument())
}
override string getAPrimaryQlClass() { result = "MoveAssignmentOperator" }
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Namespace.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Namespace.qll
index d46abc6b4db..47ebc9d35c5 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Namespace.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Namespace.qll
@@ -38,8 +38,8 @@ class Namespace extends NameQualifyingElement, @namespace {
* unless the namespace has exactly one declaration entry.
*/
override Location getLocation() {
- if strictcount(getADeclarationEntry()) = 1
- then result = getADeclarationEntry().getLocation()
+ if strictcount(this.getADeclarationEntry()) = 1
+ then result = this.getADeclarationEntry().getLocation()
else result instanceof UnknownDefaultLocation
}
@@ -50,7 +50,7 @@ class Namespace extends NameQualifyingElement, @namespace {
predicate hasName(string name) { name = this.getName() }
/** Holds if this namespace is anonymous. */
- predicate isAnonymous() { hasName("(unnamed namespace)") }
+ predicate isAnonymous() { this.hasName("(unnamed namespace)") }
/** Gets the name of the parent namespace, if it exists. */
private string getParentName() {
@@ -60,9 +60,9 @@ class Namespace extends NameQualifyingElement, @namespace {
/** Gets the qualified name of this namespace. For example: `a::b`. */
string getQualifiedName() {
- if exists(getParentName())
- then result = getParentNamespace().getQualifiedName() + "::" + getName()
- else result = getName()
+ if exists(this.getParentName())
+ then result = this.getParentNamespace().getQualifiedName() + "::" + this.getName()
+ else result = this.getName()
}
/** Gets the parent namespace, if any. */
@@ -99,7 +99,7 @@ class Namespace extends NameQualifyingElement, @namespace {
/** Gets a version of the `QualifiedName` that is more suitable for display purposes. */
string getFriendlyName() { result = this.getQualifiedName() }
- final override string toString() { result = getFriendlyName() }
+ final override string toString() { result = this.getFriendlyName() }
/** Gets a declaration of (part of) this namespace. */
NamespaceDeclarationEntry getADeclarationEntry() { result.getNamespace() = this }
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Parameter.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Parameter.qll
index b87bfe6a4c7..47b77b542c1 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Parameter.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Parameter.qll
@@ -40,12 +40,12 @@ class Parameter extends LocalScopeVariable, @parameter {
*/
override string getName() {
exists(VariableDeclarationEntry vde |
- vde = getANamedDeclarationEntry() and result = vde.getName()
+ vde = this.getANamedDeclarationEntry() and result = vde.getName()
|
- vde.isDefinition() or not getANamedDeclarationEntry().isDefinition()
+ vde.isDefinition() or not this.getANamedDeclarationEntry().isDefinition()
)
or
- not exists(getANamedDeclarationEntry()) and
+ not exists(this.getANamedDeclarationEntry()) and
result = "(unnamed parameter " + this.getIndex().toString() + ")"
}
@@ -58,8 +58,12 @@ class Parameter extends LocalScopeVariable, @parameter {
*/
string getTypedName() {
exists(string typeString, string nameString |
- (if exists(getType().getName()) then typeString = getType().getName() else typeString = "") and
- (if exists(getName()) then nameString = getName() else nameString = "") and
+ (
+ if exists(this.getType().getName())
+ then typeString = this.getType().getName()
+ else typeString = ""
+ ) and
+ (if exists(this.getName()) then nameString = this.getName() else nameString = "") and
(
if typeString != "" and nameString != ""
then result = typeString + " " + nameString
@@ -69,7 +73,7 @@ class Parameter extends LocalScopeVariable, @parameter {
}
private VariableDeclarationEntry getANamedDeclarationEntry() {
- result = getAnEffectiveDeclarationEntry() and result.getName() != ""
+ result = this.getAnEffectiveDeclarationEntry() and result.getName() != ""
}
/**
@@ -82,13 +86,13 @@ class Parameter extends LocalScopeVariable, @parameter {
* own).
*/
private VariableDeclarationEntry getAnEffectiveDeclarationEntry() {
- if getFunction().isConstructedFrom(_)
+ if this.getFunction().isConstructedFrom(_)
then
exists(Function prototypeInstantiation |
- prototypeInstantiation.getParameter(getIndex()) = result.getVariable() and
- getFunction().isConstructedFrom(prototypeInstantiation)
+ prototypeInstantiation.getParameter(this.getIndex()) = result.getVariable() and
+ this.getFunction().isConstructedFrom(prototypeInstantiation)
)
- else result = getADeclarationEntry()
+ else result = this.getADeclarationEntry()
}
/**
@@ -114,7 +118,7 @@ class Parameter extends LocalScopeVariable, @parameter {
* `getName()` is not "(unnamed parameter i)" (where `i` is the index
* of the parameter).
*/
- predicate isNamed() { exists(getANamedDeclarationEntry()) }
+ predicate isNamed() { exists(this.getANamedDeclarationEntry()) }
/**
* Gets the function to which this parameter belongs, if it is a function
@@ -157,9 +161,9 @@ class Parameter extends LocalScopeVariable, @parameter {
*/
override Location getLocation() {
exists(VariableDeclarationEntry vde |
- vde = getAnEffectiveDeclarationEntry() and result = vde.getLocation()
+ vde = this.getAnEffectiveDeclarationEntry() and result = vde.getLocation()
|
- vde.isDefinition() or not getAnEffectiveDeclarationEntry().isDefinition()
+ vde.isDefinition() or not this.getAnEffectiveDeclarationEntry().isDefinition()
)
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Preprocessor.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Preprocessor.qll
index 2389db07f2a..91b7aa1aab9 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Preprocessor.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Preprocessor.qll
@@ -29,8 +29,8 @@ class PreprocessorDirective extends Locatable, @preprocdirect {
PreprocessorBranch getAGuard() {
exists(PreprocessorEndif e, int line |
result.getEndIf() = e and
- e.getFile() = getFile() and
- result.getFile() = getFile() and
+ e.getFile() = this.getFile() and
+ result.getFile() = this.getFile() and
line = this.getLocation().getStartLine() and
result.getLocation().getStartLine() < line and
line < e.getLocation().getEndLine()
@@ -69,7 +69,9 @@ class PreprocessorBranchDirective extends PreprocessorDirective, TPreprocessorBr
* directives in different translation units, then there can be more than
* one result.
*/
- PreprocessorEndif getEndIf() { preprocpair(unresolveElement(getIf()), unresolveElement(result)) }
+ PreprocessorEndif getEndIf() {
+ preprocpair(unresolveElement(this.getIf()), unresolveElement(result))
+ }
/**
* Gets the next `#elif`, `#else` or `#endif` matching this branching
@@ -137,7 +139,7 @@ class PreprocessorBranch extends PreprocessorBranchDirective, @ppd_branch {
* which evaluated it, or was not taken by any translation unit which
* evaluated it.
*/
- predicate wasPredictable() { not (wasTaken() and wasNotTaken()) }
+ predicate wasPredictable() { not (this.wasTaken() and this.wasNotTaken()) }
}
/**
@@ -268,7 +270,7 @@ class PreprocessorUndef extends PreprocessorDirective, @ppd_undef {
/**
* Gets the name of the macro that is undefined.
*/
- string getName() { result = getHead() }
+ string getName() { result = this.getHead() }
}
/**
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Print.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Print.qll
index f8d30f55a88..64ae5b960d1 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Print.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Print.qll
@@ -105,8 +105,8 @@ private class DumpType extends Type {
// for a `SpecifiedType`, insert the qualifiers after
// `getDeclaratorSuffixBeforeQualifiers()`.
result =
- getTypeSpecifier() + getDeclaratorPrefix() + getDeclaratorSuffixBeforeQualifiers() +
- getDeclaratorSuffix()
+ this.getTypeSpecifier() + this.getDeclaratorPrefix() +
+ this.getDeclaratorSuffixBeforeQualifiers() + this.getDeclaratorSuffix()
}
/**
@@ -147,29 +147,35 @@ private class DumpType extends Type {
}
private class BuiltInDumpType extends DumpType, BuiltInType {
- override string getTypeSpecifier() { result = toString() }
+ override string getTypeSpecifier() { result = this.toString() }
}
private class IntegralDumpType extends BuiltInDumpType, IntegralType {
- override string getTypeSpecifier() { result = getCanonicalArithmeticType().toString() }
+ override string getTypeSpecifier() { result = this.getCanonicalArithmeticType().toString() }
}
private class DerivedDumpType extends DumpType, DerivedType {
- override string getTypeSpecifier() { result = getBaseType().(DumpType).getTypeSpecifier() }
+ override string getTypeSpecifier() { result = this.getBaseType().(DumpType).getTypeSpecifier() }
override string getDeclaratorSuffixBeforeQualifiers() {
- result = getBaseType().(DumpType).getDeclaratorSuffixBeforeQualifiers()
+ result = this.getBaseType().(DumpType).getDeclaratorSuffixBeforeQualifiers()
}
- override string getDeclaratorSuffix() { result = getBaseType().(DumpType).getDeclaratorSuffix() }
+ override string getDeclaratorSuffix() {
+ result = this.getBaseType().(DumpType).getDeclaratorSuffix()
+ }
}
private class DecltypeDumpType extends DumpType, Decltype {
- override string getTypeSpecifier() { result = getBaseType().(DumpType).getTypeSpecifier() }
+ override string getTypeSpecifier() { result = this.getBaseType().(DumpType).getTypeSpecifier() }
- override string getDeclaratorPrefix() { result = getBaseType().(DumpType).getDeclaratorPrefix() }
+ override string getDeclaratorPrefix() {
+ result = this.getBaseType().(DumpType).getDeclaratorPrefix()
+ }
- override string getDeclaratorSuffix() { result = getBaseType().(DumpType).getDeclaratorSuffix() }
+ override string getDeclaratorSuffix() {
+ result = this.getBaseType().(DumpType).getDeclaratorSuffix()
+ }
}
private class PointerIshDumpType extends DerivedDumpType {
@@ -180,10 +186,10 @@ private class PointerIshDumpType extends DerivedDumpType {
override string getDeclaratorPrefix() {
exists(string declarator |
- result = getBaseType().(DumpType).getDeclaratorPrefix() + declarator and
- if getBaseType().getUnspecifiedType() instanceof ArrayType
- then declarator = "(" + getDeclaratorToken() + ")"
- else declarator = getDeclaratorToken()
+ result = this.getBaseType().(DumpType).getDeclaratorPrefix() + declarator and
+ if this.getBaseType().getUnspecifiedType() instanceof ArrayType
+ then declarator = "(" + this.getDeclaratorToken() + ")"
+ else declarator = this.getDeclaratorToken()
)
}
@@ -206,13 +212,13 @@ private class RValueReferenceDumpType extends PointerIshDumpType, RValueReferenc
}
private class PointerToMemberDumpType extends DumpType, PointerToMemberType {
- override string getTypeSpecifier() { result = getBaseType().(DumpType).getTypeSpecifier() }
+ override string getTypeSpecifier() { result = this.getBaseType().(DumpType).getTypeSpecifier() }
override string getDeclaratorPrefix() {
exists(string declarator, string parenDeclarator, Type baseType |
- declarator = getClass().(DumpType).getTypeIdentityString() + "::*" and
- result = getBaseType().(DumpType).getDeclaratorPrefix() + " " + parenDeclarator and
- baseType = getBaseType().getUnspecifiedType() and
+ declarator = this.getClass().(DumpType).getTypeIdentityString() + "::*" and
+ result = this.getBaseType().(DumpType).getDeclaratorPrefix() + " " + parenDeclarator and
+ baseType = this.getBaseType().getUnspecifiedType() and
if baseType instanceof ArrayType or baseType instanceof RoutineType
then parenDeclarator = "(" + declarator
else parenDeclarator = declarator
@@ -221,38 +227,44 @@ private class PointerToMemberDumpType extends DumpType, PointerToMemberType {
override string getDeclaratorSuffixBeforeQualifiers() {
exists(Type baseType |
- baseType = getBaseType().getUnspecifiedType() and
+ baseType = this.getBaseType().getUnspecifiedType() and
if baseType instanceof ArrayType or baseType instanceof RoutineType
- then result = ")" + getBaseType().(DumpType).getDeclaratorSuffixBeforeQualifiers()
- else result = getBaseType().(DumpType).getDeclaratorSuffixBeforeQualifiers()
+ then result = ")" + this.getBaseType().(DumpType).getDeclaratorSuffixBeforeQualifiers()
+ else result = this.getBaseType().(DumpType).getDeclaratorSuffixBeforeQualifiers()
)
}
- override string getDeclaratorSuffix() { result = getBaseType().(DumpType).getDeclaratorSuffix() }
+ override string getDeclaratorSuffix() {
+ result = this.getBaseType().(DumpType).getDeclaratorSuffix()
+ }
}
private class ArrayDumpType extends DerivedDumpType, ArrayType {
- override string getDeclaratorPrefix() { result = getBaseType().(DumpType).getDeclaratorPrefix() }
+ override string getDeclaratorPrefix() {
+ result = this.getBaseType().(DumpType).getDeclaratorPrefix()
+ }
override string getDeclaratorSuffixBeforeQualifiers() {
- if exists(getArraySize())
+ if exists(this.getArraySize())
then
result =
- "[" + getArraySize().toString() + "]" +
- getBaseType().(DumpType).getDeclaratorSuffixBeforeQualifiers()
- else result = "[]" + getBaseType().(DumpType).getDeclaratorSuffixBeforeQualifiers()
+ "[" + this.getArraySize().toString() + "]" +
+ this.getBaseType().(DumpType).getDeclaratorSuffixBeforeQualifiers()
+ else result = "[]" + this.getBaseType().(DumpType).getDeclaratorSuffixBeforeQualifiers()
}
}
private class FunctionPointerIshDumpType extends DerivedDumpType, FunctionPointerIshType {
override string getDeclaratorSuffixBeforeQualifiers() {
- result = ")" + getBaseType().(DumpType).getDeclaratorSuffixBeforeQualifiers()
+ result = ")" + this.getBaseType().(DumpType).getDeclaratorSuffixBeforeQualifiers()
}
- override string getDeclaratorSuffix() { result = getBaseType().(DumpType).getDeclaratorSuffix() }
+ override string getDeclaratorSuffix() {
+ result = this.getBaseType().(DumpType).getDeclaratorSuffix()
+ }
override string getDeclaratorPrefix() {
- result = getBaseType().(DumpType).getDeclaratorPrefix() + "(" + getDeclaratorToken()
+ result = this.getBaseType().(DumpType).getDeclaratorPrefix() + "(" + this.getDeclaratorToken()
}
/**
@@ -274,10 +286,10 @@ private class BlockDumpType extends FunctionPointerIshDumpType, BlockType {
}
private class RoutineDumpType extends DumpType, RoutineType {
- override string getTypeSpecifier() { result = getReturnType().(DumpType).getTypeSpecifier() }
+ override string getTypeSpecifier() { result = this.getReturnType().(DumpType).getTypeSpecifier() }
override string getDeclaratorPrefix() {
- result = getReturnType().(DumpType).getDeclaratorPrefix()
+ result = this.getReturnType().(DumpType).getDeclaratorPrefix()
}
language[monotonicAggregates]
@@ -285,39 +297,41 @@ private class RoutineDumpType extends DumpType, RoutineType {
result =
"(" +
concat(int i |
- exists(getParameterType(i))
+ exists(this.getParameterType(i))
|
- getParameterTypeString(getParameterType(i)), ", " order by i
+ getParameterTypeString(this.getParameterType(i)), ", " order by i
) + ")"
}
override string getDeclaratorSuffix() {
result =
- getReturnType().(DumpType).getDeclaratorSuffixBeforeQualifiers() +
- getReturnType().(DumpType).getDeclaratorSuffix()
+ this.getReturnType().(DumpType).getDeclaratorSuffixBeforeQualifiers() +
+ this.getReturnType().(DumpType).getDeclaratorSuffix()
}
}
private class SpecifiedDumpType extends DerivedDumpType, SpecifiedType {
override string getDeclaratorPrefix() {
exists(string basePrefix |
- basePrefix = getBaseType().(DumpType).getDeclaratorPrefix() and
- if getBaseType().getUnspecifiedType() instanceof RoutineType
+ basePrefix = this.getBaseType().(DumpType).getDeclaratorPrefix() and
+ if this.getBaseType().getUnspecifiedType() instanceof RoutineType
then result = basePrefix
- else result = basePrefix + " " + getSpecifierString()
+ else result = basePrefix + " " + this.getSpecifierString()
)
}
override string getDeclaratorSuffixBeforeQualifiers() {
exists(string baseSuffix |
- baseSuffix = getBaseType().(DumpType).getDeclaratorSuffixBeforeQualifiers() and
- if getBaseType().getUnspecifiedType() instanceof RoutineType
- then result = baseSuffix + " " + getSpecifierString()
+ baseSuffix = this.getBaseType().(DumpType).getDeclaratorSuffixBeforeQualifiers() and
+ if this.getBaseType().getUnspecifiedType() instanceof RoutineType
+ then result = baseSuffix + " " + this.getSpecifierString()
else result = baseSuffix
)
}
- override string getDeclaratorSuffix() { result = getBaseType().(DumpType).getDeclaratorSuffix() }
+ override string getDeclaratorSuffix() {
+ result = this.getBaseType().(DumpType).getDeclaratorSuffix()
+ }
}
private class UserDumpType extends DumpType, DumpDeclaration, UserType {
@@ -330,18 +344,18 @@ private class UserDumpType extends DumpType, DumpDeclaration, UserType {
// "lambda [] type at line 12, col. 40"
// Use `min(getSimpleName())` to work around an extractor bug where a lambda can have different names
// from different compilation units.
- simpleName = "(" + min(getSimpleName()) + ")"
- else simpleName = getSimpleName()
+ simpleName = "(" + min(this.getSimpleName()) + ")"
+ else simpleName = this.getSimpleName()
) and
- result = getScopePrefix(this) + simpleName + getTemplateArgumentsString()
+ result = getScopePrefix(this) + simpleName + this.getTemplateArgumentsString()
)
}
- override string getTypeSpecifier() { result = getIdentityString() }
+ override string getTypeSpecifier() { result = this.getIdentityString() }
}
private class DumpProxyClass extends UserDumpType, ProxyClass {
- override string getIdentityString() { result = getName() }
+ override string getIdentityString() { result = this.getName() }
}
private class DumpVariable extends DumpDeclaration, Variable {
@@ -360,9 +374,9 @@ private class DumpVariable extends DumpDeclaration, Variable {
private class DumpFunction extends DumpDeclaration, Function {
override string getIdentityString() {
result =
- getType().(DumpType).getTypeSpecifier() + getType().(DumpType).getDeclaratorPrefix() + " " +
- getScopePrefix(this) + getName() + getTemplateArgumentsString() +
- getDeclaratorSuffixBeforeQualifiers() + getDeclaratorSuffix()
+ this.getType().(DumpType).getTypeSpecifier() + this.getType().(DumpType).getDeclaratorPrefix()
+ + " " + getScopePrefix(this) + this.getName() + this.getTemplateArgumentsString() +
+ this.getDeclaratorSuffixBeforeQualifiers() + this.getDeclaratorSuffix()
}
language[monotonicAggregates]
@@ -370,28 +384,29 @@ private class DumpFunction extends DumpDeclaration, Function {
result =
"(" +
concat(int i |
- exists(getParameter(i).getType())
+ exists(this.getParameter(i).getType())
|
- getParameterTypeString(getParameter(i).getType()), ", " order by i
- ) + ")" + getQualifierString()
+ getParameterTypeString(this.getParameter(i).getType()), ", " order by i
+ ) + ")" + this.getQualifierString()
}
private string getQualifierString() {
- if exists(getACVQualifier())
+ if exists(this.getACVQualifier())
then
- result = " " + strictconcat(string qualifier | qualifier = getACVQualifier() | qualifier, " ")
+ result =
+ " " + strictconcat(string qualifier | qualifier = this.getACVQualifier() | qualifier, " ")
else result = ""
}
private string getACVQualifier() {
- result = getASpecifier().getName() and
+ result = this.getASpecifier().getName() and
result = ["const", "volatile"]
}
private string getDeclaratorSuffix() {
result =
- getType().(DumpType).getDeclaratorSuffixBeforeQualifiers() +
- getType().(DumpType).getDeclaratorSuffix()
+ this.getType().(DumpType).getDeclaratorSuffixBeforeQualifiers() +
+ this.getType().(DumpType).getDeclaratorSuffix()
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Specifier.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Specifier.qll
index 4a425b690f4..fe2919c3ed6 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Specifier.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Specifier.qll
@@ -31,11 +31,7 @@ class Specifier extends Element, @specifier {
* A C/C++ function specifier: `inline`, `virtual`, or `explicit`.
*/
class FunctionSpecifier extends Specifier {
- FunctionSpecifier() {
- this.hasName("inline") or
- this.hasName("virtual") or
- this.hasName("explicit")
- }
+ FunctionSpecifier() { this.hasName(["inline", "virtual", "explicit"]) }
override string getAPrimaryQlClass() { result = "FunctionSpecifier" }
}
@@ -45,13 +41,7 @@ class FunctionSpecifier extends Specifier {
* or `mutable".
*/
class StorageClassSpecifier extends Specifier {
- StorageClassSpecifier() {
- this.hasName("auto") or
- this.hasName("register") or
- this.hasName("static") or
- this.hasName("extern") or
- this.hasName("mutable")
- }
+ StorageClassSpecifier() { this.hasName(["auto", "register", "static", "extern", "mutable"]) }
override string getAPrimaryQlClass() { result = "StorageClassSpecifier" }
}
@@ -60,11 +50,7 @@ class StorageClassSpecifier extends Specifier {
* A C++ access specifier: `public`, `protected`, or `private`.
*/
class AccessSpecifier extends Specifier {
- AccessSpecifier() {
- this.hasName("public") or
- this.hasName("protected") or
- this.hasName("private")
- }
+ AccessSpecifier() { this.hasName(["public", "protected", "private"]) }
/**
* Gets the visibility of a field with access specifier `this` if it is
@@ -140,7 +126,7 @@ class Attribute extends Element, @attribute {
AttributeArgument getArgument(int i) { result.getAttribute() = this and result.getIndex() = i }
/** Gets an argument of the attribute. */
- AttributeArgument getAnArgument() { result = getArgument(_) }
+ AttributeArgument getAnArgument() { result = this.getArgument(_) }
}
/**
@@ -166,7 +152,7 @@ class StdAttribute extends Attribute, @stdattribute {
* Holds if this attribute has the given namespace and name.
*/
predicate hasQualifiedName(string namespace, string name) {
- namespace = getNamespace() and hasName(name)
+ namespace = this.getNamespace() and this.hasName(name)
}
}
@@ -184,7 +170,7 @@ class Declspec extends Attribute, @declspec { }
*/
class MicrosoftAttribute extends Attribute, @msattribute {
AttributeArgument getNamedArgument(string name) {
- result = getAnArgument() and result.getName() = name
+ result = this.getAnArgument() and result.getName() = name
}
}
@@ -212,13 +198,13 @@ class AlignAs extends Attribute, @alignas {
* ```
*/
class FormatAttribute extends GnuAttribute {
- FormatAttribute() { getName() = "format" }
+ FormatAttribute() { this.getName() = "format" }
/**
* Gets the archetype of this format attribute, for example
* `"printf"`.
*/
- string getArchetype() { result = getArgument(0).getValueText() }
+ string getArchetype() { result = this.getArgument(0).getValueText() }
/**
* Gets the index in (1-based) format attribute notation associated
@@ -236,7 +222,7 @@ class FormatAttribute extends GnuAttribute {
* Gets the (0-based) index of the format string,
* according to this attribute.
*/
- int getFormatIndex() { result = getArgument(1).getValueInt() - firstArgumentNumber() }
+ int getFormatIndex() { result = this.getArgument(1).getValueInt() - this.firstArgumentNumber() }
/**
* Gets the (0-based) index of the first format argument (if any),
@@ -244,8 +230,8 @@ class FormatAttribute extends GnuAttribute {
*/
int getFirstFormatArgIndex() {
exists(int val |
- val = getArgument(2).getValueInt() and
- result = val - firstArgumentNumber() and
+ val = this.getArgument(2).getValueInt() and
+ result = val - this.firstArgumentNumber() and
not val = 0 // indicates a `vprintf` style format function with arguments not directly available.
)
}
@@ -277,7 +263,7 @@ class AttributeArgument extends Element, @attribute_arg {
/**
* Gets the value of this argument, if its value is integral.
*/
- int getValueInt() { result = getValueText().toInt() }
+ int getValueInt() { result = this.getValueText().toInt() }
/**
* Gets the value of this argument, if its value is a type.
@@ -304,11 +290,11 @@ class AttributeArgument extends Element, @attribute_arg {
then result = "empty argument"
else
exists(string prefix, string tail |
- (if exists(getName()) then prefix = getName() + "=" else prefix = "") and
+ (if exists(this.getName()) then prefix = this.getName() + "=" else prefix = "") and
(
if exists(@attribute_arg_type self | self = underlyingElement(this))
- then tail = getValueType().getName()
- else tail = getValueText()
+ then tail = this.getValueType().getName()
+ else tail = this.getValueText()
) and
result = prefix + tail
)
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Struct.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Struct.qll
index 50a208894b4..5465472374f 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Struct.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Struct.qll
@@ -41,7 +41,7 @@ class Struct extends Class {
* ```
*/
class LocalStruct extends Struct {
- LocalStruct() { isLocal() }
+ LocalStruct() { this.isLocal() }
override string getAPrimaryQlClass() { not this instanceof LocalUnion and result = "LocalStruct" }
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/TestFile.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/TestFile.qll
index b9e3fe3a614..a2e496ab019 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/TestFile.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/TestFile.qll
@@ -10,8 +10,8 @@ import semmle.code.cpp.File
*/
private class GoogleTestHeader extends File {
GoogleTestHeader() {
- getBaseName() = "gtest.h" and
- getParentContainer().getBaseName() = "gtest"
+ this.getBaseName() = "gtest.h" and
+ this.getParentContainer().getBaseName() = "gtest"
}
}
@@ -30,8 +30,8 @@ private class GoogleTest extends MacroInvocation {
*/
private class BoostTestFolder extends Folder {
BoostTestFolder() {
- getBaseName() = "test" and
- getParentContainer().getBaseName() = "boost"
+ this.getBaseName() = "test" and
+ this.getParentContainer().getBaseName() = "boost"
}
}
@@ -49,7 +49,7 @@ private class BoostTest extends MacroInvocation {
* The `cppunit` directory.
*/
private class CppUnitFolder extends Folder {
- CppUnitFolder() { getBaseName() = "cppunit" }
+ CppUnitFolder() { this.getBaseName() = "cppunit" }
}
/**
@@ -57,8 +57,8 @@ private class CppUnitFolder extends Folder {
*/
private class CppUnitClass extends Class {
CppUnitClass() {
- getFile().getParentContainer+() instanceof CppUnitFolder and
- getNamespace().getParentNamespace*().getName() = "CppUnit"
+ this.getFile().getParentContainer+() instanceof CppUnitFolder and
+ this.getNamespace().getParentNamespace*().getName() = "CppUnit"
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Type.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Type.qll
index bf3defd4f40..f8552144ea8 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Type.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Type.qll
@@ -81,7 +81,7 @@ class Type extends Locatable, @type {
* Holds if this type refers to type `t` (by default,
* a type always refers to itself).
*/
- predicate refersTo(Type t) { refersToDirectly*(t) }
+ predicate refersTo(Type t) { this.refersToDirectly*(t) }
/**
* Holds if this type refers to type `t` directly.
@@ -1080,11 +1080,11 @@ class DerivedType extends Type, @derivedtype {
override predicate refersToDirectly(Type t) { t = this.getBaseType() }
- override predicate involvesReference() { getBaseType().involvesReference() }
+ override predicate involvesReference() { this.getBaseType().involvesReference() }
- override predicate involvesTemplateParameter() { getBaseType().involvesTemplateParameter() }
+ override predicate involvesTemplateParameter() { this.getBaseType().involvesTemplateParameter() }
- override Type stripType() { result = getBaseType().stripType() }
+ override Type stripType() { result = this.getBaseType().stripType() }
/**
* Holds if this type has the `__autoreleasing` specifier or if it points to
@@ -1165,33 +1165,35 @@ class Decltype extends Type, @decltype {
*/
predicate parenthesesWouldChangeMeaning() { decltypes(underlyingElement(this), _, _, true) }
- override Type getUnderlyingType() { result = getBaseType().getUnderlyingType() }
+ override Type getUnderlyingType() { result = this.getBaseType().getUnderlyingType() }
- override Type stripTopLevelSpecifiers() { result = getBaseType().stripTopLevelSpecifiers() }
+ override Type stripTopLevelSpecifiers() { result = this.getBaseType().stripTopLevelSpecifiers() }
- override Type stripType() { result = getBaseType().stripType() }
+ override Type stripType() { result = this.getBaseType().stripType() }
- override Type resolveTypedefs() { result = getBaseType().resolveTypedefs() }
+ override Type resolveTypedefs() { result = this.getBaseType().resolveTypedefs() }
- override Location getLocation() { result = getExpr().getLocation() }
+ override Location getLocation() { result = this.getExpr().getLocation() }
override string toString() { result = "decltype(...)" }
override string getName() { none() }
- override int getSize() { result = getBaseType().getSize() }
+ override int getSize() { result = this.getBaseType().getSize() }
- override int getAlignment() { result = getBaseType().getAlignment() }
+ override int getAlignment() { result = this.getBaseType().getAlignment() }
- override int getPointerIndirectionLevel() { result = getBaseType().getPointerIndirectionLevel() }
+ override int getPointerIndirectionLevel() {
+ result = this.getBaseType().getPointerIndirectionLevel()
+ }
override string explain() {
result = "decltype resulting in {" + this.getBaseType().explain() + "}"
}
- override predicate involvesReference() { getBaseType().involvesReference() }
+ override predicate involvesReference() { this.getBaseType().involvesReference() }
- override predicate involvesTemplateParameter() { getBaseType().involvesTemplateParameter() }
+ override predicate involvesTemplateParameter() { this.getBaseType().involvesTemplateParameter() }
override predicate isDeeplyConst() { this.getBaseType().isDeeplyConst() }
@@ -1223,7 +1225,7 @@ class PointerType extends DerivedType {
override predicate isDeeplyConstBelow() { this.getBaseType().isDeeplyConst() }
override Type resolveTypedefs() {
- result.(PointerType).getBaseType() = getBaseType().resolveTypedefs()
+ result.(PointerType).getBaseType() = this.getBaseType().resolveTypedefs()
}
}
@@ -1240,7 +1242,9 @@ class ReferenceType extends DerivedType {
override string getAPrimaryQlClass() { result = "ReferenceType" }
- override int getPointerIndirectionLevel() { result = getBaseType().getPointerIndirectionLevel() }
+ override int getPointerIndirectionLevel() {
+ result = this.getBaseType().getPointerIndirectionLevel()
+ }
override string explain() { result = "reference to {" + this.getBaseType().explain() + "}" }
@@ -1251,7 +1255,7 @@ class ReferenceType extends DerivedType {
override predicate involvesReference() { any() }
override Type resolveTypedefs() {
- result.(ReferenceType).getBaseType() = getBaseType().resolveTypedefs()
+ result.(ReferenceType).getBaseType() = this.getBaseType().resolveTypedefs()
}
}
@@ -1330,11 +1334,11 @@ class SpecifiedType extends DerivedType {
}
override Type resolveTypedefs() {
- result.(SpecifiedType).getBaseType() = getBaseType().resolveTypedefs() and
- result.getASpecifier() = getASpecifier()
+ result.(SpecifiedType).getBaseType() = this.getBaseType().resolveTypedefs() and
+ result.getASpecifier() = this.getASpecifier()
}
- override Type stripTopLevelSpecifiers() { result = getBaseType().stripTopLevelSpecifiers() }
+ override Type stripTopLevelSpecifiers() { result = this.getBaseType().stripTopLevelSpecifiers() }
}
/**
@@ -1433,7 +1437,8 @@ class GNUVectorType extends DerivedType {
override int getAlignment() { arraysizes(underlyingElement(this), _, _, result) }
override string explain() {
- result = "GNU " + getNumElements() + " element vector of {" + this.getBaseType().explain() + "}"
+ result =
+ "GNU " + this.getNumElements() + " element vector of {" + this.getBaseType().explain() + "}"
}
override predicate isDeeplyConstBelow() { this.getBaseType().isDeeplyConst() }
@@ -1468,7 +1473,9 @@ class FunctionReferenceType extends FunctionPointerIshType {
override string getAPrimaryQlClass() { result = "FunctionReferenceType" }
- override int getPointerIndirectionLevel() { result = getBaseType().getPointerIndirectionLevel() }
+ override int getPointerIndirectionLevel() {
+ result = this.getBaseType().getPointerIndirectionLevel()
+ }
override string explain() {
result = "reference to {" + this.getBaseType().(RoutineType).explain() + "}"
@@ -1535,8 +1542,8 @@ class FunctionPointerIshType extends DerivedType {
int getNumberOfParameters() { result = count(int i | exists(this.getParameterType(i))) }
override predicate involvesTemplateParameter() {
- getReturnType().involvesTemplateParameter() or
- getAParameterType().involvesTemplateParameter()
+ this.getReturnType().involvesTemplateParameter() or
+ this.getAParameterType().involvesTemplateParameter()
}
override predicate isDeeplyConstBelow() { this.getBaseType().isDeeplyConst() }
@@ -1581,7 +1588,7 @@ class PointerToMemberType extends Type, @ptrtomember {
this.getBaseType().explain() + "}"
}
- override predicate involvesTemplateParameter() { getBaseType().involvesTemplateParameter() }
+ override predicate involvesTemplateParameter() { this.getBaseType().involvesTemplateParameter() }
override predicate isDeeplyConstBelow() { this.getBaseType().isDeeplyConst() }
}
@@ -1650,7 +1657,6 @@ class RoutineType extends Type, @routinetype {
i = 0 and result = "" and not exists(this.getAParameterType())
or
(
- exists(this.getParameterType(i)) and
if i < max(int j | exists(this.getParameterType(j)))
then
// Not the last one
@@ -1671,8 +1677,8 @@ class RoutineType extends Type, @routinetype {
override predicate isDeeplyConstBelow() { none() } // Current limitation: no such thing as a const routine type
override predicate involvesTemplateParameter() {
- getReturnType().involvesTemplateParameter() or
- getAParameterType().involvesTemplateParameter()
+ this.getReturnType().involvesTemplateParameter() or
+ this.getAParameterType().involvesTemplateParameter()
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/TypedefType.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/TypedefType.qll
index aaf452ce4bb..51bcf6f6127 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/TypedefType.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/TypedefType.qll
@@ -25,7 +25,7 @@ class TypedefType extends UserType {
override Type getUnderlyingType() { result = this.getBaseType().getUnderlyingType() }
- override Type stripTopLevelSpecifiers() { result = getBaseType().stripTopLevelSpecifiers() }
+ override Type stripTopLevelSpecifiers() { result = this.getBaseType().stripTopLevelSpecifiers() }
override int getSize() { result = this.getBaseType().getSize() }
@@ -43,11 +43,11 @@ class TypedefType extends UserType {
result = this.getBaseType().getASpecifier()
}
- override predicate involvesReference() { getBaseType().involvesReference() }
+ override predicate involvesReference() { this.getBaseType().involvesReference() }
- override Type resolveTypedefs() { result = getBaseType().resolveTypedefs() }
+ override Type resolveTypedefs() { result = this.getBaseType().resolveTypedefs() }
- override Type stripType() { result = getBaseType().stripType() }
+ override Type stripType() { result = this.getBaseType().stripType() }
}
/**
@@ -90,7 +90,7 @@ class UsingAliasTypedefType extends TypedefType {
* ```
*/
class LocalTypedefType extends TypedefType {
- LocalTypedefType() { isLocal() }
+ LocalTypedefType() { this.isLocal() }
override string getAPrimaryQlClass() { result = "LocalTypedefType" }
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Union.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Union.qll
index 6dcb2f0796c..2f1b5b07b25 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Union.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Union.qll
@@ -37,7 +37,7 @@ class Union extends Struct {
* ```
*/
class LocalUnion extends Union {
- LocalUnion() { isLocal() }
+ LocalUnion() { this.isLocal() }
override string getAPrimaryQlClass() { result = "LocalUnion" }
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/UserType.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/UserType.qll
index 2ab0603f06c..13697722190 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/UserType.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/UserType.qll
@@ -30,19 +30,19 @@ class UserType extends Type, Declaration, NameQualifyingElement, AccessHolder, @
* Gets the simple name of this type, without any template parameters. For example
* if the name of the type is `"myType"`, the simple name is just `"myType"`.
*/
- string getSimpleName() { result = getName().regexpReplaceAll("<.*", "") }
+ string getSimpleName() { result = this.getName().regexpReplaceAll("<.*", "") }
override predicate hasName(string name) { usertypes(underlyingElement(this), name, _) }
/** Holds if this type is anonymous. */
- predicate isAnonymous() { getName().matches("(unnamed%") }
+ predicate isAnonymous() { this.getName().matches("(unnamed%") }
override predicate hasSpecifier(string s) { Type.super.hasSpecifier(s) }
override Specifier getASpecifier() { result = Type.super.getASpecifier() }
override Location getLocation() {
- if hasDefinition()
+ if this.hasDefinition()
then result = this.getDefinitionLocation()
else result = this.getADeclarationLocation()
}
@@ -53,16 +53,16 @@ class UserType extends Type, Declaration, NameQualifyingElement, AccessHolder, @
else exists(Class t | this.(Class).isConstructedFrom(t) and result = t.getADeclarationEntry())
}
- override Location getADeclarationLocation() { result = getADeclarationEntry().getLocation() }
+ override Location getADeclarationLocation() { result = this.getADeclarationEntry().getLocation() }
override TypeDeclarationEntry getDefinition() {
- result = getADeclarationEntry() and
+ result = this.getADeclarationEntry() and
result.isDefinition()
}
override Location getDefinitionLocation() {
- if exists(getDefinition())
- then result = getDefinition().getLocation()
+ if exists(this.getDefinition())
+ then result = this.getDefinition().getLocation()
else
exists(Class t |
this.(Class).isConstructedFrom(t) and result = t.getDefinition().getLocation()
@@ -80,7 +80,7 @@ class UserType extends Type, Declaration, NameQualifyingElement, AccessHolder, @
* Holds if this is a local type (that is, a type that has a directly-enclosing
* function).
*/
- predicate isLocal() { exists(getEnclosingFunction()) }
+ predicate isLocal() { exists(this.getEnclosingFunction()) }
/*
* Dummy implementations of inherited methods. This class must not be
@@ -107,9 +107,9 @@ class UserType extends Type, Declaration, NameQualifyingElement, AccessHolder, @
* ```
*/
class TypeDeclarationEntry extends DeclarationEntry, @type_decl {
- override UserType getDeclaration() { result = getType() }
+ override UserType getDeclaration() { result = this.getType() }
- override string getName() { result = getType().getName() }
+ override string getName() { result = this.getType().getName() }
override string getAPrimaryQlClass() { result = "TypeDeclarationEntry" }
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Variable.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Variable.qll
index 12e25f33afe..b0c9bac7f66 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Variable.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/Variable.qll
@@ -104,17 +104,17 @@ class Variable extends Declaration, @variable {
override VariableDeclarationEntry getADeclarationEntry() { result.getDeclaration() = this }
- override Location getADeclarationLocation() { result = getADeclarationEntry().getLocation() }
+ override Location getADeclarationLocation() { result = this.getADeclarationEntry().getLocation() }
override VariableDeclarationEntry getDefinition() {
- result = getADeclarationEntry() and
+ result = this.getADeclarationEntry() and
result.isDefinition()
}
- override Location getDefinitionLocation() { result = getDefinition().getLocation() }
+ override Location getDefinitionLocation() { result = this.getDefinition().getLocation() }
override Location getLocation() {
- if exists(getDefinition())
+ if exists(this.getDefinition())
then result = this.getDefinitionLocation()
else result = this.getADeclarationLocation()
}
@@ -199,7 +199,7 @@ class Variable extends Declaration, @variable {
* ```
*/
class VariableDeclarationEntry extends DeclarationEntry, @var_decl {
- override Variable getDeclaration() { result = getVariable() }
+ override Variable getDeclaration() { result = this.getVariable() }
override string getAPrimaryQlClass() { result = "VariableDeclarationEntry" }
@@ -276,32 +276,33 @@ class ParameterDeclarationEntry extends VariableDeclarationEntry {
int getIndex() { param_decl_bind(underlyingElement(this), result, _) }
private string getAnonymousParameterDescription() {
- not exists(getName()) and
+ not exists(this.getName()) and
exists(string idx |
idx =
- ((getIndex() + 1).toString() + "th")
+ ((this.getIndex() + 1).toString() + "th")
.replaceAll("1th", "1st")
.replaceAll("2th", "2nd")
.replaceAll("3th", "3rd")
.replaceAll("11st", "11th")
.replaceAll("12nd", "12th")
.replaceAll("13rd", "13th") and
- if exists(getCanonicalName())
- then result = "declaration of " + getCanonicalName() + " as anonymous " + idx + " parameter"
+ if exists(this.getCanonicalName())
+ then
+ result = "declaration of " + this.getCanonicalName() + " as anonymous " + idx + " parameter"
else result = "declaration of " + idx + " parameter"
)
}
override string toString() {
- isDefinition() and
- result = "definition of " + getName()
+ this.isDefinition() and
+ result = "definition of " + this.getName()
or
- not isDefinition() and
- if getName() = getCanonicalName()
- then result = "declaration of " + getName()
- else result = "declaration of " + getCanonicalName() + " as " + getName()
+ not this.isDefinition() and
+ if this.getName() = this.getCanonicalName()
+ then result = "declaration of " + this.getName()
+ else result = "declaration of " + this.getCanonicalName() + " as " + this.getName()
or
- result = getAnonymousParameterDescription()
+ result = this.getAnonymousParameterDescription()
}
/**
@@ -311,8 +312,12 @@ class ParameterDeclarationEntry extends VariableDeclarationEntry {
*/
string getTypedName() {
exists(string typeString, string nameString |
- (if exists(getType().getName()) then typeString = getType().getName() else typeString = "") and
- (if exists(getName()) then nameString = getName() else nameString = "") and
+ (
+ if exists(this.getType().getName())
+ then typeString = this.getType().getName()
+ else typeString = ""
+ ) and
+ (if exists(this.getName()) then nameString = this.getName() else nameString = "") and
if typeString != "" and nameString != ""
then result = typeString + " " + nameString
else result = typeString + nameString
@@ -540,7 +545,7 @@ class MemberVariable extends Variable, @membervariable {
}
/** Holds if this member is mutable. */
- predicate isMutable() { getADeclarationEntry().hasSpecifier("mutable") }
+ predicate isMutable() { this.getADeclarationEntry().hasSpecifier("mutable") }
private Type getAType() { membervariables(underlyingElement(this), unresolveElement(result), _) }
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/XML.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/XML.qll
index 4c762f4bf65..76f3b3cb022 100755
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/XML.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/XML.qll
@@ -108,7 +108,7 @@ class XMLParent extends @xmlparent {
}
/** Gets the text value contained in this XML parent. */
- string getTextValue() { result = allCharactersString() }
+ string getTextValue() { result = this.allCharactersString() }
/** Gets a printable representation of this XML parent. */
string toString() { result = this.getName() }
@@ -119,7 +119,7 @@ class XMLFile extends XMLParent, File {
XMLFile() { xmlEncoding(this, _) }
/** Gets a printable representation of this XML file. */
- override string toString() { result = getName() }
+ override string toString() { result = this.getName() }
/** Gets the name of this XML file. */
override string getName() { result = File.super.getAbsolutePath() }
@@ -129,14 +129,14 @@ class XMLFile extends XMLParent, File {
*
* Gets the path of this XML file.
*/
- deprecated string getPath() { result = getAbsolutePath() }
+ deprecated string getPath() { result = this.getAbsolutePath() }
/**
* DEPRECATED: Use `getParentContainer().getAbsolutePath()` instead.
*
* Gets the path of the folder that contains this XML file.
*/
- deprecated string getFolder() { result = getParentContainer().getAbsolutePath() }
+ deprecated string getFolder() { result = this.getParentContainer().getAbsolutePath() }
/** Gets the encoding of this XML file. */
string getEncoding() { xmlEncoding(this, result) }
@@ -200,7 +200,7 @@ class XMLDTD extends XMLLocatable, @xmldtd {
*/
class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
/** Holds if this XML element has the given `name`. */
- predicate hasName(string name) { name = getName() }
+ predicate hasName(string name) { name = this.getName() }
/** Gets the name of this XML element. */
override string getName() { xmlElements(this, result, _, _, _) }
@@ -239,7 +239,7 @@ class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
string getAttributeValue(string name) { result = this.getAttribute(name).getValue() }
/** Gets a printable representation of this XML element. */
- override string toString() { result = getName() }
+ override string toString() { result = this.getName() }
}
/**
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/CommonType.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/CommonType.qll
index 5d6c64630a6..26e60538ec6 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/CommonType.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/CommonType.qll
@@ -375,8 +375,8 @@ class Wchar_t extends Type {
class MicrosoftInt8Type extends IntegralType {
MicrosoftInt8Type() {
this instanceof CharType and
- not isExplicitlyUnsigned() and
- not isExplicitlySigned()
+ not this.isExplicitlyUnsigned() and
+ not this.isExplicitlySigned()
}
}
@@ -391,8 +391,8 @@ class MicrosoftInt8Type extends IntegralType {
class MicrosoftInt16Type extends IntegralType {
MicrosoftInt16Type() {
this instanceof ShortType and
- not isExplicitlyUnsigned() and
- not isExplicitlySigned()
+ not this.isExplicitlyUnsigned() and
+ not this.isExplicitlySigned()
}
}
@@ -407,8 +407,8 @@ class MicrosoftInt16Type extends IntegralType {
class MicrosoftInt32Type extends IntegralType {
MicrosoftInt32Type() {
this instanceof IntType and
- not isExplicitlyUnsigned() and
- not isExplicitlySigned()
+ not this.isExplicitlyUnsigned() and
+ not this.isExplicitlySigned()
}
}
@@ -423,8 +423,8 @@ class MicrosoftInt32Type extends IntegralType {
class MicrosoftInt64Type extends IntegralType {
MicrosoftInt64Type() {
this instanceof LongLongType and
- not isExplicitlyUnsigned() and
- not isExplicitlySigned()
+ not this.isExplicitlyUnsigned() and
+ not this.isExplicitlySigned()
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/Dependency.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/Dependency.qll
index 1b885fb8f5f..ec95b29177b 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/Dependency.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/Dependency.qll
@@ -33,7 +33,7 @@ DependencyOptions getDependencyOptions() { any() }
class DependsSource extends Element {
DependsSource() {
// not inside a template instantiation
- not exists(Element other | isFromTemplateInstantiation(other)) or
+ not exists(Element other | this.isFromTemplateInstantiation(other)) or
// allow DeclarationEntrys of template specializations
this.(DeclarationEntry).getDeclaration().(Function).isConstructedFrom(_) or
this.(DeclarationEntry).getDeclaration().(Class).isConstructedFrom(_)
@@ -275,7 +275,7 @@ private predicate dependsOnDeclarationEntry(Element src, DeclarationEntry dest)
dependsOnTransitive(src, mid) and
not mid instanceof Type and
not mid instanceof EnumConstant and
- getDeclarationEntries(mid, dest.(DeclarationEntry)) and
+ getDeclarationEntries(mid, dest) and
not dest instanceof TypeDeclarationEntry
)
or
@@ -283,9 +283,9 @@ private predicate dependsOnDeclarationEntry(Element src, DeclarationEntry dest)
// dependency from a Type / Variable / Function use -> any (visible) definition
dependsOnTransitive(src, mid) and
not mid instanceof EnumConstant and
- getDeclarationEntries(mid, dest.(DeclarationEntry)) and
+ getDeclarationEntries(mid, dest) and
// must be definition
- dest.(DeclarationEntry).isDefinition()
+ dest.isDefinition()
)
}
@@ -307,7 +307,7 @@ private predicate dependsOnFull(DependsSource src, Symbol dest, int category) {
// dependency from a Variable / Function use -> non-visible definition (link time)
dependsOnTransitive(src, mid) and
not mid instanceof EnumConstant and
- getDeclarationEntries(mid, dest.(DeclarationEntry)) and
+ getDeclarationEntries(mid, dest) and
not dest instanceof TypeDeclarationEntry and
// must be definition
dest.(DeclarationEntry).isDefinition() and
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/Exclusions.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/Exclusions.qll
index a0dfea20046..ad60bb3288a 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/Exclusions.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/Exclusions.qll
@@ -81,8 +81,8 @@ predicate functionContainsPreprocCode(Function f) {
}
/**
- * Holds if `e` is completely or partially from a macro definition, as opposed
- * to being passed in as an argument.
+ * Holds if `e` is completely or partially from a macro invocation `mi`, as
+ * opposed to being passed in as an argument.
*
* In the following example, the call to `f` is from a macro definition,
* while `y`, `+`, `1`, and `;` are not. This assumes that no identifier apart
@@ -93,8 +93,8 @@ predicate functionContainsPreprocCode(Function f) {
* M(y + 1);
* ```
*/
-predicate isFromMacroDefinition(Element e) {
- exists(MacroInvocation mi, Location eLocation, Location miLocation |
+private predicate isFromMacroInvocation(Element e, MacroInvocation mi) {
+ exists(Location eLocation, Location miLocation |
mi.getAnExpandedElement() = e and
eLocation = e.getLocation() and
miLocation = mi.getLocation() and
@@ -109,3 +109,36 @@ predicate isFromMacroDefinition(Element e) {
eLocation.getEndColumn() >= miLocation.getEndColumn()
)
}
+
+/**
+ * Holds if `e` is completely or partially from a macro definition, as opposed
+ * to being passed in as an argument.
+ *
+ * In the following example, the call to `f` is from a macro definition,
+ * while `y`, `+`, `1`, and `;` are not. This assumes that no identifier apart
+ * from `M` refers to a macro.
+ * ```
+ * #define M(x) f(x)
+ * ...
+ * M(y + 1);
+ * ```
+ */
+predicate isFromMacroDefinition(Element e) { isFromMacroInvocation(e, _) }
+
+/**
+ * Holds if `e` is completely or partially from a _system macro_ definition, as
+ * opposed to being passed in as an argument. A system macro is a macro whose
+ * definition is outside the source directory of the database.
+ *
+ * If the system macro is invoked through a non-system macro, then this
+ * predicate does not hold.
+ *
+ * See also `isFromMacroDefinition`.
+ */
+predicate isFromSystemMacroDefinition(Element e) {
+ exists(MacroInvocation mi |
+ isFromMacroInvocation(e, mi) and
+ // Has no relative path in the database, meaning it's a system file.
+ not exists(mi.getMacro().getFile().getRelativePath())
+ )
+}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/NullTermination.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/NullTermination.qll
index 2f811ab83a0..71effe7906f 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/NullTermination.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/NullTermination.qll
@@ -3,17 +3,33 @@ private import semmle.code.cpp.models.interfaces.ArrayFunction
private import semmle.code.cpp.models.implementations.Strcat
import semmle.code.cpp.dataflow.DataFlow
-private predicate mayAddNullTerminatorHelper(Expr e, VariableAccess va, Expr e0) {
- exists(StackVariable v0, Expr val |
- exprDefinition(v0, e, val) and
- val.getAChild*() = va and
- mayAddNullTerminator(e0, v0.getAnAccess())
+/**
+ * Holds if the expression `e` assigns something including `va` to a
+ * stack variable `v0`.
+ */
+private predicate mayAddNullTerminatorHelper(Expr e, VariableAccess va, StackVariable v0) {
+ exists(Expr val |
+ exprDefinition(v0, e, val) and // `e` is `v0 := val`
+ val.getAChild*() = va
+ )
+}
+
+bindingset[n1, n2]
+private predicate controlFlowNodeSuccessorTransitive(ControlFlowNode n1, ControlFlowNode n2) {
+ exists(BasicBlock bb1, int pos1, BasicBlock bb2, int pos2 |
+ pragma[only_bind_into](bb1).getNode(pos1) = n1 and
+ pragma[only_bind_into](bb2).getNode(pos2) = n2 and
+ (
+ bb1 = bb2 and pos1 < pos2
+ or
+ bb1.getASuccessor+() = bb2
+ )
)
}
/**
- * Holds if the expression `e` may add a null terminator to the string in
- * variable `v`.
+ * Holds if the expression `e` may add a null terminator to the string
+ * accessed by `va`.
*/
predicate mayAddNullTerminator(Expr e, VariableAccess va) {
// Assignment: dereferencing or array access
@@ -30,14 +46,10 @@ predicate mayAddNullTerminator(Expr e, VariableAccess va) {
)
or
// Assignment to another stack variable
- exists(Expr e0, BasicBlock bb, int pos, BasicBlock bb0, int pos0 |
- mayAddNullTerminatorHelper(e, va, e0) and
- bb.getNode(pos) = e and
- bb0.getNode(pos0) = e0
- |
- bb = bb0 and pos < pos0
- or
- bb.getASuccessor+() = bb0
+ exists(StackVariable v0, Expr e0 |
+ mayAddNullTerminatorHelper(e, va, v0) and
+ mayAddNullTerminator(pragma[only_bind_into](e0), pragma[only_bind_into](v0.getAnAccess())) and
+ controlFlowNodeSuccessorTransitive(e, e0)
)
or
// Assignment to non-stack variable
@@ -119,14 +131,9 @@ predicate variableMustBeNullTerminated(VariableAccess va) {
variableMustBeNullTerminated(use) and
// Simplified: check that `p` may not be null terminated on *any*
// path to `use` (including the one found via `parameterUsePair`)
- not exists(Expr e, BasicBlock bb1, int pos1, BasicBlock bb2, int pos2 |
- mayAddNullTerminator(e, p.getAnAccess()) and
- bb1.getNode(pos1) = e and
- bb2.getNode(pos2) = use
- |
- bb1 = bb2 and pos1 < pos2
- or
- bb1.getASuccessor+() = bb2
+ not exists(Expr e |
+ mayAddNullTerminator(pragma[only_bind_into](e), p.getAnAccess()) and
+ controlFlowNodeSuccessorTransitive(e, use)
)
)
)
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/Printf.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/Printf.qll
index 46ca9ccf009..54de9df553b 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/Printf.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/Printf.qll
@@ -6,9 +6,11 @@ import semmle.code.cpp.Type
import semmle.code.cpp.commons.CommonType
import semmle.code.cpp.commons.StringAnalysis
import semmle.code.cpp.models.interfaces.FormattingFunction
+private import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
+private import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils
class PrintfFormatAttribute extends FormatAttribute {
- PrintfFormatAttribute() { getArchetype() = ["printf", "__printf__"] }
+ PrintfFormatAttribute() { this.getArchetype() = ["printf", "__printf__"] }
}
/**
@@ -20,13 +22,13 @@ class AttributeFormattingFunction extends FormattingFunction {
AttributeFormattingFunction() {
exists(PrintfFormatAttribute printf_attrib |
- printf_attrib = getAnAttribute() and
+ printf_attrib = this.getAnAttribute() and
exists(printf_attrib.getFirstFormatArgIndex()) // exclude `vprintf` style format functions
)
}
override int getFormatParameterIndex() {
- forex(PrintfFormatAttribute printf_attrib | printf_attrib = getAnAttribute() |
+ forex(PrintfFormatAttribute printf_attrib | printf_attrib = this.getAnAttribute() |
result = printf_attrib.getFormatIndex()
)
}
@@ -132,7 +134,7 @@ deprecated predicate variadicFormatter(Function f, int formatParamIndex) {
class UserDefinedFormattingFunction extends FormattingFunction {
override string getAPrimaryQlClass() { result = "UserDefinedFormattingFunction" }
- UserDefinedFormattingFunction() { isVarargs() and callsVariadicFormatter(this, _, _, _) }
+ UserDefinedFormattingFunction() { this.isVarargs() and callsVariadicFormatter(this, _, _, _) }
override int getFormatParameterIndex() { callsVariadicFormatter(this, _, result, _) }
@@ -175,9 +177,7 @@ class FormattingFunctionCall extends Expr {
/**
* Gets the index at which the format string occurs in the argument list.
*/
- int getFormatParameterIndex() {
- result = this.getTarget().(FormattingFunction).getFormatParameterIndex()
- }
+ int getFormatParameterIndex() { result = this.getTarget().getFormatParameterIndex() }
/**
* Gets the format expression used in this call.
@@ -191,7 +191,7 @@ class FormattingFunctionCall extends Expr {
exists(int i |
result = this.getArgument(i) and
n >= 0 and
- n = i - getTarget().(FormattingFunction).getFirstFormatArgumentIndex()
+ n = i - this.getTarget().getFirstFormatArgumentIndex()
)
}
@@ -251,7 +251,7 @@ class FormattingFunctionCall extends Expr {
int getNumFormatArgument() {
result = count(this.getFormatArgument(_)) and
// format arguments must be known
- exists(getTarget().(FormattingFunction).getFirstFormatArgumentIndex())
+ exists(this.getTarget().getFirstFormatArgumentIndex())
}
/**
@@ -270,6 +270,18 @@ class FormattingFunctionCall extends Expr {
}
}
+/**
+ * Gets the number of digits required to represent the integer represented by `f`.
+ *
+ * `f` is assumed to be nonnegative.
+ */
+bindingset[f]
+private int lengthInBase10(float f) {
+ f = 0 and result = 1
+ or
+ result = f.log10().floor() + 1
+}
+
/**
* A class to represent format strings that occur as arguments to invocations of formatting functions.
*/
@@ -289,33 +301,27 @@ class FormatLiteral extends Literal {
* a `char *` (either way, `%S` will have the opposite meaning).
* DEPRECATED: Use getDefaultCharType() instead.
*/
- deprecated predicate isWideCharDefault() {
- getUse().getTarget().(FormattingFunction).isWideCharDefault()
- }
+ deprecated predicate isWideCharDefault() { this.getUse().getTarget().isWideCharDefault() }
/**
* Gets the default character type expected for `%s` by this format literal. Typically
* `char` or `wchar_t`.
*/
- Type getDefaultCharType() {
- result = getUse().getTarget().(FormattingFunction).getDefaultCharType()
- }
+ Type getDefaultCharType() { result = this.getUse().getTarget().getDefaultCharType() }
/**
* Gets the non-default character type expected for `%S` by this format literal. Typically
* `wchar_t` or `char`. On some snapshots there may be multiple results where we can't tell
* which is correct for a particular function.
*/
- Type getNonDefaultCharType() {
- result = getUse().getTarget().(FormattingFunction).getNonDefaultCharType()
- }
+ Type getNonDefaultCharType() { result = this.getUse().getTarget().getNonDefaultCharType() }
/**
* Gets the wide character type for this format literal. This is usually `wchar_t`. On some
* snapshots there may be multiple results where we can't tell which is correct for a
* particular function.
*/
- Type getWideCharType() { result = getUse().getTarget().(FormattingFunction).getWideCharType() }
+ Type getWideCharType() { result = this.getUse().getTarget().getWideCharType() }
/**
* Holds if this `FormatLiteral` is in a context that supports
@@ -353,7 +359,7 @@ class FormatLiteral extends Literal {
}
private string getFlagRegexp() {
- if isMicrosoft() then result = "[-+ #0']*" else result = "[-+ #0'I]*"
+ if this.isMicrosoft() then result = "[-+ #0']*" else result = "[-+ #0'I]*"
}
private string getFieldWidthRegexp() { result = "(?:[1-9][0-9]*|\\*|\\*[0-9]+\\$)?" }
@@ -361,13 +367,13 @@ class FormatLiteral extends Literal {
private string getPrecRegexp() { result = "(?:\\.(?:[0-9]*|\\*|\\*[0-9]+\\$))?" }
private string getLengthRegexp() {
- if isMicrosoft()
+ if this.isMicrosoft()
then result = "(?:hh?|ll?|L|q|j|z|t|w|I32|I64|I)?"
else result = "(?:hh?|ll?|L|q|j|z|Z|t)?"
}
private string getConvCharRegexp() {
- if isMicrosoft()
+ if this.isMicrosoft()
then result = "[aAcCdeEfFgGimnopsSuxXZ@]"
else result = "[aAcCdeEfFgGimnopsSuxX@]"
}
@@ -747,16 +753,16 @@ class FormatLiteral extends Literal {
* Gets the argument type required by the nth conversion specifier.
*/
Type getConversionType(int n) {
- result = getConversionType1(n) or
- result = getConversionType1b(n) or
- result = getConversionType2(n) or
- result = getConversionType3(n) or
- result = getConversionType4(n) or
- result = getConversionType6(n) or
- result = getConversionType7(n) or
- result = getConversionType8(n) or
- result = getConversionType9(n) or
- result = getConversionType10(n)
+ result = this.getConversionType1(n) or
+ result = this.getConversionType1b(n) or
+ result = this.getConversionType2(n) or
+ result = this.getConversionType3(n) or
+ result = this.getConversionType4(n) or
+ result = this.getConversionType6(n) or
+ result = this.getConversionType7(n) or
+ result = this.getConversionType8(n) or
+ result = this.getConversionType9(n) or
+ result = this.getConversionType10(n)
}
private Type getConversionType1(int n) {
@@ -786,15 +792,15 @@ class FormatLiteral extends Literal {
or
conv = ["c", "C"] and
len = ["l", "w"] and
- result = getWideCharType()
+ result = this.getWideCharType()
or
conv = "c" and
(len != "l" and len != "w" and len != "h") and
- result = getDefaultCharType()
+ result = this.getDefaultCharType()
or
conv = "C" and
(len != "l" and len != "w" and len != "h") and
- result = getNonDefaultCharType()
+ result = this.getNonDefaultCharType()
)
)
}
@@ -831,15 +837,15 @@ class FormatLiteral extends Literal {
or
conv = ["s", "S"] and
len = ["l", "w"] and
- result.(PointerType).getBaseType() = getWideCharType()
+ result.(PointerType).getBaseType() = this.getWideCharType()
or
conv = "s" and
(len != "l" and len != "w" and len != "h") and
- result.(PointerType).getBaseType() = getDefaultCharType()
+ result.(PointerType).getBaseType() = this.getDefaultCharType()
or
conv = "S" and
(len != "l" and len != "w" and len != "h") and
- result.(PointerType).getBaseType() = getNonDefaultCharType()
+ result.(PointerType).getBaseType() = this.getNonDefaultCharType()
)
)
}
@@ -894,19 +900,19 @@ class FormatLiteral extends Literal {
exists(string len, string conv |
this.parseConvSpec(n, _, _, _, _, _, len, conv) and
(len != "l" and len != "w" and len != "h") and
- getUse().getTarget().(FormattingFunction).getFormatCharType().getSize() > 1 and // wide function
+ this.getUse().getTarget().getFormatCharType().getSize() > 1 and // wide function
(
conv = "c" and
- result = getNonDefaultCharType()
+ result = this.getNonDefaultCharType()
or
conv = "C" and
- result = getDefaultCharType()
+ result = this.getDefaultCharType()
or
conv = "s" and
- result.(PointerType).getBaseType() = getNonDefaultCharType()
+ result.(PointerType).getBaseType() = this.getNonDefaultCharType()
or
conv = "S" and
- result.(PointerType).getBaseType() = getDefaultCharType()
+ result.(PointerType).getBaseType() = this.getDefaultCharType()
)
)
}
@@ -939,9 +945,13 @@ class FormatLiteral extends Literal {
* not account for positional arguments (`$`).
*/
int getFormatArgumentIndexFor(int n, int mode) {
- hasFormatArgumentIndexFor(n, mode) and
+ this.hasFormatArgumentIndexFor(n, mode) and
(3 * n) + mode =
- rank[result + 1](int n2, int mode2 | hasFormatArgumentIndexFor(n2, mode2) | (3 * n2) + mode2)
+ rank[result + 1](int n2, int mode2 |
+ this.hasFormatArgumentIndexFor(n2, mode2)
+ |
+ (3 * n2) + mode2
+ )
}
/**
@@ -951,7 +961,7 @@ class FormatLiteral extends Literal {
int getNumArgNeeded(int n) {
exists(this.getConvSpecOffset(n)) and
exists(this.getConversionChar(n)) and
- result = count(int mode | hasFormatArgumentIndexFor(n, mode))
+ result = count(int mode | this.hasFormatArgumentIndexFor(n, mode))
}
/**
@@ -963,7 +973,7 @@ class FormatLiteral extends Literal {
// At least one conversion specifier has a parameter field, in which case,
// they all should have.
result = max(string s | this.getParameterField(_) = s + "$" | s.toInt())
- else result = count(int n, int mode | hasFormatArgumentIndexFor(n, mode))
+ else result = count(int n, int mode | this.hasFormatArgumentIndexFor(n, mode))
}
/**
@@ -1050,65 +1060,89 @@ class FormatLiteral extends Literal {
or
this.getConversionChar(n).toLowerCase() = ["d", "i"] and
// e.g. -2^31 = "-2147483648"
- exists(int sizeBits |
- sizeBits =
- min(int bits |
- bits = getIntegralDisplayType(n).getSize() * 8
- or
- exists(IntegralType t |
- t = getUse().getConversionArgument(n).getType().getUnderlyingType()
- |
- t.isSigned() and bits = t.getSize() * 8
- )
- ) and
- len = 1 + ((sizeBits - 1) / 10.0.log2()).ceil()
- // this calculation is as %u (below) only we take out the sign bit (- 1) and allow a whole
- // character for it to be expressed as '-'.
- )
+ len =
+ min(float cand |
+ // The first case handles length sub-specifiers
+ // Subtract one in the exponent because one bit is for the sign.
+ // Add 1 to account for the possible sign in the output.
+ cand = 1 + lengthInBase10(2.pow(this.getIntegralDisplayType(n).getSize() * 8 - 1))
+ or
+ // The second case uses range analysis to deduce a length that's shorter than the length
+ // of the number -2^31.
+ exists(Expr arg, float lower, float upper |
+ arg = this.getUse().getConversionArgument(n) and
+ lower = lowerBound(arg.getFullyConverted()) and
+ upper = upperBound(arg.getFullyConverted())
+ |
+ cand =
+ max(int cand0 |
+ // Include the sign bit in the length if it can be negative
+ (
+ if lower < 0
+ then cand0 = 1 + lengthInBase10(lower.abs())
+ else cand0 = lengthInBase10(lower)
+ )
+ or
+ (
+ if upper < 0
+ then cand0 = 1 + lengthInBase10(upper.abs())
+ else cand0 = lengthInBase10(upper)
+ )
+ )
+ )
+ )
or
this.getConversionChar(n).toLowerCase() = "u" and
// e.g. 2^32 - 1 = "4294967295"
- exists(int sizeBits |
- sizeBits =
- min(int bits |
- bits = getIntegralDisplayType(n).getSize() * 8
- or
- exists(IntegralType t |
- t = getUse().getConversionArgument(n).getType().getUnderlyingType()
- |
- t.isUnsigned() and bits = t.getSize() * 8
- )
- ) and
- len = (sizeBits / 10.0.log2()).ceil()
- // convert the size from bits to decimal characters, and round up as you can't have
- // fractional characters (10.0.log2() is the number of bits expressed per decimal character)
- )
+ len =
+ min(float cand |
+ // The first case handles length sub-specifiers
+ cand = 2.pow(this.getIntegralDisplayType(n).getSize() * 8)
+ or
+ // The second case uses range analysis to deduce a length that's shorter than
+ // the length of the number 2^31 - 1.
+ exists(Expr arg, float lower |
+ arg = this.getUse().getConversionArgument(n) and
+ lower = lowerBound(arg.getFullyConverted())
+ |
+ cand =
+ max(float cand0 |
+ // If lower can be negative we use `(unsigned)-1` as the candidate value.
+ lower < 0 and
+ cand0 = 2.pow(any(IntType t | t.isUnsigned()).getSize() * 8)
+ or
+ cand0 = upperBound(arg.getFullyConverted())
+ )
+ )
+ |
+ lengthInBase10(cand)
+ )
or
this.getConversionChar(n).toLowerCase() = "x" and
// e.g. "12345678"
exists(int sizeBytes, int baseLen |
sizeBytes =
min(int bytes |
- bytes = getIntegralDisplayType(n).getSize()
+ bytes = this.getIntegralDisplayType(n).getSize()
or
exists(IntegralType t |
- t = getUse().getConversionArgument(n).getType().getUnderlyingType()
+ t = this.getUse().getConversionArgument(n).getType().getUnderlyingType()
|
t.isUnsigned() and bytes = t.getSize()
)
) and
baseLen = sizeBytes * 2 and
(
- if hasAlternateFlag(n) then len = 2 + baseLen else len = baseLen // "0x"
+ if this.hasAlternateFlag(n) then len = 2 + baseLen else len = baseLen // "0x"
)
)
or
this.getConversionChar(n).toLowerCase() = "p" and
exists(PointerType ptrType, int baseLen |
- ptrType = getFullyConverted().getType() and
+ ptrType = this.getFullyConverted().getType() and
baseLen = max(ptrType.getSize() * 2) and // e.g. "0x1234567812345678"; exact format is platform dependent
(
- if hasAlternateFlag(n) then len = 2 + baseLen else len = baseLen // "0x"
+ if this.hasAlternateFlag(n) then len = 2 + baseLen else len = baseLen // "0x"
)
)
or
@@ -1117,17 +1151,17 @@ class FormatLiteral extends Literal {
exists(int sizeBits, int baseLen |
sizeBits =
min(int bits |
- bits = getIntegralDisplayType(n).getSize() * 8
+ bits = this.getIntegralDisplayType(n).getSize() * 8
or
exists(IntegralType t |
- t = getUse().getConversionArgument(n).getType().getUnderlyingType()
+ t = this.getUse().getConversionArgument(n).getType().getUnderlyingType()
|
t.isUnsigned() and bits = t.getSize() * 8
)
) and
baseLen = (sizeBits / 3.0).ceil() and
(
- if hasAlternateFlag(n) then len = 1 + baseLen else len = baseLen // "0"
+ if this.hasAlternateFlag(n) then len = 1 + baseLen else len = baseLen // "0"
)
)
or
@@ -1150,8 +1184,8 @@ class FormatLiteral extends Literal {
*/
int getMaxConvertedLengthLimited(int n) {
if this.getConversionChar(n).toLowerCase() = "f"
- then result = getMaxConvertedLength(n).minimum(8)
- else result = getMaxConvertedLength(n)
+ then result = this.getMaxConvertedLength(n).minimum(8)
+ else result = this.getMaxConvertedLength(n)
}
/**
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/Scanf.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/Scanf.qll
index 461030f389d..58d980318d9 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/Scanf.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/Scanf.qll
@@ -24,7 +24,7 @@ abstract class ScanfFunction extends Function {
* Holds if the default meaning of `%s` is a `wchar_t*` string
* (rather than a `char*`).
*/
- predicate isWideCharDefault() { exists(getName().indexOf("wscanf")) }
+ predicate isWideCharDefault() { exists(this.getName().indexOf("wscanf")) }
}
/**
@@ -34,10 +34,10 @@ class Scanf extends ScanfFunction {
Scanf() {
this instanceof TopLevelFunction and
(
- hasGlobalOrStdOrBslName("scanf") or // scanf(format, args...)
- hasGlobalOrStdOrBslName("wscanf") or // wscanf(format, args...)
- hasGlobalName("_scanf_l") or // _scanf_l(format, locale, args...)
- hasGlobalName("_wscanf_l") // _wscanf_l(format, locale, args...)
+ this.hasGlobalOrStdOrBslName("scanf") or // scanf(format, args...)
+ this.hasGlobalOrStdOrBslName("wscanf") or // wscanf(format, args...)
+ this.hasGlobalName("_scanf_l") or // _scanf_l(format, locale, args...)
+ this.hasGlobalName("_wscanf_l") // _wscanf_l(format, locale, args...)
)
}
@@ -53,10 +53,10 @@ class Fscanf extends ScanfFunction {
Fscanf() {
this instanceof TopLevelFunction and
(
- hasGlobalOrStdOrBslName("fscanf") or // fscanf(src_stream, format, args...)
- hasGlobalOrStdOrBslName("fwscanf") or // fwscanf(src_stream, format, args...)
- hasGlobalName("_fscanf_l") or // _fscanf_l(src_stream, format, locale, args...)
- hasGlobalName("_fwscanf_l") // _fwscanf_l(src_stream, format, locale, args...)
+ this.hasGlobalOrStdOrBslName("fscanf") or // fscanf(src_stream, format, args...)
+ this.hasGlobalOrStdOrBslName("fwscanf") or // fwscanf(src_stream, format, args...)
+ this.hasGlobalName("_fscanf_l") or // _fscanf_l(src_stream, format, locale, args...)
+ this.hasGlobalName("_fwscanf_l") // _fwscanf_l(src_stream, format, locale, args...)
)
}
@@ -72,10 +72,10 @@ class Sscanf extends ScanfFunction {
Sscanf() {
this instanceof TopLevelFunction and
(
- hasGlobalOrStdOrBslName("sscanf") or // sscanf(src_stream, format, args...)
- hasGlobalOrStdOrBslName("swscanf") or // swscanf(src, format, args...)
- hasGlobalName("_sscanf_l") or // _sscanf_l(src, format, locale, args...)
- hasGlobalName("_swscanf_l") // _swscanf_l(src, format, locale, args...)
+ this.hasGlobalOrStdOrBslName("sscanf") or // sscanf(src_stream, format, args...)
+ this.hasGlobalOrStdOrBslName("swscanf") or // swscanf(src, format, args...)
+ this.hasGlobalName("_sscanf_l") or // _sscanf_l(src, format, locale, args...)
+ this.hasGlobalName("_swscanf_l") // _swscanf_l(src, format, locale, args...)
)
}
@@ -91,10 +91,10 @@ class Snscanf extends ScanfFunction {
Snscanf() {
this instanceof TopLevelFunction and
(
- hasGlobalName("_snscanf") or // _snscanf(src, max_amount, format, args...)
- hasGlobalName("_snwscanf") or // _snwscanf(src, max_amount, format, args...)
- hasGlobalName("_snscanf_l") or // _snscanf_l(src, max_amount, format, locale, args...)
- hasGlobalName("_snwscanf_l") // _snwscanf_l(src, max_amount, format, locale, args...)
+ this.hasGlobalName("_snscanf") or // _snscanf(src, max_amount, format, args...)
+ this.hasGlobalName("_snwscanf") or // _snwscanf(src, max_amount, format, args...)
+ this.hasGlobalName("_snscanf_l") or // _snscanf_l(src, max_amount, format, locale, args...)
+ this.hasGlobalName("_snwscanf_l") // _snwscanf_l(src, max_amount, format, locale, args...)
// note that the max_amount is not a limit on the output length, it's an input length
// limit used with non null-terminated strings.
)
@@ -120,18 +120,18 @@ class ScanfFunctionCall extends FunctionCall {
/**
* Gets the `scanf`-like function that is called.
*/
- ScanfFunction getScanfFunction() { result = getTarget() }
+ ScanfFunction getScanfFunction() { result = this.getTarget() }
/**
* Gets the position at which the input string or stream parameter occurs,
* if this function call does not read from standard input.
*/
- int getInputParameterIndex() { result = getScanfFunction().getInputParameterIndex() }
+ int getInputParameterIndex() { result = this.getScanfFunction().getInputParameterIndex() }
/**
* Gets the position at which the format parameter occurs.
*/
- int getFormatParameterIndex() { result = getScanfFunction().getFormatParameterIndex() }
+ int getFormatParameterIndex() { result = this.getScanfFunction().getFormatParameterIndex() }
/**
* Gets the format expression used in this call.
@@ -142,7 +142,7 @@ class ScanfFunctionCall extends FunctionCall {
* Holds if the default meaning of `%s` is a `wchar_t*` string
* (rather than a `char*`).
*/
- predicate isWideCharDefault() { getScanfFunction().isWideCharDefault() }
+ predicate isWideCharDefault() { this.getScanfFunction().isWideCharDefault() }
}
/**
@@ -158,7 +158,7 @@ class ScanfFormatLiteral extends Expr {
ScanfFunctionCall getUse() { result.getFormat() = this }
/** Holds if the default meaning of `%s` is a `wchar_t*` (rather than a `char*`). */
- predicate isWideCharDefault() { getUse().getTarget().(ScanfFunction).isWideCharDefault() }
+ predicate isWideCharDefault() { this.getUse().getTarget().(ScanfFunction).isWideCharDefault() }
/**
* Gets the format string itself, transformed as follows:
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/Synchronization.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/Synchronization.qll
index 92955ae3580..f1b9cf80d64 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/Synchronization.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/commons/Synchronization.qll
@@ -40,8 +40,8 @@ abstract class MutexType extends Type {
* Gets a call that locks or tries to lock any mutex of this type.
*/
FunctionCall getLockAccess() {
- result = getMustlockAccess() or
- result = getTrylockAccess()
+ result = this.getMustlockAccess() or
+ result = this.getTrylockAccess()
}
/**
@@ -63,22 +63,22 @@ abstract class MutexType extends Type {
/**
* DEPRECATED: use mustlockAccess(fc, arg) instead.
*/
- deprecated Function getMustlockFunction() { result = getMustlockAccess().getTarget() }
+ deprecated Function getMustlockFunction() { result = this.getMustlockAccess().getTarget() }
/**
* DEPRECATED: use trylockAccess(fc, arg) instead.
*/
- deprecated Function getTrylockFunction() { result = getTrylockAccess().getTarget() }
+ deprecated Function getTrylockFunction() { result = this.getTrylockAccess().getTarget() }
/**
* DEPRECATED: use lockAccess(fc, arg) instead.
*/
- deprecated Function getLockFunction() { result = getLockAccess().getTarget() }
+ deprecated Function getLockFunction() { result = this.getLockAccess().getTarget() }
/**
* DEPRECATED: use unlockAccess(fc, arg) instead.
*/
- deprecated Function getUnlockFunction() { result = getUnlockAccess().getTarget() }
+ deprecated Function getUnlockFunction() { result = this.getUnlockAccess().getTarget() }
}
/**
@@ -155,17 +155,17 @@ class DefaultMutexType extends MutexType {
override predicate mustlockAccess(FunctionCall fc, Expr arg) {
fc.getTarget() = mustlockCandidate() and
- lockArgType(fc, arg)
+ this.lockArgType(fc, arg)
}
override predicate trylockAccess(FunctionCall fc, Expr arg) {
fc.getTarget() = trylockCandidate() and
- lockArgType(fc, arg)
+ this.lockArgType(fc, arg)
}
override predicate unlockAccess(FunctionCall fc, Expr arg) {
fc.getTarget() = unlockCandidate() and
- lockArgType(fc, arg)
+ this.lockArgType(fc, arg)
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/BasicBlocks.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/BasicBlocks.qll
index e235eba355b..34373d943af 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/BasicBlocks.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/BasicBlocks.qll
@@ -201,7 +201,7 @@ class BasicBlock extends ControlFlowNodeBase {
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
- hasLocationInfoInternal(filepath, startline, startcolumn, filepath, endline, endcolumn)
+ this.hasLocationInfoInternal(filepath, startline, startcolumn, filepath, endline, endcolumn)
}
pragma[noinline]
@@ -276,7 +276,7 @@ class EntryBasicBlock extends BasicBlock {
*/
class ExitBasicBlock extends BasicBlock {
ExitBasicBlock() {
- getEnd() instanceof Function or
- aborting(getEnd())
+ this.getEnd() instanceof Function or
+ aborting(this.getEnd())
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/ControlFlowGraph.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/ControlFlowGraph.qll
index bac051f6474..c7b3d1dc16f 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/ControlFlowGraph.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/ControlFlowGraph.qll
@@ -66,7 +66,7 @@ class ControlFlowNode extends Locatable, ControlFlowNodeBase {
*/
ControlFlowNode getATrueSuccessor() {
qlCFGTrueSuccessor(this, result) and
- result = getASuccessor()
+ result = this.getASuccessor()
}
/**
@@ -75,7 +75,7 @@ class ControlFlowNode extends Locatable, ControlFlowNodeBase {
*/
ControlFlowNode getAFalseSuccessor() {
qlCFGFalseSuccessor(this, result) and
- result = getASuccessor()
+ result = this.getASuccessor()
}
/** Gets the `BasicBlock` containing this control-flow node. */
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/DefinitionsAndUses.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/DefinitionsAndUses.qll
index f6eb0a8a645..dcabba51ce2 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/DefinitionsAndUses.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/DefinitionsAndUses.qll
@@ -25,7 +25,7 @@ predicate definitionUsePair(SemanticStackVariable var, Expr def, Expr use) {
* Holds if the definition `def` of some stack variable can reach `node`, which
* is a definition or use, without crossing definitions of the same variable.
*/
-predicate definitionReaches(Expr def, Expr node) { def.(Def).reaches(true, _, node.(DefOrUse)) }
+predicate definitionReaches(Expr def, Expr node) { def.(Def).reaches(true, _, node) }
private predicate hasAddressOfAccess(SemanticStackVariable var) {
var.getAnAccess().isAddressOfAccessNonConst()
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/IRGuards.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/IRGuards.qll
index d96fc34259c..9aee2556c1d 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/IRGuards.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/IRGuards.qll
@@ -121,7 +121,7 @@ private class GuardConditionFromBinaryLogicalOperator extends GuardCondition {
override predicate ensuresLt(Expr left, Expr right, int k, BasicBlock block, boolean isLessThan) {
exists(boolean testIsTrue |
- comparesLt(left, right, k, isLessThan, testIsTrue) and this.controls(block, testIsTrue)
+ this.comparesLt(left, right, k, isLessThan, testIsTrue) and this.controls(block, testIsTrue)
)
}
@@ -135,7 +135,7 @@ private class GuardConditionFromBinaryLogicalOperator extends GuardCondition {
override predicate ensuresEq(Expr left, Expr right, int k, BasicBlock block, boolean areEqual) {
exists(boolean testIsTrue |
- comparesEq(left, right, k, areEqual, testIsTrue) and this.controls(block, testIsTrue)
+ this.comparesEq(left, right, k, areEqual, testIsTrue) and this.controls(block, testIsTrue)
)
}
}
@@ -147,27 +147,29 @@ private class GuardConditionFromBinaryLogicalOperator extends GuardCondition {
private class GuardConditionFromShortCircuitNot extends GuardCondition, NotExpr {
GuardConditionFromShortCircuitNot() {
not exists(Instruction inst | this.getFullyConverted() = inst.getAST()) and
- exists(IRGuardCondition ir | getOperand() = ir.getAST())
+ exists(IRGuardCondition ir | this.getOperand() = ir.getAST())
}
override predicate controls(BasicBlock controlled, boolean testIsTrue) {
- getOperand().(GuardCondition).controls(controlled, testIsTrue.booleanNot())
+ this.getOperand().(GuardCondition).controls(controlled, testIsTrue.booleanNot())
}
override predicate comparesLt(Expr left, Expr right, int k, boolean isLessThan, boolean testIsTrue) {
- getOperand().(GuardCondition).comparesLt(left, right, k, isLessThan, testIsTrue.booleanNot())
+ this.getOperand()
+ .(GuardCondition)
+ .comparesLt(left, right, k, isLessThan, testIsTrue.booleanNot())
}
override predicate ensuresLt(Expr left, Expr right, int k, BasicBlock block, boolean isLessThan) {
- getOperand().(GuardCondition).ensuresLt(left, right, k, block, isLessThan.booleanNot())
+ this.getOperand().(GuardCondition).ensuresLt(left, right, k, block, isLessThan.booleanNot())
}
override predicate comparesEq(Expr left, Expr right, int k, boolean areEqual, boolean testIsTrue) {
- getOperand().(GuardCondition).comparesEq(left, right, k, areEqual, testIsTrue.booleanNot())
+ this.getOperand().(GuardCondition).comparesEq(left, right, k, areEqual, testIsTrue.booleanNot())
}
override predicate ensuresEq(Expr left, Expr right, int k, BasicBlock block, boolean areEqual) {
- getOperand().(GuardCondition).ensuresEq(left, right, k, block, areEqual.booleanNot())
+ this.getOperand().(GuardCondition).ensuresEq(left, right, k, block, areEqual.booleanNot())
}
}
@@ -303,9 +305,9 @@ class IRGuardCondition extends Instruction {
cached
predicate controlsEdge(IRBlock pred, IRBlock succ, boolean testIsTrue) {
pred.getASuccessor() = succ and
- controls(pred, testIsTrue)
+ this.controls(pred, testIsTrue)
or
- succ = getBranchSuccessor(testIsTrue) and
+ succ = this.getBranchSuccessor(testIsTrue) and
branch.getCondition() = this and
branch.getBlock() = pred
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/LocalScopeVariableReachability.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/LocalScopeVariableReachability.qll
index 32857146029..f6685865830 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/LocalScopeVariableReachability.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/LocalScopeVariableReachability.qll
@@ -73,19 +73,19 @@ abstract deprecated class LocalScopeVariableReachability extends string {
*/
exists(BasicBlock bb, int i |
- isSource(source, v) and
+ this.isSource(source, v) and
bb.getNode(i) = source and
not bb.isUnreachable()
|
exists(int j |
j > i and
sink = bb.getNode(j) and
- isSink(sink, v) and
- not exists(int k | isBarrier(bb.getNode(k), v) | k in [i + 1 .. j - 1])
+ this.isSink(sink, v) and
+ not exists(int k | this.isBarrier(bb.getNode(k), v) | k in [i + 1 .. j - 1])
)
or
- not exists(int k | isBarrier(bb.getNode(k), v) | k > i) and
- bbSuccessorEntryReaches(bb, v, sink, _)
+ not exists(int k | this.isBarrier(bb.getNode(k), v) | k > i) and
+ this.bbSuccessorEntryReaches(bb, v, sink, _)
)
}
@@ -97,11 +97,11 @@ abstract deprecated class LocalScopeVariableReachability extends string {
bbSuccessorEntryReachesLoopInvariant(bb, succ, skipsFirstLoopAlwaysTrueUponEntry,
succSkipsFirstLoopAlwaysTrueUponEntry)
|
- bbEntryReachesLocally(succ, v, node) and
+ this.bbEntryReachesLocally(succ, v, node) and
succSkipsFirstLoopAlwaysTrueUponEntry = false
or
- not isBarrier(succ.getNode(_), v) and
- bbSuccessorEntryReaches(succ, v, node, succSkipsFirstLoopAlwaysTrueUponEntry)
+ not this.isBarrier(succ.getNode(_), v) and
+ this.bbSuccessorEntryReaches(succ, v, node, succSkipsFirstLoopAlwaysTrueUponEntry)
)
}
@@ -110,7 +110,7 @@ abstract deprecated class LocalScopeVariableReachability extends string {
) {
exists(int n |
node = bb.getNode(n) and
- isSink(node, v)
+ this.isSink(node, v)
|
not exists(this.firstBarrierIndexIn(bb, v))
or
@@ -119,7 +119,7 @@ abstract deprecated class LocalScopeVariableReachability extends string {
}
private int firstBarrierIndexIn(BasicBlock bb, SemanticStackVariable v) {
- result = min(int m | isBarrier(bb.getNode(m), v))
+ result = min(int m | this.isBarrier(bb.getNode(m), v))
}
}
@@ -271,7 +271,7 @@ abstract deprecated class LocalScopeVariableReachabilityWithReassignment extends
* accounts for loops where the condition is provably true upon entry.
*/
override predicate reaches(ControlFlowNode source, SemanticStackVariable v, ControlFlowNode sink) {
- reachesTo(source, v, sink, _)
+ this.reachesTo(source, v, sink, _)
}
/**
@@ -281,21 +281,21 @@ abstract deprecated class LocalScopeVariableReachabilityWithReassignment extends
ControlFlowNode source, SemanticStackVariable v, ControlFlowNode sink, SemanticStackVariable v0
) {
exists(ControlFlowNode def |
- actualSourceReaches(source, v, def, v0) and
+ this.actualSourceReaches(source, v, def, v0) and
LocalScopeVariableReachability.super.reaches(def, v0, sink) and
- isSinkActual(sink, v0)
+ this.isSinkActual(sink, v0)
)
}
private predicate actualSourceReaches(
ControlFlowNode source, SemanticStackVariable v, ControlFlowNode def, SemanticStackVariable v0
) {
- isSourceActual(source, v) and def = source and v0 = v
+ this.isSourceActual(source, v) and def = source and v0 = v
or
exists(ControlFlowNode source1, SemanticStackVariable v1 |
- actualSourceReaches(source, v, source1, v1)
+ this.actualSourceReaches(source, v, source1, v1)
|
- reassignment(source1, v1, def, v0)
+ this.reassignment(source1, v1, def, v0)
)
}
@@ -307,14 +307,14 @@ abstract deprecated class LocalScopeVariableReachabilityWithReassignment extends
}
final override predicate isSource(ControlFlowNode node, LocalScopeVariable v) {
- isSourceActual(node, v)
+ this.isSourceActual(node, v)
or
// Reassignment generates a new (non-actual) source
- reassignment(_, _, node, v)
+ this.reassignment(_, _, node, v)
}
final override predicate isSink(ControlFlowNode node, LocalScopeVariable v) {
- isSinkActual(node, v)
+ this.isSinkActual(node, v)
or
// Reassignment generates a new (non-actual) sink
exprDefinition(_, node, v.getAnAccess())
@@ -347,21 +347,21 @@ abstract deprecated class LocalScopeVariableReachabilityExt extends string {
/** See `LocalScopeVariableReachability.reaches`. */
predicate reaches(ControlFlowNode source, SemanticStackVariable v, ControlFlowNode sink) {
exists(BasicBlock bb, int i |
- isSource(source, v) and
+ this.isSource(source, v) and
bb.getNode(i) = source and
not bb.isUnreachable()
|
exists(int j |
j > i and
sink = bb.getNode(j) and
- isSink(sink, v) and
- not exists(int k | isBarrier(source, bb.getNode(k), bb.getNode(k + 1), v) |
+ this.isSink(sink, v) and
+ not exists(int k | this.isBarrier(source, bb.getNode(k), bb.getNode(k + 1), v) |
k in [i .. j - 1]
)
)
or
- not exists(int k | isBarrier(source, bb.getNode(k), bb.getNode(k + 1), v) | k >= i) and
- bbSuccessorEntryReaches(source, bb, v, sink, _)
+ not exists(int k | this.isBarrier(source, bb.getNode(k), bb.getNode(k + 1), v) | k >= i) and
+ this.bbSuccessorEntryReaches(source, bb, v, sink, _)
)
}
@@ -372,22 +372,22 @@ abstract deprecated class LocalScopeVariableReachabilityExt extends string {
exists(BasicBlock succ, boolean succSkipsFirstLoopAlwaysTrueUponEntry |
bbSuccessorEntryReachesLoopInvariant(bb, succ, skipsFirstLoopAlwaysTrueUponEntry,
succSkipsFirstLoopAlwaysTrueUponEntry) and
- not isBarrier(source, bb.getEnd(), succ.getStart(), v)
+ not this.isBarrier(source, bb.getEnd(), succ.getStart(), v)
|
- bbEntryReachesLocally(source, succ, v, node) and
+ this.bbEntryReachesLocally(source, succ, v, node) and
succSkipsFirstLoopAlwaysTrueUponEntry = false
or
- not exists(int k | isBarrier(source, succ.getNode(k), succ.getNode(k + 1), v)) and
- bbSuccessorEntryReaches(source, succ, v, node, succSkipsFirstLoopAlwaysTrueUponEntry)
+ not exists(int k | this.isBarrier(source, succ.getNode(k), succ.getNode(k + 1), v)) and
+ this.bbSuccessorEntryReaches(source, succ, v, node, succSkipsFirstLoopAlwaysTrueUponEntry)
)
}
private predicate bbEntryReachesLocally(
ControlFlowNode source, BasicBlock bb, SemanticStackVariable v, ControlFlowNode node
) {
- isSource(source, v) and
- exists(int n | node = bb.getNode(n) and isSink(node, v) |
- not exists(int m | m < n | isBarrier(source, bb.getNode(m), bb.getNode(m + 1), v))
+ this.isSource(source, v) and
+ exists(int n | node = bb.getNode(n) and this.isSink(node, v) |
+ not exists(int m | m < n | this.isBarrier(source, bb.getNode(m), bb.getNode(m + 1), v))
)
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/SSA.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/SSA.qll
index 5c0f6b3ac14..c7af3fe4326 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/SSA.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/SSA.qll
@@ -59,10 +59,10 @@ class SsaDefinition extends ControlFlowNodeBase {
ControlFlowNode getDefinition() { result = this }
/** Gets the `BasicBlock` containing this definition. */
- BasicBlock getBasicBlock() { result.contains(getDefinition()) }
+ BasicBlock getBasicBlock() { result.contains(this.getDefinition()) }
/** Holds if this definition is a phi node for variable `v`. */
- predicate isPhiNode(StackVariable v) { exists(StandardSSA x | x.phi_node(v, this.(BasicBlock))) }
+ predicate isPhiNode(StackVariable v) { exists(StandardSSA x | x.phi_node(v, this)) }
/** Gets the location of this definition. */
Location getLocation() { result = this.(ControlFlowNode).getLocation() }
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/SSAUtils.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/SSAUtils.qll
index caae23c3bbd..3b02a0c828f 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/SSAUtils.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/SSAUtils.qll
@@ -292,7 +292,7 @@ library class SSAHelper extends int {
*/
cached
string toString(ControlFlowNode node, StackVariable v) {
- if phi_node(v, node.(BasicBlock))
+ if phi_node(v, node)
then result = "SSA phi(" + v.getName() + ")"
else (
ssa_defn(v, node, _, _) and result = "SSA def(" + v.getName() + ")"
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/StackVariableReachability.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/StackVariableReachability.qll
index 6c50d254faa..7b5fcd504b1 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/StackVariableReachability.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/StackVariableReachability.qll
@@ -72,19 +72,19 @@ abstract class StackVariableReachability extends string {
*/
exists(BasicBlock bb, int i |
- isSource(source, v) and
+ this.isSource(source, v) and
bb.getNode(i) = source and
not bb.isUnreachable()
|
exists(int j |
j > i and
sink = bb.getNode(j) and
- isSink(sink, v) and
- not exists(int k | isBarrier(bb.getNode(k), v) | k in [i + 1 .. j - 1])
+ this.isSink(sink, v) and
+ not exists(int k | this.isBarrier(bb.getNode(k), v) | k in [i + 1 .. j - 1])
)
or
- not exists(int k | isBarrier(bb.getNode(k), v) | k > i) and
- bbSuccessorEntryReaches(bb, v, sink, _)
+ not exists(int k | this.isBarrier(bb.getNode(k), v) | k > i) and
+ this.bbSuccessorEntryReaches(bb, v, sink, _)
)
}
@@ -96,11 +96,11 @@ abstract class StackVariableReachability extends string {
bbSuccessorEntryReachesLoopInvariant(bb, succ, skipsFirstLoopAlwaysTrueUponEntry,
succSkipsFirstLoopAlwaysTrueUponEntry)
|
- bbEntryReachesLocally(succ, v, node) and
+ this.bbEntryReachesLocally(succ, v, node) and
succSkipsFirstLoopAlwaysTrueUponEntry = false
or
- not isBarrier(succ.getNode(_), v) and
- bbSuccessorEntryReaches(succ, v, node, succSkipsFirstLoopAlwaysTrueUponEntry)
+ not this.isBarrier(succ.getNode(_), v) and
+ this.bbSuccessorEntryReaches(succ, v, node, succSkipsFirstLoopAlwaysTrueUponEntry)
)
}
@@ -109,7 +109,7 @@ abstract class StackVariableReachability extends string {
) {
exists(int n |
node = bb.getNode(n) and
- isSink(node, v)
+ this.isSink(node, v)
|
not exists(this.firstBarrierIndexIn(bb, v))
or
@@ -118,7 +118,7 @@ abstract class StackVariableReachability extends string {
}
private int firstBarrierIndexIn(BasicBlock bb, SemanticStackVariable v) {
- result = min(int m | isBarrier(bb.getNode(m), v))
+ result = min(int m | this.isBarrier(bb.getNode(m), v))
}
}
@@ -268,7 +268,7 @@ abstract class StackVariableReachabilityWithReassignment extends StackVariableRe
* accounts for loops where the condition is provably true upon entry.
*/
override predicate reaches(ControlFlowNode source, SemanticStackVariable v, ControlFlowNode sink) {
- reachesTo(source, v, sink, _)
+ this.reachesTo(source, v, sink, _)
}
/**
@@ -278,21 +278,21 @@ abstract class StackVariableReachabilityWithReassignment extends StackVariableRe
ControlFlowNode source, SemanticStackVariable v, ControlFlowNode sink, SemanticStackVariable v0
) {
exists(ControlFlowNode def |
- actualSourceReaches(source, v, def, v0) and
+ this.actualSourceReaches(source, v, def, v0) and
StackVariableReachability.super.reaches(def, v0, sink) and
- isSinkActual(sink, v0)
+ this.isSinkActual(sink, v0)
)
}
private predicate actualSourceReaches(
ControlFlowNode source, SemanticStackVariable v, ControlFlowNode def, SemanticStackVariable v0
) {
- isSourceActual(source, v) and def = source and v0 = v
+ this.isSourceActual(source, v) and def = source and v0 = v
or
exists(ControlFlowNode source1, SemanticStackVariable v1 |
- actualSourceReaches(source, v, source1, v1)
+ this.actualSourceReaches(source, v, source1, v1)
|
- reassignment(source1, v1, def, v0)
+ this.reassignment(source1, v1, def, v0)
)
}
@@ -304,14 +304,14 @@ abstract class StackVariableReachabilityWithReassignment extends StackVariableRe
}
final override predicate isSource(ControlFlowNode node, StackVariable v) {
- isSourceActual(node, v)
+ this.isSourceActual(node, v)
or
// Reassignment generates a new (non-actual) source
- reassignment(_, _, node, v)
+ this.reassignment(_, _, node, v)
}
final override predicate isSink(ControlFlowNode node, StackVariable v) {
- isSinkActual(node, v)
+ this.isSinkActual(node, v)
or
// Reassignment generates a new (non-actual) sink
exprDefinition(_, node, v.getAnAccess())
@@ -342,21 +342,21 @@ abstract class StackVariableReachabilityExt extends string {
/** See `StackVariableReachability.reaches`. */
predicate reaches(ControlFlowNode source, SemanticStackVariable v, ControlFlowNode sink) {
exists(BasicBlock bb, int i |
- isSource(source, v) and
+ this.isSource(source, v) and
bb.getNode(i) = source and
not bb.isUnreachable()
|
exists(int j |
j > i and
sink = bb.getNode(j) and
- isSink(sink, v) and
- not exists(int k | isBarrier(source, bb.getNode(k), bb.getNode(k + 1), v) |
+ this.isSink(sink, v) and
+ not exists(int k | this.isBarrier(source, bb.getNode(k), bb.getNode(k + 1), v) |
k in [i .. j - 1]
)
)
or
- not exists(int k | isBarrier(source, bb.getNode(k), bb.getNode(k + 1), v) | k >= i) and
- bbSuccessorEntryReaches(source, bb, v, sink, _)
+ not exists(int k | this.isBarrier(source, bb.getNode(k), bb.getNode(k + 1), v) | k >= i) and
+ this.bbSuccessorEntryReaches(source, bb, v, sink, _)
)
}
@@ -367,22 +367,22 @@ abstract class StackVariableReachabilityExt extends string {
exists(BasicBlock succ, boolean succSkipsFirstLoopAlwaysTrueUponEntry |
bbSuccessorEntryReachesLoopInvariant(bb, succ, skipsFirstLoopAlwaysTrueUponEntry,
succSkipsFirstLoopAlwaysTrueUponEntry) and
- not isBarrier(source, bb.getEnd(), succ.getStart(), v)
+ not this.isBarrier(source, bb.getEnd(), succ.getStart(), v)
|
- bbEntryReachesLocally(source, succ, v, node) and
+ this.bbEntryReachesLocally(source, succ, v, node) and
succSkipsFirstLoopAlwaysTrueUponEntry = false
or
- not exists(int k | isBarrier(source, succ.getNode(k), succ.getNode(k + 1), v)) and
- bbSuccessorEntryReaches(source, succ, v, node, succSkipsFirstLoopAlwaysTrueUponEntry)
+ not exists(int k | this.isBarrier(source, succ.getNode(k), succ.getNode(k + 1), v)) and
+ this.bbSuccessorEntryReaches(source, succ, v, node, succSkipsFirstLoopAlwaysTrueUponEntry)
)
}
private predicate bbEntryReachesLocally(
ControlFlowNode source, BasicBlock bb, SemanticStackVariable v, ControlFlowNode node
) {
- isSource(source, v) and
- exists(int n | node = bb.getNode(n) and isSink(node, v) |
- not exists(int m | m < n | isBarrier(source, bb.getNode(m), bb.getNode(m + 1), v))
+ this.isSource(source, v) and
+ exists(int n | node = bb.getNode(n) and this.isSink(node, v) |
+ not exists(int m | m < n | this.isBarrier(source, bb.getNode(m), bb.getNode(m + 1), v))
)
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/SubBasicBlocks.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/SubBasicBlocks.qll
index fa9d2e94081..4fbea43b805 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/SubBasicBlocks.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/SubBasicBlocks.qll
@@ -80,7 +80,7 @@ class SubBasicBlock extends ControlFlowNodeBase {
* returns a 0-based position, while `getRankInBasicBlock` returns a 1-based
* position.
*/
- deprecated int getPosInBasicBlock(BasicBlock bb) { result = getRankInBasicBlock(bb) - 1 }
+ deprecated int getPosInBasicBlock(BasicBlock bb) { result = this.getRankInBasicBlock(bb) - 1 }
pragma[noinline]
private int getIndexInBasicBlock(BasicBlock bb) { this = bb.getNode(result) }
@@ -102,7 +102,7 @@ class SubBasicBlock extends ControlFlowNodeBase {
exists(BasicBlock bb |
exists(int outerIndex |
result = bb.getNode(outerIndex) and
- index = outerToInnerIndex(bb, outerIndex)
+ index = this.outerToInnerIndex(bb, outerIndex)
)
)
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/internal/CFG.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/internal/CFG.qll
index 31ef5570451..ca1964a43c3 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/internal/CFG.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/internal/CFG.qll
@@ -231,7 +231,7 @@ private class PostOrderInitializer extends Initializer {
or
this.getDeclaration() = for.getRangeVariable()
or
- this.getDeclaration() = for.getBeginEndDeclaration().(DeclStmt).getADeclaration()
+ this.getDeclaration() = for.getBeginEndDeclaration().getADeclaration()
)
}
}
@@ -1143,7 +1143,7 @@ private class ExceptionSource extends Node {
this.reachesParent(mid) and
not mid = any(TryStmt try).getStmt() and
not mid = any(MicrosoftTryStmt try).getStmt() and
- parent = mid.(Node).getParentNode()
+ parent = mid.getParentNode()
)
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/internal/ConstantExprs.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/internal/ConstantExprs.qll
index ff27baae965..d2b24db0938 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/internal/ConstantExprs.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/controlflow/internal/ConstantExprs.qll
@@ -385,7 +385,7 @@ library class ExprEvaluator extends int {
abstract predicate interesting(Expr e);
/** Gets the value of (interesting) expression `e`, if any. */
- int getValue(Expr e) { result = getValueInternal(e, e) }
+ int getValue(Expr e) { result = this.getValueInternal(e, e) }
/**
* When evaluating a syntactic subexpression of `e`, we may
@@ -425,9 +425,9 @@ library class ExprEvaluator extends int {
* calculates the values bottom-up.
*/
predicate interestingInternal(Expr e, Expr req, boolean sub) {
- interesting(e) and req = e and sub = true
+ this.interesting(e) and req = e and sub = true
or
- exists(Expr mid | interestingInternal(e, mid, sub) |
+ exists(Expr mid | this.interestingInternal(e, mid, sub) |
req = mid.(NotExpr).getOperand() or
req = mid.(BinaryLogicalOperation).getAnOperand() or
req = mid.(RelationalOperation).getAnOperand() or
@@ -442,36 +442,36 @@ library class ExprEvaluator extends int {
)
or
exists(VariableAccess va, Variable v, boolean sub1 |
- interestingVariableAccess(e, va, v, sub1) and
+ this.interestingVariableAccess(e, va, v, sub1) and
req = v.getAnAssignedValue() and
- (sub1 = true implies not ignoreVariableAssignment(e, v, req)) and
+ (sub1 = true implies not this.ignoreVariableAssignment(e, v, req)) and
sub = false
)
or
exists(Function f |
- interestingFunction(e, f) and
+ this.interestingFunction(e, f) and
returnStmt(f, req) and
sub = false
)
}
private predicate interestingVariableAccess(Expr e, VariableAccess va, Variable v, boolean sub) {
- interestingInternal(e, va, sub) and
+ this.interestingInternal(e, va, sub) and
v = getVariableTarget(va) and
(
v.hasInitializer()
or
- sub = true and allowVariableWithoutInitializer(e, v)
+ sub = true and this.allowVariableWithoutInitializer(e, v)
) and
tractableVariable(v) and
forall(StmtParent def | nonAnalyzableVariableDefinition(v, def) |
sub = true and
- ignoreNonAnalyzableVariableDefinition(e, v, def)
+ this.ignoreNonAnalyzableVariableDefinition(e, v, def)
)
}
private predicate interestingFunction(Expr e, Function f) {
- exists(FunctionCall fc | interestingInternal(e, fc, _) |
+ exists(FunctionCall fc | this.interestingInternal(e, fc, _) |
f = fc.getTarget() and
not obviouslyNonConstant(f) and
not f.getUnspecifiedType() instanceof VoidType
@@ -481,10 +481,10 @@ library class ExprEvaluator extends int {
/** Gets the value of subexpressions `req` for expression `e`, if any. */
private int getValueInternal(Expr e, Expr req) {
(
- interestingInternal(e, req, true) and
+ this.interestingInternal(e, req, true) and
(
result = req.(CompileTimeConstantInt).getIntValue() or
- result = getCompoundValue(e, req.(CompileTimeVariableExpr))
+ result = this.getCompoundValue(e, req)
) and
(
req.getUnderlyingType().(IntegralType).isSigned() or
@@ -495,109 +495,126 @@ library class ExprEvaluator extends int {
/** Gets the value of compound subexpressions `val` for expression `e`, if any. */
private int getCompoundValue(Expr e, CompileTimeVariableExpr val) {
- interestingInternal(e, val, true) and
+ this.interestingInternal(e, val, true) and
(
exists(NotExpr req | req = val |
- result = 1 and getValueInternal(e, req.getOperand()) = 0
+ result = 1 and this.getValueInternal(e, req.getOperand()) = 0
or
- result = 0 and getValueInternal(e, req.getOperand()) != 0
+ result = 0 and this.getValueInternal(e, req.getOperand()) != 0
)
or
exists(LogicalAndExpr req | req = val |
result = 1 and
- getValueInternal(e, req.getLeftOperand()) != 0 and
- getValueInternal(e, req.getRightOperand()) != 0
+ this.getValueInternal(e, req.getLeftOperand()) != 0 and
+ this.getValueInternal(e, req.getRightOperand()) != 0
or
- result = 0 and getValueInternal(e, req.getAnOperand()) = 0
+ result = 0 and this.getValueInternal(e, req.getAnOperand()) = 0
)
or
exists(LogicalOrExpr req | req = val |
- result = 1 and getValueInternal(e, req.getAnOperand()) != 0
+ result = 1 and this.getValueInternal(e, req.getAnOperand()) != 0
or
result = 0 and
- getValueInternal(e, req.getLeftOperand()) = 0 and
- getValueInternal(e, req.getRightOperand()) = 0
+ this.getValueInternal(e, req.getLeftOperand()) = 0 and
+ this.getValueInternal(e, req.getRightOperand()) = 0
)
or
exists(LTExpr req | req = val |
result = 1 and
- getValueInternal(e, req.getLeftOperand()) < getValueInternal(e, req.getRightOperand())
+ this.getValueInternal(e, req.getLeftOperand()) <
+ this.getValueInternal(e, req.getRightOperand())
or
result = 0 and
- getValueInternal(e, req.getLeftOperand()) >= getValueInternal(e, req.getRightOperand())
+ this.getValueInternal(e, req.getLeftOperand()) >=
+ this.getValueInternal(e, req.getRightOperand())
)
or
exists(GTExpr req | req = val |
result = 1 and
- getValueInternal(e, req.getLeftOperand()) > getValueInternal(e, req.getRightOperand())
+ this.getValueInternal(e, req.getLeftOperand()) >
+ this.getValueInternal(e, req.getRightOperand())
or
result = 0 and
- getValueInternal(e, req.getLeftOperand()) <= getValueInternal(e, req.getRightOperand())
+ this.getValueInternal(e, req.getLeftOperand()) <=
+ this.getValueInternal(e, req.getRightOperand())
)
or
exists(LEExpr req | req = val |
result = 1 and
- getValueInternal(e, req.getLeftOperand()) <= getValueInternal(e, req.getRightOperand())
+ this.getValueInternal(e, req.getLeftOperand()) <=
+ this.getValueInternal(e, req.getRightOperand())
or
result = 0 and
- getValueInternal(e, req.getLeftOperand()) > getValueInternal(e, req.getRightOperand())
+ this.getValueInternal(e, req.getLeftOperand()) >
+ this.getValueInternal(e, req.getRightOperand())
)
or
exists(GEExpr req | req = val |
result = 1 and
- getValueInternal(e, req.getLeftOperand()) >= getValueInternal(e, req.getRightOperand())
+ this.getValueInternal(e, req.getLeftOperand()) >=
+ this.getValueInternal(e, req.getRightOperand())
or
result = 0 and
- getValueInternal(e, req.getLeftOperand()) < getValueInternal(e, req.getRightOperand())
+ this.getValueInternal(e, req.getLeftOperand()) <
+ this.getValueInternal(e, req.getRightOperand())
)
or
exists(EQExpr req | req = val |
result = 1 and
- getValueInternal(e, req.getLeftOperand()) = getValueInternal(e, req.getRightOperand())
+ this.getValueInternal(e, req.getLeftOperand()) =
+ this.getValueInternal(e, req.getRightOperand())
or
result = 0 and
- getValueInternal(e, req.getLeftOperand()) != getValueInternal(e, req.getRightOperand())
+ this.getValueInternal(e, req.getLeftOperand()) !=
+ this.getValueInternal(e, req.getRightOperand())
)
or
exists(NEExpr req | req = val |
result = 0 and
- getValueInternal(e, req.getLeftOperand()) = getValueInternal(e, req.getRightOperand())
+ this.getValueInternal(e, req.getLeftOperand()) =
+ this.getValueInternal(e, req.getRightOperand())
or
result = 1 and
- getValueInternal(e, req.getLeftOperand()) != getValueInternal(e, req.getRightOperand())
+ this.getValueInternal(e, req.getLeftOperand()) !=
+ this.getValueInternal(e, req.getRightOperand())
)
or
exists(AddExpr req | req = val |
result =
- getValueInternal(e, req.getLeftOperand()) + getValueInternal(e, req.getRightOperand())
+ this.getValueInternal(e, req.getLeftOperand()) +
+ this.getValueInternal(e, req.getRightOperand())
)
or
exists(SubExpr req | req = val |
result =
- getValueInternal(e, req.getLeftOperand()) - getValueInternal(e, req.getRightOperand())
+ this.getValueInternal(e, req.getLeftOperand()) -
+ this.getValueInternal(e, req.getRightOperand())
)
or
exists(MulExpr req | req = val |
result =
- getValueInternal(e, req.getLeftOperand()) * getValueInternal(e, req.getRightOperand())
+ this.getValueInternal(e, req.getLeftOperand()) *
+ this.getValueInternal(e, req.getRightOperand())
)
or
exists(RemExpr req | req = val |
result =
- getValueInternal(e, req.getLeftOperand()) % getValueInternal(e, req.getRightOperand())
+ this.getValueInternal(e, req.getLeftOperand()) %
+ this.getValueInternal(e, req.getRightOperand())
)
or
exists(DivExpr req | req = val |
result =
- getValueInternal(e, req.getLeftOperand()) / getValueInternal(e, req.getRightOperand())
+ this.getValueInternal(e, req.getLeftOperand()) /
+ this.getValueInternal(e, req.getRightOperand())
)
or
- exists(AssignExpr req | req = val | result = getValueInternal(e, req.getRValue()))
+ exists(AssignExpr req | req = val | result = this.getValueInternal(e, req.getRValue()))
or
- result = getVariableValue(e, val.(VariableAccess))
+ result = this.getVariableValue(e, val)
or
exists(FunctionCall call | call = val and not callWithMultipleTargets(call) |
- result = getFunctionValue(call.getTarget())
+ result = this.getFunctionValue(call.getTarget())
)
)
}
@@ -605,7 +622,7 @@ library class ExprEvaluator extends int {
language[monotonicAggregates]
private int getVariableValue(Expr e, VariableAccess va) {
exists(Variable v |
- interestingVariableAccess(e, va, v, true) and
+ this.interestingVariableAccess(e, va, v, true) and
// All assignments must have the same int value
result =
unique(Expr value |
@@ -619,14 +636,16 @@ library class ExprEvaluator extends int {
/** Holds if the function `f` is considered by the analysis and may return `ret`. */
pragma[noinline]
private predicate interestingReturnValue(Function f, Expr ret) {
- interestingFunction(_, f) and
+ this.interestingFunction(_, f) and
returnStmt(f, ret)
}
private int getFunctionValue(Function f) {
// All returns must have the same int value
// And it must have at least one return
- forex(Expr ret | interestingReturnValue(f, ret) | result = getValueInternalNonSubExpr(ret))
+ forex(Expr ret | this.interestingReturnValue(f, ret) |
+ result = this.getValueInternalNonSubExpr(ret)
+ )
}
/**
@@ -641,10 +660,10 @@ library class ExprEvaluator extends int {
* omitted).
*/
private int getValueInternalNonSubExpr(Expr req) {
- interestingInternal(_, req, false) and
+ this.interestingInternal(_, req, false) and
(
result = req.(CompileTimeConstantInt).getIntValue() or
- result = getCompoundValueNonSubExpr(req.(CompileTimeVariableExpr))
+ result = this.getCompoundValueNonSubExpr(req)
) and
(
req.getUnderlyingType().(IntegralType).isSigned() or
@@ -655,131 +674,131 @@ library class ExprEvaluator extends int {
private int getCompoundValueNonSubExpr(CompileTimeVariableExpr val) {
(
exists(NotExpr req | req = val |
- result = 1 and getValueInternalNonSubExpr(req.getOperand()) = 0
+ result = 1 and this.getValueInternalNonSubExpr(req.getOperand()) = 0
or
- result = 0 and getValueInternalNonSubExpr(req.getOperand()) != 0
+ result = 0 and this.getValueInternalNonSubExpr(req.getOperand()) != 0
)
or
exists(LogicalAndExpr req | req = val |
result = 1 and
- getValueInternalNonSubExpr(req.getLeftOperand()) != 0 and
- getValueInternalNonSubExpr(req.getRightOperand()) != 0
+ this.getValueInternalNonSubExpr(req.getLeftOperand()) != 0 and
+ this.getValueInternalNonSubExpr(req.getRightOperand()) != 0
or
- result = 0 and getValueInternalNonSubExpr(req.getAnOperand()) = 0
+ result = 0 and this.getValueInternalNonSubExpr(req.getAnOperand()) = 0
)
or
exists(LogicalOrExpr req | req = val |
- result = 1 and getValueInternalNonSubExpr(req.getAnOperand()) != 0
+ result = 1 and this.getValueInternalNonSubExpr(req.getAnOperand()) != 0
or
result = 0 and
- getValueInternalNonSubExpr(req.getLeftOperand()) = 0 and
- getValueInternalNonSubExpr(req.getRightOperand()) = 0
+ this.getValueInternalNonSubExpr(req.getLeftOperand()) = 0 and
+ this.getValueInternalNonSubExpr(req.getRightOperand()) = 0
)
or
exists(LTExpr req | req = val |
result = 1 and
- getValueInternalNonSubExpr(req.getLeftOperand()) <
- getValueInternalNonSubExpr(req.getRightOperand())
+ this.getValueInternalNonSubExpr(req.getLeftOperand()) <
+ this.getValueInternalNonSubExpr(req.getRightOperand())
or
result = 0 and
- getValueInternalNonSubExpr(req.getLeftOperand()) >=
- getValueInternalNonSubExpr(req.getRightOperand())
+ this.getValueInternalNonSubExpr(req.getLeftOperand()) >=
+ this.getValueInternalNonSubExpr(req.getRightOperand())
)
or
exists(GTExpr req | req = val |
result = 1 and
- getValueInternalNonSubExpr(req.getLeftOperand()) >
- getValueInternalNonSubExpr(req.getRightOperand())
+ this.getValueInternalNonSubExpr(req.getLeftOperand()) >
+ this.getValueInternalNonSubExpr(req.getRightOperand())
or
result = 0 and
- getValueInternalNonSubExpr(req.getLeftOperand()) <=
- getValueInternalNonSubExpr(req.getRightOperand())
+ this.getValueInternalNonSubExpr(req.getLeftOperand()) <=
+ this.getValueInternalNonSubExpr(req.getRightOperand())
)
or
exists(LEExpr req | req = val |
result = 1 and
- getValueInternalNonSubExpr(req.getLeftOperand()) <=
- getValueInternalNonSubExpr(req.getRightOperand())
+ this.getValueInternalNonSubExpr(req.getLeftOperand()) <=
+ this.getValueInternalNonSubExpr(req.getRightOperand())
or
result = 0 and
- getValueInternalNonSubExpr(req.getLeftOperand()) >
- getValueInternalNonSubExpr(req.getRightOperand())
+ this.getValueInternalNonSubExpr(req.getLeftOperand()) >
+ this.getValueInternalNonSubExpr(req.getRightOperand())
)
or
exists(GEExpr req | req = val |
result = 1 and
- getValueInternalNonSubExpr(req.getLeftOperand()) >=
- getValueInternalNonSubExpr(req.getRightOperand())
+ this.getValueInternalNonSubExpr(req.getLeftOperand()) >=
+ this.getValueInternalNonSubExpr(req.getRightOperand())
or
result = 0 and
- getValueInternalNonSubExpr(req.getLeftOperand()) <
- getValueInternalNonSubExpr(req.getRightOperand())
+ this.getValueInternalNonSubExpr(req.getLeftOperand()) <
+ this.getValueInternalNonSubExpr(req.getRightOperand())
)
or
exists(EQExpr req | req = val |
result = 1 and
- getValueInternalNonSubExpr(req.getLeftOperand()) =
- getValueInternalNonSubExpr(req.getRightOperand())
+ this.getValueInternalNonSubExpr(req.getLeftOperand()) =
+ this.getValueInternalNonSubExpr(req.getRightOperand())
or
result = 0 and
- getValueInternalNonSubExpr(req.getLeftOperand()) !=
- getValueInternalNonSubExpr(req.getRightOperand())
+ this.getValueInternalNonSubExpr(req.getLeftOperand()) !=
+ this.getValueInternalNonSubExpr(req.getRightOperand())
)
or
exists(NEExpr req | req = val |
result = 0 and
- getValueInternalNonSubExpr(req.getLeftOperand()) =
- getValueInternalNonSubExpr(req.getRightOperand())
+ this.getValueInternalNonSubExpr(req.getLeftOperand()) =
+ this.getValueInternalNonSubExpr(req.getRightOperand())
or
result = 1 and
- getValueInternalNonSubExpr(req.getLeftOperand()) !=
- getValueInternalNonSubExpr(req.getRightOperand())
+ this.getValueInternalNonSubExpr(req.getLeftOperand()) !=
+ this.getValueInternalNonSubExpr(req.getRightOperand())
)
or
exists(AddExpr req | req = val |
result =
- getValueInternalNonSubExpr(req.getLeftOperand()) +
- getValueInternalNonSubExpr(req.getRightOperand())
+ this.getValueInternalNonSubExpr(req.getLeftOperand()) +
+ this.getValueInternalNonSubExpr(req.getRightOperand())
)
or
exists(SubExpr req | req = val |
result =
- getValueInternalNonSubExpr(req.getLeftOperand()) -
- getValueInternalNonSubExpr(req.getRightOperand())
+ this.getValueInternalNonSubExpr(req.getLeftOperand()) -
+ this.getValueInternalNonSubExpr(req.getRightOperand())
)
or
exists(MulExpr req | req = val |
result =
- getValueInternalNonSubExpr(req.getLeftOperand()) *
- getValueInternalNonSubExpr(req.getRightOperand())
+ this.getValueInternalNonSubExpr(req.getLeftOperand()) *
+ this.getValueInternalNonSubExpr(req.getRightOperand())
)
or
exists(RemExpr req | req = val |
result =
- getValueInternalNonSubExpr(req.getLeftOperand()) %
- getValueInternalNonSubExpr(req.getRightOperand())
+ this.getValueInternalNonSubExpr(req.getLeftOperand()) %
+ this.getValueInternalNonSubExpr(req.getRightOperand())
)
or
exists(DivExpr req | req = val |
result =
- getValueInternalNonSubExpr(req.getLeftOperand()) /
- getValueInternalNonSubExpr(req.getRightOperand())
+ this.getValueInternalNonSubExpr(req.getLeftOperand()) /
+ this.getValueInternalNonSubExpr(req.getRightOperand())
)
or
- exists(AssignExpr req | req = val | result = getValueInternalNonSubExpr(req.getRValue()))
+ exists(AssignExpr req | req = val | result = this.getValueInternalNonSubExpr(req.getRValue()))
or
- result = getVariableValueNonSubExpr(val.(VariableAccess))
+ result = this.getVariableValueNonSubExpr(val)
or
exists(FunctionCall call | call = val and not callWithMultipleTargets(call) |
- result = getFunctionValue(call.getTarget())
+ result = this.getFunctionValue(call.getTarget())
)
)
}
private int getVariableValueNonSubExpr(VariableAccess va) {
// All assignments must have the same int value
- result = getMinVariableValueNonSubExpr(va) and
- result = getMaxVariableValueNonSubExpr(va)
+ result = this.getMinVariableValueNonSubExpr(va) and
+ result = this.getMaxVariableValueNonSubExpr(va)
}
/**
@@ -790,8 +809,9 @@ library class ExprEvaluator extends int {
pragma[noopt]
private int getMinVariableValueNonSubExpr(VariableAccess va) {
exists(Variable v |
- interestingVariableAccess(_, va, v, false) and
- result = min(Expr value | value = v.getAnAssignedValue() | getValueInternalNonSubExpr(value))
+ this.interestingVariableAccess(_, va, v, false) and
+ result =
+ min(Expr value | value = v.getAnAssignedValue() | this.getValueInternalNonSubExpr(value))
)
}
@@ -803,8 +823,9 @@ library class ExprEvaluator extends int {
pragma[noopt]
private int getMaxVariableValueNonSubExpr(VariableAccess va) {
exists(Variable v |
- interestingVariableAccess(_, va, v, false) and
- result = max(Expr value | value = v.getAnAssignedValue() | getValueInternalNonSubExpr(value))
+ this.interestingVariableAccess(_, va, v, false) and
+ result =
+ max(Expr value | value = v.getAnAssignedValue() | this.getValueInternalNonSubExpr(value))
)
}
}
@@ -967,9 +988,9 @@ library class LoopEntryConditionEvaluator extends ExprEvaluator {
abstract predicate isLoopBody(Expr e, StmtParent s);
private predicate isLoopBodyDescendant(Expr e, StmtParent s) {
- isLoopBody(e, s)
+ this.isLoopBody(e, s)
or
- exists(StmtParent mid | isLoopBodyDescendant(e, mid) |
+ exists(StmtParent mid | this.isLoopBodyDescendant(e, mid) |
s = mid.(Stmt).getAChild() or
s = mid.(Expr).getAChild()
)
@@ -977,13 +998,13 @@ library class LoopEntryConditionEvaluator extends ExprEvaluator {
// Same as `interestingInternal(e, sub, true)` but avoids negative recursion
private predicate interestingSubExpr(Expr e, Expr sub) {
- interesting(e) and e = sub
+ this.interesting(e) and e = sub
or
- exists(Expr mid | interestingSubExpr(e, mid) and sub = mid.getAChild())
+ exists(Expr mid | this.interestingSubExpr(e, mid) and sub = mid.getAChild())
}
private predicate maybeInterestingVariable(Expr e, Variable v) {
- exists(VariableAccess va | interestingSubExpr(e, va) | va.getTarget() = v)
+ exists(VariableAccess va | this.interestingSubExpr(e, va) | va.getTarget() = v)
}
/**
@@ -995,9 +1016,9 @@ library class LoopEntryConditionEvaluator extends ExprEvaluator {
* definition of `v`.
*/
private predicate reachesLoopEntryFromLoopBody(Expr e, Variable v, StmtParent valueOrDef) {
- maybeInterestingVariable(e, v) and
+ this.maybeInterestingVariable(e, v) and
(valueOrDef = v.getAnAssignedValue() or nonAnalyzableVariableDefinition(v, valueOrDef)) and
- isLoopBodyDescendant(e, valueOrDef) and
+ this.isLoopBodyDescendant(e, valueOrDef) and
/*
* Use primitive basic blocks in reachability analysis for better performance.
* This is similar to the pattern used in e.g. `DefinitionsAndUses` and
@@ -1007,16 +1028,16 @@ library class LoopEntryConditionEvaluator extends ExprEvaluator {
exists(PrimitiveBasicBlock bb1, int pos1 | bb1.getNode(pos1) = valueOrDef |
// Reaches in same basic block
exists(int pos2 |
- loopEntryAt(bb1, pos2, e) and
+ this.loopEntryAt(bb1, pos2, e) and
pos2 > pos1 and
- not exists(int k | assignmentAt(bb1, k, v) | k in [pos1 + 1 .. pos2 - 1])
+ not exists(int k | this.assignmentAt(bb1, k, v) | k in [pos1 + 1 .. pos2 - 1])
)
or
// Reaches in a successor block
exists(PrimitiveBasicBlock bb2 |
bb2 = bb1.getASuccessor() and
- not exists(int pos3 | assignmentAt(bb1, pos3, v) and pos3 > pos1) and
- bbReachesLoopEntry(bb2, e, v)
+ not exists(int pos3 | this.assignmentAt(bb1, pos3, v) and pos3 > pos1) and
+ this.bbReachesLoopEntry(bb2, e, v)
)
)
}
@@ -1024,12 +1045,12 @@ library class LoopEntryConditionEvaluator extends ExprEvaluator {
private predicate loopEntryAt(PrimitiveBasicBlock bb, int pos, Expr e) {
exists(Node cfn |
bb.getNode(pos) = cfn and
- isLoopEntry(e, cfn)
+ this.isLoopEntry(e, cfn)
)
}
private predicate assignmentAt(PrimitiveBasicBlock bb, int pos, Variable v) {
- maybeInterestingVariable(_, v) and
+ this.maybeInterestingVariable(_, v) and
bb.getNode(pos) = v.getAnAssignedValue()
}
@@ -1038,19 +1059,19 @@ library class LoopEntryConditionEvaluator extends ExprEvaluator {
* the loop belonging to `e` without crossing an assignment to `v`.
*/
private predicate bbReachesLoopEntry(PrimitiveBasicBlock bb, Expr e, Variable v) {
- bbReachesLoopEntryLocally(bb, e, v)
+ this.bbReachesLoopEntryLocally(bb, e, v)
or
exists(PrimitiveBasicBlock succ | succ = bb.getASuccessor() |
- bbReachesLoopEntry(succ, e, v) and
- not assignmentAt(bb, _, v)
+ this.bbReachesLoopEntry(succ, e, v) and
+ not this.assignmentAt(bb, _, v)
)
}
private predicate bbReachesLoopEntryLocally(PrimitiveBasicBlock bb, Expr e, Variable v) {
exists(int pos |
- loopEntryAt(bb, pos, e) and
- maybeInterestingVariable(e, v) and
- not exists(int pos1 | assignmentAt(bb, pos1, v) | pos1 < pos)
+ this.loopEntryAt(bb, pos, e) and
+ this.maybeInterestingVariable(e, v) and
+ not exists(int pos1 | this.assignmentAt(bb, pos1, v) | pos1 < pos)
)
}
@@ -1084,10 +1105,10 @@ library class LoopEntryConditionEvaluator extends ExprEvaluator {
* ```
*/
override predicate ignoreNonAnalyzableVariableDefinition(Expr e, Variable v, StmtParent def) {
- maybeInterestingVariable(e, v) and
+ this.maybeInterestingVariable(e, v) and
nonAnalyzableVariableDefinition(v, def) and
- isLoopBodyDescendant(e, def) and
- not reachesLoopEntryFromLoopBody(e, v, def)
+ this.isLoopBodyDescendant(e, def) and
+ not this.reachesLoopEntryFromLoopBody(e, v, def)
}
/**
@@ -1120,10 +1141,10 @@ library class LoopEntryConditionEvaluator extends ExprEvaluator {
* ```
*/
override predicate ignoreVariableAssignment(Expr e, Variable v, Expr value) {
- maybeInterestingVariable(e, v) and
+ this.maybeInterestingVariable(e, v) and
value = v.getAnAssignedValue() and
- isLoopBodyDescendant(e, value) and
- not reachesLoopEntryFromLoopBody(e, v, value)
+ this.isLoopBodyDescendant(e, value) and
+ not this.reachesLoopEntryFromLoopBody(e, v, value)
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll
index f43a550af57..c28ceabb438 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll
@@ -2,6 +2,42 @@ private import DataFlowImplSpecific::Private
private import DataFlowImplSpecific::Public
import Cached
+module DataFlowImplCommonPublic {
+ private newtype TFlowFeature =
+ TFeatureHasSourceCallContext() or
+ TFeatureHasSinkCallContext() or
+ TFeatureEqualSourceSinkCallContext()
+
+ /** A flow configuration feature for use in `Configuration::getAFeature()`. */
+ class FlowFeature extends TFlowFeature {
+ string toString() { none() }
+ }
+
+ /**
+ * A flow configuration feature that implies that sources have some existing
+ * call context.
+ */
+ class FeatureHasSourceCallContext extends FlowFeature, TFeatureHasSourceCallContext {
+ override string toString() { result = "FeatureHasSourceCallContext" }
+ }
+
+ /**
+ * A flow configuration feature that implies that sinks have some existing
+ * call context.
+ */
+ class FeatureHasSinkCallContext extends FlowFeature, TFeatureHasSinkCallContext {
+ override string toString() { result = "FeatureHasSinkCallContext" }
+ }
+
+ /**
+ * A flow configuration feature that implies that source-sink pairs have some
+ * shared existing call context.
+ */
+ class FeatureEqualSourceSinkCallContext extends FlowFeature, TFeatureEqualSourceSinkCallContext {
+ override string toString() { result = "FeatureEqualSourceSinkCallContext" }
+ }
+}
+
/**
* The cost limits for the `AccessPathFront` to `AccessPathApprox` expansion.
*
@@ -251,7 +287,7 @@ private module Cached {
predicate forceCachingInSameStage() { any() }
cached
- predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = n.getEnclosingCallable() }
+ predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = nodeGetEnclosingCallable(n) }
cached
predicate callEnclosingCallable(DataFlowCall call, DataFlowCallable c) {
@@ -316,9 +352,7 @@ private module Cached {
}
cached
- predicate parameterNode(Node n, DataFlowCallable c, int i) {
- n.(ParameterNode).isParameterOf(c, i)
- }
+ predicate parameterNode(Node p, DataFlowCallable c, int pos) { isParameterNode(p, c, pos) }
cached
predicate argumentNode(Node n, DataFlowCall call, int pos) {
@@ -801,6 +835,9 @@ private module Cached {
exists(Node n | getNodeEnclosingCallable(n) = callable | isUnreachableInCallCached(n, call))
}
+ cached
+ predicate allowParameterReturnInSelfCached(ParamNode p) { allowParameterReturnInSelf(p) }
+
cached
newtype TCallContext =
TAnyCallContext() or
@@ -937,7 +974,7 @@ class CallContextSpecificCall extends CallContextCall, TSpecificCall {
}
override predicate relevantFor(DataFlowCallable callable) {
- recordDataFlowCallSite(getCall(), callable)
+ recordDataFlowCallSite(this.getCall(), callable)
}
override predicate matchesCall(DataFlowCall call) { call = this.getCall() }
@@ -1257,7 +1294,7 @@ abstract class AccessPathFront extends TAccessPathFront {
TypedContent getHead() { this = TFrontHead(result) }
- predicate isClearedAt(Node n) { clearsContentCached(n, getHead().getContent()) }
+ predicate isClearedAt(Node n) { clearsContentCached(n, this.getHead().getContent()) }
}
class AccessPathFrontNil extends AccessPathFront, TFrontNil {
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplConsistency.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplConsistency.qll
index a55e65a81f6..acf31338f9a 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplConsistency.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplConsistency.qll
@@ -31,7 +31,7 @@ module Consistency {
query predicate uniqueEnclosingCallable(Node n, string msg) {
exists(int c |
n instanceof RelevantNode and
- c = count(n.getEnclosingCallable()) and
+ c = count(nodeGetEnclosingCallable(n)) and
c != 1 and
msg = "Node should have one enclosing callable but has " + c + "."
)
@@ -85,13 +85,13 @@ module Consistency {
}
query predicate parameterCallable(ParameterNode p, string msg) {
- exists(DataFlowCallable c | p.isParameterOf(c, _) and c != p.getEnclosingCallable()) and
+ exists(DataFlowCallable c | isParameterNode(p, c, _) and c != nodeGetEnclosingCallable(p)) and
msg = "Callable mismatch for parameter."
}
query predicate localFlowIsLocal(Node n1, Node n2, string msg) {
simpleLocalFlowStep(n1, n2) and
- n1.getEnclosingCallable() != n2.getEnclosingCallable() and
+ nodeGetEnclosingCallable(n1) != nodeGetEnclosingCallable(n2) and
msg = "Local flow step does not preserve enclosing callable."
}
@@ -106,7 +106,7 @@ module Consistency {
query predicate unreachableNodeCCtx(Node n, DataFlowCall call, string msg) {
isUnreachableInCall(n, call) and
exists(DataFlowCallable c |
- c = n.getEnclosingCallable() and
+ c = nodeGetEnclosingCallable(n) and
not viableCallable(call) = c
) and
msg = "Call context for isUnreachableInCall is inconsistent with call graph."
@@ -120,7 +120,7 @@ module Consistency {
n.(ArgumentNode).argumentOf(call, _) and
msg = "ArgumentNode and call does not share enclosing callable."
) and
- n.getEnclosingCallable() != call.getEnclosingCallable()
+ nodeGetEnclosingCallable(n) != call.getEnclosingCallable()
}
// This predicate helps the compiler forget that in some languages
@@ -151,7 +151,7 @@ module Consistency {
}
query predicate postIsInSameCallable(PostUpdateNode n, string msg) {
- n.getEnclosingCallable() != n.getPreUpdateNode().getEnclosingCallable() and
+ nodeGetEnclosingCallable(n) != nodeGetEnclosingCallable(n.getPreUpdateNode()) and
msg = "PostUpdateNode does not share callable with its pre-update node."
}
@@ -175,6 +175,7 @@ module Consistency {
query predicate postWithInFlow(Node n, string msg) {
isPostUpdateNode(n) and
+ not clearsContent(n, _) and
simpleLocalFlowStep(_, n) and
msg = "PostUpdateNode should not be the target of local flow."
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll
index 7767b644cbd..462a6a98328 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll
@@ -3,6 +3,12 @@ private import DataFlowUtil
private import DataFlowDispatch
private import FlowVar
+/** Gets the callable in which this node occurs. */
+DataFlowCallable nodeGetEnclosingCallable(Node n) { result = n.getEnclosingCallable() }
+
+/** Holds if `p` is a `ParameterNode` of `c` with position `pos`. */
+predicate isParameterNode(ParameterNode p, DataFlowCallable c, int pos) { p.isParameterOf(c, pos) }
+
/** Gets the instance argument of a non-static call. */
private Node getInstanceArgument(Call call) {
result.asExpr() = call.getQualifier()
@@ -287,3 +293,12 @@ predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { no
/** Extra data-flow steps needed for lambda flow analysis. */
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() }
+
+/**
+ * Holds if flow is allowed to pass from parameter `p` and back to itself as a
+ * side-effect, resulting in a summary from `p` to itself.
+ *
+ * One example would be to allow flow like `p.foo = p.bar;`, which is disallowed
+ * by default as a heuristic.
+ */
+predicate allowParameterReturnInSelf(ParameterNode p) { none() }
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll
index 2dfd02d14ef..c67374c3db9 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll
@@ -106,13 +106,13 @@ class Node extends TNode {
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
- getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
/**
* Gets an upper bound on the type of this node.
*/
- Type getTypeBound() { result = getType() }
+ Type getTypeBound() { result = this.getType() }
}
/**
@@ -293,11 +293,11 @@ abstract class PostUpdateNode extends Node {
*/
abstract Node getPreUpdateNode();
- override Function getFunction() { result = getPreUpdateNode().getFunction() }
+ override Function getFunction() { result = this.getPreUpdateNode().getFunction() }
- override Type getType() { result = getPreUpdateNode().getType() }
+ override Type getType() { result = this.getPreUpdateNode().getType() }
- override Location getLocation() { result = getPreUpdateNode().getLocation() }
+ override Location getLocation() { result = this.getPreUpdateNode().getLocation() }
}
abstract private class PartialDefinitionNode extends PostUpdateNode, TPartialDefinitionNode {
@@ -309,7 +309,7 @@ abstract private class PartialDefinitionNode extends PostUpdateNode, TPartialDef
PartialDefinition getPartialDefinition() { result = pd }
- override string toString() { result = getPreUpdateNode().toString() + " [post update]" }
+ override string toString() { result = this.getPreUpdateNode().toString() + " [post update]" }
}
private class VariablePartialDefinitionNode extends PartialDefinitionNode {
@@ -380,13 +380,13 @@ private class ObjectInitializerNode extends PostUpdateNode, TExprNode {
class PreObjectInitializerNode extends Node, TPreObjectInitializerNode {
Expr getExpr() { this = TPreObjectInitializerNode(result) }
- override Function getFunction() { result = getExpr().getEnclosingFunction() }
+ override Function getFunction() { result = this.getExpr().getEnclosingFunction() }
- override Type getType() { result = getExpr().getType() }
+ override Type getType() { result = this.getExpr().getType() }
- override Location getLocation() { result = getExpr().getLocation() }
+ override Location getLocation() { result = this.getExpr().getLocation() }
- override string toString() { result = getExpr().toString() + " [pre init]" }
+ override string toString() { result = this.getExpr().toString() + " [pre init]" }
}
/**
@@ -401,7 +401,7 @@ private class PostConstructorInitThis extends PostUpdateNode, TPostConstructorIn
}
override string toString() {
- result = getPreUpdateNode().getConstructorFieldInit().toString() + " [post-this]"
+ result = this.getPreUpdateNode().getConstructorFieldInit().toString() + " [post-this]"
}
}
@@ -416,15 +416,17 @@ private class PostConstructorInitThis extends PostUpdateNode, TPostConstructorIn
class PreConstructorInitThis extends Node, TPreConstructorInitThis {
ConstructorFieldInit getConstructorFieldInit() { this = TPreConstructorInitThis(result) }
- override Constructor getFunction() { result = getConstructorFieldInit().getEnclosingFunction() }
-
- override PointerType getType() {
- result.getBaseType() = getConstructorFieldInit().getEnclosingFunction().getDeclaringType()
+ override Constructor getFunction() {
+ result = this.getConstructorFieldInit().getEnclosingFunction()
}
- override Location getLocation() { result = getConstructorFieldInit().getLocation() }
+ override PointerType getType() {
+ result.getBaseType() = this.getConstructorFieldInit().getEnclosingFunction().getDeclaringType()
+ }
- override string toString() { result = getConstructorFieldInit().toString() + " [pre-this]" }
+ override Location getLocation() { result = this.getConstructorFieldInit().getLocation() }
+
+ override string toString() { result = this.getConstructorFieldInit().toString() + " [pre-this]" }
}
/**
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/FlowVar.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/FlowVar.qll
index e3f8b6f68fb..c01edf0429a 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/FlowVar.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/FlowVar.qll
@@ -354,7 +354,7 @@ module FlowVar_internal {
result = def.getAUse(v)
or
exists(SsaDefinition descendentDef |
- getASuccessorSsaVar+() = TSsaVar(descendentDef, _) and
+ this.getASuccessorSsaVar+() = TSsaVar(descendentDef, _) and
result = descendentDef.getAUse(v)
)
)
@@ -515,7 +515,7 @@ module FlowVar_internal {
this.bbInLoopCondition(bbInside) and
not this.bbInLoop(bbOutside) and
bbOutside = bbInside.getASuccessor() and
- not reachesWithoutAssignment(bbInside, v)
+ not this.reachesWithoutAssignment(bbInside, v)
}
/**
@@ -546,7 +546,7 @@ module FlowVar_internal {
private predicate bbInLoop(BasicBlock bb) {
bbDominates(this.(Loop).getStmt(), bb)
or
- bbInLoopCondition(bb)
+ this.bbInLoopCondition(bb)
}
/** Holds if `sbb` is inside this loop. */
@@ -563,7 +563,7 @@ module FlowVar_internal {
bb = this.(Loop).getStmt() and
v = this.getARelevantVariable()
or
- reachesWithoutAssignment(bb.getAPredecessor(), v) and
+ this.reachesWithoutAssignment(bb.getAPredecessor(), v) and
this.bbInLoop(bb)
) and
not assignsToVar(bb, v)
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/SubBasicBlocks.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/SubBasicBlocks.qll
index fa9d2e94081..4fbea43b805 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/SubBasicBlocks.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/SubBasicBlocks.qll
@@ -80,7 +80,7 @@ class SubBasicBlock extends ControlFlowNodeBase {
* returns a 0-based position, while `getRankInBasicBlock` returns a 1-based
* position.
*/
- deprecated int getPosInBasicBlock(BasicBlock bb) { result = getRankInBasicBlock(bb) - 1 }
+ deprecated int getPosInBasicBlock(BasicBlock bb) { result = this.getRankInBasicBlock(bb) - 1 }
pragma[noinline]
private int getIndexInBasicBlock(BasicBlock bb) { this = bb.getNode(result) }
@@ -102,7 +102,7 @@ class SubBasicBlock extends ControlFlowNodeBase {
exists(BasicBlock bb |
exists(int outerIndex |
result = bb.getNode(outerIndex) and
- index = outerToInnerIndex(bb, outerIndex)
+ index = this.outerToInnerIndex(bb, outerIndex)
)
)
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/tainttracking1/TaintTrackingImpl.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/tainttracking1/TaintTrackingImpl.qll
index f4f73b8247c..acb029c23d9 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/tainttracking1/TaintTrackingImpl.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/tainttracking1/TaintTrackingImpl.qll
@@ -75,24 +75,26 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isSanitizer(DataFlow::Node node) { none() }
final override predicate isBarrier(DataFlow::Node node) {
- isSanitizer(node) or
+ this.isSanitizer(node) or
defaultTaintSanitizer(node)
}
/** Holds if taint propagation into `node` is prohibited. */
predicate isSanitizerIn(DataFlow::Node node) { none() }
- final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) }
+ final override predicate isBarrierIn(DataFlow::Node node) { this.isSanitizerIn(node) }
/** Holds if taint propagation out of `node` is prohibited. */
predicate isSanitizerOut(DataFlow::Node node) { none() }
- final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) }
+ final override predicate isBarrierOut(DataFlow::Node node) { this.isSanitizerOut(node) }
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
- final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { isSanitizerGuard(guard) }
+ final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
+ this.isSanitizerGuard(guard)
+ }
/**
* Holds if the additional taint propagation step from `node1` to `node2`
@@ -101,7 +103,7 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
- isAdditionalTaintStep(node1, node2) or
+ this.isAdditionalTaintStep(node1, node2) or
defaultAdditionalTaintStep(node1, node2)
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/tainttracking2/TaintTrackingImpl.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/tainttracking2/TaintTrackingImpl.qll
index f4f73b8247c..acb029c23d9 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/tainttracking2/TaintTrackingImpl.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/dataflow/internal/tainttracking2/TaintTrackingImpl.qll
@@ -75,24 +75,26 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isSanitizer(DataFlow::Node node) { none() }
final override predicate isBarrier(DataFlow::Node node) {
- isSanitizer(node) or
+ this.isSanitizer(node) or
defaultTaintSanitizer(node)
}
/** Holds if taint propagation into `node` is prohibited. */
predicate isSanitizerIn(DataFlow::Node node) { none() }
- final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) }
+ final override predicate isBarrierIn(DataFlow::Node node) { this.isSanitizerIn(node) }
/** Holds if taint propagation out of `node` is prohibited. */
predicate isSanitizerOut(DataFlow::Node node) { none() }
- final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) }
+ final override predicate isBarrierOut(DataFlow::Node node) { this.isSanitizerOut(node) }
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
- final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { isSanitizerGuard(guard) }
+ final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
+ this.isSanitizerGuard(guard)
+ }
/**
* Holds if the additional taint propagation step from `node1` to `node2`
@@ -101,7 +103,7 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
- isAdditionalTaintStep(node1, node2) or
+ this.isAdditionalTaintStep(node1, node2) or
defaultAdditionalTaintStep(node1, node2)
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/Access.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/Access.qll
index e18c0c78dc6..35cf1974127 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/Access.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/Access.qll
@@ -203,7 +203,7 @@ class PointerFieldAccess extends FieldAccess {
PointerFieldAccess() {
exists(PointerType t |
- t = getQualifier().getFullyConverted().getUnspecifiedType() and
+ t = this.getQualifier().getFullyConverted().getUnspecifiedType() and
t.getBaseType() instanceof Class
)
}
@@ -218,7 +218,9 @@ class PointerFieldAccess extends FieldAccess {
class DotFieldAccess extends FieldAccess {
override string getAPrimaryQlClass() { result = "DotFieldAccess" }
- DotFieldAccess() { exists(Class c | c = getQualifier().getFullyConverted().getUnspecifiedType()) }
+ DotFieldAccess() {
+ exists(Class c | c = this.getQualifier().getFullyConverted().getUnspecifiedType())
+ }
}
/**
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/ArithmeticOperation.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/ArithmeticOperation.qll
index b94c9cee724..615192c86a7 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/ArithmeticOperation.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/ArithmeticOperation.qll
@@ -148,7 +148,7 @@ class PostfixIncrExpr extends IncrementOperation, PostfixCrementOperation, @post
override int getPrecedence() { result = 17 }
- override string toString() { result = "... " + getOperator() }
+ override string toString() { result = "... " + this.getOperator() }
}
/**
@@ -166,7 +166,7 @@ class PostfixDecrExpr extends DecrementOperation, PostfixCrementOperation, @post
override int getPrecedence() { result = 17 }
- override string toString() { result = "... " + getOperator() }
+ override string toString() { result = "... " + this.getOperator() }
}
/**
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/BuiltInOperations.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/BuiltInOperations.qll
index b1f97f18802..dcbedde4475 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/BuiltInOperations.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/BuiltInOperations.qll
@@ -35,12 +35,12 @@ class BuiltInVarArgsStart extends VarArgsExpr, @vastartexpr {
/**
* Gets the `va_list` argument.
*/
- final Expr getVAList() { result = getChild(0) }
+ final Expr getVAList() { result = this.getChild(0) }
/**
* Gets the argument that specifies the last named parameter before the ellipsis.
*/
- final VariableAccess getLastNamedParameter() { result = getChild(1) }
+ final VariableAccess getLastNamedParameter() { result = this.getChild(1) }
}
/**
@@ -60,7 +60,7 @@ class BuiltInVarArgsEnd extends VarArgsExpr, @vaendexpr {
/**
* Gets the `va_list` argument.
*/
- final Expr getVAList() { result = getChild(0) }
+ final Expr getVAList() { result = this.getChild(0) }
}
/**
@@ -78,7 +78,7 @@ class BuiltInVarArg extends VarArgsExpr, @vaargexpr {
/**
* Gets the `va_list` argument.
*/
- final Expr getVAList() { result = getChild(0) }
+ final Expr getVAList() { result = this.getChild(0) }
}
/**
@@ -98,12 +98,12 @@ class BuiltInVarArgCopy extends VarArgsExpr, @vacopyexpr {
/**
* Gets the destination `va_list` argument.
*/
- final Expr getDestinationVAList() { result = getChild(0) }
+ final Expr getDestinationVAList() { result = this.getChild(0) }
/**
* Gets the the source `va_list` argument.
*/
- final Expr getSourceVAList() { result = getChild(1) }
+ final Expr getSourceVAList() { result = this.getChild(1) }
}
/**
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/Call.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/Call.qll
index 6f6f710ac4b..b4761dffe9a 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/Call.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/Call.qll
@@ -71,10 +71,10 @@ class Call extends Expr, NameQualifiableElement, TCall {
* at index 2, respectively.
*/
Expr getAnArgumentSubExpr(int index) {
- result = getArgument(index)
+ result = this.getArgument(index)
or
exists(Expr mid |
- mid = getAnArgumentSubExpr(index) and
+ mid = this.getAnArgumentSubExpr(index) and
not mid instanceof Call and
not mid instanceof SizeofOperator and
result = mid.getAChild()
@@ -167,27 +167,27 @@ class FunctionCall extends Call, @funbindexpr {
override string getAPrimaryQlClass() { result = "FunctionCall" }
/** Gets an explicit template argument for this call. */
- Locatable getAnExplicitTemplateArgument() { result = getExplicitTemplateArgument(_) }
+ Locatable getAnExplicitTemplateArgument() { result = this.getExplicitTemplateArgument(_) }
/** Gets an explicit template argument value for this call. */
- Locatable getAnExplicitTemplateArgumentKind() { result = getExplicitTemplateArgumentKind(_) }
+ Locatable getAnExplicitTemplateArgumentKind() { result = this.getExplicitTemplateArgumentKind(_) }
/** Gets a template argument for this call. */
- Locatable getATemplateArgument() { result = getTarget().getATemplateArgument() }
+ Locatable getATemplateArgument() { result = this.getTarget().getATemplateArgument() }
/** Gets a template argument value for this call. */
- Locatable getATemplateArgumentKind() { result = getTarget().getATemplateArgumentKind() }
+ Locatable getATemplateArgumentKind() { result = this.getTarget().getATemplateArgumentKind() }
/** Gets the nth explicit template argument for this call. */
Locatable getExplicitTemplateArgument(int n) {
- n < getNumberOfExplicitTemplateArguments() and
- result = getTemplateArgument(n)
+ n < this.getNumberOfExplicitTemplateArguments() and
+ result = this.getTemplateArgument(n)
}
/** Gets the nth explicit template argument value for this call. */
Locatable getExplicitTemplateArgumentKind(int n) {
- n < getNumberOfExplicitTemplateArguments() and
- result = getTemplateArgumentKind(n)
+ n < this.getNumberOfExplicitTemplateArguments() and
+ result = this.getTemplateArgumentKind(n)
}
/** Gets the number of explicit template arguments for this call. */
@@ -198,19 +198,19 @@ class FunctionCall extends Call, @funbindexpr {
}
/** Gets the number of template arguments for this call. */
- int getNumberOfTemplateArguments() { result = count(int i | exists(getTemplateArgument(i))) }
+ int getNumberOfTemplateArguments() { result = count(int i | exists(this.getTemplateArgument(i))) }
/** Gets the nth template argument for this call (indexed from 0). */
- Locatable getTemplateArgument(int n) { result = getTarget().getTemplateArgument(n) }
+ Locatable getTemplateArgument(int n) { result = this.getTarget().getTemplateArgument(n) }
/** Gets the nth template argument value for this call (indexed from 0). */
- Locatable getTemplateArgumentKind(int n) { result = getTarget().getTemplateArgumentKind(n) }
+ Locatable getTemplateArgumentKind(int n) { result = this.getTarget().getTemplateArgumentKind(n) }
/** Holds if any template arguments for this call are implicit / deduced. */
predicate hasImplicitTemplateArguments() {
exists(int i |
- exists(getTemplateArgument(i)) and
- not exists(getExplicitTemplateArgument(i))
+ exists(this.getTemplateArgument(i)) and
+ not exists(this.getExplicitTemplateArgument(i))
)
}
@@ -233,9 +233,9 @@ class FunctionCall extends Call, @funbindexpr {
* visible at the call site.
*/
Type getExpectedReturnType() {
- if getTargetType() instanceof RoutineType
- then result = getTargetType().(RoutineType).getReturnType()
- else result = getTarget().getType()
+ if this.getTargetType() instanceof RoutineType
+ then result = this.getTargetType().(RoutineType).getReturnType()
+ else result = this.getTarget().getType()
}
/**
@@ -247,9 +247,9 @@ class FunctionCall extends Call, @funbindexpr {
* was visible at the call site.
*/
Type getExpectedParameterType(int n) {
- if getTargetType() instanceof RoutineType
- then result = getTargetType().(RoutineType).getParameterType(n)
- else result = getTarget().getParameter(n).getType()
+ if this.getTargetType() instanceof RoutineType
+ then result = this.getTargetType().(RoutineType).getParameterType(n)
+ else result = this.getTarget().getParameter(n).getType()
}
/**
@@ -263,7 +263,7 @@ class FunctionCall extends Call, @funbindexpr {
/**
* Gets the type of this expression, that is, the return type of the function being called.
*/
- override Type getType() { result = getExpectedReturnType() }
+ override Type getType() { result = this.getExpectedReturnType() }
/**
* Holds if this is a call to a virtual function.
@@ -280,7 +280,7 @@ class FunctionCall extends Call, @funbindexpr {
/** Gets a textual representation of this function call. */
override string toString() {
- if exists(getTarget())
+ if exists(this.getTarget())
then result = "call to " + this.getTarget().getName()
else result = "call to unknown function"
}
@@ -288,15 +288,15 @@ class FunctionCall extends Call, @funbindexpr {
override predicate mayBeImpure() {
this.getChild(_).mayBeImpure() or
this.getTarget().mayHaveSideEffects() or
- isVirtual() or
- getTarget().getAnAttribute().getName() = "weak"
+ this.isVirtual() or
+ this.getTarget().getAnAttribute().getName() = "weak"
}
override predicate mayBeGloballyImpure() {
this.getChild(_).mayBeGloballyImpure() or
this.getTarget().mayHaveSideEffects() or
- isVirtual() or
- getTarget().getAnAttribute().getName() = "weak"
+ this.isVirtual() or
+ this.getTarget().getAnAttribute().getName() = "weak"
}
}
@@ -367,7 +367,7 @@ class OverloadedPointerDereferenceExpr extends FunctionCall {
* ```
*/
class OverloadedArrayExpr extends FunctionCall {
- OverloadedArrayExpr() { getTarget().hasName("operator[]") }
+ OverloadedArrayExpr() { this.getTarget().hasName("operator[]") }
override string getAPrimaryQlClass() { result = "OverloadedArrayExpr" }
@@ -585,7 +585,7 @@ class ConstructorFieldInit extends ConstructorInit, @ctorfieldinit {
*/
Expr getExpr() { result = this.getChild(0) }
- override string toString() { result = "constructor init of field " + getTarget().getName() }
+ override string toString() { result = "constructor init of field " + this.getTarget().getName() }
override predicate mayBeImpure() { this.getExpr().mayBeImpure() }
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/Cast.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/Cast.qll
index ebe88ddf71c..273023a8229 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/Cast.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/Cast.qll
@@ -188,8 +188,8 @@ private predicate isPointerToMemberOrNullPointer(Type type) {
class ArithmeticConversion extends Cast {
ArithmeticConversion() {
conversionkinds(underlyingElement(this), 0) and
- isArithmeticOrEnum(getUnspecifiedType()) and
- isArithmeticOrEnum(getExpr().getUnspecifiedType())
+ isArithmeticOrEnum(this.getUnspecifiedType()) and
+ isArithmeticOrEnum(this.getExpr().getUnspecifiedType())
}
override string getSemanticConversionString() { result = "arithmetic conversion" }
@@ -204,8 +204,8 @@ class ArithmeticConversion extends Cast {
*/
class IntegralConversion extends ArithmeticConversion {
IntegralConversion() {
- isIntegralOrEnum(getUnspecifiedType()) and
- isIntegralOrEnum(getExpr().getUnspecifiedType())
+ isIntegralOrEnum(this.getUnspecifiedType()) and
+ isIntegralOrEnum(this.getExpr().getUnspecifiedType())
}
override string getAPrimaryQlClass() {
@@ -224,8 +224,8 @@ class IntegralConversion extends ArithmeticConversion {
*/
class FloatingPointConversion extends ArithmeticConversion {
FloatingPointConversion() {
- getUnspecifiedType() instanceof FloatingPointType and
- getExpr().getUnspecifiedType() instanceof FloatingPointType
+ this.getUnspecifiedType() instanceof FloatingPointType and
+ this.getExpr().getUnspecifiedType() instanceof FloatingPointType
}
override string getAPrimaryQlClass() {
@@ -244,8 +244,8 @@ class FloatingPointConversion extends ArithmeticConversion {
*/
class FloatingPointToIntegralConversion extends ArithmeticConversion {
FloatingPointToIntegralConversion() {
- isIntegralOrEnum(getUnspecifiedType()) and
- getExpr().getUnspecifiedType() instanceof FloatingPointType
+ isIntegralOrEnum(this.getUnspecifiedType()) and
+ this.getExpr().getUnspecifiedType() instanceof FloatingPointType
}
override string getAPrimaryQlClass() {
@@ -264,8 +264,8 @@ class FloatingPointToIntegralConversion extends ArithmeticConversion {
*/
class IntegralToFloatingPointConversion extends ArithmeticConversion {
IntegralToFloatingPointConversion() {
- getUnspecifiedType() instanceof FloatingPointType and
- isIntegralOrEnum(getExpr().getUnspecifiedType())
+ this.getUnspecifiedType() instanceof FloatingPointType and
+ isIntegralOrEnum(this.getExpr().getUnspecifiedType())
}
override string getAPrimaryQlClass() {
@@ -290,8 +290,8 @@ class IntegralToFloatingPointConversion extends ArithmeticConversion {
class PointerConversion extends Cast {
PointerConversion() {
conversionkinds(underlyingElement(this), 0) and
- isPointerOrNullPointer(getUnspecifiedType()) and
- isPointerOrNullPointer(getExpr().getUnspecifiedType())
+ isPointerOrNullPointer(this.getUnspecifiedType()) and
+ isPointerOrNullPointer(this.getExpr().getUnspecifiedType())
}
override string getAPrimaryQlClass() { not exists(qlCast(this)) and result = "PointerConversion" }
@@ -315,8 +315,8 @@ class PointerToMemberConversion extends Cast {
PointerToMemberConversion() {
conversionkinds(underlyingElement(this), 0) and
exists(Type fromType, Type toType |
- fromType = getExpr().getUnspecifiedType() and
- toType = getUnspecifiedType() and
+ fromType = this.getExpr().getUnspecifiedType() and
+ toType = this.getUnspecifiedType() and
isPointerToMemberOrNullPointer(fromType) and
isPointerToMemberOrNullPointer(toType) and
// A conversion from nullptr to nullptr is a `PointerConversion`, not a
@@ -345,8 +345,8 @@ class PointerToMemberConversion extends Cast {
class PointerToIntegralConversion extends Cast {
PointerToIntegralConversion() {
conversionkinds(underlyingElement(this), 0) and
- isIntegralOrEnum(getUnspecifiedType()) and
- isPointerOrNullPointer(getExpr().getUnspecifiedType())
+ isIntegralOrEnum(this.getUnspecifiedType()) and
+ isPointerOrNullPointer(this.getExpr().getUnspecifiedType())
}
override string getAPrimaryQlClass() {
@@ -366,8 +366,8 @@ class PointerToIntegralConversion extends Cast {
class IntegralToPointerConversion extends Cast {
IntegralToPointerConversion() {
conversionkinds(underlyingElement(this), 0) and
- isPointerOrNullPointer(getUnspecifiedType()) and
- isIntegralOrEnum(getExpr().getUnspecifiedType())
+ isPointerOrNullPointer(this.getUnspecifiedType()) and
+ isIntegralOrEnum(this.getExpr().getUnspecifiedType())
}
override string getAPrimaryQlClass() {
@@ -403,7 +403,7 @@ class BoolConversion extends Cast {
class VoidConversion extends Cast {
VoidConversion() {
conversionkinds(underlyingElement(this), 0) and
- getUnspecifiedType() instanceof VoidType
+ this.getUnspecifiedType() instanceof VoidType
}
override string getAPrimaryQlClass() { not exists(qlCast(this)) and result = "VoidConversion" }
@@ -434,8 +434,8 @@ class InheritanceConversion extends Cast {
* conversion is to an indirect virtual base class.
*/
final ClassDerivation getDerivation() {
- result.getBaseClass() = getBaseClass() and
- result.getDerivedClass() = getDerivedClass()
+ result.getBaseClass() = this.getBaseClass() and
+ result.getDerivedClass() = this.getDerivedClass()
}
/**
@@ -490,12 +490,12 @@ class BaseClassConversion extends InheritanceConversion {
override Class getBaseClass() { result = getConversionClass(this) }
- override Class getDerivedClass() { result = getConversionClass(getExpr()) }
+ override Class getDerivedClass() { result = getConversionClass(this.getExpr()) }
/**
* Holds if this conversion is to a virtual base class.
*/
- predicate isVirtual() { getDerivation().isVirtual() or not exists(getDerivation()) }
+ predicate isVirtual() { this.getDerivation().isVirtual() or not exists(this.getDerivation()) }
}
/**
@@ -515,7 +515,7 @@ class DerivedClassConversion extends InheritanceConversion {
override string getSemanticConversionString() { result = "derived class conversion" }
- override Class getBaseClass() { result = getConversionClass(getExpr()) }
+ override Class getBaseClass() { result = getConversionClass(this.getExpr()) }
override Class getDerivedClass() { result = getConversionClass(this) }
}
@@ -637,8 +637,8 @@ class DynamicCast extends Cast, @dynamic_cast {
*/
class UuidofOperator extends Expr, @uuidof {
override string toString() {
- if exists(getTypeOperand())
- then result = "__uuidof(" + getTypeOperand().getName() + ")"
+ if exists(this.getTypeOperand())
+ then result = "__uuidof(" + this.getTypeOperand().getName() + ")"
else result = "__uuidof(0)"
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/Expr.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/Expr.qll
index f77518c2f56..a10a94f357d 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/Expr.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/Expr.qll
@@ -26,12 +26,12 @@ class Expr extends StmtParent, @expr {
Function getEnclosingFunction() { result = exprEnclosingElement(this) }
/** Gets the nearest enclosing set of curly braces around this expression in the source, if any. */
- BlockStmt getEnclosingBlock() { result = getEnclosingStmt().getEnclosingBlock() }
+ BlockStmt getEnclosingBlock() { result = this.getEnclosingStmt().getEnclosingBlock() }
override Stmt getEnclosingStmt() {
result = this.getParent().(Expr).getEnclosingStmt()
or
- result = this.getParent().(Stmt)
+ result = this.getParent()
or
exists(Expr other | result = other.getEnclosingStmt() and other.getConversion() = this)
or
@@ -219,13 +219,13 @@ class Expr extends StmtParent, @expr {
* Holds if this expression is a _glvalue_. A _glvalue_ is either an _lvalue_ or an
* _xvalue_.
*/
- predicate isGLValueCategory() { isLValueCategory() or isXValueCategory() }
+ predicate isGLValueCategory() { this.isLValueCategory() or this.isXValueCategory() }
/**
* Holds if this expression is an _rvalue_. An _rvalue_ is either a _prvalue_ or an
* _xvalue_.
*/
- predicate isRValueCategory() { isPRValueCategory() or isXValueCategory() }
+ predicate isRValueCategory() { this.isPRValueCategory() or this.isXValueCategory() }
/**
* Gets a string representation of the value category of the expression.
@@ -240,15 +240,15 @@ class Expr extends StmtParent, @expr {
* `hasLValueToRvalueConversion()` holds.
*/
string getValueCategoryString() {
- isLValueCategory() and
+ this.isLValueCategory() and
result = "lvalue"
or
- isXValueCategory() and
+ this.isXValueCategory() and
result = "xvalue"
or
(
- isPRValueCategory() and
- if hasLValueToRValueConversion() then result = "prvalue(load)" else result = "prvalue"
+ this.isPRValueCategory() and
+ if this.hasLValueToRValueConversion() then result = "prvalue(load)" else result = "prvalue"
)
}
@@ -263,7 +263,7 @@ class Expr extends StmtParent, @expr {
* such as an expression inside a sizeof.
*/
predicate isUnevaluated() {
- exists(Element e | e = getParentWithConversions+() |
+ exists(Element e | e = this.getParentWithConversions+() |
e instanceof SizeofOperator
or
exists(Expr e2 |
@@ -279,7 +279,7 @@ class Expr extends StmtParent, @expr {
e instanceof AlignofOperator
)
or
- exists(Decltype d | d.getExpr() = getParentWithConversions*())
+ exists(Decltype d | d.getExpr() = this.getParentWithConversions*())
}
/**
@@ -725,7 +725,7 @@ class PointerDereferenceExpr extends UnaryOperation, @indirect {
*
* Gets the expression that is being dereferenced.
*/
- deprecated Expr getExpr() { result = getOperand() }
+ deprecated Expr getExpr() { result = this.getOperand() }
override string getOperator() { result = "*" }
@@ -780,15 +780,15 @@ class NewOrNewArrayExpr extends Expr, @any_new_expr {
* Gets the alignment argument passed to the allocation function, if any.
*/
Expr getAlignmentArgument() {
- hasAlignedAllocation() and
+ this.hasAlignedAllocation() and
(
// If we have an allocator call, the alignment is the second argument to
// that call.
- result = getAllocatorCall().getArgument(1)
+ result = this.getAllocatorCall().getArgument(1)
or
// Otherwise, the alignment winds up as child number 3 of the `new`
// itself.
- result = getChild(3)
+ result = this.getChild(3)
)
}
@@ -916,7 +916,7 @@ class NewArrayExpr extends NewOrNewArrayExpr, @new_array_expr {
* Gets the element type of the array being allocated.
*/
Type getAllocatedElementType() {
- result = getType().getUnderlyingType().(PointerType).getBaseType()
+ result = this.getType().getUnderlyingType().(PointerType).getBaseType()
}
/**
@@ -946,7 +946,12 @@ class DeleteExpr extends Expr, @delete_expr {
*/
Type getDeletedObjectType() {
result =
- getExpr().getFullyConverted().getType().stripTopLevelSpecifiers().(PointerType).getBaseType()
+ this.getExpr()
+ .getFullyConverted()
+ .getType()
+ .stripTopLevelSpecifiers()
+ .(PointerType)
+ .getBaseType()
}
/**
@@ -957,7 +962,7 @@ class DeleteExpr extends Expr, @delete_expr {
/**
* Gets the destructor to be called to destroy the object, if any.
*/
- Destructor getDestructor() { result = getDestructorCall().getTarget() }
+ Destructor getDestructor() { result = this.getDestructorCall().getTarget() }
/**
* Gets the `operator delete` that deallocates storage. Does not hold
@@ -1020,7 +1025,12 @@ class DeleteArrayExpr extends Expr, @delete_array_expr {
*/
Type getDeletedElementType() {
result =
- getExpr().getFullyConverted().getType().stripTopLevelSpecifiers().(PointerType).getBaseType()
+ this.getExpr()
+ .getFullyConverted()
+ .getType()
+ .stripTopLevelSpecifiers()
+ .(PointerType)
+ .getBaseType()
}
/**
@@ -1034,7 +1044,7 @@ class DeleteArrayExpr extends Expr, @delete_array_expr {
/**
* Gets the destructor to be called to destroy each element in the array, if any.
*/
- Destructor getDestructor() { result = getDestructorCall().getTarget() }
+ Destructor getDestructor() { result = this.getDestructorCall().getTarget() }
/**
* Gets the `operator delete[]` that deallocates storage.
@@ -1101,7 +1111,7 @@ class StmtExpr extends Expr, @expr_stmt {
* x = ({ dosomething(); a+b; });
* ```
*/
- Expr getResultExpr() { result = getStmtResultExpr(getStmt()) }
+ Expr getResultExpr() { result = getStmtResultExpr(this.getStmt()) }
}
/** Get the result expression of a statement. (Helper function for StmtExpr.) */
@@ -1230,20 +1240,20 @@ class FoldExpr extends Expr, @foldexpr {
predicate isRightFold() { fold(underlyingElement(this), _, false) }
/** Holds if this is a unary fold expression. */
- predicate isUnaryFold() { getNumChild() = 1 }
+ predicate isUnaryFold() { this.getNumChild() = 1 }
/** Holds if this is a binary fold expression. */
- predicate isBinaryFold() { getNumChild() = 2 }
+ predicate isBinaryFold() { this.getNumChild() = 2 }
/**
* Gets the child expression containing the unexpanded parameter pack.
*/
Expr getPackExpr() {
this.isUnaryFold() and
- result = getChild(0)
+ result = this.getChild(0)
or
this.isBinaryFold() and
- if this.isRightFold() then result = getChild(0) else result = getChild(1)
+ if this.isRightFold() then result = this.getChild(0) else result = this.getChild(1)
}
/**
@@ -1251,7 +1261,7 @@ class FoldExpr extends Expr, @foldexpr {
*/
Expr getInitExpr() {
this.isBinaryFold() and
- if this.isRightFold() then result = getChild(1) else result = getChild(0)
+ if this.isRightFold() then result = this.getChild(1) else result = this.getChild(0)
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/Lambda.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/Lambda.qll
index 8a51001f4d5..c885831c444 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/Lambda.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/Lambda.qll
@@ -24,7 +24,7 @@ class LambdaExpression extends Expr, @lambdaexpr {
/**
* Gets an implicitly or explicitly captured value of this lambda expression.
*/
- LambdaCapture getACapture() { result = getCapture(_) }
+ LambdaCapture getACapture() { result = this.getCapture(_) }
/**
* Gets the nth implicitly or explicitly captured value of this lambda expression.
@@ -58,13 +58,13 @@ class LambdaExpression extends Expr, @lambdaexpr {
* - The return type.
* - The statements comprising the lambda body.
*/
- Operator getLambdaFunction() { result = getType().(Closure).getLambdaFunction() }
+ Operator getLambdaFunction() { result = this.getType().(Closure).getLambdaFunction() }
/**
* Gets the initializer that initializes the captured variables in the closure, if any.
* A lambda that does not capture any variables will not have an initializer.
*/
- ClassAggregateLiteral getInitializer() { result = getChild(0) }
+ ClassAggregateLiteral getInitializer() { result = this.getChild(0) }
}
/**
@@ -103,7 +103,7 @@ class Closure extends Class {
* ```
*/
class LambdaCapture extends Locatable, @lambdacapture {
- override string toString() { result = getField().getName() }
+ override string toString() { result = this.getField().getName() }
override string getAPrimaryQlClass() { result = "LambdaCapture" }
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/Literal.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/Literal.qll
index 31790f85bfb..18ca03740ac 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/Literal.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/exprs/Literal.qll
@@ -60,12 +60,12 @@ class TextLiteral extends Literal {
/** Gets a hex escape sequence that appears in the character or string literal (see [lex.ccon] in the C++ Standard). */
string getAHexEscapeSequence(int occurrence, int offset) {
- result = getValueText().regexpFind("(?= 0 and
- elementIndex < getArraySize()
+ elementIndex < this.getArraySize()
}
/**
@@ -298,8 +298,8 @@ class ArrayOrVectorAggregateLiteral extends AggregateLiteral {
*/
bindingset[elementIndex]
predicate isValueInitialized(int elementIndex) {
- isInitialized(elementIndex) and
- not exists(getElementExpr(elementIndex))
+ this.isInitialized(elementIndex) and
+ not exists(this.getElementExpr(elementIndex))
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/internal/AddressConstantExpression.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/internal/AddressConstantExpression.qll
index 436be8384e8..b75703b11fc 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/internal/AddressConstantExpression.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/internal/AddressConstantExpression.qll
@@ -31,7 +31,7 @@ private predicate addressConstantVariable(Variable v) {
private predicate constantAddressLValue(Expr lvalue) {
lvalue.(VariableAccess).getTarget() =
any(Variable v |
- v.(Variable).isStatic()
+ v.isStatic()
or
v instanceof GlobalOrNamespaceVariable
)
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/internal/QualifiedName.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/internal/QualifiedName.qll
index 692ce1fee19..7cf0c647142 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/internal/QualifiedName.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/internal/QualifiedName.qll
@@ -173,7 +173,7 @@ class LocalVariable extends LocalScopeVariable, @localvariable { }
class VariableDeclarationEntry extends @var_decl {
string toString() { result = "QualifiedName DeclarationEntry" }
- Variable getDeclaration() { result = getVariable() }
+ Variable getDeclaration() { result = this.getVariable() }
/**
* Gets the variable which is being declared or defined.
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll
index ece55d181bf..626e81e095c 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll
@@ -474,6 +474,24 @@ module TaintedWithPath {
}
}
+ /**
+ * INTERNAL: Do not use.
+ */
+ module Private {
+ /** Gets a predecessor `PathNode` of `pathNode`, if any. */
+ PathNode getAPredecessor(PathNode pathNode) { edges(result, pathNode) }
+
+ /** Gets the element that `pathNode` wraps, if any. */
+ Element getElementFromPathNode(PathNode pathNode) {
+ exists(DataFlow::Node node | node = pathNode.(WrapPathNode).inner().getNode() |
+ result = node.asExpr() or
+ result = node.asParameter()
+ )
+ or
+ result = pathNode.(EndpointPathNode).inner()
+ }
+ }
+
private class WrapPathNode extends PathNode, TWrapPathNode {
DataFlow3::PathNode inner() { this = TWrapPathNode(result) }
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowDispatch.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowDispatch.qll
index 4ebd8cbf758..9b421df2df3 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowDispatch.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowDispatch.qll
@@ -63,8 +63,10 @@ private module VirtualDispatch {
|
// Call argument
exists(DataFlowCall call, int i |
- other.(DataFlow::ParameterNode).isParameterOf(call.getStaticCallTarget(), i) and
- src.(ArgumentNode).argumentOf(call, i)
+ other
+ .(DataFlow::ParameterNode)
+ .isParameterOf(pragma[only_bind_into](call).getStaticCallTarget(), i) and
+ src.(ArgumentNode).argumentOf(call, pragma[only_bind_into](pragma[only_bind_out](i)))
) and
allowOtherFromArg = true and
allowFromArg = true
@@ -128,6 +130,7 @@ private module VirtualDispatch {
*
* Used to fix a join ordering issue in flowsFrom.
*/
+ pragma[noinline]
private predicate returnNodeWithKindAndEnclosingCallable(
ReturnNode node, ReturnKind kind, DataFlowCallable callable
) {
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll
index f43a550af57..c28ceabb438 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll
@@ -2,6 +2,42 @@ private import DataFlowImplSpecific::Private
private import DataFlowImplSpecific::Public
import Cached
+module DataFlowImplCommonPublic {
+ private newtype TFlowFeature =
+ TFeatureHasSourceCallContext() or
+ TFeatureHasSinkCallContext() or
+ TFeatureEqualSourceSinkCallContext()
+
+ /** A flow configuration feature for use in `Configuration::getAFeature()`. */
+ class FlowFeature extends TFlowFeature {
+ string toString() { none() }
+ }
+
+ /**
+ * A flow configuration feature that implies that sources have some existing
+ * call context.
+ */
+ class FeatureHasSourceCallContext extends FlowFeature, TFeatureHasSourceCallContext {
+ override string toString() { result = "FeatureHasSourceCallContext" }
+ }
+
+ /**
+ * A flow configuration feature that implies that sinks have some existing
+ * call context.
+ */
+ class FeatureHasSinkCallContext extends FlowFeature, TFeatureHasSinkCallContext {
+ override string toString() { result = "FeatureHasSinkCallContext" }
+ }
+
+ /**
+ * A flow configuration feature that implies that source-sink pairs have some
+ * shared existing call context.
+ */
+ class FeatureEqualSourceSinkCallContext extends FlowFeature, TFeatureEqualSourceSinkCallContext {
+ override string toString() { result = "FeatureEqualSourceSinkCallContext" }
+ }
+}
+
/**
* The cost limits for the `AccessPathFront` to `AccessPathApprox` expansion.
*
@@ -251,7 +287,7 @@ private module Cached {
predicate forceCachingInSameStage() { any() }
cached
- predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = n.getEnclosingCallable() }
+ predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = nodeGetEnclosingCallable(n) }
cached
predicate callEnclosingCallable(DataFlowCall call, DataFlowCallable c) {
@@ -316,9 +352,7 @@ private module Cached {
}
cached
- predicate parameterNode(Node n, DataFlowCallable c, int i) {
- n.(ParameterNode).isParameterOf(c, i)
- }
+ predicate parameterNode(Node p, DataFlowCallable c, int pos) { isParameterNode(p, c, pos) }
cached
predicate argumentNode(Node n, DataFlowCall call, int pos) {
@@ -801,6 +835,9 @@ private module Cached {
exists(Node n | getNodeEnclosingCallable(n) = callable | isUnreachableInCallCached(n, call))
}
+ cached
+ predicate allowParameterReturnInSelfCached(ParamNode p) { allowParameterReturnInSelf(p) }
+
cached
newtype TCallContext =
TAnyCallContext() or
@@ -937,7 +974,7 @@ class CallContextSpecificCall extends CallContextCall, TSpecificCall {
}
override predicate relevantFor(DataFlowCallable callable) {
- recordDataFlowCallSite(getCall(), callable)
+ recordDataFlowCallSite(this.getCall(), callable)
}
override predicate matchesCall(DataFlowCall call) { call = this.getCall() }
@@ -1257,7 +1294,7 @@ abstract class AccessPathFront extends TAccessPathFront {
TypedContent getHead() { this = TFrontHead(result) }
- predicate isClearedAt(Node n) { clearsContentCached(n, getHead().getContent()) }
+ predicate isClearedAt(Node n) { clearsContentCached(n, this.getHead().getContent()) }
}
class AccessPathFrontNil extends AccessPathFront, TFrontNil {
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplConsistency.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplConsistency.qll
index a55e65a81f6..acf31338f9a 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplConsistency.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplConsistency.qll
@@ -31,7 +31,7 @@ module Consistency {
query predicate uniqueEnclosingCallable(Node n, string msg) {
exists(int c |
n instanceof RelevantNode and
- c = count(n.getEnclosingCallable()) and
+ c = count(nodeGetEnclosingCallable(n)) and
c != 1 and
msg = "Node should have one enclosing callable but has " + c + "."
)
@@ -85,13 +85,13 @@ module Consistency {
}
query predicate parameterCallable(ParameterNode p, string msg) {
- exists(DataFlowCallable c | p.isParameterOf(c, _) and c != p.getEnclosingCallable()) and
+ exists(DataFlowCallable c | isParameterNode(p, c, _) and c != nodeGetEnclosingCallable(p)) and
msg = "Callable mismatch for parameter."
}
query predicate localFlowIsLocal(Node n1, Node n2, string msg) {
simpleLocalFlowStep(n1, n2) and
- n1.getEnclosingCallable() != n2.getEnclosingCallable() and
+ nodeGetEnclosingCallable(n1) != nodeGetEnclosingCallable(n2) and
msg = "Local flow step does not preserve enclosing callable."
}
@@ -106,7 +106,7 @@ module Consistency {
query predicate unreachableNodeCCtx(Node n, DataFlowCall call, string msg) {
isUnreachableInCall(n, call) and
exists(DataFlowCallable c |
- c = n.getEnclosingCallable() and
+ c = nodeGetEnclosingCallable(n) and
not viableCallable(call) = c
) and
msg = "Call context for isUnreachableInCall is inconsistent with call graph."
@@ -120,7 +120,7 @@ module Consistency {
n.(ArgumentNode).argumentOf(call, _) and
msg = "ArgumentNode and call does not share enclosing callable."
) and
- n.getEnclosingCallable() != call.getEnclosingCallable()
+ nodeGetEnclosingCallable(n) != call.getEnclosingCallable()
}
// This predicate helps the compiler forget that in some languages
@@ -151,7 +151,7 @@ module Consistency {
}
query predicate postIsInSameCallable(PostUpdateNode n, string msg) {
- n.getEnclosingCallable() != n.getPreUpdateNode().getEnclosingCallable() and
+ nodeGetEnclosingCallable(n) != nodeGetEnclosingCallable(n.getPreUpdateNode()) and
msg = "PostUpdateNode does not share callable with its pre-update node."
}
@@ -175,6 +175,7 @@ module Consistency {
query predicate postWithInFlow(Node n, string msg) {
isPostUpdateNode(n) and
+ not clearsContent(n, _) and
simpleLocalFlowStep(_, n) and
msg = "PostUpdateNode should not be the target of local flow."
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll
index 00996a6ebfc..d454277a13c 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll
@@ -3,6 +3,12 @@ private import DataFlowUtil
private import semmle.code.cpp.ir.IR
private import DataFlowDispatch
+/** Gets the callable in which this node occurs. */
+DataFlowCallable nodeGetEnclosingCallable(Node n) { result = n.getEnclosingCallable() }
+
+/** Holds if `p` is a `ParameterNode` of `c` with position `pos`. */
+predicate isParameterNode(ParameterNode p, DataFlowCallable c, int pos) { p.isParameterOf(c, pos) }
+
/**
* A data flow node that occurs as the argument of a call and is passed as-is
* to the callable. Instance arguments (`this` pointer) and read side effects
@@ -106,11 +112,9 @@ class ReturnNode extends InstructionNode {
Instruction primary;
ReturnNode() {
- exists(ReturnValueInstruction ret | instr = ret.getReturnValue() and primary = ret)
+ exists(ReturnValueInstruction ret | instr = ret and primary = ret)
or
- exists(ReturnIndirectionInstruction rii |
- instr = rii.getSideEffectOperand().getAnyDef() and primary = rii
- )
+ exists(ReturnIndirectionInstruction rii | instr = rii and primary = rii)
}
/** Gets the kind of this returned value. */
@@ -184,108 +188,16 @@ OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) {
*/
predicate jumpStep(Node n1, Node n2) { none() }
-private predicate fieldStoreStepNoChi(Node node1, FieldContent f, PostUpdateNode node2) {
- exists(StoreInstruction store, Class c |
- store = node2.asInstruction() and
- store.getSourceValueOperand() = node1.asOperand() and
- getWrittenField(store, f.(FieldContent).getAField(), c) and
- f.hasOffset(c, _, _)
- )
-}
-
-private FieldAddressInstruction getFieldInstruction(Instruction instr) {
- result = instr or
- result = instr.(CopyValueInstruction).getUnary()
-}
-
-pragma[noinline]
-private predicate getWrittenField(Instruction instr, Field f, Class c) {
- exists(FieldAddressInstruction fa |
- fa =
- getFieldInstruction([
- instr.(StoreInstruction).getDestinationAddress(),
- instr.(WriteSideEffectInstruction).getDestinationAddress()
- ]) and
- f = fa.getField() and
- c = f.getDeclaringType()
- )
-}
-
-private predicate fieldStoreStepChi(Node node1, FieldContent f, PostUpdateNode node2) {
- exists(ChiPartialOperand operand, ChiInstruction chi |
- chi.getPartialOperand() = operand and
- node1.asOperand() = operand and
- node2.asInstruction() = chi and
- exists(Class c |
- c = chi.getResultType() and
- exists(int startBit, int endBit |
- chi.getUpdatedInterval(startBit, endBit) and
- f.hasOffset(c, startBit, endBit)
- )
- or
- getWrittenField(operand.getDef(), f.getAField(), c) and
- f.hasOffset(c, _, _)
- )
- )
-}
-
-private predicate arrayStoreStepChi(Node node1, ArrayContent a, PostUpdateNode node2) {
- exists(a) and
- exists(ChiPartialOperand operand, ChiInstruction chi, StoreInstruction store |
- chi.getPartialOperand() = operand and
- store = operand.getDef() and
- node1.asOperand() = operand and
- // This `ChiInstruction` will always have a non-conflated result because both `ArrayStoreNode`
- // and `PointerStoreNode` require it in their characteristic predicates.
- node2.asInstruction() = chi and
- (
- // `x[i] = taint()`
- // This matches the characteristic predicate in `ArrayStoreNode`.
- store.getDestinationAddress() instanceof PointerAddInstruction
- or
- // `*p = taint()`
- // This matches the characteristic predicate in `PointerStoreNode`.
- store.getDestinationAddress().(CopyValueInstruction).getUnary() instanceof LoadInstruction
- )
- )
-}
-
/**
* Holds if data can flow from `node1` to `node2` via an assignment to `f`.
* Thus, `node2` references an object with a field `f` that contains the
* value of `node1`.
*/
-predicate storeStep(Node node1, Content f, PostUpdateNode node2) {
- fieldStoreStepNoChi(node1, f, node2) or
- fieldStoreStepChi(node1, f, node2) or
- arrayStoreStepChi(node1, f, node2) or
- fieldStoreStepAfterArraySuppression(node1, f, node2)
-}
-
-// This predicate pushes the correct `FieldContent` onto the access path when the
-// `suppressArrayRead` predicate has popped off an `ArrayContent`.
-private predicate fieldStoreStepAfterArraySuppression(
- Node node1, FieldContent f, PostUpdateNode node2
-) {
- exists(WriteSideEffectInstruction write, ChiInstruction chi, Class c |
- not chi.isResultConflated() and
- node1.asInstruction() = chi and
- node2.asInstruction() = chi and
- chi.getPartial() = write and
- getWrittenField(write, f.getAField(), c) and
- f.hasOffset(c, _, _)
- )
-}
-
-bindingset[result, i]
-private int unbindInt(int i) { i <= result and i >= result }
-
-pragma[noinline]
-private predicate getLoadedField(LoadInstruction load, Field f, Class c) {
- exists(FieldAddressInstruction fa |
- fa = load.getSourceAddress() and
- f = fa.getField() and
- c = f.getDeclaringType()
+predicate storeStep(StoreNodeInstr node1, FieldContent f, StoreNodeInstr node2) {
+ exists(FieldAddressInstruction fai |
+ node1.getInstruction() = fai and
+ node2.getInstruction() = fai.getObjectAddress() and
+ f.getField() = fai.getField()
)
}
@@ -294,122 +206,14 @@ private predicate getLoadedField(LoadInstruction load, Field f, Class c) {
* Thus, `node1` references an object with a field `f` whose value ends up in
* `node2`.
*/
-private predicate fieldReadStep(Node node1, FieldContent f, Node node2) {
- exists(LoadOperand operand |
- node2.asOperand() = operand and
- node1.asInstruction() = operand.getAnyDef() and
- exists(Class c |
- c = operand.getAnyDef().getResultType() and
- exists(int startBit, int endBit |
- operand.getUsedInterval(unbindInt(startBit), unbindInt(endBit)) and
- f.hasOffset(c, startBit, endBit)
- )
- or
- getLoadedField(operand.getUse(), f.getAField(), c) and
- f.hasOffset(c, _, _)
- )
+predicate readStep(ReadNode node1, FieldContent f, ReadNode node2) {
+ exists(FieldAddressInstruction fai |
+ node1.getInstruction() = fai.getObjectAddress() and
+ node2.getInstruction() = fai and
+ f.getField() = fai.getField()
)
}
-/**
- * When a store step happens in a function that looks like an array write such as:
- * ```cpp
- * void f(int* pa) {
- * pa = source();
- * }
- * ```
- * it can be a write to an array, but it can also happen that `f` is called as `f(&a.x)`. If that is
- * the case, the `ArrayContent` that was written by the call to `f` should be popped off the access
- * path, and a `FieldContent` containing `x` should be pushed instead.
- * So this case pops `ArrayContent` off the access path, and the `fieldStoreStepAfterArraySuppression`
- * predicate in `storeStep` ensures that we push the right `FieldContent` onto the access path.
- */
-predicate suppressArrayRead(Node node1, ArrayContent a, Node node2) {
- exists(a) and
- exists(WriteSideEffectInstruction write, ChiInstruction chi |
- node1.asInstruction() = write and
- node2.asInstruction() = chi and
- chi.getPartial() = write and
- getWrittenField(write, _, _)
- )
-}
-
-private class ArrayToPointerConvertInstruction extends ConvertInstruction {
- ArrayToPointerConvertInstruction() {
- this.getUnary().getResultType() instanceof ArrayType and
- this.getResultType() instanceof PointerType
- }
-}
-
-private Instruction skipOneCopyValueInstructionRec(CopyValueInstruction copy) {
- copy.getUnary() = result and not result instanceof CopyValueInstruction
- or
- result = skipOneCopyValueInstructionRec(copy.getUnary())
-}
-
-private Instruction skipCopyValueInstructions(Operand op) {
- not result instanceof CopyValueInstruction and result = op.getDef()
- or
- result = skipOneCopyValueInstructionRec(op.getDef())
-}
-
-private predicate arrayReadStep(Node node1, ArrayContent a, Node node2) {
- exists(a) and
- // Explicit dereferences such as `*p` or `p[i]` where `p` is a pointer or array.
- exists(LoadOperand operand, Instruction address |
- operand.isDefinitionInexact() and
- node1.asInstruction() = operand.getAnyDef() and
- operand = node2.asOperand() and
- address = skipCopyValueInstructions(operand.getAddressOperand()) and
- (
- address instanceof LoadInstruction or
- address instanceof ArrayToPointerConvertInstruction or
- address instanceof PointerOffsetInstruction
- )
- )
-}
-
-/**
- * In cases such as:
- * ```cpp
- * void f(int* pa) {
- * *pa = source();
- * }
- * ...
- * int x;
- * f(&x);
- * use(x);
- * ```
- * the load on `x` in `use(x)` will exactly overlap with its definition (in this case the definition
- * is a `WriteSideEffect`). This predicate pops the `ArrayContent` (pushed by the store in `f`)
- * from the access path.
- */
-private predicate exactReadStep(Node node1, ArrayContent a, Node node2) {
- exists(a) and
- exists(WriteSideEffectInstruction write, ChiInstruction chi |
- not chi.isResultConflated() and
- chi.getPartial() = write and
- node1.asInstruction() = write and
- node2.asInstruction() = chi and
- // To distinquish this case from the `arrayReadStep` case we require that the entire variable was
- // overwritten by the `WriteSideEffectInstruction` (i.e., there is a load that reads the
- // entire variable).
- exists(LoadInstruction load | load.getSourceValue() = chi)
- )
-}
-
-/**
- * Holds if data can flow from `node1` to `node2` via a read of `f`.
- * Thus, `node1` references an object with a field `f` whose value ends up in
- * `node2`.
- */
-predicate readStep(Node node1, Content f, Node node2) {
- fieldReadStep(node1, f, node2) or
- arrayReadStep(node1, f, node2) or
- exactReadStep(node1, f, node2) or
- suppressArrayRead(node1, f, node2)
-}
-
/**
* Holds if values stored inside content `c` are cleared at node `n`.
*/
@@ -441,7 +245,7 @@ private predicate suppressUnusedNode(Node n) { any() }
// Java QL library compatibility wrappers
//////////////////////////////////////////////////////////////////////////////
/** A node that performs a type cast. */
-class CastNode extends InstructionNode {
+class CastNode extends Node {
CastNode() { none() } // stub implementation
}
@@ -507,3 +311,12 @@ predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { no
/** Extra data-flow steps needed for lambda flow analysis. */
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() }
+
+/**
+ * Holds if flow is allowed to pass from parameter `p` and back to itself as a
+ * side-effect, resulting in a summary from `p` to itself.
+ *
+ * One example would be to allow flow like `p.foo = p.bar;`, which is disallowed
+ * by default as a heuristic.
+ */
+predicate allowParameterReturnInSelf(ParameterNode p) { none() }
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll
index 9e7a95e010d..c3455d4790a 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll
@@ -10,19 +10,78 @@ private import semmle.code.cpp.ir.ValueNumbering
private import semmle.code.cpp.ir.IR
private import semmle.code.cpp.controlflow.IRGuards
private import semmle.code.cpp.models.interfaces.DataFlow
+private import DataFlowPrivate
+private import SsaInternals as Ssa
cached
private module Cached {
+ /**
+ * The IR dataflow graph consists of the following nodes:
+ * - `InstructionNode`, which represents an `Instruction` in the graph.
+ * - `OperandNode`, which represents an `Operand` in the graph.
+ * - `VariableNode`, which is used to model global variables.
+ * - Two kinds of `StoreNode`s:
+ * 1. `StoreNodeInstr`, which represents the value of an address computed by an `Instruction` that
+ * has been updated by a write operation.
+ * 2. `StoreNodeOperand`, which represents the value of an address in an `ArgumentOperand` after a
+ * function call that may have changed the value.
+ * - `ReadNode`, which represents the result of reading a field of an object.
+ * - `SsaPhiNode`, which represents phi nodes as computed by the shared SSA library.
+ *
+ * The following section describes how flow is generally transferred between these nodes:
+ * - Flow between `InstructionNode`s and `OperandNode`s follow the def-use information as computed by
+ * the IR. Because the IR compute must-alias information for memory operands, we only follow def-use
+ * flow for register operands.
+ * - Flow can enter a `StoreNode` in two ways (both done in `StoreNode.flowInto`):
+ * 1. Flow is transferred from a `StoreValueOperand` to a `StoreNodeInstr`. Flow will then proceed
+ * along the chain of addresses computed by `StoreNodeInstr.getInner` to identify field writes
+ * and call `storeStep` accordingly (i.e., for an expression like `a.b.c = x`, we visit `c`, then
+ * `b`, then `a`).
+ * 2. Flow is transfered from a `WriteSideEffectInstruction` to a `StoreNodeOperand` after flow
+ * returns to a caller. Flow will then proceed to the defining instruction of the operand (because
+ * the `StoreNodeInstr` computed by `StoreNodeOperand.getInner()` is the `StoreNode` containing
+ * the defining instruction), and then along the chain computed by `StoreNodeInstr.getInner` like
+ * above.
+ * In both cases, flow leaves a `StoreNode` once the entire chain has been traversed, and the shared
+ * SSA library is used to find the next use of the variable at the end of the chain.
+ * - Flow can enter a `ReadNode` through an `OperandNode` that represents an address of some variable.
+ * Flow will then proceed along the chain of addresses computed by `ReadNode.getOuter` (i.e., for an
+ * expression like `use(a.b.c)` we visit `a`, then `b`, then `c`) and call `readStep` accordingly.
+ * Once the entire chain has been traversed, flow is transferred to the load instruction that reads
+ * the final address of the chain.
+ * - Flow can enter a `SsaPhiNode` from an `InstructionNode`, a `StoreNode` or another `SsaPhiNode`
+ * (in `toPhiNode`), depending on which node provided the previous definition of the underlying
+ * variable. Flow leaves a `SsaPhiNode` (in `fromPhiNode`) by using the shared SSA library to
+ * determine the next use of the variable.
+ */
cached
newtype TIRDataFlowNode =
TInstructionNode(Instruction i) or
TOperandNode(Operand op) or
- TVariableNode(Variable var)
+ TVariableNode(Variable var) or
+ TStoreNodeInstr(Instruction i) { Ssa::explicitWrite(_, _, i) } or
+ TStoreNodeOperand(ArgumentOperand op) { Ssa::explicitWrite(_, _, op.getDef()) } or
+ TReadNode(Instruction i) { needsPostReadNode(i) } or
+ TSsaPhiNode(Ssa::PhiNode phi)
cached
predicate localFlowStepCached(Node nodeFrom, Node nodeTo) {
simpleLocalFlowStep(nodeFrom, nodeTo)
}
+
+ private predicate needsPostReadNode(Instruction iFrom) {
+ // If the instruction generates an address that flows to a load.
+ Ssa::addressFlowTC(iFrom, Ssa::getSourceAddress(_)) and
+ (
+ // And it is either a field address
+ iFrom instanceof FieldAddressInstruction
+ or
+ // Or it is instruction that either uses or is used for an address that needs a post read node.
+ exists(Instruction mid | needsPostReadNode(mid) |
+ Ssa::addressFlow(mid, iFrom) or Ssa::addressFlow(iFrom, mid)
+ )
+ )
+ }
}
private import Cached
@@ -110,7 +169,7 @@ class Node extends TIRDataFlowNode {
/**
* Gets an upper bound on the type of this node.
*/
- IRType getTypeBound() { result = getType() }
+ IRType getTypeBound() { result = this.getType() }
/** Gets the location of this element. */
Location getLocation() { none() } // overridden by subclasses
@@ -180,6 +239,234 @@ class OperandNode extends Node, TOperandNode {
override string toString() { result = this.getOperand().toString() }
}
+/**
+ * INTERNAL: do not use.
+ *
+ * A `StoreNode` is a node that has been (or is about to be) the
+ * source or target of a `storeStep`.
+ */
+abstract private class StoreNode extends Node {
+ /** Holds if this node should receive flow from `addr`. */
+ abstract predicate flowInto(Instruction addr);
+
+ override Declaration getEnclosingCallable() { result = this.getFunction() }
+
+ /** Holds if this `StoreNode` is the root of the address computation used by a store operation. */
+ predicate isTerminal() {
+ not exists(this.getInner()) and
+ not storeStep(this, _, _)
+ }
+
+ /** Gets the store operation that uses the address computed by this `StoreNode`. */
+ abstract Instruction getStoreInstruction();
+
+ /** Holds if the store operation associated with this `StoreNode` overwrites the entire variable. */
+ final predicate isCertain() { Ssa::explicitWrite(true, this.getStoreInstruction(), _) }
+
+ /**
+ * Gets the `StoreNode` that computes the address used by this `StoreNode`.
+ */
+ abstract StoreNode getInner();
+
+ /** The inverse of `StoreNode.getInner`. */
+ final StoreNode getOuter() { result.getInner() = this }
+}
+
+class StoreNodeInstr extends StoreNode, TStoreNodeInstr {
+ Instruction instr;
+
+ StoreNodeInstr() { this = TStoreNodeInstr(instr) }
+
+ override predicate flowInto(Instruction addr) { this.getInstruction() = addr }
+
+ /** Gets the underlying instruction. */
+ Instruction getInstruction() { result = instr }
+
+ override Function getFunction() { result = this.getInstruction().getEnclosingFunction() }
+
+ override IRType getType() { result = this.getInstruction().getResultIRType() }
+
+ override Location getLocation() { result = this.getInstruction().getLocation() }
+
+ override string toString() {
+ result = instructionNode(this.getInstruction()).toString() + " [store]"
+ }
+
+ override Instruction getStoreInstruction() {
+ Ssa::explicitWrite(_, result, this.getInstruction())
+ }
+
+ override StoreNodeInstr getInner() {
+ Ssa::addressFlow(result.getInstruction(), this.getInstruction())
+ }
+}
+
+/**
+ * To avoid having `PostUpdateNode`s with multiple pre-update nodes (which can cause performance
+ * problems) we attach the `PostUpdateNode` that represent output arguments to an operand instead of
+ * an instruction.
+ *
+ * To see why we need this, consider the expression `b->set(new C())`. The IR of this expression looks
+ * like (simplified):
+ * ```
+ * r1(glval) = FunctionAddress[set] :
+ * r2(glval) = FunctionAddress[operator new] :
+ * r3(unsigned long) = Constant[8] :
+ * r4(void *) = Call[operator new] : func:r2, 0:r3
+ * r5(C *) = Convert : r4
+ * r6(glval) = FunctionAddress[C] :
+ * v1(void) = Call[C] : func:r6, this:r5
+ * v2(void) = Call[set] : func:r1, this:r0, 0:r5
+ * ```
+ *
+ * Notice that both the call to `C` and the call to `set` will have an argument that is the
+ * result of calling `operator new` (i.e., `r4`). If we only have `PostUpdateNode`s that are
+ * instructions, both `PostUpdateNode`s would have `r4` as their pre-update node.
+ *
+ * We avoid this issue by having a `PostUpdateNode` for each argument, and let the pre-update node of
+ * each `PostUpdateNode` be the argument _operand_, instead of the defining instruction.
+ */
+class StoreNodeOperand extends StoreNode, TStoreNodeOperand {
+ ArgumentOperand operand;
+
+ StoreNodeOperand() { this = TStoreNodeOperand(operand) }
+
+ override predicate flowInto(Instruction addr) { this.getOperand().getDef() = addr }
+
+ /** Gets the underlying operand. */
+ Operand getOperand() { result = operand }
+
+ override Function getFunction() { result = operand.getDef().getEnclosingFunction() }
+
+ override IRType getType() { result = operand.getIRType() }
+
+ override Location getLocation() { result = operand.getLocation() }
+
+ override string toString() { result = operandNode(this.getOperand()).toString() + " [store]" }
+
+ override WriteSideEffectInstruction getStoreInstruction() {
+ Ssa::explicitWrite(_, result, operand.getDef())
+ }
+
+ /**
+ * The result of `StoreNodeOperand.getInner` is the `StoreNodeInstr` representation the instruction
+ * that defines this operand. This means the graph of `getInner` looks like this:
+ * ```
+ * I---I---I
+ * \ \ \
+ * O O O
+ * ```
+ * where each `StoreNodeOperand` "hooks" into the chain computed by `StoreNodeInstr.getInner`.
+ * This means that the chain of `getInner` calls on the argument `&o.f` on an expression
+ * like `func(&o.f)` is:
+ * ```
+ * r4---r3---r2
+ * \
+ * 0:r4
+ * ```
+ * where the IR for `func(&o.f)` looks like (simplified):
+ * ```
+ * r1(glval) = FunctionAddress[func] :
+ * r2(glval) = VariableAddress[o] :
+ * r3(glval) = FieldAddress[f] : r2
+ * r4(int *) = CopyValue : r3
+ * v1(void) = Call[func] : func:r1, 0:r4
+ * ```
+ */
+ override StoreNodeInstr getInner() { operand.getDef() = result.getInstruction() }
+}
+
+/**
+ * INTERNAL: do not use.
+ *
+ * A `ReadNode` is a node that has been (or is about to be) the
+ * source or target of a `readStep`.
+ */
+class ReadNode extends Node, TReadNode {
+ Instruction i;
+
+ ReadNode() { this = TReadNode(i) }
+
+ /** Gets the underlying instruction. */
+ Instruction getInstruction() { result = i }
+
+ override Declaration getEnclosingCallable() { result = this.getFunction() }
+
+ override Function getFunction() { result = this.getInstruction().getEnclosingFunction() }
+
+ override IRType getType() { result = this.getInstruction().getResultIRType() }
+
+ override Location getLocation() { result = this.getInstruction().getLocation() }
+
+ override string toString() {
+ result = instructionNode(this.getInstruction()).toString() + " [read]"
+ }
+
+ /** Gets a load instruction that uses the address computed by this read node. */
+ final Instruction getALoadInstruction() {
+ Ssa::addressFlowTC(this.getInstruction(), Ssa::getSourceAddress(result))
+ }
+
+ /**
+ * Gets a read node with an underlying instruction that is used by this
+ * underlying instruction to compute an address of a load instruction.
+ */
+ final ReadNode getInner() { Ssa::addressFlow(result.getInstruction(), this.getInstruction()) }
+
+ /** The inverse of `ReadNode.getInner`. */
+ final ReadNode getOuter() { result.getInner() = this }
+
+ /** Holds if this read node computes a value that will not be used for any future read nodes. */
+ final predicate isTerminal() {
+ not exists(this.getOuter()) and
+ not readStep(this, _, _)
+ }
+
+ /** Holds if this read node computes a value that has not yet been used for any read operations. */
+ final predicate isInitial() {
+ not exists(this.getInner()) and
+ not readStep(_, _, this)
+ }
+}
+
+/**
+ * INTERNAL: do not use.
+ *
+ * A phi node produced by the shared SSA library, viewed as a node in a data flow graph.
+ */
+class SsaPhiNode extends Node, TSsaPhiNode {
+ Ssa::PhiNode phi;
+
+ SsaPhiNode() { this = TSsaPhiNode(phi) }
+
+ /* Get the phi node associated with this node. */
+ Ssa::PhiNode getPhiNode() { result = phi }
+
+ override Declaration getEnclosingCallable() { result = this.getFunction() }
+
+ override Function getFunction() { result = phi.getBasicBlock().getEnclosingFunction() }
+
+ override IRType getType() { result instanceof IRVoidType }
+
+ override Location getLocation() { result = phi.getBasicBlock().getLocation() }
+
+ /** Holds if this phi node has input from the `rnk`'th write operation in block `block`. */
+ final predicate hasInputAtRankInBlock(IRBlock block, int rnk) {
+ hasInputAtRankInBlock(block, rnk, _)
+ }
+
+ /**
+ * Holds if this phi node has input from the definition `input` (which is the `rnk`'th write
+ * operation in block `block`).
+ */
+ cached
+ final predicate hasInputAtRankInBlock(IRBlock block, int rnk, Ssa::Definition input) {
+ Ssa::phiHasInputFromBlock(phi, input, _) and input.definesAt(_, block, rnk)
+ }
+
+ override string toString() { result = "Phi" }
+}
+
/**
* An expression, viewed as a node in a data flow graph.
*/
@@ -313,15 +600,14 @@ deprecated class UninitializedNode extends Node {
* Nodes corresponding to AST elements, for example `ExprNode`, usually refer
* to the value before the update with the exception of `ClassInstanceExpr`,
* which represents the value after the constructor has run.
- *
- * This class exists to match the interface used by Java. There are currently no non-abstract
- * classes that extend it. When we implement field flow, we can revisit this.
*/
-abstract class PostUpdateNode extends InstructionNode {
+abstract class PostUpdateNode extends Node {
/**
* Gets the node before the state update.
*/
abstract Node getPreUpdateNode();
+
+ override string toString() { result = this.getPreUpdateNode() + " [post update]" }
}
/**
@@ -332,7 +618,7 @@ abstract class PostUpdateNode extends InstructionNode {
* value, but does not necessarily replace it entirely. For example:
* ```
* x.y = 1; // a partial definition of the object `x`.
- * x.y.z = 1; // a partial definition of the object `x.y`.
+ * x.y.z = 1; // a partial definition of the object `x.y` and `x`.
* x.setY(1); // a partial definition of the object `x`.
* setY(&x); // a partial definition of the object `x`.
* ```
@@ -341,135 +627,34 @@ abstract private class PartialDefinitionNode extends PostUpdateNode {
abstract Expr getDefinedExpr();
}
-private class ExplicitFieldStoreQualifierNode extends PartialDefinitionNode {
- override ChiInstruction instr;
- StoreInstruction store;
-
- ExplicitFieldStoreQualifierNode() {
- not instr.isResultConflated() and
- instr.getPartial() = store and
- (
- instr.getUpdatedInterval(_, _) or
- store.getDestinationAddress() instanceof FieldAddressInstruction
- )
+private class FieldPartialDefinitionNode extends PartialDefinitionNode, StoreNodeInstr {
+ FieldPartialDefinitionNode() {
+ this.getInstruction() = any(FieldAddressInstruction fai).getObjectAddress()
}
- // By using an operand as the result of this predicate we avoid the dataflow inconsistency errors
- // caused by having multiple nodes sharing the same pre update node. This inconsistency error can cause
- // a tuple explosion in the big step dataflow relation since it can make many nodes be the entry node
- // into a big step.
- override Node getPreUpdateNode() { result.asOperand() = instr.getTotalOperand() }
+ override Node getPreUpdateNode() { result.asInstruction() = this.getInstruction() }
+
+ override Expr getDefinedExpr() { result = this.getInstruction().getUnconvertedResultExpression() }
+
+ override string toString() { result = PartialDefinitionNode.super.toString() }
+}
+
+private class NonPartialDefinitionPostUpdate extends PostUpdateNode, StoreNodeInstr {
+ NonPartialDefinitionPostUpdate() { not this instanceof PartialDefinitionNode }
+
+ override Node getPreUpdateNode() { result.asInstruction() = this.getInstruction() }
+
+ override string toString() { result = PostUpdateNode.super.toString() }
+}
+
+private class ArgumentPostUpdateNode extends PartialDefinitionNode, StoreNodeOperand {
+ override ArgumentNode getPreUpdateNode() { result.asOperand() = operand }
override Expr getDefinedExpr() {
- result =
- store
- .getDestinationAddress()
- .(FieldAddressInstruction)
- .getObjectAddress()
- .getUnconvertedResultExpression()
- }
-}
-
-/**
- * Not every store instruction generates a chi instruction that we can attach a PostUpdateNode to.
- * For instance, an update to a field of a struct containing only one field. Even if the store does
- * have a chi instruction, a subsequent use of the result of the store may be linked directly to the
- * result of the store as an inexact definition if the store totally overlaps the use. For these
- * cases we attach the PostUpdateNode to the store instruction. There's no obvious pre update node
- * for this case (as the entire memory is updated), so `getPreUpdateNode` is implemented as
- * `none()`.
- */
-private class ExplicitSingleFieldStoreQualifierNode extends PartialDefinitionNode {
- override StoreInstruction instr;
-
- ExplicitSingleFieldStoreQualifierNode() {
- (
- instr.getAUse().isDefinitionInexact()
- or
- not exists(ChiInstruction chi | chi.getPartial() = instr)
- ) and
- // Without this condition any store would create a `PostUpdateNode`.
- instr.getDestinationAddress() instanceof FieldAddressInstruction
+ result = this.getOperand().getDef().getUnconvertedResultExpression()
}
- override Node getPreUpdateNode() { none() }
-
- override Expr getDefinedExpr() {
- result =
- instr
- .getDestinationAddress()
- .(FieldAddressInstruction)
- .getObjectAddress()
- .getUnconvertedResultExpression()
- }
-}
-
-private FieldAddressInstruction getFieldInstruction(Instruction instr) {
- result = instr or
- result = instr.(CopyValueInstruction).getUnary()
-}
-
-/**
- * The target of a `fieldStoreStepAfterArraySuppression` store step, which is used to convert
- * an `ArrayContent` to a `FieldContent` when the `WriteSideEffect` instruction stores
- * into a field. See the QLDoc for `suppressArrayRead` for an example of where such a conversion
- * is inserted.
- */
-private class WriteSideEffectFieldStoreQualifierNode extends PartialDefinitionNode {
- override ChiInstruction instr;
- WriteSideEffectInstruction write;
- FieldAddressInstruction field;
-
- WriteSideEffectFieldStoreQualifierNode() {
- not instr.isResultConflated() and
- instr.getPartial() = write and
- field = getFieldInstruction(write.getDestinationAddress())
- }
-
- override Node getPreUpdateNode() { result.asOperand() = instr.getTotalOperand() }
-
- override Expr getDefinedExpr() {
- result = field.getObjectAddress().getUnconvertedResultExpression()
- }
-}
-
-/**
- * The `PostUpdateNode` that is the target of a `arrayStoreStepChi` store step. The overriden
- * `ChiInstruction` corresponds to the instruction represented by `node2` in `arrayStoreStepChi`.
- */
-private class ArrayStoreNode extends PartialDefinitionNode {
- override ChiInstruction instr;
- PointerAddInstruction add;
-
- ArrayStoreNode() {
- not instr.isResultConflated() and
- exists(StoreInstruction store |
- instr.getPartial() = store and
- add = store.getDestinationAddress()
- )
- }
-
- override Node getPreUpdateNode() { result.asOperand() = instr.getTotalOperand() }
-
- override Expr getDefinedExpr() { result = add.getLeft().getUnconvertedResultExpression() }
-}
-
-/**
- * The `PostUpdateNode` that is the target of a `arrayStoreStepChi` store step. The overriden
- * `ChiInstruction` corresponds to the instruction represented by `node2` in `arrayStoreStepChi`.
- */
-private class PointerStoreNode extends PostUpdateNode {
- override ChiInstruction instr;
-
- PointerStoreNode() {
- not instr.isResultConflated() and
- exists(StoreInstruction store |
- instr.getPartial() = store and
- store.getDestinationAddress().(CopyValueInstruction).getUnary() instanceof LoadInstruction
- )
- }
-
- override Node getPreUpdateNode() { result.asOperand() = instr.getTotalOperand() }
+ override string toString() { result = PartialDefinitionNode.super.toString() }
}
/**
@@ -548,6 +733,11 @@ class VariableNode extends Node, TVariableNode {
*/
InstructionNode instructionNode(Instruction instr) { result.getInstruction() = instr }
+/**
+ * Gets the node corresponding to `operand`.
+ */
+OperandNode operandNode(Operand operand) { result.getOperand() = operand }
+
/**
* DEPRECATED: use `definitionByReferenceNodeFromArgument` instead.
*
@@ -614,61 +804,167 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
or
// Instruction -> Operand flow
simpleOperandLocalFlowStep(nodeFrom.asInstruction(), nodeTo.asOperand())
+ or
+ // Flow into, through, and out of store nodes
+ StoreNodeFlow::flowInto(nodeFrom, nodeTo)
+ or
+ StoreNodeFlow::flowThrough(nodeFrom, nodeTo)
+ or
+ StoreNodeFlow::flowOutOf(nodeFrom, nodeTo)
+ or
+ // Flow into, through, and out of read nodes
+ ReadNodeFlow::flowInto(nodeFrom, nodeTo)
+ or
+ ReadNodeFlow::flowThrough(nodeFrom, nodeTo)
+ or
+ ReadNodeFlow::flowOutOf(nodeFrom, nodeTo)
+ or
+ // Adjacent-def-use and adjacent-use-use flow
+ adjacentDefUseFlow(nodeFrom, nodeTo)
}
-pragma[noinline]
-private predicate getFieldSizeOfClass(Class c, Type type, int size) {
- exists(Field f |
- f.getDeclaringType() = c and
- f.getUnderlyingType() = type and
- type.getSize() = size
+private predicate adjacentDefUseFlow(Node nodeFrom, Node nodeTo) {
+ // Flow that isn't already covered by field flow out of store/read nodes.
+ not nodeFrom.asInstruction() = any(StoreNode pun).getStoreInstruction() and
+ not nodeFrom.asInstruction() = any(ReadNode pun).getALoadInstruction() and
+ (
+ //Def-use flow
+ Ssa::ssaFlow(nodeFrom, nodeTo)
+ or
+ exists(Instruction loadAddress | loadAddress = Ssa::getSourceAddressFromNode(nodeFrom) |
+ // Use-use flow through reads
+ exists(Node address |
+ Ssa::addressFlowTC(address.asInstruction(), loadAddress) and
+ Ssa::ssaFlow(address, nodeTo)
+ )
+ or
+ // Use-use flow through stores.
+ exists(Node store |
+ Ssa::explicitWrite(_, store.asInstruction(), loadAddress) and
+ Ssa::ssaFlow(store, nodeTo)
+ )
+ )
)
}
-private predicate isSingleFieldClass(Type type, Operand op) {
- exists(int size, Class c |
- c = op.getType().getUnderlyingType() and
- c.getSize() = size and
- getFieldSizeOfClass(c, type, size)
- )
+private module ReadNodeFlow {
+ /** Holds if the read node `nodeTo` should receive flow from `nodeFrom`. */
+ predicate flowInto(Node nodeFrom, ReadNode nodeTo) {
+ nodeTo.isInitial() and
+ (
+ // If we entered through an address operand.
+ nodeFrom.asOperand().getDef() = nodeTo.getInstruction()
+ or
+ // If we entered flow through a memory-producing instruction.
+ // This can happen if we have flow to an `InitializeParameterIndirection` through
+ // a `ReadSideEffectInstruction`.
+ exists(Instruction load, Instruction def |
+ def = nodeFrom.asInstruction() and
+ def = Ssa::getSourceValueOperand(load).getAnyDef() and
+ not def = any(StoreNode store).getStoreInstruction() and
+ pragma[only_bind_into](nodeTo).getALoadInstruction() = load
+ )
+ )
+ }
+
+ /** Holds if the read node `nodeTo` should receive flow from the read node `nodeFrom`. */
+ predicate flowThrough(ReadNode nodeFrom, ReadNode nodeTo) {
+ not readStep(nodeFrom, _, _) and
+ nodeFrom.getOuter() = nodeTo
+ }
+
+ /**
+ * Holds if flow should leave the read node `nFrom` and enter the node `nodeTo`.
+ * This happens either because there is use-use flow from one of the variables used in
+ * the read operation, or because we have traversed all the field dereferences in the
+ * read operation.
+ */
+ predicate flowOutOf(ReadNode nFrom, Node nodeTo) {
+ // Use-use flow to another use of the same variable instruction
+ Ssa::ssaFlow(nFrom, nodeTo)
+ or
+ not exists(nFrom.getInner()) and
+ exists(Node store |
+ Ssa::explicitWrite(_, store.asInstruction(), nFrom.getInstruction()) and
+ Ssa::ssaFlow(store, nodeTo)
+ )
+ or
+ // Flow out of read nodes and into memory instructions if we cannot move any further through
+ // read nodes.
+ nFrom.isTerminal() and
+ (
+ exists(Instruction load |
+ load = nodeTo.asInstruction() and
+ Ssa::getSourceAddress(load) = nFrom.getInstruction()
+ )
+ or
+ exists(CallInstruction call, int i |
+ call.getArgument(i) = nodeTo.asInstruction() and
+ call.getArgument(i) = nFrom.getInstruction()
+ )
+ )
+ }
+}
+
+private module StoreNodeFlow {
+ /** Holds if the store node `nodeTo` should receive flow from `nodeFrom`. */
+ predicate flowInto(Node nodeFrom, StoreNode nodeTo) {
+ nodeTo.flowInto(Ssa::getDestinationAddress(nodeFrom.asInstruction()))
+ }
+
+ /** Holds if the store node `nodeTo` should receive flow from `nodeFom`. */
+ predicate flowThrough(StoreNode nFrom, StoreNode nodeTo) {
+ // Flow through a post update node that doesn't need a store step.
+ not storeStep(nFrom, _, _) and
+ nodeTo.getOuter() = nFrom
+ }
+
+ /**
+ * Holds if flow should leave the store node `nodeFrom` and enter the node `nodeTo`.
+ * This happens because we have traversed an entire chain of field dereferences
+ * after a store operation.
+ */
+ predicate flowOutOf(StoreNodeInstr nFrom, Node nodeTo) {
+ nFrom.isTerminal() and
+ Ssa::ssaFlow(nFrom, nodeTo)
+ }
}
private predicate simpleOperandLocalFlowStep(Instruction iFrom, Operand opTo) {
// Propagate flow from an instruction to its exact uses.
+ // We do this for all instruction/operand pairs, except when the operand is the
+ // side effect operand of a ReturnIndirectionInstruction, or the load operand of a LoadInstruction.
+ // This is because we get these flows through the shared SSA library already, and including this
+ // flow here will create multiple dataflow paths which creates a blowup in stage 3 of dataflow.
+ (
+ not any(ReturnIndirectionInstruction ret).getSideEffectOperand() = opTo and
+ not any(LoadInstruction load).getSourceValueOperand() = opTo and
+ not any(ReturnValueInstruction ret).getReturnValueOperand() = opTo
+ ) and
opTo.getDef() = iFrom
- or
- opTo = any(ReadSideEffectInstruction read).getSideEffectOperand() and
- not iFrom.isResultConflated() and
- iFrom = opTo.getAnyDef()
- or
- // Loading a single `int` from an `int *` parameter is not an exact load since
- // the parameter may point to an entire array rather than a single `int`. The
- // following rule ensures that any flow going into the
- // `InitializeIndirectionInstruction`, even if it's for a different array
- // element, will propagate to a load of the first element.
- //
- // Since we're linking `InitializeIndirectionInstruction` and
- // `LoadInstruction` together directly, this rule will break if there's any
- // reassignment of the parameter indirection, including a conditional one that
- // leads to a phi node.
- exists(InitializeIndirectionInstruction init |
- iFrom = init and
- opTo.(LoadOperand).getAnyDef() = init and
- // Check that the types match. Otherwise we can get flow from an object to
- // its fields, which leads to field conflation when there's flow from other
- // fields to the object elsewhere.
- init.getParameter().getType().getUnspecifiedType().(DerivedType).getBaseType() =
- opTo.getType().getUnspecifiedType()
- )
- or
- // Flow from stores to structs with a single field to a load of that field.
- exists(LoadInstruction load |
- load.getSourceValueOperand() = opTo and
- opTo.getAnyDef() = iFrom and
- isSingleFieldClass(pragma[only_bind_out](pragma[only_bind_out](iFrom).getResultType()), opTo)
+}
+
+pragma[noinline]
+private predicate getAddressType(LoadInstruction load, Type t) {
+ exists(Instruction address |
+ address = load.getSourceAddress() and
+ t = address.getResultType()
)
}
+/**
+ * Like the AST dataflow library, we want to conflate the address and value of a reference. This class
+ * represents the `LoadInstruction` that is generated from a reference dereference.
+ */
+private class ReferenceDereferenceInstruction extends LoadInstruction {
+ ReferenceDereferenceInstruction() {
+ exists(ReferenceType ref |
+ getAddressType(this, ref) and
+ this.getResultType() = ref.getBaseType()
+ )
+ }
+}
+
private predicate simpleInstructionLocalFlowStep(Operand opFrom, Instruction iTo) {
iTo.(CopyInstruction).getSourceValueOperand() = opFrom
or
@@ -681,40 +977,8 @@ private predicate simpleInstructionLocalFlowStep(Operand opFrom, Instruction iTo
or
iTo.(InheritanceConversionInstruction).getUnaryOperand() = opFrom
or
- // A chi instruction represents a point where a new value (the _partial_
- // operand) may overwrite an old value (the _total_ operand), but the alias
- // analysis couldn't determine that it surely will overwrite every bit of it or
- // that it surely will overwrite no bit of it.
- //
- // By allowing flow through the total operand, we ensure that flow is not lost
- // due to shortcomings of the alias analysis. We may get false flow in cases
- // where the data is indeed overwritten.
- //
- // Flow through the partial operand belongs in the taint-tracking libraries
- // for now.
- iTo.getAnOperand().(ChiTotalOperand) = opFrom
- or
- // Add flow from write side-effects to non-conflated chi instructions through their
- // partial operands. From there, a `readStep` will find subsequent reads of that field.
- // Consider the following example:
- // ```
- // void setX(Point* p, int new_x) {
- // p->x = new_x;
- // }
- // ...
- // setX(&p, taint());
- // ```
- // Here, a `WriteSideEffectInstruction` will provide a new definition for `p->x` after the call to
- // `setX`, which will be melded into `p` through a chi instruction.
- exists(ChiInstruction chi | chi = iTo |
- opFrom.getAnyDef() instanceof WriteSideEffectInstruction and
- chi.getPartialOperand() = opFrom and
- not chi.isResultConflated() and
- // In a call such as `set_value(&x->val);` we don't want the memory representing `x` to receive
- // dataflow by a simple step. Instead, this is handled by field flow. If we add a simple step here
- // we can get field-to-object flow.
- not chi.isPartialUpdate()
- )
+ // Conflate references and values like in AST dataflow.
+ iTo.(ReferenceDereferenceInstruction).getSourceAddressOperand() = opFrom
or
// Flow through modeled functions
modelFlow(opFrom, iTo)
@@ -788,25 +1052,14 @@ predicate localInstructionFlow(Instruction e1, Instruction e2) {
*/
predicate localExprFlow(Expr e1, Expr e2) { localFlow(exprNode(e1), exprNode(e2)) }
-/**
- * Gets a field corresponding to the bit range `[startBit..endBit)` of class `c`, if any.
- */
-private Field getAField(Class c, int startBit, int endBit) {
- result.getDeclaringType() = c and
- startBit = 8 * result.getByteOffset() and
- endBit = 8 * result.getType().getSize() + startBit
- or
- exists(Field f, Class cInner |
- f = c.getAField() and
- cInner = f.getUnderlyingType() and
- result = getAField(cInner, startBit - 8 * f.getByteOffset(), endBit - 8 * f.getByteOffset())
- )
-}
-
private newtype TContent =
- TFieldContent(Class c, int startBit, int endBit) { exists(getAField(c, startBit, endBit)) } or
- TCollectionContent() or
- TArrayContent()
+ TFieldContent(Field f) {
+ // As reads and writes to union fields can create flow even though the reads and writes
+ // target different fields, we don't want a read (write) to create a read (write) step.
+ not f.getDeclaringType() instanceof Union
+ } or
+ TCollectionContent() or // Not used in C/C++
+ TArrayContent() // Not used in C/C++.
/**
* A description of the way data may be stored inside an object. Examples
@@ -824,18 +1077,13 @@ class Content extends TContent {
/** A reference through an instance field. */
class FieldContent extends Content, TFieldContent {
- Class c;
- int startBit;
- int endBit;
+ Field f;
- FieldContent() { this = TFieldContent(c, startBit, endBit) }
+ FieldContent() { this = TFieldContent(f) }
- // Ensure that there's just 1 result for `toString`.
- override string toString() { result = min(Field f | f = getAField() | f.toString()) }
+ override string toString() { result = f.toString() }
- predicate hasOffset(Class cl, int start, int end) { cl = c and start = startBit and end = endBit }
-
- Field getAField() { result = getAField(c, startBit, endBit) }
+ Field getField() { result = f }
}
/** A reference through an array. */
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplCommon.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplCommon.qll
new file mode 100644
index 00000000000..eae5d23f544
--- /dev/null
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplCommon.qll
@@ -0,0 +1,636 @@
+/**
+ * Provides a language-independent implementation of static single assignment
+ * (SSA) form.
+ */
+
+private import SsaImplSpecific
+
+private BasicBlock getABasicBlockPredecessor(BasicBlock bb) { getABasicBlockSuccessor(result) = bb }
+
+/**
+ * Liveness analysis (based on source variables) to restrict the size of the
+ * SSA representation.
+ */
+private module Liveness {
+ /**
+ * A classification of variable references into reads (of a given kind) and
+ * (certain or uncertain) writes.
+ */
+ private newtype TRefKind =
+ Read(boolean certain) { certain in [false, true] } or
+ Write(boolean certain) { certain in [false, true] }
+
+ private class RefKind extends TRefKind {
+ string toString() {
+ exists(boolean certain | this = Read(certain) and result = "read (" + certain + ")")
+ or
+ exists(boolean certain | this = Write(certain) and result = "write (" + certain + ")")
+ }
+
+ int getOrder() {
+ this = Read(_) and
+ result = 0
+ or
+ this = Write(_) and
+ result = 1
+ }
+ }
+
+ /**
+ * Holds if the `i`th node of basic block `bb` is a reference to `v` of kind `k`.
+ */
+ private predicate ref(BasicBlock bb, int i, SourceVariable v, RefKind k) {
+ exists(boolean certain | variableRead(bb, i, v, certain) | k = Read(certain))
+ or
+ exists(boolean certain | variableWrite(bb, i, v, certain) | k = Write(certain))
+ }
+
+ private newtype OrderedRefIndex =
+ MkOrderedRefIndex(int i, int tag) {
+ exists(RefKind rk | ref(_, i, _, rk) | tag = rk.getOrder())
+ }
+
+ private OrderedRefIndex refOrd(BasicBlock bb, int i, SourceVariable v, RefKind k, int ord) {
+ ref(bb, i, v, k) and
+ result = MkOrderedRefIndex(i, ord) and
+ ord = k.getOrder()
+ }
+
+ /**
+ * Gets the (1-based) rank of the reference to `v` at the `i`th node of
+ * basic block `bb`, which has the given reference kind `k`.
+ *
+ * Reads are considered before writes when they happen at the same index.
+ */
+ private int refRank(BasicBlock bb, int i, SourceVariable v, RefKind k) {
+ refOrd(bb, i, v, k, _) =
+ rank[result](int j, int ord, OrderedRefIndex res |
+ res = refOrd(bb, j, v, _, ord)
+ |
+ res order by j, ord
+ )
+ }
+
+ private int maxRefRank(BasicBlock bb, SourceVariable v) {
+ result = refRank(bb, _, v, _) and
+ not result + 1 = refRank(bb, _, v, _)
+ }
+
+ /**
+ * Gets the (1-based) rank of the first reference to `v` inside basic block `bb`
+ * that is either a read or a certain write.
+ */
+ private int firstReadOrCertainWrite(BasicBlock bb, SourceVariable v) {
+ result =
+ min(int r, RefKind k |
+ r = refRank(bb, _, v, k) and
+ k != Write(false)
+ |
+ r
+ )
+ }
+
+ /**
+ * Holds if source variable `v` is live at the beginning of basic block `bb`.
+ */
+ predicate liveAtEntry(BasicBlock bb, SourceVariable v) {
+ // The first read or certain write to `v` inside `bb` is a read
+ refRank(bb, _, v, Read(_)) = firstReadOrCertainWrite(bb, v)
+ or
+ // There is no certain write to `v` inside `bb`, but `v` is live at entry
+ // to a successor basic block of `bb`
+ not exists(firstReadOrCertainWrite(bb, v)) and
+ liveAtExit(bb, v)
+ }
+
+ /**
+ * Holds if source variable `v` is live at the end of basic block `bb`.
+ */
+ predicate liveAtExit(BasicBlock bb, SourceVariable v) {
+ liveAtEntry(getABasicBlockSuccessor(bb), v)
+ }
+
+ /**
+ * Holds if variable `v` is live in basic block `bb` at index `i`.
+ * The rank of `i` is `rnk` as defined by `refRank()`.
+ */
+ private predicate liveAtRank(BasicBlock bb, int i, SourceVariable v, int rnk) {
+ exists(RefKind kind | rnk = refRank(bb, i, v, kind) |
+ rnk = maxRefRank(bb, v) and
+ liveAtExit(bb, v)
+ or
+ ref(bb, i, v, kind) and
+ kind = Read(_)
+ or
+ exists(RefKind nextKind |
+ liveAtRank(bb, _, v, rnk + 1) and
+ rnk + 1 = refRank(bb, _, v, nextKind) and
+ nextKind != Write(true)
+ )
+ )
+ }
+
+ /**
+ * Holds if variable `v` is live after the (certain or uncertain) write at
+ * index `i` inside basic block `bb`.
+ */
+ predicate liveAfterWrite(BasicBlock bb, int i, SourceVariable v) {
+ exists(int rnk | rnk = refRank(bb, i, v, Write(_)) | liveAtRank(bb, i, v, rnk))
+ }
+}
+
+private import Liveness
+
+/**
+ * Holds if `df` is in the dominance frontier of `bb`.
+ *
+ * This is equivalent to:
+ *
+ * ```ql
+ * bb = getImmediateBasicBlockDominator*(getABasicBlockPredecessor(df)) and
+ * not bb = getImmediateBasicBlockDominator+(df)
+ * ```
+ */
+private predicate inDominanceFrontier(BasicBlock bb, BasicBlock df) {
+ bb = getABasicBlockPredecessor(df) and not bb = getImmediateBasicBlockDominator(df)
+ or
+ exists(BasicBlock prev | inDominanceFrontier(prev, df) |
+ bb = getImmediateBasicBlockDominator(prev) and
+ not bb = getImmediateBasicBlockDominator(df)
+ )
+}
+
+/**
+ * Holds if `bb` is in the dominance frontier of a block containing a
+ * definition of `v`.
+ */
+pragma[noinline]
+private predicate inDefDominanceFrontier(BasicBlock bb, SourceVariable v) {
+ exists(BasicBlock defbb, Definition def |
+ def.definesAt(v, defbb, _) and
+ inDominanceFrontier(defbb, bb)
+ )
+}
+
+cached
+newtype TDefinition =
+ TWriteDef(SourceVariable v, BasicBlock bb, int i) {
+ variableWrite(bb, i, v, _) and
+ liveAfterWrite(bb, i, v)
+ } or
+ TPhiNode(SourceVariable v, BasicBlock bb) {
+ inDefDominanceFrontier(bb, v) and
+ liveAtEntry(bb, v)
+ }
+
+private module SsaDefReaches {
+ newtype TSsaRefKind =
+ SsaRead() or
+ SsaDef()
+
+ /**
+ * A classification of SSA variable references into reads and definitions.
+ */
+ class SsaRefKind extends TSsaRefKind {
+ string toString() {
+ this = SsaRead() and
+ result = "SsaRead"
+ or
+ this = SsaDef() and
+ result = "SsaDef"
+ }
+
+ int getOrder() {
+ this = SsaRead() and
+ result = 0
+ or
+ this = SsaDef() and
+ result = 1
+ }
+ }
+
+ /**
+ * Holds if the `i`th node of basic block `bb` is a reference to `v`,
+ * either a read (when `k` is `SsaRead()`) or an SSA definition (when `k`
+ * is `SsaDef()`).
+ *
+ * Unlike `Liveness::ref`, this includes `phi` nodes.
+ */
+ predicate ssaRef(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) {
+ variableRead(bb, i, v, _) and
+ k = SsaRead()
+ or
+ exists(Definition def | def.definesAt(v, bb, i)) and
+ k = SsaDef()
+ }
+
+ private newtype OrderedSsaRefIndex =
+ MkOrderedSsaRefIndex(int i, SsaRefKind k) { ssaRef(_, i, _, k) }
+
+ private OrderedSsaRefIndex ssaRefOrd(BasicBlock bb, int i, SourceVariable v, SsaRefKind k, int ord) {
+ ssaRef(bb, i, v, k) and
+ result = MkOrderedSsaRefIndex(i, k) and
+ ord = k.getOrder()
+ }
+
+ /**
+ * Gets the (1-based) rank of the reference to `v` at the `i`th node of basic
+ * block `bb`, which has the given reference kind `k`.
+ *
+ * For example, if `bb` is a basic block with a phi node for `v` (considered
+ * to be at index -1), reads `v` at node 2, and defines it at node 5, we have:
+ *
+ * ```ql
+ * ssaRefRank(bb, -1, v, SsaDef()) = 1 // phi node
+ * ssaRefRank(bb, 2, v, Read()) = 2 // read at node 2
+ * ssaRefRank(bb, 5, v, SsaDef()) = 3 // definition at node 5
+ * ```
+ *
+ * Reads are considered before writes when they happen at the same index.
+ */
+ int ssaRefRank(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) {
+ ssaRefOrd(bb, i, v, k, _) =
+ rank[result](int j, int ord, OrderedSsaRefIndex res |
+ res = ssaRefOrd(bb, j, v, _, ord)
+ |
+ res order by j, ord
+ )
+ }
+
+ int maxSsaRefRank(BasicBlock bb, SourceVariable v) {
+ result = ssaRefRank(bb, _, v, _) and
+ not result + 1 = ssaRefRank(bb, _, v, _)
+ }
+
+ /**
+ * Holds if the SSA definition `def` reaches rank index `rnk` in its own
+ * basic block `bb`.
+ */
+ predicate ssaDefReachesRank(BasicBlock bb, Definition def, int rnk, SourceVariable v) {
+ exists(int i |
+ rnk = ssaRefRank(bb, i, v, SsaDef()) and
+ def.definesAt(v, bb, i)
+ )
+ or
+ ssaDefReachesRank(bb, def, rnk - 1, v) and
+ rnk = ssaRefRank(bb, _, v, SsaRead())
+ }
+
+ /**
+ * Holds if the SSA definition of `v` at `def` reaches index `i` in the same
+ * basic block `bb`, without crossing another SSA definition of `v`.
+ */
+ predicate ssaDefReachesReadWithinBlock(SourceVariable v, Definition def, BasicBlock bb, int i) {
+ exists(int rnk |
+ ssaDefReachesRank(bb, def, rnk, v) and
+ rnk = ssaRefRank(bb, i, v, SsaRead())
+ )
+ }
+
+ /**
+ * Holds if the SSA definition of `v` at `def` reaches uncertain SSA definition
+ * `redef` in the same basic block, without crossing another SSA definition of `v`.
+ */
+ predicate ssaDefReachesUncertainDefWithinBlock(
+ SourceVariable v, Definition def, UncertainWriteDefinition redef
+ ) {
+ exists(BasicBlock bb, int rnk, int i |
+ ssaDefReachesRank(bb, def, rnk, v) and
+ rnk = ssaRefRank(bb, i, v, SsaDef()) - 1 and
+ redef.definesAt(v, bb, i)
+ )
+ }
+
+ /**
+ * Same as `ssaRefRank()`, but restricted to a particular SSA definition `def`.
+ */
+ int ssaDefRank(Definition def, SourceVariable v, BasicBlock bb, int i, SsaRefKind k) {
+ v = def.getSourceVariable() and
+ result = ssaRefRank(bb, i, v, k) and
+ (
+ ssaDefReachesRead(_, def, bb, i)
+ or
+ def.definesAt(_, bb, i)
+ )
+ }
+
+ /**
+ * Holds if the reference to `def` at index `i` in basic block `bb` is the
+ * last reference to `v` inside `bb`.
+ */
+ pragma[noinline]
+ predicate lastSsaRef(Definition def, SourceVariable v, BasicBlock bb, int i) {
+ ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v)
+ }
+
+ predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v) {
+ exists(ssaDefRank(def, v, bb, _, _))
+ }
+
+ pragma[noinline]
+ private predicate ssaDefReachesThroughBlock(Definition def, BasicBlock bb) {
+ ssaDefReachesEndOfBlock(bb, def, _) and
+ not defOccursInBlock(_, bb, def.getSourceVariable())
+ }
+
+ /**
+ * Holds if `def` is accessed in basic block `bb1` (either a read or a write),
+ * `bb2` is a transitive successor of `bb1`, `def` is live at the end of `bb1`,
+ * and the underlying variable for `def` is neither read nor written in any block
+ * on the path between `bb1` and `bb2`.
+ */
+ predicate varBlockReaches(Definition def, BasicBlock bb1, BasicBlock bb2) {
+ defOccursInBlock(def, bb1, _) and
+ bb2 = getABasicBlockSuccessor(bb1)
+ or
+ exists(BasicBlock mid |
+ varBlockReaches(def, bb1, mid) and
+ ssaDefReachesThroughBlock(def, mid) and
+ bb2 = getABasicBlockSuccessor(mid)
+ )
+ }
+
+ /**
+ * Holds if `def` is accessed in basic block `bb1` (either a read or a write),
+ * `def` is read at index `i2` in basic block `bb2`, `bb2` is in a transitive
+ * successor block of `bb1`, and `def` is neither read nor written in any block
+ * on a path between `bb1` and `bb2`.
+ */
+ predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) {
+ varBlockReaches(def, bb1, bb2) and
+ ssaRefRank(bb2, i2, def.getSourceVariable(), SsaRead()) = 1
+ }
+}
+
+private import SsaDefReaches
+
+pragma[nomagic]
+predicate liveThrough(BasicBlock bb, SourceVariable v) {
+ liveAtExit(bb, v) and
+ not ssaRef(bb, _, v, SsaDef())
+}
+
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Holds if the SSA definition of `v` at `def` reaches the end of basic
+ * block `bb`, at which point it is still live, without crossing another
+ * SSA definition of `v`.
+ */
+pragma[nomagic]
+predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def, SourceVariable v) {
+ exists(int last | last = maxSsaRefRank(bb, v) |
+ ssaDefReachesRank(bb, def, last, v) and
+ liveAtExit(bb, v)
+ )
+ or
+ // The construction of SSA form ensures that each read of a variable is
+ // dominated by its definition. An SSA definition therefore reaches a
+ // control flow node if it is the _closest_ SSA definition that dominates
+ // the node. If two definitions dominate a node then one must dominate the
+ // other, so therefore the definition of _closest_ is given by the dominator
+ // tree. Thus, reaching definitions can be calculated in terms of dominance.
+ ssaDefReachesEndOfBlock(getImmediateBasicBlockDominator(bb), def, pragma[only_bind_into](v)) and
+ liveThrough(bb, pragma[only_bind_into](v))
+}
+
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Holds if `inp` is an input to the phi node `phi` along the edge originating in `bb`.
+ */
+pragma[nomagic]
+predicate phiHasInputFromBlock(PhiNode phi, Definition inp, BasicBlock bb) {
+ exists(SourceVariable v, BasicBlock bbDef |
+ phi.definesAt(v, bbDef, _) and
+ getABasicBlockPredecessor(bbDef) = bb and
+ ssaDefReachesEndOfBlock(bb, inp, v)
+ )
+}
+
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Holds if the SSA definition of `v` at `def` reaches a read at index `i` in
+ * basic block `bb`, without crossing another SSA definition of `v`. The read
+ * is of kind `rk`.
+ */
+pragma[nomagic]
+predicate ssaDefReachesRead(SourceVariable v, Definition def, BasicBlock bb, int i) {
+ ssaDefReachesReadWithinBlock(v, def, bb, i)
+ or
+ variableRead(bb, i, v, _) and
+ ssaDefReachesEndOfBlock(getABasicBlockPredecessor(bb), def, v) and
+ not ssaDefReachesReadWithinBlock(v, _, bb, i)
+}
+
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Holds if `def` is accessed at index `i1` in basic block `bb1` (either a read
+ * or a write), `def` is read at index `i2` in basic block `bb2`, and there is a
+ * path between them without any read of `def`.
+ */
+pragma[nomagic]
+predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) {
+ exists(int rnk |
+ rnk = ssaDefRank(def, _, bb1, i1, _) and
+ rnk + 1 = ssaDefRank(def, _, bb1, i2, SsaRead()) and
+ variableRead(bb1, i2, _, _) and
+ bb2 = bb1
+ )
+ or
+ lastSsaRef(def, _, bb1, i1) and
+ defAdjacentRead(def, bb1, bb2, i2)
+}
+
+pragma[noinline]
+private predicate adjacentDefRead(
+ Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v
+) {
+ adjacentDefRead(def, bb1, i1, bb2, i2) and
+ v = def.getSourceVariable()
+}
+
+private predicate adjacentDefReachesRead(
+ Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2
+) {
+ exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) |
+ ssaRef(bb1, i1, v, SsaDef())
+ or
+ variableRead(bb1, i1, v, true)
+ )
+ or
+ exists(BasicBlock bb3, int i3 |
+ adjacentDefReachesRead(def, bb1, i1, bb3, i3) and
+ variableRead(bb3, i3, _, false) and
+ adjacentDefRead(def, bb3, i3, bb2, i2)
+ )
+}
+
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Same as `adjacentDefRead`, but ignores uncertain reads.
+ */
+pragma[nomagic]
+predicate adjacentDefNoUncertainReads(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) {
+ adjacentDefReachesRead(def, bb1, i1, bb2, i2) and
+ variableRead(bb2, i2, _, true)
+}
+
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Holds if the node at index `i` in `bb` is a last reference to SSA definition
+ * `def`. The reference is last because it can reach another write `next`,
+ * without passing through another read or write.
+ */
+pragma[nomagic]
+predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) {
+ exists(SourceVariable v |
+ // Next reference to `v` inside `bb` is a write
+ exists(int rnk, int j |
+ rnk = ssaDefRank(def, v, bb, i, _) and
+ next.definesAt(v, bb, j) and
+ rnk + 1 = ssaRefRank(bb, j, v, SsaDef())
+ )
+ or
+ // Can reach a write using one or more steps
+ lastSsaRef(def, v, bb, i) and
+ exists(BasicBlock bb2 |
+ varBlockReaches(def, bb, bb2) and
+ 1 = ssaDefRank(next, v, bb2, _, SsaDef())
+ )
+ )
+}
+
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Holds if `inp` is an immediately preceding definition of uncertain definition
+ * `def`. Since `def` is uncertain, the value from the preceding definition might
+ * still be valid.
+ */
+pragma[nomagic]
+predicate uncertainWriteDefinitionInput(UncertainWriteDefinition def, Definition inp) {
+ lastRefRedef(inp, _, _, def)
+}
+
+private predicate adjacentDefReachesUncertainRead(
+ Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2
+) {
+ adjacentDefReachesRead(def, bb1, i1, bb2, i2) and
+ variableRead(bb2, i2, _, false)
+}
+
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Same as `lastRefRedef`, but ignores uncertain reads.
+ */
+pragma[nomagic]
+predicate lastRefRedefNoUncertainReads(Definition def, BasicBlock bb, int i, Definition next) {
+ lastRefRedef(def, bb, i, next) and
+ not variableRead(bb, i, def.getSourceVariable(), false)
+ or
+ exists(BasicBlock bb0, int i0 |
+ lastRefRedef(def, bb0, i0, next) and
+ adjacentDefReachesUncertainRead(def, bb, i, bb0, i0)
+ )
+}
+
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Holds if the node at index `i` in `bb` is a last reference to SSA
+ * definition `def`.
+ *
+ * That is, the node can reach the end of the enclosing callable, or another
+ * SSA definition for the underlying source variable, without passing through
+ * another read.
+ */
+pragma[nomagic]
+predicate lastRef(Definition def, BasicBlock bb, int i) {
+ lastRefRedef(def, bb, i, _)
+ or
+ lastSsaRef(def, _, bb, i) and
+ (
+ // Can reach exit directly
+ bb instanceof ExitBasicBlock
+ or
+ // Can reach a block using one or more steps, where `def` is no longer live
+ exists(BasicBlock bb2 | varBlockReaches(def, bb, bb2) |
+ not defOccursInBlock(def, bb2, _) and
+ not ssaDefReachesEndOfBlock(bb2, def, _)
+ )
+ )
+}
+
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Same as `lastRefRedef`, but ignores uncertain reads.
+ */
+pragma[nomagic]
+predicate lastRefNoUncertainReads(Definition def, BasicBlock bb, int i) {
+ lastRef(def, bb, i) and
+ not variableRead(bb, i, def.getSourceVariable(), false)
+ or
+ exists(BasicBlock bb0, int i0 |
+ lastRef(def, bb0, i0) and
+ adjacentDefReachesUncertainRead(def, bb, i, bb0, i0)
+ )
+}
+
+/** A static single assignment (SSA) definition. */
+class Definition extends TDefinition {
+ /** Gets the source variable underlying this SSA definition. */
+ SourceVariable getSourceVariable() { this.definesAt(result, _, _) }
+
+ /**
+ * Holds if this SSA definition defines `v` at index `i` in basic block `bb`.
+ * Phi nodes are considered to be at index `-1`, while normal variable writes
+ * are at the index of the control flow node they wrap.
+ */
+ final predicate definesAt(SourceVariable v, BasicBlock bb, int i) {
+ this = TWriteDef(v, bb, i)
+ or
+ this = TPhiNode(v, bb) and i = -1
+ }
+
+ /** Gets the basic block to which this SSA definition belongs. */
+ final BasicBlock getBasicBlock() { this.definesAt(_, result, _) }
+
+ /** Gets a textual representation of this SSA definition. */
+ string toString() { none() }
+}
+
+/** An SSA definition that corresponds to a write. */
+class WriteDefinition extends Definition, TWriteDef {
+ private SourceVariable v;
+ private BasicBlock bb;
+ private int i;
+
+ WriteDefinition() { this = TWriteDef(v, bb, i) }
+
+ override string toString() { result = "WriteDef" }
+}
+
+/** A phi node. */
+class PhiNode extends Definition, TPhiNode {
+ override string toString() { result = "Phi" }
+}
+
+/**
+ * An SSA definition that represents an uncertain update of the underlying
+ * source variable.
+ */
+class UncertainWriteDefinition extends WriteDefinition {
+ UncertainWriteDefinition() {
+ exists(SourceVariable v, BasicBlock bb, int i |
+ this.definesAt(v, bb, i) and
+ variableWrite(bb, i, v, false)
+ )
+ }
+}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplSpecific.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplSpecific.qll
new file mode 100644
index 00000000000..20f9d1894b1
--- /dev/null
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplSpecific.qll
@@ -0,0 +1,18 @@
+private import semmle.code.cpp.ir.IR
+private import SsaInternals as Ssa
+
+class BasicBlock = IRBlock;
+
+class SourceVariable = Ssa::SourceVariable;
+
+BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { result.immediatelyDominates(bb) }
+
+BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() }
+
+class ExitBasicBlock extends IRBlock {
+ ExitBasicBlock() { this.getLastInstruction() instanceof ExitFunctionInstruction }
+}
+
+predicate variableWrite = Ssa::variableWrite/4;
+
+predicate variableRead = Ssa::variableRead/4;
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternals.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternals.qll
new file mode 100644
index 00000000000..1a353072be5
--- /dev/null
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternals.qll
@@ -0,0 +1,600 @@
+import SsaImplCommon
+private import cpp as Cpp
+private import semmle.code.cpp.ir.IR
+private import DataFlowUtil
+private import DataFlowImplCommon as DataFlowImplCommon
+private import semmle.code.cpp.models.interfaces.Allocation as Alloc
+private import semmle.code.cpp.models.interfaces.DataFlow as DataFlow
+
+private module SourceVariables {
+ private newtype TSourceVariable =
+ TSourceIRVariable(IRVariable var) or
+ TSourceIRVariableIndirection(InitializeIndirectionInstruction init)
+
+ abstract class SourceVariable extends TSourceVariable {
+ IRVariable var;
+
+ abstract string toString();
+ }
+
+ class SourceIRVariable extends SourceVariable, TSourceIRVariable {
+ SourceIRVariable() { this = TSourceIRVariable(var) }
+
+ IRVariable getIRVariable() { result = var }
+
+ override string toString() { result = this.getIRVariable().toString() }
+ }
+
+ class SourceIRVariableIndirection extends SourceVariable, TSourceIRVariableIndirection {
+ InitializeIndirectionInstruction init;
+
+ SourceIRVariableIndirection() {
+ this = TSourceIRVariableIndirection(init) and var = init.getIRVariable()
+ }
+
+ IRVariable getUnderlyingIRVariable() { result = var }
+
+ override string toString() { result = "*" + this.getUnderlyingIRVariable().toString() }
+ }
+}
+
+import SourceVariables
+
+cached
+private newtype TDefOrUse =
+ TExplicitDef(Instruction store) { explicitWrite(_, store, _) } or
+ TInitializeParam(Instruction instr) {
+ instr instanceof InitializeParameterInstruction
+ or
+ instr instanceof InitializeIndirectionInstruction
+ } or
+ TExplicitUse(Operand op) { isExplicitUse(op) } or
+ TReturnParamIndirection(Operand op) { returnParameterIndirection(op, _) }
+
+pragma[nomagic]
+private int getRank(DefOrUse defOrUse, IRBlock block) {
+ defOrUse =
+ rank[result](int i, DefOrUse cand |
+ block.getInstruction(i) = toInstruction(cand)
+ |
+ cand order by i
+ )
+}
+
+private class DefOrUse extends TDefOrUse {
+ /** Gets the instruction associated with this definition, if any. */
+ Instruction asDef() { none() }
+
+ /** Gets the operand associated with this use, if any. */
+ Operand asUse() { none() }
+
+ /** Gets a textual representation of this element. */
+ abstract string toString();
+
+ /** Gets the block of this definition or use. */
+ abstract IRBlock getBlock();
+
+ /** Holds if this definition or use has rank `rank` in block `block`. */
+ cached
+ final predicate hasRankInBlock(IRBlock block, int rnk) { rnk = getRank(this, block) }
+
+ /** Gets the location of this element. */
+ abstract Cpp::Location getLocation();
+}
+
+private Instruction toInstruction(DefOrUse defOrUse) {
+ result = defOrUse.asDef()
+ or
+ result = defOrUse.asUse().getUse()
+}
+
+abstract class Def extends DefOrUse {
+ Instruction store;
+
+ /** Gets the instruction of this definition. */
+ Instruction getInstruction() { result = store }
+
+ /** Gets the variable that is defined by this definition. */
+ abstract SourceVariable getSourceVariable();
+
+ /** Holds if this definition is guaranteed to happen. */
+ abstract predicate isCertain();
+
+ override Instruction asDef() { result = this.getInstruction() }
+
+ override string toString() { result = "Def" }
+
+ override IRBlock getBlock() { result = this.getInstruction().getBlock() }
+
+ override Cpp::Location getLocation() { result = store.getLocation() }
+}
+
+private class ExplicitDef extends Def, TExplicitDef {
+ ExplicitDef() { this = TExplicitDef(store) }
+
+ override SourceVariable getSourceVariable() {
+ exists(VariableInstruction var |
+ explicitWrite(_, this.getInstruction(), var) and
+ result.(SourceIRVariable).getIRVariable() = var.getIRVariable()
+ )
+ }
+
+ override predicate isCertain() { explicitWrite(true, this.getInstruction(), _) }
+}
+
+private class ParameterDef extends Def, TInitializeParam {
+ ParameterDef() { this = TInitializeParam(store) }
+
+ override SourceVariable getSourceVariable() {
+ result.(SourceIRVariable).getIRVariable() =
+ store.(InitializeParameterInstruction).getIRVariable()
+ or
+ result.(SourceIRVariableIndirection).getUnderlyingIRVariable() =
+ store.(InitializeIndirectionInstruction).getIRVariable()
+ }
+
+ override predicate isCertain() { any() }
+}
+
+abstract class Use extends DefOrUse {
+ Operand use;
+
+ override Operand asUse() { result = use }
+
+ /** Gets the underlying operand of this use. */
+ Operand getOperand() { result = use }
+
+ override string toString() { result = "Use" }
+
+ /** Gets the variable that is used by this use. */
+ abstract SourceVariable getSourceVariable();
+
+ override IRBlock getBlock() { result = use.getUse().getBlock() }
+
+ override Cpp::Location getLocation() { result = use.getLocation() }
+}
+
+private class ExplicitUse extends Use, TExplicitUse {
+ ExplicitUse() { this = TExplicitUse(use) }
+
+ override SourceVariable getSourceVariable() {
+ exists(VariableInstruction var |
+ use.getDef() = var and
+ if use.getUse() instanceof ReadSideEffectInstruction
+ then result.(SourceIRVariableIndirection).getUnderlyingIRVariable() = var.getIRVariable()
+ else result.(SourceIRVariable).getIRVariable() = var.getIRVariable()
+ )
+ }
+}
+
+private class ReturnParameterIndirection extends Use, TReturnParamIndirection {
+ ReturnParameterIndirection() { this = TReturnParamIndirection(use) }
+
+ override SourceVariable getSourceVariable() {
+ exists(ReturnIndirectionInstruction ret |
+ returnParameterIndirection(use, ret) and
+ result.(SourceIRVariableIndirection).getUnderlyingIRVariable() = ret.getIRVariable()
+ )
+ }
+}
+
+private predicate isExplicitUse(Operand op) {
+ op.getDef() instanceof VariableAddressInstruction and
+ not exists(LoadInstruction load |
+ load.getSourceAddressOperand() = op and
+ load.getAUse().getUse() instanceof InitializeIndirectionInstruction
+ )
+}
+
+private predicate returnParameterIndirection(Operand op, ReturnIndirectionInstruction ret) {
+ ret.getSourceAddressOperand() = op
+}
+
+/**
+ * Holds if `iFrom` computes an address that is used by `iTo`.
+ */
+predicate addressFlow(Instruction iFrom, Instruction iTo) {
+ iTo.(CopyValueInstruction).getSourceValue() = iFrom
+ or
+ iTo.(ConvertInstruction).getUnary() = iFrom
+ or
+ iTo.(CheckedConvertOrNullInstruction).getUnary() = iFrom
+ or
+ iTo.(InheritanceConversionInstruction).getUnary() = iFrom
+ or
+ iTo.(PointerArithmeticInstruction).getLeft() = iFrom
+ or
+ iTo.(FieldAddressInstruction).getObjectAddress() = iFrom
+ or
+ // We traverse `LoadInstruction`s since we want to conclude that the
+ // destination of the store operation `*x = source()` is derived from `x`.
+ iTo.(LoadInstruction).getSourceAddress() = iFrom
+ or
+ // We want to include `ReadSideEffectInstruction`s for the same reason that we include
+ // `LoadInstruction`s, but only when a `WriteSideEffectInstruction` for the same index exists as well
+ // (as otherwise we know that the callee won't override the data). However, given an index `i`, the
+ // destination of the `WriteSideEffectInstruction` for `i` is identical to the source address of the
+ // `ReadSideEffectInstruction` for `i`. So we don't have to talk about the `ReadSideEffectInstruction`
+ // at all.
+ exists(WriteSideEffectInstruction write |
+ write.getPrimaryInstruction() = iTo and
+ write.getDestinationAddress() = iFrom
+ )
+}
+
+/**
+ * The reflexive, transitive closure of `addressFlow` that ends as the address of a
+ * store or read operation.
+ */
+cached
+predicate addressFlowTC(Instruction iFrom, Instruction iTo) {
+ iTo = [getDestinationAddress(_), getSourceAddress(_)] and
+ addressFlow*(iFrom, iTo)
+}
+
+/**
+ * Gets the destination address of `instr` if it is a `StoreInstruction` or
+ * a `WriteSideEffectInstruction`.
+ */
+Instruction getDestinationAddress(Instruction instr) {
+ result =
+ [
+ instr.(StoreInstruction).getDestinationAddress(),
+ instr.(WriteSideEffectInstruction).getDestinationAddress()
+ ]
+}
+
+class ReferenceToInstruction extends CopyValueInstruction {
+ ReferenceToInstruction() {
+ this.getResultType() instanceof Cpp::ReferenceType and
+ not this.getUnary().getResultType() instanceof Cpp::ReferenceType
+ }
+
+ Instruction getSourceAddress() { result = getSourceAddressOperand().getDef() }
+
+ Operand getSourceAddressOperand() { result = this.getUnaryOperand() }
+}
+
+/** Gets the source address of `instr` if it is an instruction that behaves like a `LoadInstruction`. */
+Instruction getSourceAddress(Instruction instr) { result = getSourceAddressOperand(instr).getDef() }
+
+/**
+ * Gets the operand that represents the source address of `instr` if it is an
+ * instruction that behaves like a `LoadInstruction`.
+ */
+Operand getSourceAddressOperand(Instruction instr) {
+ result =
+ [
+ instr.(LoadInstruction).getSourceAddressOperand(),
+ instr.(ReadSideEffectInstruction).getArgumentOperand(),
+ // `ReferenceToInstruction` is really more of an address-of operation,
+ // but by including it in this list we break out of `flowOutOfAddressStep` at an
+ // instruction that, at the source level, looks like a use of a variable.
+ instr.(ReferenceToInstruction).getSourceAddressOperand()
+ ]
+}
+
+/**
+ * Gets the source address of `node` if it's an instruction or operand that
+ * behaves like a `LoadInstruction`.
+ */
+Instruction getSourceAddressFromNode(Node node) {
+ result = getSourceAddress(node.asInstruction())
+ or
+ result = getSourceAddress(node.asOperand().(SideEffectOperand).getUse())
+}
+
+/** Gets the source value of `instr` if it's an instruction that behaves like a `LoadInstruction`. */
+Instruction getSourceValue(Instruction instr) { result = getSourceValueOperand(instr).getDef() }
+
+/**
+ * Gets the operand that represents the source value of `instr` if it's an instruction
+ * that behaves like a `LoadInstruction`.
+ */
+Operand getSourceValueOperand(Instruction instr) {
+ result = instr.(LoadInstruction).getSourceValueOperand()
+ or
+ result = instr.(ReadSideEffectInstruction).getSideEffectOperand()
+ or
+ // See the comment on the `ReferenceToInstruction` disjunct in `getSourceAddressOperand` for why
+ // this case is included.
+ result = instr.(ReferenceToInstruction).getSourceValueOperand()
+}
+
+/**
+ * Holds if `instr` is a `StoreInstruction` or a `WriteSideEffectInstruction` that writes to an address.
+ * The addresses is computed using `address`, and `certain` is `true` if the write is guaranteed to overwrite
+ * the entire variable.
+ */
+cached
+predicate explicitWrite(boolean certain, Instruction instr, Instruction address) {
+ exists(StoreInstruction store |
+ store = instr and addressFlowTC(address, store.getDestinationAddress())
+ |
+ // Set `certain = false` if the address is derived from any instructions that prevents us from
+ // concluding that the entire variable is overridden.
+ if
+ addressFlowTC(any(Instruction i |
+ i instanceof FieldAddressInstruction or
+ i instanceof PointerArithmeticInstruction or
+ i instanceof LoadInstruction or
+ i instanceof InheritanceConversionInstruction
+ ), store.getDestinationAddress())
+ then certain = false
+ else certain = true
+ )
+ or
+ addressFlowTC(address, instr.(WriteSideEffectInstruction).getDestinationAddress()) and
+ certain = false
+}
+
+cached
+private module Cached {
+ private predicate defUseFlow(Node nodeFrom, Node nodeTo) {
+ exists(IRBlock bb1, int i1, IRBlock bb2, int i2, DefOrUse defOrUse, Use use |
+ defOrUse.hasRankInBlock(bb1, i1) and
+ use.hasRankInBlock(bb2, i2) and
+ adjacentDefRead(_, bb1, i1, bb2, i2) and
+ nodeFrom.asInstruction() = toInstruction(defOrUse) and
+ flowOutOfAddressStep(use.getOperand(), nodeTo)
+ )
+ }
+
+ private predicate fromStoreNode(StoreNodeInstr nodeFrom, Node nodeTo) {
+ // Def-use flow from a `StoreNode`.
+ exists(IRBlock bb1, int i1, IRBlock bb2, int i2, Def def, Use use |
+ nodeFrom.isTerminal() and
+ def.getInstruction() = nodeFrom.getStoreInstruction() and
+ def.hasRankInBlock(bb1, i1) and
+ adjacentDefRead(_, bb1, i1, bb2, i2) and
+ use.hasRankInBlock(bb2, i2) and
+ flowOutOfAddressStep(use.getOperand(), nodeTo)
+ )
+ or
+ // This final case is a bit annoying. The write side effect on an expression like `a = new A;` writes
+ // to a fresh address returned by `operator new`, and there's no easy way to use the shared SSA
+ // library to hook that up to the assignment to `a`. So instead we flow to the _first_ use of the
+ // value computed by `operator new` that occurs after `nodeFrom` (to avoid a loop in the
+ // dataflow graph).
+ exists(WriteSideEffectInstruction write, IRBlock bb, int i1, int i2, Operand op |
+ nodeFrom.getInstruction().(CallInstruction).getStaticCallTarget() instanceof
+ Alloc::OperatorNewAllocationFunction and
+ write = nodeFrom.getStoreInstruction() and
+ bb.getInstruction(i1) = write and
+ bb.getInstruction(i2) = op.getUse() and
+ // Flow to an instruction that occurs later in the block.
+ conversionFlow*(nodeFrom.getInstruction(), op.getDef()) and
+ nodeTo.asOperand() = op and
+ i2 > i1 and
+ // There is no previous instruction that also occurs after `nodeFrom`.
+ not exists(Instruction instr, int i |
+ bb.getInstruction(i) = instr and
+ conversionFlow(instr, op.getDef()) and
+ i1 < i and
+ i < i2
+ )
+ )
+ }
+
+ private predicate fromReadNode(ReadNode nodeFrom, Node nodeTo) {
+ exists(IRBlock bb1, int i1, IRBlock bb2, int i2, Use use1, Use use2 |
+ use1.hasRankInBlock(bb1, i1) and
+ use2.hasRankInBlock(bb2, i2) and
+ use1.getOperand().getDef() = nodeFrom.getInstruction() and
+ adjacentDefRead(_, bb1, i1, bb2, i2) and
+ flowOutOfAddressStep(use2.getOperand(), nodeTo)
+ )
+ }
+
+ private predicate fromPhiNode(SsaPhiNode nodeFrom, Node nodeTo) {
+ exists(PhiNode phi, Use use, IRBlock block, int rnk |
+ phi = nodeFrom.getPhiNode() and
+ adjacentDefRead(phi, _, _, block, rnk) and
+ use.hasRankInBlock(block, rnk) and
+ flowOutOfAddressStep(use.getOperand(), nodeTo)
+ )
+ }
+
+ private predicate toPhiNode(Node nodeFrom, SsaPhiNode nodeTo) {
+ // Flow to phi nodes
+ exists(Def def, IRBlock block, int rnk |
+ def.hasRankInBlock(block, rnk) and
+ nodeTo.hasInputAtRankInBlock(block, rnk)
+ |
+ exists(StoreNodeInstr storeNode |
+ storeNode = nodeFrom and
+ storeNode.isTerminal() and
+ def.getInstruction() = storeNode.getStoreInstruction()
+ )
+ or
+ def.getInstruction() = nodeFrom.asInstruction()
+ )
+ or
+ // Phi -> phi flow
+ nodeTo.hasInputAtRankInBlock(_, _, nodeFrom.(SsaPhiNode).getPhiNode())
+ }
+
+ /**
+ * Holds if `nodeFrom` is a read or write, and `nTo` is the next subsequent read of the variable
+ * written (or read) by `storeOrRead`.
+ */
+ cached
+ predicate ssaFlow(Node nodeFrom, Node nodeTo) {
+ // Def-use/use-use flow from an `InstructionNode`.
+ defUseFlow(nodeFrom, nodeTo)
+ or
+ // Def-use flow from a `StoreNode`.
+ fromStoreNode(nodeFrom, nodeTo)
+ or
+ // Use-use flow from a `ReadNode`.
+ fromReadNode(nodeFrom, nodeTo)
+ or
+ fromPhiNode(nodeFrom, nodeTo)
+ or
+ toPhiNode(nodeFrom, nodeTo)
+ or
+ // When we want to transfer flow out of a `StoreNode` we perform two steps:
+ // 1. Find the next use of the address being stored to
+ // 2. Find the `LoadInstruction` that loads the address
+ // When the address being stored into doesn't have a `LoadInstruction` associated with it because it's
+ // passed into a `CallInstruction` we transfer flow to the `ReadSideEffect`, which will then flow into
+ // the callee. We then pickup the flow from the `InitializeIndirectionInstruction` and use the shared
+ // SSA library to determine where the next use of the address that received the flow is.
+ exists(Node init, Node mid |
+ nodeFrom.asInstruction().(InitializeIndirectionInstruction).getIRVariable() =
+ init.asInstruction().(InitializeParameterInstruction).getIRVariable() and
+ // No need for the flow if the next use is the instruction that returns the flow out of the callee.
+ not mid.asInstruction() instanceof ReturnIndirectionInstruction and
+ // Find the next use of the address
+ ssaFlow(init, mid) and
+ // And flow to the next load of that address
+ flowOutOfAddressStep([mid.asInstruction().getAUse(), mid.asOperand()], nodeTo)
+ )
+ }
+
+ /**
+ * Holds if `iTo` is a conversion-like instruction that copies
+ * the value computed by `iFrom`.
+ *
+ * This predicate is used by `fromStoreNode` to find the next use of a pointer that
+ * points to freshly allocated memory.
+ */
+ private predicate conversionFlow(Instruction iFrom, Instruction iTo) {
+ iTo.(CopyValueInstruction).getSourceValue() = iFrom
+ or
+ iTo.(ConvertInstruction).getUnary() = iFrom
+ or
+ iTo.(CheckedConvertOrNullInstruction).getUnary() = iFrom
+ or
+ iTo.(InheritanceConversionInstruction).getUnary() = iFrom
+ }
+
+ pragma[noinline]
+ private predicate callTargetHasInputOutput(
+ CallInstruction call, DataFlow::FunctionInput input, DataFlow::FunctionOutput output
+ ) {
+ exists(DataFlow::DataFlowFunction func |
+ call.getStaticCallTarget() = func and
+ func.hasDataFlow(input, output)
+ )
+ }
+
+ /**
+ * The role of `flowOutOfAddressStep` is to select the node for which we want dataflow to end up in
+ * after the shared SSA library's `adjacentDefRead` predicate has determined that `operand` is the
+ * next use of some variable.
+ *
+ * More precisely, this predicate holds if `operand` is an operand that represents an address, and:
+ * - `nodeTo` is the next load of that address, or
+ * - `nodeTo` is a `ReadNode` that uses the definition of `operand` to start a sequence of reads, or
+ * - `nodeTo` is the outer-most `StoreNode` that uses the address represented by `operand`. We obtain
+ * use-use flow in this case since `StoreNodeFlow::flowOutOf` will then provide flow to the next of
+ * of `operand`.
+ *
+ * There is one final (slightly annoying) case: When `operand` is a an argument to a modeled function
+ * without any `ReadSideEffect` (such as `std::move`). Here, the address flows from the argument to
+ * the return value, which might then be read later.
+ */
+ private predicate flowOutOfAddressStep(Operand operand, Node nodeTo) {
+ // Flow into a read node
+ exists(ReadNode readNode | readNode = nodeTo |
+ readNode.isInitial() and
+ operand.getDef() = readNode.getInstruction()
+ )
+ or
+ exists(StoreNodeInstr storeNode, Instruction def |
+ storeNode = nodeTo and
+ def = operand.getDef()
+ |
+ storeNode.isTerminal() and
+ not addressFlow(def, _) and
+ // Only transfer flow to a store node if it doesn't immediately overwrite the address
+ // we've just written to.
+ explicitWrite(false, storeNode.getStoreInstruction(), def)
+ )
+ or
+ operand = getSourceAddressOperand(nodeTo.asInstruction())
+ or
+ exists(ReturnIndirectionInstruction ret |
+ ret.getSourceAddressOperand() = operand and
+ ret = nodeTo.asInstruction()
+ )
+ or
+ exists(ReturnValueInstruction ret |
+ ret.getReturnAddressOperand() = operand and
+ nodeTo.asInstruction() = ret
+ )
+ or
+ exists(CallInstruction call, int index, ReadSideEffectInstruction read |
+ call.getArgumentOperand(index) = operand and
+ read = getSideEffectFor(call, index) and
+ nodeTo.asOperand() = read.getSideEffectOperand()
+ )
+ or
+ exists(CopyInstruction copy |
+ not exists(getSourceAddressOperand(copy)) and
+ copy.getSourceValueOperand() = operand and
+ flowOutOfAddressStep(copy.getAUse(), nodeTo)
+ )
+ or
+ exists(ConvertInstruction convert |
+ convert.getUnaryOperand() = operand and
+ flowOutOfAddressStep(convert.getAUse(), nodeTo)
+ )
+ or
+ exists(CheckedConvertOrNullInstruction convert |
+ convert.getUnaryOperand() = operand and
+ flowOutOfAddressStep(convert.getAUse(), nodeTo)
+ )
+ or
+ exists(InheritanceConversionInstruction convert |
+ convert.getUnaryOperand() = operand and
+ flowOutOfAddressStep(convert.getAUse(), nodeTo)
+ )
+ or
+ exists(PointerArithmeticInstruction arith |
+ arith.getLeftOperand() = operand and
+ flowOutOfAddressStep(arith.getAUse(), nodeTo)
+ )
+ or
+ // Flow through a modeled function that has parameter -> return value flow.
+ exists(
+ CallInstruction call, int index, DataFlow::FunctionInput input,
+ DataFlow::FunctionOutput output
+ |
+ callTargetHasInputOutput(call, input, output) and
+ call.getArgumentOperand(index) = operand and
+ not getSideEffectFor(call, index) instanceof ReadSideEffectInstruction and
+ input.isParameter(index) and
+ output.isReturnValue() and
+ flowOutOfAddressStep(call.getAUse(), nodeTo)
+ )
+ }
+}
+
+import Cached
+
+/**
+ * Holds if the `i`'th write in block `bb` writes to the variable `v`.
+ * `certain` is `true` if the write is guaranteed to overwrite the entire variable.
+ */
+predicate variableWrite(IRBlock bb, int i, SourceVariable v, boolean certain) {
+ DataFlowImplCommon::forceCachingInSameStage() and
+ exists(Def def |
+ def.hasRankInBlock(bb, i) and
+ v = def.getSourceVariable() and
+ (if def.isCertain() then certain = true else certain = false)
+ )
+}
+
+/**
+ * Holds if the `i`'th read in block `bb` reads to the variable `v`.
+ * `certain` is `true` if the read is guaranteed. For C++, this is always the case.
+ */
+predicate variableRead(IRBlock bb, int i, SourceVariable v, boolean certain) {
+ exists(Use use |
+ use.hasRankInBlock(bb, i) and
+ v = use.getSourceVariable() and
+ certain = true
+ )
+}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/TaintTrackingUtil.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/TaintTrackingUtil.qll
index f563e47db9f..287958ebea4 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/TaintTrackingUtil.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/TaintTrackingUtil.qll
@@ -44,8 +44,6 @@ private predicate instructionToOperandTaintStep(Instruction fromInstr, Operand t
fromInstr = readInstr.getArgumentDef() and
toOperand = readInstr.getSideEffectOperand()
)
- or
- toOperand.(LoadOperand).getAnyDef() = fromInstr
}
/**
@@ -84,8 +82,6 @@ private predicate operandToInstructionTaintStep(Operand opFrom, Instruction inst
instrTo.(FieldAddressInstruction).getField().getDeclaringType() instanceof Union
)
or
- instrTo.(LoadInstruction).getSourceAddressOperand() = opFrom
- or
// Flow from an element to an array or union that contains it.
instrTo.(ChiInstruction).getPartialOperand() = opFrom and
not instrTo.isResultConflated() and
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/tainttracking1/TaintTrackingImpl.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/tainttracking1/TaintTrackingImpl.qll
index f4f73b8247c..acb029c23d9 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/tainttracking1/TaintTrackingImpl.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/tainttracking1/TaintTrackingImpl.qll
@@ -75,24 +75,26 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isSanitizer(DataFlow::Node node) { none() }
final override predicate isBarrier(DataFlow::Node node) {
- isSanitizer(node) or
+ this.isSanitizer(node) or
defaultTaintSanitizer(node)
}
/** Holds if taint propagation into `node` is prohibited. */
predicate isSanitizerIn(DataFlow::Node node) { none() }
- final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) }
+ final override predicate isBarrierIn(DataFlow::Node node) { this.isSanitizerIn(node) }
/** Holds if taint propagation out of `node` is prohibited. */
predicate isSanitizerOut(DataFlow::Node node) { none() }
- final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) }
+ final override predicate isBarrierOut(DataFlow::Node node) { this.isSanitizerOut(node) }
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
- final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { isSanitizerGuard(guard) }
+ final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
+ this.isSanitizerGuard(guard)
+ }
/**
* Holds if the additional taint propagation step from `node1` to `node2`
@@ -101,7 +103,7 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
- isAdditionalTaintStep(node1, node2) or
+ this.isAdditionalTaintStep(node1, node2) or
defaultAdditionalTaintStep(node1, node2)
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/tainttracking2/TaintTrackingImpl.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/tainttracking2/TaintTrackingImpl.qll
index f4f73b8247c..acb029c23d9 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/tainttracking2/TaintTrackingImpl.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/tainttracking2/TaintTrackingImpl.qll
@@ -75,24 +75,26 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isSanitizer(DataFlow::Node node) { none() }
final override predicate isBarrier(DataFlow::Node node) {
- isSanitizer(node) or
+ this.isSanitizer(node) or
defaultTaintSanitizer(node)
}
/** Holds if taint propagation into `node` is prohibited. */
predicate isSanitizerIn(DataFlow::Node node) { none() }
- final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) }
+ final override predicate isBarrierIn(DataFlow::Node node) { this.isSanitizerIn(node) }
/** Holds if taint propagation out of `node` is prohibited. */
predicate isSanitizerOut(DataFlow::Node node) { none() }
- final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) }
+ final override predicate isBarrierOut(DataFlow::Node node) { this.isSanitizerOut(node) }
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
- final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { isSanitizerGuard(guard) }
+ final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
+ this.isSanitizerGuard(guard)
+ }
/**
* Holds if the additional taint propagation step from `node1` to `node2`
@@ -101,7 +103,7 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
- isAdditionalTaintStep(node1, node2) or
+ this.isAdditionalTaintStep(node1, node2) or
defaultAdditionalTaintStep(node1, node2)
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/tainttracking3/TaintTrackingImpl.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/tainttracking3/TaintTrackingImpl.qll
index f4f73b8247c..acb029c23d9 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/tainttracking3/TaintTrackingImpl.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/tainttracking3/TaintTrackingImpl.qll
@@ -75,24 +75,26 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isSanitizer(DataFlow::Node node) { none() }
final override predicate isBarrier(DataFlow::Node node) {
- isSanitizer(node) or
+ this.isSanitizer(node) or
defaultTaintSanitizer(node)
}
/** Holds if taint propagation into `node` is prohibited. */
predicate isSanitizerIn(DataFlow::Node node) { none() }
- final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) }
+ final override predicate isBarrierIn(DataFlow::Node node) { this.isSanitizerIn(node) }
/** Holds if taint propagation out of `node` is prohibited. */
predicate isSanitizerOut(DataFlow::Node node) { none() }
- final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) }
+ final override predicate isBarrierOut(DataFlow::Node node) { this.isSanitizerOut(node) }
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
- final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { isSanitizerGuard(guard) }
+ final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
+ this.isSanitizerGuard(guard)
+ }
/**
* Holds if the additional taint propagation step from `node1` to `node2`
@@ -101,7 +103,7 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
- isAdditionalTaintStep(node1, node2) or
+ this.isAdditionalTaintStep(node1, node2) or
defaultAdditionalTaintStep(node1, node2)
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/IRBlock.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/IRBlock.qll
index 4b86f9a7cec..bb8630a5e0c 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/IRBlock.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/IRBlock.qll
@@ -24,7 +24,7 @@ class IRBlockBase extends TIRBlock {
final string toString() { result = getFirstInstruction(this).toString() }
/** Gets the source location of the first non-`Phi` instruction in this block. */
- final Language::Location getLocation() { result = getFirstInstruction().getLocation() }
+ final Language::Location getLocation() { result = this.getFirstInstruction().getLocation() }
/**
* INTERNAL: Do not use.
@@ -39,7 +39,7 @@ class IRBlockBase extends TIRBlock {
) and
this =
rank[result + 1](IRBlock funcBlock, int sortOverride, int sortKey1, int sortKey2 |
- funcBlock.getEnclosingFunction() = getEnclosingFunction() and
+ funcBlock.getEnclosingFunction() = this.getEnclosingFunction() and
funcBlock.getFirstInstruction().hasSortKeys(sortKey1, sortKey2) and
// Ensure that the block containing `EnterFunction` always comes first.
if funcBlock.getFirstInstruction() instanceof EnterFunctionInstruction
@@ -59,15 +59,15 @@ class IRBlockBase extends TIRBlock {
* Get the `Phi` instructions that appear at the start of this block.
*/
final PhiInstruction getAPhiInstruction() {
- Construction::getPhiInstructionBlockStart(result) = getFirstInstruction()
+ Construction::getPhiInstructionBlockStart(result) = this.getFirstInstruction()
}
/**
* Gets an instruction in this block. This includes `Phi` instructions.
*/
final Instruction getAnInstruction() {
- result = getInstruction(_) or
- result = getAPhiInstruction()
+ result = this.getInstruction(_) or
+ result = this.getAPhiInstruction()
}
/**
@@ -78,7 +78,9 @@ class IRBlockBase extends TIRBlock {
/**
* Gets the last instruction in this block.
*/
- final Instruction getLastInstruction() { result = getInstruction(getInstructionCount() - 1) }
+ final Instruction getLastInstruction() {
+ result = this.getInstruction(this.getInstructionCount() - 1)
+ }
/**
* Gets the number of non-`Phi` instructions in this block.
@@ -149,7 +151,7 @@ class IRBlock extends IRBlockBase {
* Block `A` dominates block `B` if any control flow path from the entry block of the function to
* block `B` must pass through block `A`. A block always dominates itself.
*/
- final predicate dominates(IRBlock block) { strictlyDominates(block) or this = block }
+ final predicate dominates(IRBlock block) { this.strictlyDominates(block) or this = block }
/**
* Gets a block on the dominance frontier of this block.
@@ -159,8 +161,8 @@ class IRBlock extends IRBlockBase {
*/
pragma[noinline]
final IRBlock dominanceFrontier() {
- dominates(result.getAPredecessor()) and
- not strictlyDominates(result)
+ this.dominates(result.getAPredecessor()) and
+ not this.strictlyDominates(result)
}
/**
@@ -189,7 +191,7 @@ class IRBlock extends IRBlockBase {
* Block `A` post-dominates block `B` if any control flow path from `B` to the exit block of the
* function must pass through block `A`. A block always post-dominates itself.
*/
- final predicate postDominates(IRBlock block) { strictlyPostDominates(block) or this = block }
+ final predicate postDominates(IRBlock block) { this.strictlyPostDominates(block) or this = block }
/**
* Gets a block on the post-dominance frontier of this block.
@@ -199,16 +201,16 @@ class IRBlock extends IRBlockBase {
*/
pragma[noinline]
final IRBlock postPominanceFrontier() {
- postDominates(result.getASuccessor()) and
- not strictlyPostDominates(result)
+ this.postDominates(result.getASuccessor()) and
+ not this.strictlyPostDominates(result)
}
/**
* Holds if this block is reachable from the entry block of its function.
*/
final predicate isReachableFromFunctionEntry() {
- this = getEnclosingIRFunction().getEntryBlock() or
- getAPredecessor().isReachableFromFunctionEntry()
+ this = this.getEnclosingIRFunction().getEntryBlock() or
+ this.getAPredecessor().isReachableFromFunctionEntry()
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll
index 6f471d8a7e8..1c2cc493338 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll
@@ -41,7 +41,7 @@ class Instruction extends Construction::TStageInstruction {
}
/** Gets a textual representation of this element. */
- final string toString() { result = getOpcode().toString() + ": " + getAST().toString() }
+ final string toString() { result = this.getOpcode().toString() + ": " + this.getAST().toString() }
/**
* Gets a string showing the result, opcode, and operands of the instruction, equivalent to what
@@ -50,7 +50,8 @@ class Instruction extends Construction::TStageInstruction {
* `mu0_28(int) = Store r0_26, r0_27`
*/
final string getDumpString() {
- result = getResultString() + " = " + getOperationString() + " " + getOperandsString()
+ result =
+ this.getResultString() + " = " + this.getOperationString() + " " + this.getOperandsString()
}
private predicate shouldGenerateDumpStrings() {
@@ -66,10 +67,13 @@ class Instruction extends Construction::TStageInstruction {
* VariableAddress[x]
*/
final string getOperationString() {
- shouldGenerateDumpStrings() and
- if exists(getImmediateString())
- then result = getOperationPrefix() + getOpcode().toString() + "[" + getImmediateString() + "]"
- else result = getOperationPrefix() + getOpcode().toString()
+ this.shouldGenerateDumpStrings() and
+ if exists(this.getImmediateString())
+ then
+ result =
+ this.getOperationPrefix() + this.getOpcode().toString() + "[" + this.getImmediateString() +
+ "]"
+ else result = this.getOperationPrefix() + this.getOpcode().toString()
}
/**
@@ -78,17 +82,17 @@ class Instruction extends Construction::TStageInstruction {
string getImmediateString() { none() }
private string getOperationPrefix() {
- shouldGenerateDumpStrings() and
+ this.shouldGenerateDumpStrings() and
if this instanceof SideEffectInstruction then result = "^" else result = ""
}
private string getResultPrefix() {
- shouldGenerateDumpStrings() and
- if getResultIRType() instanceof IRVoidType
+ this.shouldGenerateDumpStrings() and
+ if this.getResultIRType() instanceof IRVoidType
then result = "v"
else
- if hasMemoryResult()
- then if isResultModeled() then result = "m" else result = "mu"
+ if this.hasMemoryResult()
+ then if this.isResultModeled() then result = "m" else result = "mu"
else result = "r"
}
@@ -97,7 +101,7 @@ class Instruction extends Construction::TStageInstruction {
* used by debugging and printing code only.
*/
int getDisplayIndexInBlock() {
- shouldGenerateDumpStrings() and
+ this.shouldGenerateDumpStrings() and
exists(IRBlock block |
this = block.getInstruction(result)
or
@@ -111,12 +115,12 @@ class Instruction extends Construction::TStageInstruction {
}
private int getLineRank() {
- shouldGenerateDumpStrings() and
+ this.shouldGenerateDumpStrings() and
this =
rank[result](Instruction instr |
instr =
- getAnInstructionAtLine(getEnclosingIRFunction(), getLocation().getFile(),
- getLocation().getStartLine())
+ getAnInstructionAtLine(this.getEnclosingIRFunction(), this.getLocation().getFile(),
+ this.getLocation().getStartLine())
|
instr order by instr.getBlock().getDisplayIndex(), instr.getDisplayIndexInBlock()
)
@@ -130,8 +134,9 @@ class Instruction extends Construction::TStageInstruction {
* Example: `r1_1`
*/
string getResultId() {
- shouldGenerateDumpStrings() and
- result = getResultPrefix() + getAST().getLocation().getStartLine() + "_" + getLineRank()
+ this.shouldGenerateDumpStrings() and
+ result =
+ this.getResultPrefix() + this.getAST().getLocation().getStartLine() + "_" + this.getLineRank()
}
/**
@@ -142,8 +147,8 @@ class Instruction extends Construction::TStageInstruction {
* Example: `r1_1(int*)`
*/
final string getResultString() {
- shouldGenerateDumpStrings() and
- result = getResultId() + "(" + getResultLanguageType().getDumpString() + ")"
+ this.shouldGenerateDumpStrings() and
+ result = this.getResultId() + "(" + this.getResultLanguageType().getDumpString() + ")"
}
/**
@@ -153,10 +158,10 @@ class Instruction extends Construction::TStageInstruction {
* Example: `func:r3_4, this:r3_5`
*/
string getOperandsString() {
- shouldGenerateDumpStrings() and
+ this.shouldGenerateDumpStrings() and
result =
concat(Operand operand |
- operand = getAnOperand()
+ operand = this.getAnOperand()
|
operand.getDumpString(), ", " order by operand.getDumpSortOrder()
)
@@ -190,7 +195,7 @@ class Instruction extends Construction::TStageInstruction {
* Gets the function that contains this instruction.
*/
final Language::Function getEnclosingFunction() {
- result = getEnclosingIRFunction().getFunction()
+ result = this.getEnclosingIRFunction().getFunction()
}
/**
@@ -208,7 +213,7 @@ class Instruction extends Construction::TStageInstruction {
/**
* Gets the location of the source code for this instruction.
*/
- final Language::Location getLocation() { result = getAST().getLocation() }
+ final Language::Location getLocation() { result = this.getAST().getLocation() }
/**
* Gets the `Expr` whose result is computed by this instruction, if any. The `Expr` may be a
@@ -243,7 +248,7 @@ class Instruction extends Construction::TStageInstruction {
* a result, its result type will be `IRVoidType`.
*/
cached
- final IRType getResultIRType() { result = getResultLanguageType().getIRType() }
+ final IRType getResultIRType() { result = this.getResultLanguageType().getIRType() }
/**
* Gets the type of the result produced by this instruction. If the
@@ -254,7 +259,7 @@ class Instruction extends Construction::TStageInstruction {
*/
final Language::Type getResultType() {
exists(Language::LanguageType resultType |
- resultType = getResultLanguageType() and
+ resultType = this.getResultLanguageType() and
(
resultType.hasUnspecifiedType(result, _)
or
@@ -283,7 +288,7 @@ class Instruction extends Construction::TStageInstruction {
* result of the `Load` instruction is a prvalue of type `int`, representing
* the integer value loaded from variable `x`.
*/
- final predicate isGLValue() { getResultLanguageType().hasType(_, true) }
+ final predicate isGLValue() { this.getResultLanguageType().hasType(_, true) }
/**
* Gets the size of the result produced by this instruction, in bytes. If the
@@ -292,7 +297,7 @@ class Instruction extends Construction::TStageInstruction {
* If `this.isGLValue()` holds for this instruction, the value of
* `getResultSize()` will always be the size of a pointer.
*/
- final int getResultSize() { result = getResultLanguageType().getByteSize() }
+ final int getResultSize() { result = this.getResultLanguageType().getByteSize() }
/**
* Gets the opcode that specifies the operation performed by this instruction.
@@ -314,14 +319,16 @@ class Instruction extends Construction::TStageInstruction {
/**
* Holds if this instruction produces a memory result.
*/
- final predicate hasMemoryResult() { exists(getResultMemoryAccess()) }
+ final predicate hasMemoryResult() { exists(this.getResultMemoryAccess()) }
/**
* Gets the kind of memory access performed by this instruction's result.
* Holds only for instructions with a memory result.
*/
pragma[inline]
- final MemoryAccessKind getResultMemoryAccess() { result = getOpcode().getWriteMemoryAccess() }
+ final MemoryAccessKind getResultMemoryAccess() {
+ result = this.getOpcode().getWriteMemoryAccess()
+ }
/**
* Holds if the memory access performed by this instruction's result will not always write to
@@ -332,7 +339,7 @@ class Instruction extends Construction::TStageInstruction {
* (for example, the global side effects of a function call).
*/
pragma[inline]
- final predicate hasResultMayMemoryAccess() { getOpcode().hasMayWriteMemoryAccess() }
+ final predicate hasResultMayMemoryAccess() { this.getOpcode().hasMayWriteMemoryAccess() }
/**
* Gets the operand that holds the memory address to which this instruction stores its
@@ -340,7 +347,7 @@ class Instruction extends Construction::TStageInstruction {
* is `r1`.
*/
final AddressOperand getResultAddressOperand() {
- getResultMemoryAccess().usesAddressOperand() and
+ this.getResultMemoryAccess().usesAddressOperand() and
result.getUse() = this
}
@@ -349,7 +356,7 @@ class Instruction extends Construction::TStageInstruction {
* result, if any. For example, in `m3 = Store r1, r2`, the result of `getResultAddressOperand()`
* is the instruction that defines `r1`.
*/
- final Instruction getResultAddress() { result = getResultAddressOperand().getDef() }
+ final Instruction getResultAddress() { result = this.getResultAddressOperand().getDef() }
/**
* Holds if the result of this instruction is precisely modeled in SSA. Always
@@ -368,7 +375,7 @@ class Instruction extends Construction::TStageInstruction {
*/
final predicate isResultModeled() {
// Register results are always in SSA form.
- not hasMemoryResult() or
+ not this.hasMemoryResult() or
Construction::hasModeledMemoryResult(this)
}
@@ -412,7 +419,7 @@ class Instruction extends Construction::TStageInstruction {
/**
* Gets all direct successors of this instruction.
*/
- final Instruction getASuccessor() { result = getSuccessor(_) }
+ final Instruction getASuccessor() { result = this.getSuccessor(_) }
/**
* Gets a predecessor of this instruction such that the predecessor reaches
@@ -423,7 +430,7 @@ class Instruction extends Construction::TStageInstruction {
/**
* Gets all direct predecessors of this instruction.
*/
- final Instruction getAPredecessor() { result = getPredecessor(_) }
+ final Instruction getAPredecessor() { result = this.getPredecessor(_) }
}
/**
@@ -543,7 +550,7 @@ class IndexedInstruction extends Instruction {
* at this instruction. This instruction has no predecessors.
*/
class EnterFunctionInstruction extends Instruction {
- EnterFunctionInstruction() { getOpcode() instanceof Opcode::EnterFunction }
+ EnterFunctionInstruction() { this.getOpcode() instanceof Opcode::EnterFunction }
}
/**
@@ -554,7 +561,7 @@ class EnterFunctionInstruction extends Instruction {
* struct, or union, see `FieldAddressInstruction`.
*/
class VariableAddressInstruction extends VariableInstruction {
- VariableAddressInstruction() { getOpcode() instanceof Opcode::VariableAddress }
+ VariableAddressInstruction() { this.getOpcode() instanceof Opcode::VariableAddress }
}
/**
@@ -566,7 +573,7 @@ class VariableAddressInstruction extends VariableInstruction {
* The result has an `IRFunctionAddress` type.
*/
class FunctionAddressInstruction extends FunctionInstruction {
- FunctionAddressInstruction() { getOpcode() instanceof Opcode::FunctionAddress }
+ FunctionAddressInstruction() { this.getOpcode() instanceof Opcode::FunctionAddress }
}
/**
@@ -577,7 +584,7 @@ class FunctionAddressInstruction extends FunctionInstruction {
* initializes that parameter.
*/
class InitializeParameterInstruction extends VariableInstruction {
- InitializeParameterInstruction() { getOpcode() instanceof Opcode::InitializeParameter }
+ InitializeParameterInstruction() { this.getOpcode() instanceof Opcode::InitializeParameter }
/**
* Gets the parameter initialized by this instruction.
@@ -603,7 +610,7 @@ class InitializeParameterInstruction extends VariableInstruction {
* initialized elsewhere, would not otherwise have a definition in this function.
*/
class InitializeNonLocalInstruction extends Instruction {
- InitializeNonLocalInstruction() { getOpcode() instanceof Opcode::InitializeNonLocal }
+ InitializeNonLocalInstruction() { this.getOpcode() instanceof Opcode::InitializeNonLocal }
}
/**
@@ -611,7 +618,7 @@ class InitializeNonLocalInstruction extends Instruction {
* with the value of that memory on entry to the function.
*/
class InitializeIndirectionInstruction extends VariableInstruction {
- InitializeIndirectionInstruction() { getOpcode() instanceof Opcode::InitializeIndirection }
+ InitializeIndirectionInstruction() { this.getOpcode() instanceof Opcode::InitializeIndirection }
/**
* Gets the parameter initialized by this instruction.
@@ -635,24 +642,24 @@ class InitializeIndirectionInstruction extends VariableInstruction {
* An instruction that initializes the `this` pointer parameter of the enclosing function.
*/
class InitializeThisInstruction extends Instruction {
- InitializeThisInstruction() { getOpcode() instanceof Opcode::InitializeThis }
+ InitializeThisInstruction() { this.getOpcode() instanceof Opcode::InitializeThis }
}
/**
* An instruction that computes the address of a non-static field of an object.
*/
class FieldAddressInstruction extends FieldInstruction {
- FieldAddressInstruction() { getOpcode() instanceof Opcode::FieldAddress }
+ FieldAddressInstruction() { this.getOpcode() instanceof Opcode::FieldAddress }
/**
* Gets the operand that provides the address of the object containing the field.
*/
- final UnaryOperand getObjectAddressOperand() { result = getAnOperand() }
+ final UnaryOperand getObjectAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the object containing the field.
*/
- final Instruction getObjectAddress() { result = getObjectAddressOperand().getDef() }
+ final Instruction getObjectAddress() { result = this.getObjectAddressOperand().getDef() }
}
/**
@@ -661,17 +668,19 @@ class FieldAddressInstruction extends FieldInstruction {
* This instruction is used for element access to C# arrays.
*/
class ElementsAddressInstruction extends UnaryInstruction {
- ElementsAddressInstruction() { getOpcode() instanceof Opcode::ElementsAddress }
+ ElementsAddressInstruction() { this.getOpcode() instanceof Opcode::ElementsAddress }
/**
* Gets the operand that provides the address of the array object.
*/
- final UnaryOperand getArrayObjectAddressOperand() { result = getAnOperand() }
+ final UnaryOperand getArrayObjectAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the array object.
*/
- final Instruction getArrayObjectAddress() { result = getArrayObjectAddressOperand().getDef() }
+ final Instruction getArrayObjectAddress() {
+ result = this.getArrayObjectAddressOperand().getDef()
+ }
}
/**
@@ -685,7 +694,7 @@ class ElementsAddressInstruction extends UnaryInstruction {
* taken may want to ignore any function that contains an `ErrorInstruction`.
*/
class ErrorInstruction extends Instruction {
- ErrorInstruction() { getOpcode() instanceof Opcode::Error }
+ ErrorInstruction() { this.getOpcode() instanceof Opcode::Error }
}
/**
@@ -695,7 +704,7 @@ class ErrorInstruction extends Instruction {
* an initializer, or whose initializer only partially initializes the variable.
*/
class UninitializedInstruction extends VariableInstruction {
- UninitializedInstruction() { getOpcode() instanceof Opcode::Uninitialized }
+ UninitializedInstruction() { this.getOpcode() instanceof Opcode::Uninitialized }
/**
* Gets the variable that is uninitialized.
@@ -710,7 +719,7 @@ class UninitializedInstruction extends VariableInstruction {
* least one instruction, even when the AST has no semantic effect.
*/
class NoOpInstruction extends Instruction {
- NoOpInstruction() { getOpcode() instanceof Opcode::NoOp }
+ NoOpInstruction() { this.getOpcode() instanceof Opcode::NoOp }
}
/**
@@ -732,32 +741,42 @@ class NoOpInstruction extends Instruction {
* `void`-returning function.
*/
class ReturnInstruction extends Instruction {
- ReturnInstruction() { getOpcode() instanceof ReturnOpcode }
+ ReturnInstruction() { this.getOpcode() instanceof ReturnOpcode }
}
/**
* An instruction that returns control to the caller of the function, without returning a value.
*/
class ReturnVoidInstruction extends ReturnInstruction {
- ReturnVoidInstruction() { getOpcode() instanceof Opcode::ReturnVoid }
+ ReturnVoidInstruction() { this.getOpcode() instanceof Opcode::ReturnVoid }
}
/**
* An instruction that returns control to the caller of the function, including a return value.
*/
class ReturnValueInstruction extends ReturnInstruction {
- ReturnValueInstruction() { getOpcode() instanceof Opcode::ReturnValue }
+ ReturnValueInstruction() { this.getOpcode() instanceof Opcode::ReturnValue }
/**
* Gets the operand that provides the value being returned by the function.
*/
- final LoadOperand getReturnValueOperand() { result = getAnOperand() }
+ final LoadOperand getReturnValueOperand() { result = this.getAnOperand() }
+
+ /**
+ * Gets the operand that provides the address of the value being returned by the function.
+ */
+ final AddressOperand getReturnAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the value being returned by the function, if an
* exact definition is available.
*/
- final Instruction getReturnValue() { result = getReturnValueOperand().getDef() }
+ final Instruction getReturnValue() { result = this.getReturnValueOperand().getDef() }
+
+ /**
+ * Gets the instruction whose result provides the address of the value being returned by the function.
+ */
+ final Instruction getReturnAddress() { result = this.getReturnAddressOperand().getDef() }
}
/**
@@ -770,28 +789,28 @@ class ReturnValueInstruction extends ReturnInstruction {
* that the caller initialized the memory pointed to by the parameter before the call.
*/
class ReturnIndirectionInstruction extends VariableInstruction {
- ReturnIndirectionInstruction() { getOpcode() instanceof Opcode::ReturnIndirection }
+ ReturnIndirectionInstruction() { this.getOpcode() instanceof Opcode::ReturnIndirection }
/**
* Gets the operand that provides the value of the pointed-to memory.
*/
- final SideEffectOperand getSideEffectOperand() { result = getAnOperand() }
+ final SideEffectOperand getSideEffectOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the value of the pointed-to memory, if an exact
* definition is available.
*/
- final Instruction getSideEffect() { result = getSideEffectOperand().getDef() }
+ final Instruction getSideEffect() { result = this.getSideEffectOperand().getDef() }
/**
* Gets the operand that provides the address of the pointed-to memory.
*/
- final AddressOperand getSourceAddressOperand() { result = getAnOperand() }
+ final AddressOperand getSourceAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the pointed-to memory.
*/
- final Instruction getSourceAddress() { result = getSourceAddressOperand().getDef() }
+ final Instruction getSourceAddress() { result = this.getSourceAddressOperand().getDef() }
/**
* Gets the parameter for which this instruction reads the final pointed-to value within the
@@ -826,7 +845,7 @@ class ReturnIndirectionInstruction extends VariableInstruction {
* - `StoreInstruction` - Copies a register operand to a memory result.
*/
class CopyInstruction extends Instruction {
- CopyInstruction() { getOpcode() instanceof CopyOpcode }
+ CopyInstruction() { this.getOpcode() instanceof CopyOpcode }
/**
* Gets the operand that provides the input value of the copy.
@@ -837,16 +856,16 @@ class CopyInstruction extends Instruction {
* Gets the instruction whose result provides the input value of the copy, if an exact definition
* is available.
*/
- final Instruction getSourceValue() { result = getSourceValueOperand().getDef() }
+ final Instruction getSourceValue() { result = this.getSourceValueOperand().getDef() }
}
/**
* An instruction that returns a register result containing a copy of its register operand.
*/
class CopyValueInstruction extends CopyInstruction, UnaryInstruction {
- CopyValueInstruction() { getOpcode() instanceof Opcode::CopyValue }
+ CopyValueInstruction() { this.getOpcode() instanceof Opcode::CopyValue }
- final override UnaryOperand getSourceValueOperand() { result = getAnOperand() }
+ final override UnaryOperand getSourceValueOperand() { result = this.getAnOperand() }
}
/**
@@ -863,47 +882,49 @@ private string getAddressOperandDescription(AddressOperand operand) {
* An instruction that returns a register result containing a copy of its memory operand.
*/
class LoadInstruction extends CopyInstruction {
- LoadInstruction() { getOpcode() instanceof Opcode::Load }
+ LoadInstruction() { this.getOpcode() instanceof Opcode::Load }
final override string getImmediateString() {
- result = getAddressOperandDescription(getSourceAddressOperand())
+ result = getAddressOperandDescription(this.getSourceAddressOperand())
}
/**
* Gets the operand that provides the address of the value being loaded.
*/
- final AddressOperand getSourceAddressOperand() { result = getAnOperand() }
+ final AddressOperand getSourceAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the value being loaded.
*/
- final Instruction getSourceAddress() { result = getSourceAddressOperand().getDef() }
+ final Instruction getSourceAddress() { result = this.getSourceAddressOperand().getDef() }
- final override LoadOperand getSourceValueOperand() { result = getAnOperand() }
+ final override LoadOperand getSourceValueOperand() { result = this.getAnOperand() }
}
/**
* An instruction that returns a memory result containing a copy of its register operand.
*/
class StoreInstruction extends CopyInstruction {
- StoreInstruction() { getOpcode() instanceof Opcode::Store }
+ StoreInstruction() { this.getOpcode() instanceof Opcode::Store }
final override string getImmediateString() {
- result = getAddressOperandDescription(getDestinationAddressOperand())
+ result = getAddressOperandDescription(this.getDestinationAddressOperand())
}
/**
* Gets the operand that provides the address of the location to which the value will be stored.
*/
- final AddressOperand getDestinationAddressOperand() { result = getAnOperand() }
+ final AddressOperand getDestinationAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the location to which the value will
* be stored, if an exact definition is available.
*/
- final Instruction getDestinationAddress() { result = getDestinationAddressOperand().getDef() }
+ final Instruction getDestinationAddress() {
+ result = this.getDestinationAddressOperand().getDef()
+ }
- final override StoreValueOperand getSourceValueOperand() { result = getAnOperand() }
+ final override StoreValueOperand getSourceValueOperand() { result = this.getAnOperand() }
}
/**
@@ -911,27 +932,27 @@ class StoreInstruction extends CopyInstruction {
* operand.
*/
class ConditionalBranchInstruction extends Instruction {
- ConditionalBranchInstruction() { getOpcode() instanceof Opcode::ConditionalBranch }
+ ConditionalBranchInstruction() { this.getOpcode() instanceof Opcode::ConditionalBranch }
/**
* Gets the operand that provides the Boolean condition controlling the branch.
*/
- final ConditionOperand getConditionOperand() { result = getAnOperand() }
+ final ConditionOperand getConditionOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the Boolean condition controlling the branch.
*/
- final Instruction getCondition() { result = getConditionOperand().getDef() }
+ final Instruction getCondition() { result = this.getConditionOperand().getDef() }
/**
* Gets the instruction to which control will flow if the condition is true.
*/
- final Instruction getTrueSuccessor() { result = getSuccessor(EdgeKind::trueEdge()) }
+ final Instruction getTrueSuccessor() { result = this.getSuccessor(EdgeKind::trueEdge()) }
/**
* Gets the instruction to which control will flow if the condition is false.
*/
- final Instruction getFalseSuccessor() { result = getSuccessor(EdgeKind::falseEdge()) }
+ final Instruction getFalseSuccessor() { result = this.getSuccessor(EdgeKind::falseEdge()) }
}
/**
@@ -943,14 +964,14 @@ class ConditionalBranchInstruction extends Instruction {
* successors.
*/
class ExitFunctionInstruction extends Instruction {
- ExitFunctionInstruction() { getOpcode() instanceof Opcode::ExitFunction }
+ ExitFunctionInstruction() { this.getOpcode() instanceof Opcode::ExitFunction }
}
/**
* An instruction whose result is a constant value.
*/
class ConstantInstruction extends ConstantValueInstruction {
- ConstantInstruction() { getOpcode() instanceof Opcode::Constant }
+ ConstantInstruction() { this.getOpcode() instanceof Opcode::Constant }
}
/**
@@ -959,7 +980,7 @@ class ConstantInstruction extends ConstantValueInstruction {
class IntegerConstantInstruction extends ConstantInstruction {
IntegerConstantInstruction() {
exists(IRType resultType |
- resultType = getResultIRType() and
+ resultType = this.getResultIRType() and
(resultType instanceof IRIntegerType or resultType instanceof IRBooleanType)
)
}
@@ -969,7 +990,7 @@ class IntegerConstantInstruction extends ConstantInstruction {
* An instruction whose result is a constant value of floating-point type.
*/
class FloatConstantInstruction extends ConstantInstruction {
- FloatConstantInstruction() { getResultIRType() instanceof IRFloatingPointType }
+ FloatConstantInstruction() { this.getResultIRType() instanceof IRFloatingPointType }
}
/**
@@ -978,7 +999,9 @@ class FloatConstantInstruction extends ConstantInstruction {
class StringConstantInstruction extends VariableInstruction {
override IRStringLiteral var;
- final override string getImmediateString() { result = Language::getStringLiteralText(getValue()) }
+ final override string getImmediateString() {
+ result = Language::getStringLiteralText(this.getValue())
+ }
/**
* Gets the string literal whose address is returned by this instruction.
@@ -990,37 +1013,37 @@ class StringConstantInstruction extends VariableInstruction {
* An instruction whose result is computed from two operands.
*/
class BinaryInstruction extends Instruction {
- BinaryInstruction() { getOpcode() instanceof BinaryOpcode }
+ BinaryInstruction() { this.getOpcode() instanceof BinaryOpcode }
/**
* Gets the left operand of this binary instruction.
*/
- final LeftOperand getLeftOperand() { result = getAnOperand() }
+ final LeftOperand getLeftOperand() { result = this.getAnOperand() }
/**
* Gets the right operand of this binary instruction.
*/
- final RightOperand getRightOperand() { result = getAnOperand() }
+ final RightOperand getRightOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the value of the left operand of this binary
* instruction.
*/
- final Instruction getLeft() { result = getLeftOperand().getDef() }
+ final Instruction getLeft() { result = this.getLeftOperand().getDef() }
/**
* Gets the instruction whose result provides the value of the right operand of this binary
* instruction.
*/
- final Instruction getRight() { result = getRightOperand().getDef() }
+ final Instruction getRight() { result = this.getRightOperand().getDef() }
/**
* Holds if this instruction's operands are `op1` and `op2`, in either order.
*/
final predicate hasOperands(Operand op1, Operand op2) {
- op1 = getLeftOperand() and op2 = getRightOperand()
+ op1 = this.getLeftOperand() and op2 = this.getRightOperand()
or
- op1 = getRightOperand() and op2 = getLeftOperand()
+ op1 = this.getRightOperand() and op2 = this.getLeftOperand()
}
}
@@ -1028,7 +1051,7 @@ class BinaryInstruction extends Instruction {
* An instruction that computes the result of an arithmetic operation.
*/
class ArithmeticInstruction extends Instruction {
- ArithmeticInstruction() { getOpcode() instanceof ArithmeticOpcode }
+ ArithmeticInstruction() { this.getOpcode() instanceof ArithmeticOpcode }
}
/**
@@ -1050,7 +1073,7 @@ class UnaryArithmeticInstruction extends ArithmeticInstruction, UnaryInstruction
* performed according to IEEE-754.
*/
class AddInstruction extends BinaryArithmeticInstruction {
- AddInstruction() { getOpcode() instanceof Opcode::Add }
+ AddInstruction() { this.getOpcode() instanceof Opcode::Add }
}
/**
@@ -1061,7 +1084,7 @@ class AddInstruction extends BinaryArithmeticInstruction {
* according to IEEE-754.
*/
class SubInstruction extends BinaryArithmeticInstruction {
- SubInstruction() { getOpcode() instanceof Opcode::Sub }
+ SubInstruction() { this.getOpcode() instanceof Opcode::Sub }
}
/**
@@ -1072,7 +1095,7 @@ class SubInstruction extends BinaryArithmeticInstruction {
* performed according to IEEE-754.
*/
class MulInstruction extends BinaryArithmeticInstruction {
- MulInstruction() { getOpcode() instanceof Opcode::Mul }
+ MulInstruction() { this.getOpcode() instanceof Opcode::Mul }
}
/**
@@ -1083,7 +1106,7 @@ class MulInstruction extends BinaryArithmeticInstruction {
* to IEEE-754.
*/
class DivInstruction extends BinaryArithmeticInstruction {
- DivInstruction() { getOpcode() instanceof Opcode::Div }
+ DivInstruction() { this.getOpcode() instanceof Opcode::Div }
}
/**
@@ -1093,7 +1116,7 @@ class DivInstruction extends BinaryArithmeticInstruction {
* division by zero or integer overflow is undefined.
*/
class RemInstruction extends BinaryArithmeticInstruction {
- RemInstruction() { getOpcode() instanceof Opcode::Rem }
+ RemInstruction() { this.getOpcode() instanceof Opcode::Rem }
}
/**
@@ -1104,14 +1127,14 @@ class RemInstruction extends BinaryArithmeticInstruction {
* is performed according to IEEE-754.
*/
class NegateInstruction extends UnaryArithmeticInstruction {
- NegateInstruction() { getOpcode() instanceof Opcode::Negate }
+ NegateInstruction() { this.getOpcode() instanceof Opcode::Negate }
}
/**
* An instruction that computes the result of a bitwise operation.
*/
class BitwiseInstruction extends Instruction {
- BitwiseInstruction() { getOpcode() instanceof BitwiseOpcode }
+ BitwiseInstruction() { this.getOpcode() instanceof BitwiseOpcode }
}
/**
@@ -1130,7 +1153,7 @@ class UnaryBitwiseInstruction extends BitwiseInstruction, UnaryInstruction { }
* Both operands must have the same integer type, which will also be the result type.
*/
class BitAndInstruction extends BinaryBitwiseInstruction {
- BitAndInstruction() { getOpcode() instanceof Opcode::BitAnd }
+ BitAndInstruction() { this.getOpcode() instanceof Opcode::BitAnd }
}
/**
@@ -1139,7 +1162,7 @@ class BitAndInstruction extends BinaryBitwiseInstruction {
* Both operands must have the same integer type, which will also be the result type.
*/
class BitOrInstruction extends BinaryBitwiseInstruction {
- BitOrInstruction() { getOpcode() instanceof Opcode::BitOr }
+ BitOrInstruction() { this.getOpcode() instanceof Opcode::BitOr }
}
/**
@@ -1148,7 +1171,7 @@ class BitOrInstruction extends BinaryBitwiseInstruction {
* Both operands must have the same integer type, which will also be the result type.
*/
class BitXorInstruction extends BinaryBitwiseInstruction {
- BitXorInstruction() { getOpcode() instanceof Opcode::BitXor }
+ BitXorInstruction() { this.getOpcode() instanceof Opcode::BitXor }
}
/**
@@ -1159,7 +1182,7 @@ class BitXorInstruction extends BinaryBitwiseInstruction {
* rightmost bits are zero-filled.
*/
class ShiftLeftInstruction extends BinaryBitwiseInstruction {
- ShiftLeftInstruction() { getOpcode() instanceof Opcode::ShiftLeft }
+ ShiftLeftInstruction() { this.getOpcode() instanceof Opcode::ShiftLeft }
}
/**
@@ -1172,7 +1195,7 @@ class ShiftLeftInstruction extends BinaryBitwiseInstruction {
* of the left operand.
*/
class ShiftRightInstruction extends BinaryBitwiseInstruction {
- ShiftRightInstruction() { getOpcode() instanceof Opcode::ShiftRight }
+ ShiftRightInstruction() { this.getOpcode() instanceof Opcode::ShiftRight }
}
/**
@@ -1183,7 +1206,7 @@ class PointerArithmeticInstruction extends BinaryInstruction {
int elementSize;
PointerArithmeticInstruction() {
- getOpcode() instanceof PointerArithmeticOpcode and
+ this.getOpcode() instanceof PointerArithmeticOpcode and
elementSize = Raw::getInstructionElementSize(this)
}
@@ -1206,7 +1229,7 @@ class PointerArithmeticInstruction extends BinaryInstruction {
* An instruction that adds or subtracts an integer offset from a pointer.
*/
class PointerOffsetInstruction extends PointerArithmeticInstruction {
- PointerOffsetInstruction() { getOpcode() instanceof PointerOffsetOpcode }
+ PointerOffsetInstruction() { this.getOpcode() instanceof PointerOffsetOpcode }
}
/**
@@ -1217,7 +1240,7 @@ class PointerOffsetInstruction extends PointerArithmeticInstruction {
* overflow is undefined.
*/
class PointerAddInstruction extends PointerOffsetInstruction {
- PointerAddInstruction() { getOpcode() instanceof Opcode::PointerAdd }
+ PointerAddInstruction() { this.getOpcode() instanceof Opcode::PointerAdd }
}
/**
@@ -1228,7 +1251,7 @@ class PointerAddInstruction extends PointerOffsetInstruction {
* pointer underflow is undefined.
*/
class PointerSubInstruction extends PointerOffsetInstruction {
- PointerSubInstruction() { getOpcode() instanceof Opcode::PointerSub }
+ PointerSubInstruction() { this.getOpcode() instanceof Opcode::PointerSub }
}
/**
@@ -1241,31 +1264,31 @@ class PointerSubInstruction extends PointerOffsetInstruction {
* undefined.
*/
class PointerDiffInstruction extends PointerArithmeticInstruction {
- PointerDiffInstruction() { getOpcode() instanceof Opcode::PointerDiff }
+ PointerDiffInstruction() { this.getOpcode() instanceof Opcode::PointerDiff }
}
/**
* An instruction whose result is computed from a single operand.
*/
class UnaryInstruction extends Instruction {
- UnaryInstruction() { getOpcode() instanceof UnaryOpcode }
+ UnaryInstruction() { this.getOpcode() instanceof UnaryOpcode }
/**
* Gets the sole operand of this instruction.
*/
- final UnaryOperand getUnaryOperand() { result = getAnOperand() }
+ final UnaryOperand getUnaryOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the sole operand of this instruction.
*/
- final Instruction getUnary() { result = getUnaryOperand().getDef() }
+ final Instruction getUnary() { result = this.getUnaryOperand().getDef() }
}
/**
* An instruction that converts the value of its operand to a value of a different type.
*/
class ConvertInstruction extends UnaryInstruction {
- ConvertInstruction() { getOpcode() instanceof Opcode::Convert }
+ ConvertInstruction() { this.getOpcode() instanceof Opcode::Convert }
}
/**
@@ -1279,7 +1302,7 @@ class ConvertInstruction extends UnaryInstruction {
* `as` expression.
*/
class CheckedConvertOrNullInstruction extends UnaryInstruction {
- CheckedConvertOrNullInstruction() { getOpcode() instanceof Opcode::CheckedConvertOrNull }
+ CheckedConvertOrNullInstruction() { this.getOpcode() instanceof Opcode::CheckedConvertOrNull }
}
/**
@@ -1293,7 +1316,7 @@ class CheckedConvertOrNullInstruction extends UnaryInstruction {
* expression.
*/
class CheckedConvertOrThrowInstruction extends UnaryInstruction {
- CheckedConvertOrThrowInstruction() { getOpcode() instanceof Opcode::CheckedConvertOrThrow }
+ CheckedConvertOrThrowInstruction() { this.getOpcode() instanceof Opcode::CheckedConvertOrThrow }
}
/**
@@ -1306,7 +1329,7 @@ class CheckedConvertOrThrowInstruction extends UnaryInstruction {
* the most-derived object.
*/
class CompleteObjectAddressInstruction extends UnaryInstruction {
- CompleteObjectAddressInstruction() { getOpcode() instanceof Opcode::CompleteObjectAddress }
+ CompleteObjectAddressInstruction() { this.getOpcode() instanceof Opcode::CompleteObjectAddress }
}
/**
@@ -1351,7 +1374,7 @@ class InheritanceConversionInstruction extends UnaryInstruction {
* An instruction that converts from the address of a derived class to the address of a base class.
*/
class ConvertToBaseInstruction extends InheritanceConversionInstruction {
- ConvertToBaseInstruction() { getOpcode() instanceof ConvertToBaseOpcode }
+ ConvertToBaseInstruction() { this.getOpcode() instanceof ConvertToBaseOpcode }
}
/**
@@ -1361,7 +1384,9 @@ class ConvertToBaseInstruction extends InheritanceConversionInstruction {
* If the operand holds a null address, the result is a null address.
*/
class ConvertToNonVirtualBaseInstruction extends ConvertToBaseInstruction {
- ConvertToNonVirtualBaseInstruction() { getOpcode() instanceof Opcode::ConvertToNonVirtualBase }
+ ConvertToNonVirtualBaseInstruction() {
+ this.getOpcode() instanceof Opcode::ConvertToNonVirtualBase
+ }
}
/**
@@ -1371,7 +1396,7 @@ class ConvertToNonVirtualBaseInstruction extends ConvertToBaseInstruction {
* If the operand holds a null address, the result is a null address.
*/
class ConvertToVirtualBaseInstruction extends ConvertToBaseInstruction {
- ConvertToVirtualBaseInstruction() { getOpcode() instanceof Opcode::ConvertToVirtualBase }
+ ConvertToVirtualBaseInstruction() { this.getOpcode() instanceof Opcode::ConvertToVirtualBase }
}
/**
@@ -1381,7 +1406,7 @@ class ConvertToVirtualBaseInstruction extends ConvertToBaseInstruction {
* If the operand holds a null address, the result is a null address.
*/
class ConvertToDerivedInstruction extends InheritanceConversionInstruction {
- ConvertToDerivedInstruction() { getOpcode() instanceof Opcode::ConvertToDerived }
+ ConvertToDerivedInstruction() { this.getOpcode() instanceof Opcode::ConvertToDerived }
}
/**
@@ -1390,7 +1415,7 @@ class ConvertToDerivedInstruction extends InheritanceConversionInstruction {
* The operand must have an integer type, which will also be the result type.
*/
class BitComplementInstruction extends UnaryBitwiseInstruction {
- BitComplementInstruction() { getOpcode() instanceof Opcode::BitComplement }
+ BitComplementInstruction() { this.getOpcode() instanceof Opcode::BitComplement }
}
/**
@@ -1399,14 +1424,14 @@ class BitComplementInstruction extends UnaryBitwiseInstruction {
* The operand must have a Boolean type, which will also be the result type.
*/
class LogicalNotInstruction extends UnaryInstruction {
- LogicalNotInstruction() { getOpcode() instanceof Opcode::LogicalNot }
+ LogicalNotInstruction() { this.getOpcode() instanceof Opcode::LogicalNot }
}
/**
* An instruction that compares two numeric operands.
*/
class CompareInstruction extends BinaryInstruction {
- CompareInstruction() { getOpcode() instanceof CompareOpcode }
+ CompareInstruction() { this.getOpcode() instanceof CompareOpcode }
}
/**
@@ -1417,7 +1442,7 @@ class CompareInstruction extends BinaryInstruction {
* unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareEQInstruction extends CompareInstruction {
- CompareEQInstruction() { getOpcode() instanceof Opcode::CompareEQ }
+ CompareEQInstruction() { this.getOpcode() instanceof Opcode::CompareEQ }
}
/**
@@ -1428,14 +1453,14 @@ class CompareEQInstruction extends CompareInstruction {
* `left == right`. Floating-point comparison is performed according to IEEE-754.
*/
class CompareNEInstruction extends CompareInstruction {
- CompareNEInstruction() { getOpcode() instanceof Opcode::CompareNE }
+ CompareNEInstruction() { this.getOpcode() instanceof Opcode::CompareNE }
}
/**
* An instruction that does a relative comparison of two values, such as `<` or `>=`.
*/
class RelationalInstruction extends CompareInstruction {
- RelationalInstruction() { getOpcode() instanceof RelationalOpcode }
+ RelationalInstruction() { this.getOpcode() instanceof RelationalOpcode }
/**
* Gets the operand on the "greater" (or "greater-or-equal") side
@@ -1467,11 +1492,11 @@ class RelationalInstruction extends CompareInstruction {
* are unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareLTInstruction extends RelationalInstruction {
- CompareLTInstruction() { getOpcode() instanceof Opcode::CompareLT }
+ CompareLTInstruction() { this.getOpcode() instanceof Opcode::CompareLT }
- override Instruction getLesser() { result = getLeft() }
+ override Instruction getLesser() { result = this.getLeft() }
- override Instruction getGreater() { result = getRight() }
+ override Instruction getGreater() { result = this.getRight() }
override predicate isStrict() { any() }
}
@@ -1484,11 +1509,11 @@ class CompareLTInstruction extends RelationalInstruction {
* are unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareGTInstruction extends RelationalInstruction {
- CompareGTInstruction() { getOpcode() instanceof Opcode::CompareGT }
+ CompareGTInstruction() { this.getOpcode() instanceof Opcode::CompareGT }
- override Instruction getLesser() { result = getRight() }
+ override Instruction getLesser() { result = this.getRight() }
- override Instruction getGreater() { result = getLeft() }
+ override Instruction getGreater() { result = this.getLeft() }
override predicate isStrict() { any() }
}
@@ -1502,11 +1527,11 @@ class CompareGTInstruction extends RelationalInstruction {
* are unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareLEInstruction extends RelationalInstruction {
- CompareLEInstruction() { getOpcode() instanceof Opcode::CompareLE }
+ CompareLEInstruction() { this.getOpcode() instanceof Opcode::CompareLE }
- override Instruction getLesser() { result = getLeft() }
+ override Instruction getLesser() { result = this.getLeft() }
- override Instruction getGreater() { result = getRight() }
+ override Instruction getGreater() { result = this.getRight() }
override predicate isStrict() { none() }
}
@@ -1520,11 +1545,11 @@ class CompareLEInstruction extends RelationalInstruction {
* are unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareGEInstruction extends RelationalInstruction {
- CompareGEInstruction() { getOpcode() instanceof Opcode::CompareGE }
+ CompareGEInstruction() { this.getOpcode() instanceof Opcode::CompareGE }
- override Instruction getLesser() { result = getRight() }
+ override Instruction getLesser() { result = this.getRight() }
- override Instruction getGreater() { result = getLeft() }
+ override Instruction getGreater() { result = this.getLeft() }
override predicate isStrict() { none() }
}
@@ -1543,78 +1568,78 @@ class CompareGEInstruction extends RelationalInstruction {
* of any case edge.
*/
class SwitchInstruction extends Instruction {
- SwitchInstruction() { getOpcode() instanceof Opcode::Switch }
+ SwitchInstruction() { this.getOpcode() instanceof Opcode::Switch }
/** Gets the operand that provides the integer value controlling the switch. */
- final ConditionOperand getExpressionOperand() { result = getAnOperand() }
+ final ConditionOperand getExpressionOperand() { result = this.getAnOperand() }
/** Gets the instruction whose result provides the integer value controlling the switch. */
- final Instruction getExpression() { result = getExpressionOperand().getDef() }
+ final Instruction getExpression() { result = this.getExpressionOperand().getDef() }
/** Gets the successor instructions along the case edges of the switch. */
- final Instruction getACaseSuccessor() { exists(CaseEdge edge | result = getSuccessor(edge)) }
+ final Instruction getACaseSuccessor() { exists(CaseEdge edge | result = this.getSuccessor(edge)) }
/** Gets the successor instruction along the default edge of the switch, if any. */
- final Instruction getDefaultSuccessor() { result = getSuccessor(EdgeKind::defaultEdge()) }
+ final Instruction getDefaultSuccessor() { result = this.getSuccessor(EdgeKind::defaultEdge()) }
}
/**
* An instruction that calls a function.
*/
class CallInstruction extends Instruction {
- CallInstruction() { getOpcode() instanceof Opcode::Call }
+ CallInstruction() { this.getOpcode() instanceof Opcode::Call }
final override string getImmediateString() {
- result = getStaticCallTarget().toString()
+ result = this.getStaticCallTarget().toString()
or
- not exists(getStaticCallTarget()) and result = "?"
+ not exists(this.getStaticCallTarget()) and result = "?"
}
/**
* Gets the operand the specifies the target function of the call.
*/
- final CallTargetOperand getCallTargetOperand() { result = getAnOperand() }
+ final CallTargetOperand getCallTargetOperand() { result = this.getAnOperand() }
/**
* Gets the `Instruction` that computes the target function of the call. This is usually a
* `FunctionAddress` instruction, but can also be an arbitrary instruction that produces a
* function pointer.
*/
- final Instruction getCallTarget() { result = getCallTargetOperand().getDef() }
+ final Instruction getCallTarget() { result = this.getCallTargetOperand().getDef() }
/**
* Gets all of the argument operands of the call, including the `this` pointer, if any.
*/
- final ArgumentOperand getAnArgumentOperand() { result = getAnOperand() }
+ final ArgumentOperand getAnArgumentOperand() { result = this.getAnOperand() }
/**
* Gets the `Function` that the call targets, if this is statically known.
*/
final Language::Function getStaticCallTarget() {
- result = getCallTarget().(FunctionAddressInstruction).getFunctionSymbol()
+ result = this.getCallTarget().(FunctionAddressInstruction).getFunctionSymbol()
}
/**
* Gets all of the arguments of the call, including the `this` pointer, if any.
*/
- final Instruction getAnArgument() { result = getAnArgumentOperand().getDef() }
+ final Instruction getAnArgument() { result = this.getAnArgumentOperand().getDef() }
/**
* Gets the `this` pointer argument operand of the call, if any.
*/
- final ThisArgumentOperand getThisArgumentOperand() { result = getAnOperand() }
+ final ThisArgumentOperand getThisArgumentOperand() { result = this.getAnOperand() }
/**
* Gets the `this` pointer argument of the call, if any.
*/
- final Instruction getThisArgument() { result = getThisArgumentOperand().getDef() }
+ final Instruction getThisArgument() { result = this.getThisArgumentOperand().getDef() }
/**
* Gets the argument operand at the specified index.
*/
pragma[noinline]
final PositionalArgumentOperand getPositionalArgumentOperand(int index) {
- result = getAnOperand() and
+ result = this.getAnOperand() and
result.getIndex() = index
}
@@ -1623,7 +1648,7 @@ class CallInstruction extends Instruction {
*/
pragma[noinline]
final Instruction getPositionalArgument(int index) {
- result = getPositionalArgumentOperand(index).getDef()
+ result = this.getPositionalArgumentOperand(index).getDef()
}
/**
@@ -1631,16 +1656,16 @@ class CallInstruction extends Instruction {
*/
pragma[noinline]
final ArgumentOperand getArgumentOperand(int index) {
- index >= 0 and result = getPositionalArgumentOperand(index)
+ index >= 0 and result = this.getPositionalArgumentOperand(index)
or
- index = -1 and result = getThisArgumentOperand()
+ index = -1 and result = this.getThisArgumentOperand()
}
/**
* Gets the argument at the specified index, or `this` if `index` is `-1`.
*/
pragma[noinline]
- final Instruction getArgument(int index) { result = getArgumentOperand(index).getDef() }
+ final Instruction getArgument(int index) { result = this.getArgumentOperand(index).getDef() }
/**
* Gets the number of arguments of the call, including the `this` pointer, if any.
@@ -1665,7 +1690,7 @@ class CallInstruction extends Instruction {
* An instruction representing a side effect of a function call.
*/
class SideEffectInstruction extends Instruction {
- SideEffectInstruction() { getOpcode() instanceof SideEffectOpcode }
+ SideEffectInstruction() { this.getOpcode() instanceof SideEffectOpcode }
/**
* Gets the instruction whose execution causes this side effect.
@@ -1680,7 +1705,7 @@ class SideEffectInstruction extends Instruction {
* accessed by that call.
*/
class CallSideEffectInstruction extends SideEffectInstruction {
- CallSideEffectInstruction() { getOpcode() instanceof Opcode::CallSideEffect }
+ CallSideEffectInstruction() { this.getOpcode() instanceof Opcode::CallSideEffect }
}
/**
@@ -1691,7 +1716,7 @@ class CallSideEffectInstruction extends SideEffectInstruction {
* call target cannot write to escaped memory.
*/
class CallReadSideEffectInstruction extends SideEffectInstruction {
- CallReadSideEffectInstruction() { getOpcode() instanceof Opcode::CallReadSideEffect }
+ CallReadSideEffectInstruction() { this.getOpcode() instanceof Opcode::CallReadSideEffect }
}
/**
@@ -1699,33 +1724,33 @@ class CallReadSideEffectInstruction extends SideEffectInstruction {
* specific parameter.
*/
class ReadSideEffectInstruction extends SideEffectInstruction, IndexedInstruction {
- ReadSideEffectInstruction() { getOpcode() instanceof ReadSideEffectOpcode }
+ ReadSideEffectInstruction() { this.getOpcode() instanceof ReadSideEffectOpcode }
/** Gets the operand for the value that will be read from this instruction, if known. */
- final SideEffectOperand getSideEffectOperand() { result = getAnOperand() }
+ final SideEffectOperand getSideEffectOperand() { result = this.getAnOperand() }
/** Gets the value that will be read from this instruction, if known. */
- final Instruction getSideEffect() { result = getSideEffectOperand().getDef() }
+ final Instruction getSideEffect() { result = this.getSideEffectOperand().getDef() }
/** Gets the operand for the address from which this instruction may read. */
- final AddressOperand getArgumentOperand() { result = getAnOperand() }
+ final AddressOperand getArgumentOperand() { result = this.getAnOperand() }
/** Gets the address from which this instruction may read. */
- final Instruction getArgumentDef() { result = getArgumentOperand().getDef() }
+ final Instruction getArgumentDef() { result = this.getArgumentOperand().getDef() }
}
/**
* An instruction representing the read of an indirect parameter within a function call.
*/
class IndirectReadSideEffectInstruction extends ReadSideEffectInstruction {
- IndirectReadSideEffectInstruction() { getOpcode() instanceof Opcode::IndirectReadSideEffect }
+ IndirectReadSideEffectInstruction() { this.getOpcode() instanceof Opcode::IndirectReadSideEffect }
}
/**
* An instruction representing the read of an indirect buffer parameter within a function call.
*/
class BufferReadSideEffectInstruction extends ReadSideEffectInstruction {
- BufferReadSideEffectInstruction() { getOpcode() instanceof Opcode::BufferReadSideEffect }
+ BufferReadSideEffectInstruction() { this.getOpcode() instanceof Opcode::BufferReadSideEffect }
}
/**
@@ -1733,18 +1758,18 @@ class BufferReadSideEffectInstruction extends ReadSideEffectInstruction {
*/
class SizedBufferReadSideEffectInstruction extends ReadSideEffectInstruction {
SizedBufferReadSideEffectInstruction() {
- getOpcode() instanceof Opcode::SizedBufferReadSideEffect
+ this.getOpcode() instanceof Opcode::SizedBufferReadSideEffect
}
/**
* Gets the operand that holds the number of bytes read from the buffer.
*/
- final BufferSizeOperand getBufferSizeOperand() { result = getAnOperand() }
+ final BufferSizeOperand getBufferSizeOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the number of bytes read from the buffer.
*/
- final Instruction getBufferSize() { result = getBufferSizeOperand().getDef() }
+ final Instruction getBufferSize() { result = this.getBufferSizeOperand().getDef() }
}
/**
@@ -1752,17 +1777,17 @@ class SizedBufferReadSideEffectInstruction extends ReadSideEffectInstruction {
* specific parameter.
*/
class WriteSideEffectInstruction extends SideEffectInstruction, IndexedInstruction {
- WriteSideEffectInstruction() { getOpcode() instanceof WriteSideEffectOpcode }
+ WriteSideEffectInstruction() { this.getOpcode() instanceof WriteSideEffectOpcode }
/**
* Get the operand that holds the address of the memory to be written.
*/
- final AddressOperand getDestinationAddressOperand() { result = getAnOperand() }
+ final AddressOperand getDestinationAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the memory to be written.
*/
- Instruction getDestinationAddress() { result = getDestinationAddressOperand().getDef() }
+ Instruction getDestinationAddress() { result = this.getDestinationAddressOperand().getDef() }
}
/**
@@ -1770,7 +1795,7 @@ class WriteSideEffectInstruction extends SideEffectInstruction, IndexedInstructi
*/
class IndirectMustWriteSideEffectInstruction extends WriteSideEffectInstruction {
IndirectMustWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::IndirectMustWriteSideEffect
+ this.getOpcode() instanceof Opcode::IndirectMustWriteSideEffect
}
}
@@ -1780,7 +1805,7 @@ class IndirectMustWriteSideEffectInstruction extends WriteSideEffectInstruction
*/
class BufferMustWriteSideEffectInstruction extends WriteSideEffectInstruction {
BufferMustWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::BufferMustWriteSideEffect
+ this.getOpcode() instanceof Opcode::BufferMustWriteSideEffect
}
}
@@ -1790,18 +1815,18 @@ class BufferMustWriteSideEffectInstruction extends WriteSideEffectInstruction {
*/
class SizedBufferMustWriteSideEffectInstruction extends WriteSideEffectInstruction {
SizedBufferMustWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::SizedBufferMustWriteSideEffect
+ this.getOpcode() instanceof Opcode::SizedBufferMustWriteSideEffect
}
/**
* Gets the operand that holds the number of bytes written to the buffer.
*/
- final BufferSizeOperand getBufferSizeOperand() { result = getAnOperand() }
+ final BufferSizeOperand getBufferSizeOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the number of bytes written to the buffer.
*/
- final Instruction getBufferSize() { result = getBufferSizeOperand().getDef() }
+ final Instruction getBufferSize() { result = this.getBufferSizeOperand().getDef() }
}
/**
@@ -1812,7 +1837,7 @@ class SizedBufferMustWriteSideEffectInstruction extends WriteSideEffectInstructi
*/
class IndirectMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
IndirectMayWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::IndirectMayWriteSideEffect
+ this.getOpcode() instanceof Opcode::IndirectMayWriteSideEffect
}
}
@@ -1822,7 +1847,9 @@ class IndirectMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
* Unlike `BufferWriteSideEffectInstruction`, the buffer might not be completely overwritten.
*/
class BufferMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
- BufferMayWriteSideEffectInstruction() { getOpcode() instanceof Opcode::BufferMayWriteSideEffect }
+ BufferMayWriteSideEffectInstruction() {
+ this.getOpcode() instanceof Opcode::BufferMayWriteSideEffect
+ }
}
/**
@@ -1832,18 +1859,18 @@ class BufferMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
*/
class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
SizedBufferMayWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::SizedBufferMayWriteSideEffect
+ this.getOpcode() instanceof Opcode::SizedBufferMayWriteSideEffect
}
/**
* Gets the operand that holds the number of bytes written to the buffer.
*/
- final BufferSizeOperand getBufferSizeOperand() { result = getAnOperand() }
+ final BufferSizeOperand getBufferSizeOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the number of bytes written to the buffer.
*/
- final Instruction getBufferSize() { result = getBufferSizeOperand().getDef() }
+ final Instruction getBufferSize() { result = this.getBufferSizeOperand().getDef() }
}
/**
@@ -1852,80 +1879,80 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio
*/
class InitializeDynamicAllocationInstruction extends SideEffectInstruction {
InitializeDynamicAllocationInstruction() {
- getOpcode() instanceof Opcode::InitializeDynamicAllocation
+ this.getOpcode() instanceof Opcode::InitializeDynamicAllocation
}
/**
* Gets the operand that represents the address of the allocation this instruction is initializing.
*/
- final AddressOperand getAllocationAddressOperand() { result = getAnOperand() }
+ final AddressOperand getAllocationAddressOperand() { result = this.getAnOperand() }
/**
* Gets the address for the allocation this instruction is initializing.
*/
- final Instruction getAllocationAddress() { result = getAllocationAddressOperand().getDef() }
+ final Instruction getAllocationAddress() { result = this.getAllocationAddressOperand().getDef() }
}
/**
* An instruction representing a GNU or MSVC inline assembly statement.
*/
class InlineAsmInstruction extends Instruction {
- InlineAsmInstruction() { getOpcode() instanceof Opcode::InlineAsm }
+ InlineAsmInstruction() { this.getOpcode() instanceof Opcode::InlineAsm }
}
/**
* An instruction that throws an exception.
*/
class ThrowInstruction extends Instruction {
- ThrowInstruction() { getOpcode() instanceof ThrowOpcode }
+ ThrowInstruction() { this.getOpcode() instanceof ThrowOpcode }
}
/**
* An instruction that throws a new exception.
*/
class ThrowValueInstruction extends ThrowInstruction {
- ThrowValueInstruction() { getOpcode() instanceof Opcode::ThrowValue }
+ ThrowValueInstruction() { this.getOpcode() instanceof Opcode::ThrowValue }
/**
* Gets the address operand of the exception thrown by this instruction.
*/
- final AddressOperand getExceptionAddressOperand() { result = getAnOperand() }
+ final AddressOperand getExceptionAddressOperand() { result = this.getAnOperand() }
/**
* Gets the address of the exception thrown by this instruction.
*/
- final Instruction getExceptionAddress() { result = getExceptionAddressOperand().getDef() }
+ final Instruction getExceptionAddress() { result = this.getExceptionAddressOperand().getDef() }
/**
* Gets the operand for the exception thrown by this instruction.
*/
- final LoadOperand getExceptionOperand() { result = getAnOperand() }
+ final LoadOperand getExceptionOperand() { result = this.getAnOperand() }
/**
* Gets the exception thrown by this instruction.
*/
- final Instruction getException() { result = getExceptionOperand().getDef() }
+ final Instruction getException() { result = this.getExceptionOperand().getDef() }
}
/**
* An instruction that re-throws the current exception.
*/
class ReThrowInstruction extends ThrowInstruction {
- ReThrowInstruction() { getOpcode() instanceof Opcode::ReThrow }
+ ReThrowInstruction() { this.getOpcode() instanceof Opcode::ReThrow }
}
/**
* An instruction that exits the current function by propagating an exception.
*/
class UnwindInstruction extends Instruction {
- UnwindInstruction() { getOpcode() instanceof Opcode::Unwind }
+ UnwindInstruction() { this.getOpcode() instanceof Opcode::Unwind }
}
/**
* An instruction that starts a `catch` handler.
*/
class CatchInstruction extends Instruction {
- CatchInstruction() { getOpcode() instanceof CatchOpcode }
+ CatchInstruction() { this.getOpcode() instanceof CatchOpcode }
}
/**
@@ -1935,7 +1962,7 @@ class CatchByTypeInstruction extends CatchInstruction {
Language::LanguageType exceptionType;
CatchByTypeInstruction() {
- getOpcode() instanceof Opcode::CatchByType and
+ this.getOpcode() instanceof Opcode::CatchByType and
exceptionType = Raw::getInstructionExceptionType(this)
}
@@ -1951,21 +1978,21 @@ class CatchByTypeInstruction extends CatchInstruction {
* An instruction that catches any exception.
*/
class CatchAnyInstruction extends CatchInstruction {
- CatchAnyInstruction() { getOpcode() instanceof Opcode::CatchAny }
+ CatchAnyInstruction() { this.getOpcode() instanceof Opcode::CatchAny }
}
/**
* An instruction that initializes all escaped memory.
*/
class AliasedDefinitionInstruction extends Instruction {
- AliasedDefinitionInstruction() { getOpcode() instanceof Opcode::AliasedDefinition }
+ AliasedDefinitionInstruction() { this.getOpcode() instanceof Opcode::AliasedDefinition }
}
/**
* An instruction that consumes all escaped memory on exit from the function.
*/
class AliasedUseInstruction extends Instruction {
- AliasedUseInstruction() { getOpcode() instanceof Opcode::AliasedUse }
+ AliasedUseInstruction() { this.getOpcode() instanceof Opcode::AliasedUse }
}
/**
@@ -1979,7 +2006,7 @@ class AliasedUseInstruction extends Instruction {
* runtime.
*/
class PhiInstruction extends Instruction {
- PhiInstruction() { getOpcode() instanceof Opcode::Phi }
+ PhiInstruction() { this.getOpcode() instanceof Opcode::Phi }
/**
* Gets all of the instruction's `PhiInputOperand`s, representing the values that flow from each predecessor block.
@@ -2047,29 +2074,29 @@ class PhiInstruction extends Instruction {
* https://link.springer.com/content/pdf/10.1007%2F3-540-61053-7_66.pdf.
*/
class ChiInstruction extends Instruction {
- ChiInstruction() { getOpcode() instanceof Opcode::Chi }
+ ChiInstruction() { this.getOpcode() instanceof Opcode::Chi }
/**
* Gets the operand that represents the previous state of all memory that might be aliased by the
* memory write.
*/
- final ChiTotalOperand getTotalOperand() { result = getAnOperand() }
+ final ChiTotalOperand getTotalOperand() { result = this.getAnOperand() }
/**
* Gets the operand that represents the previous state of all memory that might be aliased by the
* memory write.
*/
- final Instruction getTotal() { result = getTotalOperand().getDef() }
+ final Instruction getTotal() { result = this.getTotalOperand().getDef() }
/**
* Gets the operand that represents the new value written by the memory write.
*/
- final ChiPartialOperand getPartialOperand() { result = getAnOperand() }
+ final ChiPartialOperand getPartialOperand() { result = this.getAnOperand() }
/**
* Gets the operand that represents the new value written by the memory write.
*/
- final Instruction getPartial() { result = getPartialOperand().getDef() }
+ final Instruction getPartial() { result = this.getPartialOperand().getDef() }
/**
* Gets the bit range `[startBit, endBit)` updated by the partial operand of this `ChiInstruction`, relative to the start address of the total operand.
@@ -2093,7 +2120,7 @@ class ChiInstruction extends Instruction {
* or `Switch` instruction where that particular edge is infeasible.
*/
class UnreachedInstruction extends Instruction {
- UnreachedInstruction() { getOpcode() instanceof Opcode::Unreached }
+ UnreachedInstruction() { this.getOpcode() instanceof Opcode::Unreached }
}
/**
@@ -2106,7 +2133,7 @@ class BuiltInOperationInstruction extends Instruction {
Language::BuiltInOperation operation;
BuiltInOperationInstruction() {
- getOpcode() instanceof BuiltInOperationOpcode and
+ this.getOpcode() instanceof BuiltInOperationOpcode and
operation = Raw::getInstructionBuiltInOperation(this)
}
@@ -2122,9 +2149,9 @@ class BuiltInOperationInstruction extends Instruction {
* actual operation is specified by the `getBuiltInOperation()` predicate.
*/
class BuiltInInstruction extends BuiltInOperationInstruction {
- BuiltInInstruction() { getOpcode() instanceof Opcode::BuiltIn }
+ BuiltInInstruction() { this.getOpcode() instanceof Opcode::BuiltIn }
- final override string getImmediateString() { result = getBuiltInOperation().toString() }
+ final override string getImmediateString() { result = this.getBuiltInOperation().toString() }
}
/**
@@ -2135,7 +2162,7 @@ class BuiltInInstruction extends BuiltInOperationInstruction {
* to the `...` parameter.
*/
class VarArgsStartInstruction extends UnaryInstruction {
- VarArgsStartInstruction() { getOpcode() instanceof Opcode::VarArgsStart }
+ VarArgsStartInstruction() { this.getOpcode() instanceof Opcode::VarArgsStart }
}
/**
@@ -2145,7 +2172,7 @@ class VarArgsStartInstruction extends UnaryInstruction {
* a result.
*/
class VarArgsEndInstruction extends UnaryInstruction {
- VarArgsEndInstruction() { getOpcode() instanceof Opcode::VarArgsEnd }
+ VarArgsEndInstruction() { this.getOpcode() instanceof Opcode::VarArgsEnd }
}
/**
@@ -2155,7 +2182,7 @@ class VarArgsEndInstruction extends UnaryInstruction {
* argument.
*/
class VarArgInstruction extends UnaryInstruction {
- VarArgInstruction() { getOpcode() instanceof Opcode::VarArg }
+ VarArgInstruction() { this.getOpcode() instanceof Opcode::VarArg }
}
/**
@@ -2166,7 +2193,7 @@ class VarArgInstruction extends UnaryInstruction {
* argument of the `...` parameter.
*/
class NextVarArgInstruction extends UnaryInstruction {
- NextVarArgInstruction() { getOpcode() instanceof Opcode::NextVarArg }
+ NextVarArgInstruction() { this.getOpcode() instanceof Opcode::NextVarArg }
}
/**
@@ -2180,5 +2207,5 @@ class NextVarArgInstruction extends UnaryInstruction {
* The result is the address of the newly allocated object.
*/
class NewObjInstruction extends Instruction {
- NewObjInstruction() { getOpcode() instanceof Opcode::NewObj }
+ NewObjInstruction() { this.getOpcode() instanceof Opcode::NewObj }
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll
index d7cf89ca9aa..85d217bd361 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll
@@ -46,12 +46,12 @@ class Operand extends TStageOperand {
/**
* Gets the location of the source code for this operand.
*/
- final Language::Location getLocation() { result = getUse().getLocation() }
+ final Language::Location getLocation() { result = this.getUse().getLocation() }
/**
* Gets the function that contains this operand.
*/
- final IRFunction getEnclosingIRFunction() { result = getUse().getEnclosingIRFunction() }
+ final IRFunction getEnclosingIRFunction() { result = this.getUse().getEnclosingIRFunction() }
/**
* Gets the `Instruction` that consumes this operand.
@@ -74,7 +74,7 @@ class Operand extends TStageOperand {
*/
final Instruction getDef() {
result = this.getAnyDef() and
- getDefinitionOverlap() instanceof MustExactlyOverlap
+ this.getDefinitionOverlap() instanceof MustExactlyOverlap
}
/**
@@ -82,7 +82,7 @@ class Operand extends TStageOperand {
*
* Gets the `Instruction` that consumes this operand.
*/
- deprecated final Instruction getUseInstruction() { result = getUse() }
+ deprecated final Instruction getUseInstruction() { result = this.getUse() }
/**
* DEPRECATED: use `getAnyDef` or `getDef`. The exact replacement for this
@@ -91,7 +91,7 @@ class Operand extends TStageOperand {
*
* Gets the `Instruction` whose result is the value of the operand.
*/
- deprecated final Instruction getDefinitionInstruction() { result = getAnyDef() }
+ deprecated final Instruction getDefinitionInstruction() { result = this.getAnyDef() }
/**
* Gets the overlap relationship between the operand's definition and its use.
@@ -101,7 +101,9 @@ class Operand extends TStageOperand {
/**
* Holds if the result of the definition instruction does not exactly overlap this use.
*/
- final predicate isDefinitionInexact() { not getDefinitionOverlap() instanceof MustExactlyOverlap }
+ final predicate isDefinitionInexact() {
+ not this.getDefinitionOverlap() instanceof MustExactlyOverlap
+ }
/**
* Gets a prefix to use when dumping the operand in an operand list.
@@ -121,7 +123,7 @@ class Operand extends TStageOperand {
* For example: `this:r3_5`
*/
final string getDumpString() {
- result = getDumpLabel() + getInexactSpecifier() + getDefinitionId()
+ result = this.getDumpLabel() + this.getInexactSpecifier() + this.getDefinitionId()
}
/**
@@ -129,9 +131,9 @@ class Operand extends TStageOperand {
* definition is not modeled in SSA.
*/
private string getDefinitionId() {
- result = getAnyDef().getResultId()
+ result = this.getAnyDef().getResultId()
or
- not exists(getAnyDef()) and result = "m?"
+ not exists(this.getAnyDef()) and result = "m?"
}
/**
@@ -140,7 +142,7 @@ class Operand extends TStageOperand {
* the empty string.
*/
private string getInexactSpecifier() {
- if isDefinitionInexact() then result = "~" else result = ""
+ if this.isDefinitionInexact() then result = "~" else result = ""
}
/**
@@ -155,7 +157,7 @@ class Operand extends TStageOperand {
* the definition type, such as in the case of a partial read or a read from a pointer that
* has been cast to a different type.
*/
- Language::LanguageType getLanguageType() { result = getAnyDef().getResultLanguageType() }
+ Language::LanguageType getLanguageType() { result = this.getAnyDef().getResultLanguageType() }
/**
* Gets the language-neutral type of the value consumed by this operand. This is usually the same
@@ -164,7 +166,7 @@ class Operand extends TStageOperand {
* from the definition type, such as in the case of a partial read or a read from a pointer that
* has been cast to a different type.
*/
- final IRType getIRType() { result = getLanguageType().getIRType() }
+ final IRType getIRType() { result = this.getLanguageType().getIRType() }
/**
* Gets the type of the value consumed by this operand. This is usually the same as the
@@ -173,7 +175,7 @@ class Operand extends TStageOperand {
* the definition type, such as in the case of a partial read or a read from a pointer that
* has been cast to a different type.
*/
- final Language::Type getType() { getLanguageType().hasType(result, _) }
+ final Language::Type getType() { this.getLanguageType().hasType(result, _) }
/**
* Holds if the value consumed by this operand is a glvalue. If this
@@ -182,13 +184,13 @@ class Operand extends TStageOperand {
* not hold, the value of the operand represents a value whose type is
* given by `getType()`.
*/
- final predicate isGLValue() { getLanguageType().hasType(_, true) }
+ final predicate isGLValue() { this.getLanguageType().hasType(_, true) }
/**
* Gets the size of the value consumed by this operand, in bytes. If the operand does not have
* a known constant size, this predicate does not hold.
*/
- final int getSize() { result = getLanguageType().getByteSize() }
+ final int getSize() { result = this.getLanguageType().getByteSize() }
}
/**
@@ -205,7 +207,7 @@ class MemoryOperand extends Operand {
/**
* Gets the kind of memory access performed by the operand.
*/
- MemoryAccessKind getMemoryAccess() { result = getUse().getOpcode().getReadMemoryAccess() }
+ MemoryAccessKind getMemoryAccess() { result = this.getUse().getOpcode().getReadMemoryAccess() }
/**
* Holds if the memory access performed by this operand will not always read from every bit in the
@@ -215,7 +217,7 @@ class MemoryOperand extends Operand {
* conservative estimate of the memory that might actually be accessed at runtime (for example,
* the global side effects of a function call).
*/
- predicate hasMayReadMemoryAccess() { getUse().getOpcode().hasMayReadMemoryAccess() }
+ predicate hasMayReadMemoryAccess() { this.getUse().getOpcode().hasMayReadMemoryAccess() }
/**
* Returns the operand that holds the memory address from which the current operand loads its
@@ -223,8 +225,8 @@ class MemoryOperand extends Operand {
* is `r1`.
*/
final AddressOperand getAddressOperand() {
- getMemoryAccess().usesAddressOperand() and
- result.getUse() = getUse()
+ this.getMemoryAccess().usesAddressOperand() and
+ result.getUse() = this.getUse()
}
}
@@ -294,7 +296,7 @@ class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand, TNonPhiMemoryOpe
result = unique(Instruction defInstr | hasDefinition(defInstr, _))
}
- final override Overlap getDefinitionOverlap() { hasDefinition(_, result) }
+ final override Overlap getDefinitionOverlap() { this.hasDefinition(_, result) }
pragma[noinline]
private predicate hasDefinition(Instruction defInstr, Overlap overlap) {
@@ -449,13 +451,17 @@ class PhiInputOperand extends MemoryOperand, TPhiOperand {
final override Overlap getDefinitionOverlap() { result = overlap }
- final override int getDumpSortOrder() { result = 11 + getPredecessorBlock().getDisplayIndex() }
-
- final override string getDumpLabel() {
- result = "from " + getPredecessorBlock().getDisplayIndex().toString() + ":"
+ final override int getDumpSortOrder() {
+ result = 11 + this.getPredecessorBlock().getDisplayIndex()
}
- final override string getDumpId() { result = getPredecessorBlock().getDisplayIndex().toString() }
+ final override string getDumpLabel() {
+ result = "from " + this.getPredecessorBlock().getDisplayIndex().toString() + ":"
+ }
+
+ final override string getDumpId() {
+ result = this.getPredecessorBlock().getDisplayIndex().toString()
+ }
/**
* Gets the predecessor block from which this value comes.
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/IRBlock.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/IRBlock.qll
index 4b86f9a7cec..bb8630a5e0c 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/IRBlock.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/IRBlock.qll
@@ -24,7 +24,7 @@ class IRBlockBase extends TIRBlock {
final string toString() { result = getFirstInstruction(this).toString() }
/** Gets the source location of the first non-`Phi` instruction in this block. */
- final Language::Location getLocation() { result = getFirstInstruction().getLocation() }
+ final Language::Location getLocation() { result = this.getFirstInstruction().getLocation() }
/**
* INTERNAL: Do not use.
@@ -39,7 +39,7 @@ class IRBlockBase extends TIRBlock {
) and
this =
rank[result + 1](IRBlock funcBlock, int sortOverride, int sortKey1, int sortKey2 |
- funcBlock.getEnclosingFunction() = getEnclosingFunction() and
+ funcBlock.getEnclosingFunction() = this.getEnclosingFunction() and
funcBlock.getFirstInstruction().hasSortKeys(sortKey1, sortKey2) and
// Ensure that the block containing `EnterFunction` always comes first.
if funcBlock.getFirstInstruction() instanceof EnterFunctionInstruction
@@ -59,15 +59,15 @@ class IRBlockBase extends TIRBlock {
* Get the `Phi` instructions that appear at the start of this block.
*/
final PhiInstruction getAPhiInstruction() {
- Construction::getPhiInstructionBlockStart(result) = getFirstInstruction()
+ Construction::getPhiInstructionBlockStart(result) = this.getFirstInstruction()
}
/**
* Gets an instruction in this block. This includes `Phi` instructions.
*/
final Instruction getAnInstruction() {
- result = getInstruction(_) or
- result = getAPhiInstruction()
+ result = this.getInstruction(_) or
+ result = this.getAPhiInstruction()
}
/**
@@ -78,7 +78,9 @@ class IRBlockBase extends TIRBlock {
/**
* Gets the last instruction in this block.
*/
- final Instruction getLastInstruction() { result = getInstruction(getInstructionCount() - 1) }
+ final Instruction getLastInstruction() {
+ result = this.getInstruction(this.getInstructionCount() - 1)
+ }
/**
* Gets the number of non-`Phi` instructions in this block.
@@ -149,7 +151,7 @@ class IRBlock extends IRBlockBase {
* Block `A` dominates block `B` if any control flow path from the entry block of the function to
* block `B` must pass through block `A`. A block always dominates itself.
*/
- final predicate dominates(IRBlock block) { strictlyDominates(block) or this = block }
+ final predicate dominates(IRBlock block) { this.strictlyDominates(block) or this = block }
/**
* Gets a block on the dominance frontier of this block.
@@ -159,8 +161,8 @@ class IRBlock extends IRBlockBase {
*/
pragma[noinline]
final IRBlock dominanceFrontier() {
- dominates(result.getAPredecessor()) and
- not strictlyDominates(result)
+ this.dominates(result.getAPredecessor()) and
+ not this.strictlyDominates(result)
}
/**
@@ -189,7 +191,7 @@ class IRBlock extends IRBlockBase {
* Block `A` post-dominates block `B` if any control flow path from `B` to the exit block of the
* function must pass through block `A`. A block always post-dominates itself.
*/
- final predicate postDominates(IRBlock block) { strictlyPostDominates(block) or this = block }
+ final predicate postDominates(IRBlock block) { this.strictlyPostDominates(block) or this = block }
/**
* Gets a block on the post-dominance frontier of this block.
@@ -199,16 +201,16 @@ class IRBlock extends IRBlockBase {
*/
pragma[noinline]
final IRBlock postPominanceFrontier() {
- postDominates(result.getASuccessor()) and
- not strictlyPostDominates(result)
+ this.postDominates(result.getASuccessor()) and
+ not this.strictlyPostDominates(result)
}
/**
* Holds if this block is reachable from the entry block of its function.
*/
final predicate isReachableFromFunctionEntry() {
- this = getEnclosingIRFunction().getEntryBlock() or
- getAPredecessor().isReachableFromFunctionEntry()
+ this = this.getEnclosingIRFunction().getEntryBlock() or
+ this.getAPredecessor().isReachableFromFunctionEntry()
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/Instruction.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/Instruction.qll
index 6f471d8a7e8..1c2cc493338 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/Instruction.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/Instruction.qll
@@ -41,7 +41,7 @@ class Instruction extends Construction::TStageInstruction {
}
/** Gets a textual representation of this element. */
- final string toString() { result = getOpcode().toString() + ": " + getAST().toString() }
+ final string toString() { result = this.getOpcode().toString() + ": " + this.getAST().toString() }
/**
* Gets a string showing the result, opcode, and operands of the instruction, equivalent to what
@@ -50,7 +50,8 @@ class Instruction extends Construction::TStageInstruction {
* `mu0_28(int) = Store r0_26, r0_27`
*/
final string getDumpString() {
- result = getResultString() + " = " + getOperationString() + " " + getOperandsString()
+ result =
+ this.getResultString() + " = " + this.getOperationString() + " " + this.getOperandsString()
}
private predicate shouldGenerateDumpStrings() {
@@ -66,10 +67,13 @@ class Instruction extends Construction::TStageInstruction {
* VariableAddress[x]
*/
final string getOperationString() {
- shouldGenerateDumpStrings() and
- if exists(getImmediateString())
- then result = getOperationPrefix() + getOpcode().toString() + "[" + getImmediateString() + "]"
- else result = getOperationPrefix() + getOpcode().toString()
+ this.shouldGenerateDumpStrings() and
+ if exists(this.getImmediateString())
+ then
+ result =
+ this.getOperationPrefix() + this.getOpcode().toString() + "[" + this.getImmediateString() +
+ "]"
+ else result = this.getOperationPrefix() + this.getOpcode().toString()
}
/**
@@ -78,17 +82,17 @@ class Instruction extends Construction::TStageInstruction {
string getImmediateString() { none() }
private string getOperationPrefix() {
- shouldGenerateDumpStrings() and
+ this.shouldGenerateDumpStrings() and
if this instanceof SideEffectInstruction then result = "^" else result = ""
}
private string getResultPrefix() {
- shouldGenerateDumpStrings() and
- if getResultIRType() instanceof IRVoidType
+ this.shouldGenerateDumpStrings() and
+ if this.getResultIRType() instanceof IRVoidType
then result = "v"
else
- if hasMemoryResult()
- then if isResultModeled() then result = "m" else result = "mu"
+ if this.hasMemoryResult()
+ then if this.isResultModeled() then result = "m" else result = "mu"
else result = "r"
}
@@ -97,7 +101,7 @@ class Instruction extends Construction::TStageInstruction {
* used by debugging and printing code only.
*/
int getDisplayIndexInBlock() {
- shouldGenerateDumpStrings() and
+ this.shouldGenerateDumpStrings() and
exists(IRBlock block |
this = block.getInstruction(result)
or
@@ -111,12 +115,12 @@ class Instruction extends Construction::TStageInstruction {
}
private int getLineRank() {
- shouldGenerateDumpStrings() and
+ this.shouldGenerateDumpStrings() and
this =
rank[result](Instruction instr |
instr =
- getAnInstructionAtLine(getEnclosingIRFunction(), getLocation().getFile(),
- getLocation().getStartLine())
+ getAnInstructionAtLine(this.getEnclosingIRFunction(), this.getLocation().getFile(),
+ this.getLocation().getStartLine())
|
instr order by instr.getBlock().getDisplayIndex(), instr.getDisplayIndexInBlock()
)
@@ -130,8 +134,9 @@ class Instruction extends Construction::TStageInstruction {
* Example: `r1_1`
*/
string getResultId() {
- shouldGenerateDumpStrings() and
- result = getResultPrefix() + getAST().getLocation().getStartLine() + "_" + getLineRank()
+ this.shouldGenerateDumpStrings() and
+ result =
+ this.getResultPrefix() + this.getAST().getLocation().getStartLine() + "_" + this.getLineRank()
}
/**
@@ -142,8 +147,8 @@ class Instruction extends Construction::TStageInstruction {
* Example: `r1_1(int*)`
*/
final string getResultString() {
- shouldGenerateDumpStrings() and
- result = getResultId() + "(" + getResultLanguageType().getDumpString() + ")"
+ this.shouldGenerateDumpStrings() and
+ result = this.getResultId() + "(" + this.getResultLanguageType().getDumpString() + ")"
}
/**
@@ -153,10 +158,10 @@ class Instruction extends Construction::TStageInstruction {
* Example: `func:r3_4, this:r3_5`
*/
string getOperandsString() {
- shouldGenerateDumpStrings() and
+ this.shouldGenerateDumpStrings() and
result =
concat(Operand operand |
- operand = getAnOperand()
+ operand = this.getAnOperand()
|
operand.getDumpString(), ", " order by operand.getDumpSortOrder()
)
@@ -190,7 +195,7 @@ class Instruction extends Construction::TStageInstruction {
* Gets the function that contains this instruction.
*/
final Language::Function getEnclosingFunction() {
- result = getEnclosingIRFunction().getFunction()
+ result = this.getEnclosingIRFunction().getFunction()
}
/**
@@ -208,7 +213,7 @@ class Instruction extends Construction::TStageInstruction {
/**
* Gets the location of the source code for this instruction.
*/
- final Language::Location getLocation() { result = getAST().getLocation() }
+ final Language::Location getLocation() { result = this.getAST().getLocation() }
/**
* Gets the `Expr` whose result is computed by this instruction, if any. The `Expr` may be a
@@ -243,7 +248,7 @@ class Instruction extends Construction::TStageInstruction {
* a result, its result type will be `IRVoidType`.
*/
cached
- final IRType getResultIRType() { result = getResultLanguageType().getIRType() }
+ final IRType getResultIRType() { result = this.getResultLanguageType().getIRType() }
/**
* Gets the type of the result produced by this instruction. If the
@@ -254,7 +259,7 @@ class Instruction extends Construction::TStageInstruction {
*/
final Language::Type getResultType() {
exists(Language::LanguageType resultType |
- resultType = getResultLanguageType() and
+ resultType = this.getResultLanguageType() and
(
resultType.hasUnspecifiedType(result, _)
or
@@ -283,7 +288,7 @@ class Instruction extends Construction::TStageInstruction {
* result of the `Load` instruction is a prvalue of type `int`, representing
* the integer value loaded from variable `x`.
*/
- final predicate isGLValue() { getResultLanguageType().hasType(_, true) }
+ final predicate isGLValue() { this.getResultLanguageType().hasType(_, true) }
/**
* Gets the size of the result produced by this instruction, in bytes. If the
@@ -292,7 +297,7 @@ class Instruction extends Construction::TStageInstruction {
* If `this.isGLValue()` holds for this instruction, the value of
* `getResultSize()` will always be the size of a pointer.
*/
- final int getResultSize() { result = getResultLanguageType().getByteSize() }
+ final int getResultSize() { result = this.getResultLanguageType().getByteSize() }
/**
* Gets the opcode that specifies the operation performed by this instruction.
@@ -314,14 +319,16 @@ class Instruction extends Construction::TStageInstruction {
/**
* Holds if this instruction produces a memory result.
*/
- final predicate hasMemoryResult() { exists(getResultMemoryAccess()) }
+ final predicate hasMemoryResult() { exists(this.getResultMemoryAccess()) }
/**
* Gets the kind of memory access performed by this instruction's result.
* Holds only for instructions with a memory result.
*/
pragma[inline]
- final MemoryAccessKind getResultMemoryAccess() { result = getOpcode().getWriteMemoryAccess() }
+ final MemoryAccessKind getResultMemoryAccess() {
+ result = this.getOpcode().getWriteMemoryAccess()
+ }
/**
* Holds if the memory access performed by this instruction's result will not always write to
@@ -332,7 +339,7 @@ class Instruction extends Construction::TStageInstruction {
* (for example, the global side effects of a function call).
*/
pragma[inline]
- final predicate hasResultMayMemoryAccess() { getOpcode().hasMayWriteMemoryAccess() }
+ final predicate hasResultMayMemoryAccess() { this.getOpcode().hasMayWriteMemoryAccess() }
/**
* Gets the operand that holds the memory address to which this instruction stores its
@@ -340,7 +347,7 @@ class Instruction extends Construction::TStageInstruction {
* is `r1`.
*/
final AddressOperand getResultAddressOperand() {
- getResultMemoryAccess().usesAddressOperand() and
+ this.getResultMemoryAccess().usesAddressOperand() and
result.getUse() = this
}
@@ -349,7 +356,7 @@ class Instruction extends Construction::TStageInstruction {
* result, if any. For example, in `m3 = Store r1, r2`, the result of `getResultAddressOperand()`
* is the instruction that defines `r1`.
*/
- final Instruction getResultAddress() { result = getResultAddressOperand().getDef() }
+ final Instruction getResultAddress() { result = this.getResultAddressOperand().getDef() }
/**
* Holds if the result of this instruction is precisely modeled in SSA. Always
@@ -368,7 +375,7 @@ class Instruction extends Construction::TStageInstruction {
*/
final predicate isResultModeled() {
// Register results are always in SSA form.
- not hasMemoryResult() or
+ not this.hasMemoryResult() or
Construction::hasModeledMemoryResult(this)
}
@@ -412,7 +419,7 @@ class Instruction extends Construction::TStageInstruction {
/**
* Gets all direct successors of this instruction.
*/
- final Instruction getASuccessor() { result = getSuccessor(_) }
+ final Instruction getASuccessor() { result = this.getSuccessor(_) }
/**
* Gets a predecessor of this instruction such that the predecessor reaches
@@ -423,7 +430,7 @@ class Instruction extends Construction::TStageInstruction {
/**
* Gets all direct predecessors of this instruction.
*/
- final Instruction getAPredecessor() { result = getPredecessor(_) }
+ final Instruction getAPredecessor() { result = this.getPredecessor(_) }
}
/**
@@ -543,7 +550,7 @@ class IndexedInstruction extends Instruction {
* at this instruction. This instruction has no predecessors.
*/
class EnterFunctionInstruction extends Instruction {
- EnterFunctionInstruction() { getOpcode() instanceof Opcode::EnterFunction }
+ EnterFunctionInstruction() { this.getOpcode() instanceof Opcode::EnterFunction }
}
/**
@@ -554,7 +561,7 @@ class EnterFunctionInstruction extends Instruction {
* struct, or union, see `FieldAddressInstruction`.
*/
class VariableAddressInstruction extends VariableInstruction {
- VariableAddressInstruction() { getOpcode() instanceof Opcode::VariableAddress }
+ VariableAddressInstruction() { this.getOpcode() instanceof Opcode::VariableAddress }
}
/**
@@ -566,7 +573,7 @@ class VariableAddressInstruction extends VariableInstruction {
* The result has an `IRFunctionAddress` type.
*/
class FunctionAddressInstruction extends FunctionInstruction {
- FunctionAddressInstruction() { getOpcode() instanceof Opcode::FunctionAddress }
+ FunctionAddressInstruction() { this.getOpcode() instanceof Opcode::FunctionAddress }
}
/**
@@ -577,7 +584,7 @@ class FunctionAddressInstruction extends FunctionInstruction {
* initializes that parameter.
*/
class InitializeParameterInstruction extends VariableInstruction {
- InitializeParameterInstruction() { getOpcode() instanceof Opcode::InitializeParameter }
+ InitializeParameterInstruction() { this.getOpcode() instanceof Opcode::InitializeParameter }
/**
* Gets the parameter initialized by this instruction.
@@ -603,7 +610,7 @@ class InitializeParameterInstruction extends VariableInstruction {
* initialized elsewhere, would not otherwise have a definition in this function.
*/
class InitializeNonLocalInstruction extends Instruction {
- InitializeNonLocalInstruction() { getOpcode() instanceof Opcode::InitializeNonLocal }
+ InitializeNonLocalInstruction() { this.getOpcode() instanceof Opcode::InitializeNonLocal }
}
/**
@@ -611,7 +618,7 @@ class InitializeNonLocalInstruction extends Instruction {
* with the value of that memory on entry to the function.
*/
class InitializeIndirectionInstruction extends VariableInstruction {
- InitializeIndirectionInstruction() { getOpcode() instanceof Opcode::InitializeIndirection }
+ InitializeIndirectionInstruction() { this.getOpcode() instanceof Opcode::InitializeIndirection }
/**
* Gets the parameter initialized by this instruction.
@@ -635,24 +642,24 @@ class InitializeIndirectionInstruction extends VariableInstruction {
* An instruction that initializes the `this` pointer parameter of the enclosing function.
*/
class InitializeThisInstruction extends Instruction {
- InitializeThisInstruction() { getOpcode() instanceof Opcode::InitializeThis }
+ InitializeThisInstruction() { this.getOpcode() instanceof Opcode::InitializeThis }
}
/**
* An instruction that computes the address of a non-static field of an object.
*/
class FieldAddressInstruction extends FieldInstruction {
- FieldAddressInstruction() { getOpcode() instanceof Opcode::FieldAddress }
+ FieldAddressInstruction() { this.getOpcode() instanceof Opcode::FieldAddress }
/**
* Gets the operand that provides the address of the object containing the field.
*/
- final UnaryOperand getObjectAddressOperand() { result = getAnOperand() }
+ final UnaryOperand getObjectAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the object containing the field.
*/
- final Instruction getObjectAddress() { result = getObjectAddressOperand().getDef() }
+ final Instruction getObjectAddress() { result = this.getObjectAddressOperand().getDef() }
}
/**
@@ -661,17 +668,19 @@ class FieldAddressInstruction extends FieldInstruction {
* This instruction is used for element access to C# arrays.
*/
class ElementsAddressInstruction extends UnaryInstruction {
- ElementsAddressInstruction() { getOpcode() instanceof Opcode::ElementsAddress }
+ ElementsAddressInstruction() { this.getOpcode() instanceof Opcode::ElementsAddress }
/**
* Gets the operand that provides the address of the array object.
*/
- final UnaryOperand getArrayObjectAddressOperand() { result = getAnOperand() }
+ final UnaryOperand getArrayObjectAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the array object.
*/
- final Instruction getArrayObjectAddress() { result = getArrayObjectAddressOperand().getDef() }
+ final Instruction getArrayObjectAddress() {
+ result = this.getArrayObjectAddressOperand().getDef()
+ }
}
/**
@@ -685,7 +694,7 @@ class ElementsAddressInstruction extends UnaryInstruction {
* taken may want to ignore any function that contains an `ErrorInstruction`.
*/
class ErrorInstruction extends Instruction {
- ErrorInstruction() { getOpcode() instanceof Opcode::Error }
+ ErrorInstruction() { this.getOpcode() instanceof Opcode::Error }
}
/**
@@ -695,7 +704,7 @@ class ErrorInstruction extends Instruction {
* an initializer, or whose initializer only partially initializes the variable.
*/
class UninitializedInstruction extends VariableInstruction {
- UninitializedInstruction() { getOpcode() instanceof Opcode::Uninitialized }
+ UninitializedInstruction() { this.getOpcode() instanceof Opcode::Uninitialized }
/**
* Gets the variable that is uninitialized.
@@ -710,7 +719,7 @@ class UninitializedInstruction extends VariableInstruction {
* least one instruction, even when the AST has no semantic effect.
*/
class NoOpInstruction extends Instruction {
- NoOpInstruction() { getOpcode() instanceof Opcode::NoOp }
+ NoOpInstruction() { this.getOpcode() instanceof Opcode::NoOp }
}
/**
@@ -732,32 +741,42 @@ class NoOpInstruction extends Instruction {
* `void`-returning function.
*/
class ReturnInstruction extends Instruction {
- ReturnInstruction() { getOpcode() instanceof ReturnOpcode }
+ ReturnInstruction() { this.getOpcode() instanceof ReturnOpcode }
}
/**
* An instruction that returns control to the caller of the function, without returning a value.
*/
class ReturnVoidInstruction extends ReturnInstruction {
- ReturnVoidInstruction() { getOpcode() instanceof Opcode::ReturnVoid }
+ ReturnVoidInstruction() { this.getOpcode() instanceof Opcode::ReturnVoid }
}
/**
* An instruction that returns control to the caller of the function, including a return value.
*/
class ReturnValueInstruction extends ReturnInstruction {
- ReturnValueInstruction() { getOpcode() instanceof Opcode::ReturnValue }
+ ReturnValueInstruction() { this.getOpcode() instanceof Opcode::ReturnValue }
/**
* Gets the operand that provides the value being returned by the function.
*/
- final LoadOperand getReturnValueOperand() { result = getAnOperand() }
+ final LoadOperand getReturnValueOperand() { result = this.getAnOperand() }
+
+ /**
+ * Gets the operand that provides the address of the value being returned by the function.
+ */
+ final AddressOperand getReturnAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the value being returned by the function, if an
* exact definition is available.
*/
- final Instruction getReturnValue() { result = getReturnValueOperand().getDef() }
+ final Instruction getReturnValue() { result = this.getReturnValueOperand().getDef() }
+
+ /**
+ * Gets the instruction whose result provides the address of the value being returned by the function.
+ */
+ final Instruction getReturnAddress() { result = this.getReturnAddressOperand().getDef() }
}
/**
@@ -770,28 +789,28 @@ class ReturnValueInstruction extends ReturnInstruction {
* that the caller initialized the memory pointed to by the parameter before the call.
*/
class ReturnIndirectionInstruction extends VariableInstruction {
- ReturnIndirectionInstruction() { getOpcode() instanceof Opcode::ReturnIndirection }
+ ReturnIndirectionInstruction() { this.getOpcode() instanceof Opcode::ReturnIndirection }
/**
* Gets the operand that provides the value of the pointed-to memory.
*/
- final SideEffectOperand getSideEffectOperand() { result = getAnOperand() }
+ final SideEffectOperand getSideEffectOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the value of the pointed-to memory, if an exact
* definition is available.
*/
- final Instruction getSideEffect() { result = getSideEffectOperand().getDef() }
+ final Instruction getSideEffect() { result = this.getSideEffectOperand().getDef() }
/**
* Gets the operand that provides the address of the pointed-to memory.
*/
- final AddressOperand getSourceAddressOperand() { result = getAnOperand() }
+ final AddressOperand getSourceAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the pointed-to memory.
*/
- final Instruction getSourceAddress() { result = getSourceAddressOperand().getDef() }
+ final Instruction getSourceAddress() { result = this.getSourceAddressOperand().getDef() }
/**
* Gets the parameter for which this instruction reads the final pointed-to value within the
@@ -826,7 +845,7 @@ class ReturnIndirectionInstruction extends VariableInstruction {
* - `StoreInstruction` - Copies a register operand to a memory result.
*/
class CopyInstruction extends Instruction {
- CopyInstruction() { getOpcode() instanceof CopyOpcode }
+ CopyInstruction() { this.getOpcode() instanceof CopyOpcode }
/**
* Gets the operand that provides the input value of the copy.
@@ -837,16 +856,16 @@ class CopyInstruction extends Instruction {
* Gets the instruction whose result provides the input value of the copy, if an exact definition
* is available.
*/
- final Instruction getSourceValue() { result = getSourceValueOperand().getDef() }
+ final Instruction getSourceValue() { result = this.getSourceValueOperand().getDef() }
}
/**
* An instruction that returns a register result containing a copy of its register operand.
*/
class CopyValueInstruction extends CopyInstruction, UnaryInstruction {
- CopyValueInstruction() { getOpcode() instanceof Opcode::CopyValue }
+ CopyValueInstruction() { this.getOpcode() instanceof Opcode::CopyValue }
- final override UnaryOperand getSourceValueOperand() { result = getAnOperand() }
+ final override UnaryOperand getSourceValueOperand() { result = this.getAnOperand() }
}
/**
@@ -863,47 +882,49 @@ private string getAddressOperandDescription(AddressOperand operand) {
* An instruction that returns a register result containing a copy of its memory operand.
*/
class LoadInstruction extends CopyInstruction {
- LoadInstruction() { getOpcode() instanceof Opcode::Load }
+ LoadInstruction() { this.getOpcode() instanceof Opcode::Load }
final override string getImmediateString() {
- result = getAddressOperandDescription(getSourceAddressOperand())
+ result = getAddressOperandDescription(this.getSourceAddressOperand())
}
/**
* Gets the operand that provides the address of the value being loaded.
*/
- final AddressOperand getSourceAddressOperand() { result = getAnOperand() }
+ final AddressOperand getSourceAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the value being loaded.
*/
- final Instruction getSourceAddress() { result = getSourceAddressOperand().getDef() }
+ final Instruction getSourceAddress() { result = this.getSourceAddressOperand().getDef() }
- final override LoadOperand getSourceValueOperand() { result = getAnOperand() }
+ final override LoadOperand getSourceValueOperand() { result = this.getAnOperand() }
}
/**
* An instruction that returns a memory result containing a copy of its register operand.
*/
class StoreInstruction extends CopyInstruction {
- StoreInstruction() { getOpcode() instanceof Opcode::Store }
+ StoreInstruction() { this.getOpcode() instanceof Opcode::Store }
final override string getImmediateString() {
- result = getAddressOperandDescription(getDestinationAddressOperand())
+ result = getAddressOperandDescription(this.getDestinationAddressOperand())
}
/**
* Gets the operand that provides the address of the location to which the value will be stored.
*/
- final AddressOperand getDestinationAddressOperand() { result = getAnOperand() }
+ final AddressOperand getDestinationAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the location to which the value will
* be stored, if an exact definition is available.
*/
- final Instruction getDestinationAddress() { result = getDestinationAddressOperand().getDef() }
+ final Instruction getDestinationAddress() {
+ result = this.getDestinationAddressOperand().getDef()
+ }
- final override StoreValueOperand getSourceValueOperand() { result = getAnOperand() }
+ final override StoreValueOperand getSourceValueOperand() { result = this.getAnOperand() }
}
/**
@@ -911,27 +932,27 @@ class StoreInstruction extends CopyInstruction {
* operand.
*/
class ConditionalBranchInstruction extends Instruction {
- ConditionalBranchInstruction() { getOpcode() instanceof Opcode::ConditionalBranch }
+ ConditionalBranchInstruction() { this.getOpcode() instanceof Opcode::ConditionalBranch }
/**
* Gets the operand that provides the Boolean condition controlling the branch.
*/
- final ConditionOperand getConditionOperand() { result = getAnOperand() }
+ final ConditionOperand getConditionOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the Boolean condition controlling the branch.
*/
- final Instruction getCondition() { result = getConditionOperand().getDef() }
+ final Instruction getCondition() { result = this.getConditionOperand().getDef() }
/**
* Gets the instruction to which control will flow if the condition is true.
*/
- final Instruction getTrueSuccessor() { result = getSuccessor(EdgeKind::trueEdge()) }
+ final Instruction getTrueSuccessor() { result = this.getSuccessor(EdgeKind::trueEdge()) }
/**
* Gets the instruction to which control will flow if the condition is false.
*/
- final Instruction getFalseSuccessor() { result = getSuccessor(EdgeKind::falseEdge()) }
+ final Instruction getFalseSuccessor() { result = this.getSuccessor(EdgeKind::falseEdge()) }
}
/**
@@ -943,14 +964,14 @@ class ConditionalBranchInstruction extends Instruction {
* successors.
*/
class ExitFunctionInstruction extends Instruction {
- ExitFunctionInstruction() { getOpcode() instanceof Opcode::ExitFunction }
+ ExitFunctionInstruction() { this.getOpcode() instanceof Opcode::ExitFunction }
}
/**
* An instruction whose result is a constant value.
*/
class ConstantInstruction extends ConstantValueInstruction {
- ConstantInstruction() { getOpcode() instanceof Opcode::Constant }
+ ConstantInstruction() { this.getOpcode() instanceof Opcode::Constant }
}
/**
@@ -959,7 +980,7 @@ class ConstantInstruction extends ConstantValueInstruction {
class IntegerConstantInstruction extends ConstantInstruction {
IntegerConstantInstruction() {
exists(IRType resultType |
- resultType = getResultIRType() and
+ resultType = this.getResultIRType() and
(resultType instanceof IRIntegerType or resultType instanceof IRBooleanType)
)
}
@@ -969,7 +990,7 @@ class IntegerConstantInstruction extends ConstantInstruction {
* An instruction whose result is a constant value of floating-point type.
*/
class FloatConstantInstruction extends ConstantInstruction {
- FloatConstantInstruction() { getResultIRType() instanceof IRFloatingPointType }
+ FloatConstantInstruction() { this.getResultIRType() instanceof IRFloatingPointType }
}
/**
@@ -978,7 +999,9 @@ class FloatConstantInstruction extends ConstantInstruction {
class StringConstantInstruction extends VariableInstruction {
override IRStringLiteral var;
- final override string getImmediateString() { result = Language::getStringLiteralText(getValue()) }
+ final override string getImmediateString() {
+ result = Language::getStringLiteralText(this.getValue())
+ }
/**
* Gets the string literal whose address is returned by this instruction.
@@ -990,37 +1013,37 @@ class StringConstantInstruction extends VariableInstruction {
* An instruction whose result is computed from two operands.
*/
class BinaryInstruction extends Instruction {
- BinaryInstruction() { getOpcode() instanceof BinaryOpcode }
+ BinaryInstruction() { this.getOpcode() instanceof BinaryOpcode }
/**
* Gets the left operand of this binary instruction.
*/
- final LeftOperand getLeftOperand() { result = getAnOperand() }
+ final LeftOperand getLeftOperand() { result = this.getAnOperand() }
/**
* Gets the right operand of this binary instruction.
*/
- final RightOperand getRightOperand() { result = getAnOperand() }
+ final RightOperand getRightOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the value of the left operand of this binary
* instruction.
*/
- final Instruction getLeft() { result = getLeftOperand().getDef() }
+ final Instruction getLeft() { result = this.getLeftOperand().getDef() }
/**
* Gets the instruction whose result provides the value of the right operand of this binary
* instruction.
*/
- final Instruction getRight() { result = getRightOperand().getDef() }
+ final Instruction getRight() { result = this.getRightOperand().getDef() }
/**
* Holds if this instruction's operands are `op1` and `op2`, in either order.
*/
final predicate hasOperands(Operand op1, Operand op2) {
- op1 = getLeftOperand() and op2 = getRightOperand()
+ op1 = this.getLeftOperand() and op2 = this.getRightOperand()
or
- op1 = getRightOperand() and op2 = getLeftOperand()
+ op1 = this.getRightOperand() and op2 = this.getLeftOperand()
}
}
@@ -1028,7 +1051,7 @@ class BinaryInstruction extends Instruction {
* An instruction that computes the result of an arithmetic operation.
*/
class ArithmeticInstruction extends Instruction {
- ArithmeticInstruction() { getOpcode() instanceof ArithmeticOpcode }
+ ArithmeticInstruction() { this.getOpcode() instanceof ArithmeticOpcode }
}
/**
@@ -1050,7 +1073,7 @@ class UnaryArithmeticInstruction extends ArithmeticInstruction, UnaryInstruction
* performed according to IEEE-754.
*/
class AddInstruction extends BinaryArithmeticInstruction {
- AddInstruction() { getOpcode() instanceof Opcode::Add }
+ AddInstruction() { this.getOpcode() instanceof Opcode::Add }
}
/**
@@ -1061,7 +1084,7 @@ class AddInstruction extends BinaryArithmeticInstruction {
* according to IEEE-754.
*/
class SubInstruction extends BinaryArithmeticInstruction {
- SubInstruction() { getOpcode() instanceof Opcode::Sub }
+ SubInstruction() { this.getOpcode() instanceof Opcode::Sub }
}
/**
@@ -1072,7 +1095,7 @@ class SubInstruction extends BinaryArithmeticInstruction {
* performed according to IEEE-754.
*/
class MulInstruction extends BinaryArithmeticInstruction {
- MulInstruction() { getOpcode() instanceof Opcode::Mul }
+ MulInstruction() { this.getOpcode() instanceof Opcode::Mul }
}
/**
@@ -1083,7 +1106,7 @@ class MulInstruction extends BinaryArithmeticInstruction {
* to IEEE-754.
*/
class DivInstruction extends BinaryArithmeticInstruction {
- DivInstruction() { getOpcode() instanceof Opcode::Div }
+ DivInstruction() { this.getOpcode() instanceof Opcode::Div }
}
/**
@@ -1093,7 +1116,7 @@ class DivInstruction extends BinaryArithmeticInstruction {
* division by zero or integer overflow is undefined.
*/
class RemInstruction extends BinaryArithmeticInstruction {
- RemInstruction() { getOpcode() instanceof Opcode::Rem }
+ RemInstruction() { this.getOpcode() instanceof Opcode::Rem }
}
/**
@@ -1104,14 +1127,14 @@ class RemInstruction extends BinaryArithmeticInstruction {
* is performed according to IEEE-754.
*/
class NegateInstruction extends UnaryArithmeticInstruction {
- NegateInstruction() { getOpcode() instanceof Opcode::Negate }
+ NegateInstruction() { this.getOpcode() instanceof Opcode::Negate }
}
/**
* An instruction that computes the result of a bitwise operation.
*/
class BitwiseInstruction extends Instruction {
- BitwiseInstruction() { getOpcode() instanceof BitwiseOpcode }
+ BitwiseInstruction() { this.getOpcode() instanceof BitwiseOpcode }
}
/**
@@ -1130,7 +1153,7 @@ class UnaryBitwiseInstruction extends BitwiseInstruction, UnaryInstruction { }
* Both operands must have the same integer type, which will also be the result type.
*/
class BitAndInstruction extends BinaryBitwiseInstruction {
- BitAndInstruction() { getOpcode() instanceof Opcode::BitAnd }
+ BitAndInstruction() { this.getOpcode() instanceof Opcode::BitAnd }
}
/**
@@ -1139,7 +1162,7 @@ class BitAndInstruction extends BinaryBitwiseInstruction {
* Both operands must have the same integer type, which will also be the result type.
*/
class BitOrInstruction extends BinaryBitwiseInstruction {
- BitOrInstruction() { getOpcode() instanceof Opcode::BitOr }
+ BitOrInstruction() { this.getOpcode() instanceof Opcode::BitOr }
}
/**
@@ -1148,7 +1171,7 @@ class BitOrInstruction extends BinaryBitwiseInstruction {
* Both operands must have the same integer type, which will also be the result type.
*/
class BitXorInstruction extends BinaryBitwiseInstruction {
- BitXorInstruction() { getOpcode() instanceof Opcode::BitXor }
+ BitXorInstruction() { this.getOpcode() instanceof Opcode::BitXor }
}
/**
@@ -1159,7 +1182,7 @@ class BitXorInstruction extends BinaryBitwiseInstruction {
* rightmost bits are zero-filled.
*/
class ShiftLeftInstruction extends BinaryBitwiseInstruction {
- ShiftLeftInstruction() { getOpcode() instanceof Opcode::ShiftLeft }
+ ShiftLeftInstruction() { this.getOpcode() instanceof Opcode::ShiftLeft }
}
/**
@@ -1172,7 +1195,7 @@ class ShiftLeftInstruction extends BinaryBitwiseInstruction {
* of the left operand.
*/
class ShiftRightInstruction extends BinaryBitwiseInstruction {
- ShiftRightInstruction() { getOpcode() instanceof Opcode::ShiftRight }
+ ShiftRightInstruction() { this.getOpcode() instanceof Opcode::ShiftRight }
}
/**
@@ -1183,7 +1206,7 @@ class PointerArithmeticInstruction extends BinaryInstruction {
int elementSize;
PointerArithmeticInstruction() {
- getOpcode() instanceof PointerArithmeticOpcode and
+ this.getOpcode() instanceof PointerArithmeticOpcode and
elementSize = Raw::getInstructionElementSize(this)
}
@@ -1206,7 +1229,7 @@ class PointerArithmeticInstruction extends BinaryInstruction {
* An instruction that adds or subtracts an integer offset from a pointer.
*/
class PointerOffsetInstruction extends PointerArithmeticInstruction {
- PointerOffsetInstruction() { getOpcode() instanceof PointerOffsetOpcode }
+ PointerOffsetInstruction() { this.getOpcode() instanceof PointerOffsetOpcode }
}
/**
@@ -1217,7 +1240,7 @@ class PointerOffsetInstruction extends PointerArithmeticInstruction {
* overflow is undefined.
*/
class PointerAddInstruction extends PointerOffsetInstruction {
- PointerAddInstruction() { getOpcode() instanceof Opcode::PointerAdd }
+ PointerAddInstruction() { this.getOpcode() instanceof Opcode::PointerAdd }
}
/**
@@ -1228,7 +1251,7 @@ class PointerAddInstruction extends PointerOffsetInstruction {
* pointer underflow is undefined.
*/
class PointerSubInstruction extends PointerOffsetInstruction {
- PointerSubInstruction() { getOpcode() instanceof Opcode::PointerSub }
+ PointerSubInstruction() { this.getOpcode() instanceof Opcode::PointerSub }
}
/**
@@ -1241,31 +1264,31 @@ class PointerSubInstruction extends PointerOffsetInstruction {
* undefined.
*/
class PointerDiffInstruction extends PointerArithmeticInstruction {
- PointerDiffInstruction() { getOpcode() instanceof Opcode::PointerDiff }
+ PointerDiffInstruction() { this.getOpcode() instanceof Opcode::PointerDiff }
}
/**
* An instruction whose result is computed from a single operand.
*/
class UnaryInstruction extends Instruction {
- UnaryInstruction() { getOpcode() instanceof UnaryOpcode }
+ UnaryInstruction() { this.getOpcode() instanceof UnaryOpcode }
/**
* Gets the sole operand of this instruction.
*/
- final UnaryOperand getUnaryOperand() { result = getAnOperand() }
+ final UnaryOperand getUnaryOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the sole operand of this instruction.
*/
- final Instruction getUnary() { result = getUnaryOperand().getDef() }
+ final Instruction getUnary() { result = this.getUnaryOperand().getDef() }
}
/**
* An instruction that converts the value of its operand to a value of a different type.
*/
class ConvertInstruction extends UnaryInstruction {
- ConvertInstruction() { getOpcode() instanceof Opcode::Convert }
+ ConvertInstruction() { this.getOpcode() instanceof Opcode::Convert }
}
/**
@@ -1279,7 +1302,7 @@ class ConvertInstruction extends UnaryInstruction {
* `as` expression.
*/
class CheckedConvertOrNullInstruction extends UnaryInstruction {
- CheckedConvertOrNullInstruction() { getOpcode() instanceof Opcode::CheckedConvertOrNull }
+ CheckedConvertOrNullInstruction() { this.getOpcode() instanceof Opcode::CheckedConvertOrNull }
}
/**
@@ -1293,7 +1316,7 @@ class CheckedConvertOrNullInstruction extends UnaryInstruction {
* expression.
*/
class CheckedConvertOrThrowInstruction extends UnaryInstruction {
- CheckedConvertOrThrowInstruction() { getOpcode() instanceof Opcode::CheckedConvertOrThrow }
+ CheckedConvertOrThrowInstruction() { this.getOpcode() instanceof Opcode::CheckedConvertOrThrow }
}
/**
@@ -1306,7 +1329,7 @@ class CheckedConvertOrThrowInstruction extends UnaryInstruction {
* the most-derived object.
*/
class CompleteObjectAddressInstruction extends UnaryInstruction {
- CompleteObjectAddressInstruction() { getOpcode() instanceof Opcode::CompleteObjectAddress }
+ CompleteObjectAddressInstruction() { this.getOpcode() instanceof Opcode::CompleteObjectAddress }
}
/**
@@ -1351,7 +1374,7 @@ class InheritanceConversionInstruction extends UnaryInstruction {
* An instruction that converts from the address of a derived class to the address of a base class.
*/
class ConvertToBaseInstruction extends InheritanceConversionInstruction {
- ConvertToBaseInstruction() { getOpcode() instanceof ConvertToBaseOpcode }
+ ConvertToBaseInstruction() { this.getOpcode() instanceof ConvertToBaseOpcode }
}
/**
@@ -1361,7 +1384,9 @@ class ConvertToBaseInstruction extends InheritanceConversionInstruction {
* If the operand holds a null address, the result is a null address.
*/
class ConvertToNonVirtualBaseInstruction extends ConvertToBaseInstruction {
- ConvertToNonVirtualBaseInstruction() { getOpcode() instanceof Opcode::ConvertToNonVirtualBase }
+ ConvertToNonVirtualBaseInstruction() {
+ this.getOpcode() instanceof Opcode::ConvertToNonVirtualBase
+ }
}
/**
@@ -1371,7 +1396,7 @@ class ConvertToNonVirtualBaseInstruction extends ConvertToBaseInstruction {
* If the operand holds a null address, the result is a null address.
*/
class ConvertToVirtualBaseInstruction extends ConvertToBaseInstruction {
- ConvertToVirtualBaseInstruction() { getOpcode() instanceof Opcode::ConvertToVirtualBase }
+ ConvertToVirtualBaseInstruction() { this.getOpcode() instanceof Opcode::ConvertToVirtualBase }
}
/**
@@ -1381,7 +1406,7 @@ class ConvertToVirtualBaseInstruction extends ConvertToBaseInstruction {
* If the operand holds a null address, the result is a null address.
*/
class ConvertToDerivedInstruction extends InheritanceConversionInstruction {
- ConvertToDerivedInstruction() { getOpcode() instanceof Opcode::ConvertToDerived }
+ ConvertToDerivedInstruction() { this.getOpcode() instanceof Opcode::ConvertToDerived }
}
/**
@@ -1390,7 +1415,7 @@ class ConvertToDerivedInstruction extends InheritanceConversionInstruction {
* The operand must have an integer type, which will also be the result type.
*/
class BitComplementInstruction extends UnaryBitwiseInstruction {
- BitComplementInstruction() { getOpcode() instanceof Opcode::BitComplement }
+ BitComplementInstruction() { this.getOpcode() instanceof Opcode::BitComplement }
}
/**
@@ -1399,14 +1424,14 @@ class BitComplementInstruction extends UnaryBitwiseInstruction {
* The operand must have a Boolean type, which will also be the result type.
*/
class LogicalNotInstruction extends UnaryInstruction {
- LogicalNotInstruction() { getOpcode() instanceof Opcode::LogicalNot }
+ LogicalNotInstruction() { this.getOpcode() instanceof Opcode::LogicalNot }
}
/**
* An instruction that compares two numeric operands.
*/
class CompareInstruction extends BinaryInstruction {
- CompareInstruction() { getOpcode() instanceof CompareOpcode }
+ CompareInstruction() { this.getOpcode() instanceof CompareOpcode }
}
/**
@@ -1417,7 +1442,7 @@ class CompareInstruction extends BinaryInstruction {
* unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareEQInstruction extends CompareInstruction {
- CompareEQInstruction() { getOpcode() instanceof Opcode::CompareEQ }
+ CompareEQInstruction() { this.getOpcode() instanceof Opcode::CompareEQ }
}
/**
@@ -1428,14 +1453,14 @@ class CompareEQInstruction extends CompareInstruction {
* `left == right`. Floating-point comparison is performed according to IEEE-754.
*/
class CompareNEInstruction extends CompareInstruction {
- CompareNEInstruction() { getOpcode() instanceof Opcode::CompareNE }
+ CompareNEInstruction() { this.getOpcode() instanceof Opcode::CompareNE }
}
/**
* An instruction that does a relative comparison of two values, such as `<` or `>=`.
*/
class RelationalInstruction extends CompareInstruction {
- RelationalInstruction() { getOpcode() instanceof RelationalOpcode }
+ RelationalInstruction() { this.getOpcode() instanceof RelationalOpcode }
/**
* Gets the operand on the "greater" (or "greater-or-equal") side
@@ -1467,11 +1492,11 @@ class RelationalInstruction extends CompareInstruction {
* are unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareLTInstruction extends RelationalInstruction {
- CompareLTInstruction() { getOpcode() instanceof Opcode::CompareLT }
+ CompareLTInstruction() { this.getOpcode() instanceof Opcode::CompareLT }
- override Instruction getLesser() { result = getLeft() }
+ override Instruction getLesser() { result = this.getLeft() }
- override Instruction getGreater() { result = getRight() }
+ override Instruction getGreater() { result = this.getRight() }
override predicate isStrict() { any() }
}
@@ -1484,11 +1509,11 @@ class CompareLTInstruction extends RelationalInstruction {
* are unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareGTInstruction extends RelationalInstruction {
- CompareGTInstruction() { getOpcode() instanceof Opcode::CompareGT }
+ CompareGTInstruction() { this.getOpcode() instanceof Opcode::CompareGT }
- override Instruction getLesser() { result = getRight() }
+ override Instruction getLesser() { result = this.getRight() }
- override Instruction getGreater() { result = getLeft() }
+ override Instruction getGreater() { result = this.getLeft() }
override predicate isStrict() { any() }
}
@@ -1502,11 +1527,11 @@ class CompareGTInstruction extends RelationalInstruction {
* are unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareLEInstruction extends RelationalInstruction {
- CompareLEInstruction() { getOpcode() instanceof Opcode::CompareLE }
+ CompareLEInstruction() { this.getOpcode() instanceof Opcode::CompareLE }
- override Instruction getLesser() { result = getLeft() }
+ override Instruction getLesser() { result = this.getLeft() }
- override Instruction getGreater() { result = getRight() }
+ override Instruction getGreater() { result = this.getRight() }
override predicate isStrict() { none() }
}
@@ -1520,11 +1545,11 @@ class CompareLEInstruction extends RelationalInstruction {
* are unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareGEInstruction extends RelationalInstruction {
- CompareGEInstruction() { getOpcode() instanceof Opcode::CompareGE }
+ CompareGEInstruction() { this.getOpcode() instanceof Opcode::CompareGE }
- override Instruction getLesser() { result = getRight() }
+ override Instruction getLesser() { result = this.getRight() }
- override Instruction getGreater() { result = getLeft() }
+ override Instruction getGreater() { result = this.getLeft() }
override predicate isStrict() { none() }
}
@@ -1543,78 +1568,78 @@ class CompareGEInstruction extends RelationalInstruction {
* of any case edge.
*/
class SwitchInstruction extends Instruction {
- SwitchInstruction() { getOpcode() instanceof Opcode::Switch }
+ SwitchInstruction() { this.getOpcode() instanceof Opcode::Switch }
/** Gets the operand that provides the integer value controlling the switch. */
- final ConditionOperand getExpressionOperand() { result = getAnOperand() }
+ final ConditionOperand getExpressionOperand() { result = this.getAnOperand() }
/** Gets the instruction whose result provides the integer value controlling the switch. */
- final Instruction getExpression() { result = getExpressionOperand().getDef() }
+ final Instruction getExpression() { result = this.getExpressionOperand().getDef() }
/** Gets the successor instructions along the case edges of the switch. */
- final Instruction getACaseSuccessor() { exists(CaseEdge edge | result = getSuccessor(edge)) }
+ final Instruction getACaseSuccessor() { exists(CaseEdge edge | result = this.getSuccessor(edge)) }
/** Gets the successor instruction along the default edge of the switch, if any. */
- final Instruction getDefaultSuccessor() { result = getSuccessor(EdgeKind::defaultEdge()) }
+ final Instruction getDefaultSuccessor() { result = this.getSuccessor(EdgeKind::defaultEdge()) }
}
/**
* An instruction that calls a function.
*/
class CallInstruction extends Instruction {
- CallInstruction() { getOpcode() instanceof Opcode::Call }
+ CallInstruction() { this.getOpcode() instanceof Opcode::Call }
final override string getImmediateString() {
- result = getStaticCallTarget().toString()
+ result = this.getStaticCallTarget().toString()
or
- not exists(getStaticCallTarget()) and result = "?"
+ not exists(this.getStaticCallTarget()) and result = "?"
}
/**
* Gets the operand the specifies the target function of the call.
*/
- final CallTargetOperand getCallTargetOperand() { result = getAnOperand() }
+ final CallTargetOperand getCallTargetOperand() { result = this.getAnOperand() }
/**
* Gets the `Instruction` that computes the target function of the call. This is usually a
* `FunctionAddress` instruction, but can also be an arbitrary instruction that produces a
* function pointer.
*/
- final Instruction getCallTarget() { result = getCallTargetOperand().getDef() }
+ final Instruction getCallTarget() { result = this.getCallTargetOperand().getDef() }
/**
* Gets all of the argument operands of the call, including the `this` pointer, if any.
*/
- final ArgumentOperand getAnArgumentOperand() { result = getAnOperand() }
+ final ArgumentOperand getAnArgumentOperand() { result = this.getAnOperand() }
/**
* Gets the `Function` that the call targets, if this is statically known.
*/
final Language::Function getStaticCallTarget() {
- result = getCallTarget().(FunctionAddressInstruction).getFunctionSymbol()
+ result = this.getCallTarget().(FunctionAddressInstruction).getFunctionSymbol()
}
/**
* Gets all of the arguments of the call, including the `this` pointer, if any.
*/
- final Instruction getAnArgument() { result = getAnArgumentOperand().getDef() }
+ final Instruction getAnArgument() { result = this.getAnArgumentOperand().getDef() }
/**
* Gets the `this` pointer argument operand of the call, if any.
*/
- final ThisArgumentOperand getThisArgumentOperand() { result = getAnOperand() }
+ final ThisArgumentOperand getThisArgumentOperand() { result = this.getAnOperand() }
/**
* Gets the `this` pointer argument of the call, if any.
*/
- final Instruction getThisArgument() { result = getThisArgumentOperand().getDef() }
+ final Instruction getThisArgument() { result = this.getThisArgumentOperand().getDef() }
/**
* Gets the argument operand at the specified index.
*/
pragma[noinline]
final PositionalArgumentOperand getPositionalArgumentOperand(int index) {
- result = getAnOperand() and
+ result = this.getAnOperand() and
result.getIndex() = index
}
@@ -1623,7 +1648,7 @@ class CallInstruction extends Instruction {
*/
pragma[noinline]
final Instruction getPositionalArgument(int index) {
- result = getPositionalArgumentOperand(index).getDef()
+ result = this.getPositionalArgumentOperand(index).getDef()
}
/**
@@ -1631,16 +1656,16 @@ class CallInstruction extends Instruction {
*/
pragma[noinline]
final ArgumentOperand getArgumentOperand(int index) {
- index >= 0 and result = getPositionalArgumentOperand(index)
+ index >= 0 and result = this.getPositionalArgumentOperand(index)
or
- index = -1 and result = getThisArgumentOperand()
+ index = -1 and result = this.getThisArgumentOperand()
}
/**
* Gets the argument at the specified index, or `this` if `index` is `-1`.
*/
pragma[noinline]
- final Instruction getArgument(int index) { result = getArgumentOperand(index).getDef() }
+ final Instruction getArgument(int index) { result = this.getArgumentOperand(index).getDef() }
/**
* Gets the number of arguments of the call, including the `this` pointer, if any.
@@ -1665,7 +1690,7 @@ class CallInstruction extends Instruction {
* An instruction representing a side effect of a function call.
*/
class SideEffectInstruction extends Instruction {
- SideEffectInstruction() { getOpcode() instanceof SideEffectOpcode }
+ SideEffectInstruction() { this.getOpcode() instanceof SideEffectOpcode }
/**
* Gets the instruction whose execution causes this side effect.
@@ -1680,7 +1705,7 @@ class SideEffectInstruction extends Instruction {
* accessed by that call.
*/
class CallSideEffectInstruction extends SideEffectInstruction {
- CallSideEffectInstruction() { getOpcode() instanceof Opcode::CallSideEffect }
+ CallSideEffectInstruction() { this.getOpcode() instanceof Opcode::CallSideEffect }
}
/**
@@ -1691,7 +1716,7 @@ class CallSideEffectInstruction extends SideEffectInstruction {
* call target cannot write to escaped memory.
*/
class CallReadSideEffectInstruction extends SideEffectInstruction {
- CallReadSideEffectInstruction() { getOpcode() instanceof Opcode::CallReadSideEffect }
+ CallReadSideEffectInstruction() { this.getOpcode() instanceof Opcode::CallReadSideEffect }
}
/**
@@ -1699,33 +1724,33 @@ class CallReadSideEffectInstruction extends SideEffectInstruction {
* specific parameter.
*/
class ReadSideEffectInstruction extends SideEffectInstruction, IndexedInstruction {
- ReadSideEffectInstruction() { getOpcode() instanceof ReadSideEffectOpcode }
+ ReadSideEffectInstruction() { this.getOpcode() instanceof ReadSideEffectOpcode }
/** Gets the operand for the value that will be read from this instruction, if known. */
- final SideEffectOperand getSideEffectOperand() { result = getAnOperand() }
+ final SideEffectOperand getSideEffectOperand() { result = this.getAnOperand() }
/** Gets the value that will be read from this instruction, if known. */
- final Instruction getSideEffect() { result = getSideEffectOperand().getDef() }
+ final Instruction getSideEffect() { result = this.getSideEffectOperand().getDef() }
/** Gets the operand for the address from which this instruction may read. */
- final AddressOperand getArgumentOperand() { result = getAnOperand() }
+ final AddressOperand getArgumentOperand() { result = this.getAnOperand() }
/** Gets the address from which this instruction may read. */
- final Instruction getArgumentDef() { result = getArgumentOperand().getDef() }
+ final Instruction getArgumentDef() { result = this.getArgumentOperand().getDef() }
}
/**
* An instruction representing the read of an indirect parameter within a function call.
*/
class IndirectReadSideEffectInstruction extends ReadSideEffectInstruction {
- IndirectReadSideEffectInstruction() { getOpcode() instanceof Opcode::IndirectReadSideEffect }
+ IndirectReadSideEffectInstruction() { this.getOpcode() instanceof Opcode::IndirectReadSideEffect }
}
/**
* An instruction representing the read of an indirect buffer parameter within a function call.
*/
class BufferReadSideEffectInstruction extends ReadSideEffectInstruction {
- BufferReadSideEffectInstruction() { getOpcode() instanceof Opcode::BufferReadSideEffect }
+ BufferReadSideEffectInstruction() { this.getOpcode() instanceof Opcode::BufferReadSideEffect }
}
/**
@@ -1733,18 +1758,18 @@ class BufferReadSideEffectInstruction extends ReadSideEffectInstruction {
*/
class SizedBufferReadSideEffectInstruction extends ReadSideEffectInstruction {
SizedBufferReadSideEffectInstruction() {
- getOpcode() instanceof Opcode::SizedBufferReadSideEffect
+ this.getOpcode() instanceof Opcode::SizedBufferReadSideEffect
}
/**
* Gets the operand that holds the number of bytes read from the buffer.
*/
- final BufferSizeOperand getBufferSizeOperand() { result = getAnOperand() }
+ final BufferSizeOperand getBufferSizeOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the number of bytes read from the buffer.
*/
- final Instruction getBufferSize() { result = getBufferSizeOperand().getDef() }
+ final Instruction getBufferSize() { result = this.getBufferSizeOperand().getDef() }
}
/**
@@ -1752,17 +1777,17 @@ class SizedBufferReadSideEffectInstruction extends ReadSideEffectInstruction {
* specific parameter.
*/
class WriteSideEffectInstruction extends SideEffectInstruction, IndexedInstruction {
- WriteSideEffectInstruction() { getOpcode() instanceof WriteSideEffectOpcode }
+ WriteSideEffectInstruction() { this.getOpcode() instanceof WriteSideEffectOpcode }
/**
* Get the operand that holds the address of the memory to be written.
*/
- final AddressOperand getDestinationAddressOperand() { result = getAnOperand() }
+ final AddressOperand getDestinationAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the memory to be written.
*/
- Instruction getDestinationAddress() { result = getDestinationAddressOperand().getDef() }
+ Instruction getDestinationAddress() { result = this.getDestinationAddressOperand().getDef() }
}
/**
@@ -1770,7 +1795,7 @@ class WriteSideEffectInstruction extends SideEffectInstruction, IndexedInstructi
*/
class IndirectMustWriteSideEffectInstruction extends WriteSideEffectInstruction {
IndirectMustWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::IndirectMustWriteSideEffect
+ this.getOpcode() instanceof Opcode::IndirectMustWriteSideEffect
}
}
@@ -1780,7 +1805,7 @@ class IndirectMustWriteSideEffectInstruction extends WriteSideEffectInstruction
*/
class BufferMustWriteSideEffectInstruction extends WriteSideEffectInstruction {
BufferMustWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::BufferMustWriteSideEffect
+ this.getOpcode() instanceof Opcode::BufferMustWriteSideEffect
}
}
@@ -1790,18 +1815,18 @@ class BufferMustWriteSideEffectInstruction extends WriteSideEffectInstruction {
*/
class SizedBufferMustWriteSideEffectInstruction extends WriteSideEffectInstruction {
SizedBufferMustWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::SizedBufferMustWriteSideEffect
+ this.getOpcode() instanceof Opcode::SizedBufferMustWriteSideEffect
}
/**
* Gets the operand that holds the number of bytes written to the buffer.
*/
- final BufferSizeOperand getBufferSizeOperand() { result = getAnOperand() }
+ final BufferSizeOperand getBufferSizeOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the number of bytes written to the buffer.
*/
- final Instruction getBufferSize() { result = getBufferSizeOperand().getDef() }
+ final Instruction getBufferSize() { result = this.getBufferSizeOperand().getDef() }
}
/**
@@ -1812,7 +1837,7 @@ class SizedBufferMustWriteSideEffectInstruction extends WriteSideEffectInstructi
*/
class IndirectMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
IndirectMayWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::IndirectMayWriteSideEffect
+ this.getOpcode() instanceof Opcode::IndirectMayWriteSideEffect
}
}
@@ -1822,7 +1847,9 @@ class IndirectMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
* Unlike `BufferWriteSideEffectInstruction`, the buffer might not be completely overwritten.
*/
class BufferMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
- BufferMayWriteSideEffectInstruction() { getOpcode() instanceof Opcode::BufferMayWriteSideEffect }
+ BufferMayWriteSideEffectInstruction() {
+ this.getOpcode() instanceof Opcode::BufferMayWriteSideEffect
+ }
}
/**
@@ -1832,18 +1859,18 @@ class BufferMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
*/
class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
SizedBufferMayWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::SizedBufferMayWriteSideEffect
+ this.getOpcode() instanceof Opcode::SizedBufferMayWriteSideEffect
}
/**
* Gets the operand that holds the number of bytes written to the buffer.
*/
- final BufferSizeOperand getBufferSizeOperand() { result = getAnOperand() }
+ final BufferSizeOperand getBufferSizeOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the number of bytes written to the buffer.
*/
- final Instruction getBufferSize() { result = getBufferSizeOperand().getDef() }
+ final Instruction getBufferSize() { result = this.getBufferSizeOperand().getDef() }
}
/**
@@ -1852,80 +1879,80 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio
*/
class InitializeDynamicAllocationInstruction extends SideEffectInstruction {
InitializeDynamicAllocationInstruction() {
- getOpcode() instanceof Opcode::InitializeDynamicAllocation
+ this.getOpcode() instanceof Opcode::InitializeDynamicAllocation
}
/**
* Gets the operand that represents the address of the allocation this instruction is initializing.
*/
- final AddressOperand getAllocationAddressOperand() { result = getAnOperand() }
+ final AddressOperand getAllocationAddressOperand() { result = this.getAnOperand() }
/**
* Gets the address for the allocation this instruction is initializing.
*/
- final Instruction getAllocationAddress() { result = getAllocationAddressOperand().getDef() }
+ final Instruction getAllocationAddress() { result = this.getAllocationAddressOperand().getDef() }
}
/**
* An instruction representing a GNU or MSVC inline assembly statement.
*/
class InlineAsmInstruction extends Instruction {
- InlineAsmInstruction() { getOpcode() instanceof Opcode::InlineAsm }
+ InlineAsmInstruction() { this.getOpcode() instanceof Opcode::InlineAsm }
}
/**
* An instruction that throws an exception.
*/
class ThrowInstruction extends Instruction {
- ThrowInstruction() { getOpcode() instanceof ThrowOpcode }
+ ThrowInstruction() { this.getOpcode() instanceof ThrowOpcode }
}
/**
* An instruction that throws a new exception.
*/
class ThrowValueInstruction extends ThrowInstruction {
- ThrowValueInstruction() { getOpcode() instanceof Opcode::ThrowValue }
+ ThrowValueInstruction() { this.getOpcode() instanceof Opcode::ThrowValue }
/**
* Gets the address operand of the exception thrown by this instruction.
*/
- final AddressOperand getExceptionAddressOperand() { result = getAnOperand() }
+ final AddressOperand getExceptionAddressOperand() { result = this.getAnOperand() }
/**
* Gets the address of the exception thrown by this instruction.
*/
- final Instruction getExceptionAddress() { result = getExceptionAddressOperand().getDef() }
+ final Instruction getExceptionAddress() { result = this.getExceptionAddressOperand().getDef() }
/**
* Gets the operand for the exception thrown by this instruction.
*/
- final LoadOperand getExceptionOperand() { result = getAnOperand() }
+ final LoadOperand getExceptionOperand() { result = this.getAnOperand() }
/**
* Gets the exception thrown by this instruction.
*/
- final Instruction getException() { result = getExceptionOperand().getDef() }
+ final Instruction getException() { result = this.getExceptionOperand().getDef() }
}
/**
* An instruction that re-throws the current exception.
*/
class ReThrowInstruction extends ThrowInstruction {
- ReThrowInstruction() { getOpcode() instanceof Opcode::ReThrow }
+ ReThrowInstruction() { this.getOpcode() instanceof Opcode::ReThrow }
}
/**
* An instruction that exits the current function by propagating an exception.
*/
class UnwindInstruction extends Instruction {
- UnwindInstruction() { getOpcode() instanceof Opcode::Unwind }
+ UnwindInstruction() { this.getOpcode() instanceof Opcode::Unwind }
}
/**
* An instruction that starts a `catch` handler.
*/
class CatchInstruction extends Instruction {
- CatchInstruction() { getOpcode() instanceof CatchOpcode }
+ CatchInstruction() { this.getOpcode() instanceof CatchOpcode }
}
/**
@@ -1935,7 +1962,7 @@ class CatchByTypeInstruction extends CatchInstruction {
Language::LanguageType exceptionType;
CatchByTypeInstruction() {
- getOpcode() instanceof Opcode::CatchByType and
+ this.getOpcode() instanceof Opcode::CatchByType and
exceptionType = Raw::getInstructionExceptionType(this)
}
@@ -1951,21 +1978,21 @@ class CatchByTypeInstruction extends CatchInstruction {
* An instruction that catches any exception.
*/
class CatchAnyInstruction extends CatchInstruction {
- CatchAnyInstruction() { getOpcode() instanceof Opcode::CatchAny }
+ CatchAnyInstruction() { this.getOpcode() instanceof Opcode::CatchAny }
}
/**
* An instruction that initializes all escaped memory.
*/
class AliasedDefinitionInstruction extends Instruction {
- AliasedDefinitionInstruction() { getOpcode() instanceof Opcode::AliasedDefinition }
+ AliasedDefinitionInstruction() { this.getOpcode() instanceof Opcode::AliasedDefinition }
}
/**
* An instruction that consumes all escaped memory on exit from the function.
*/
class AliasedUseInstruction extends Instruction {
- AliasedUseInstruction() { getOpcode() instanceof Opcode::AliasedUse }
+ AliasedUseInstruction() { this.getOpcode() instanceof Opcode::AliasedUse }
}
/**
@@ -1979,7 +2006,7 @@ class AliasedUseInstruction extends Instruction {
* runtime.
*/
class PhiInstruction extends Instruction {
- PhiInstruction() { getOpcode() instanceof Opcode::Phi }
+ PhiInstruction() { this.getOpcode() instanceof Opcode::Phi }
/**
* Gets all of the instruction's `PhiInputOperand`s, representing the values that flow from each predecessor block.
@@ -2047,29 +2074,29 @@ class PhiInstruction extends Instruction {
* https://link.springer.com/content/pdf/10.1007%2F3-540-61053-7_66.pdf.
*/
class ChiInstruction extends Instruction {
- ChiInstruction() { getOpcode() instanceof Opcode::Chi }
+ ChiInstruction() { this.getOpcode() instanceof Opcode::Chi }
/**
* Gets the operand that represents the previous state of all memory that might be aliased by the
* memory write.
*/
- final ChiTotalOperand getTotalOperand() { result = getAnOperand() }
+ final ChiTotalOperand getTotalOperand() { result = this.getAnOperand() }
/**
* Gets the operand that represents the previous state of all memory that might be aliased by the
* memory write.
*/
- final Instruction getTotal() { result = getTotalOperand().getDef() }
+ final Instruction getTotal() { result = this.getTotalOperand().getDef() }
/**
* Gets the operand that represents the new value written by the memory write.
*/
- final ChiPartialOperand getPartialOperand() { result = getAnOperand() }
+ final ChiPartialOperand getPartialOperand() { result = this.getAnOperand() }
/**
* Gets the operand that represents the new value written by the memory write.
*/
- final Instruction getPartial() { result = getPartialOperand().getDef() }
+ final Instruction getPartial() { result = this.getPartialOperand().getDef() }
/**
* Gets the bit range `[startBit, endBit)` updated by the partial operand of this `ChiInstruction`, relative to the start address of the total operand.
@@ -2093,7 +2120,7 @@ class ChiInstruction extends Instruction {
* or `Switch` instruction where that particular edge is infeasible.
*/
class UnreachedInstruction extends Instruction {
- UnreachedInstruction() { getOpcode() instanceof Opcode::Unreached }
+ UnreachedInstruction() { this.getOpcode() instanceof Opcode::Unreached }
}
/**
@@ -2106,7 +2133,7 @@ class BuiltInOperationInstruction extends Instruction {
Language::BuiltInOperation operation;
BuiltInOperationInstruction() {
- getOpcode() instanceof BuiltInOperationOpcode and
+ this.getOpcode() instanceof BuiltInOperationOpcode and
operation = Raw::getInstructionBuiltInOperation(this)
}
@@ -2122,9 +2149,9 @@ class BuiltInOperationInstruction extends Instruction {
* actual operation is specified by the `getBuiltInOperation()` predicate.
*/
class BuiltInInstruction extends BuiltInOperationInstruction {
- BuiltInInstruction() { getOpcode() instanceof Opcode::BuiltIn }
+ BuiltInInstruction() { this.getOpcode() instanceof Opcode::BuiltIn }
- final override string getImmediateString() { result = getBuiltInOperation().toString() }
+ final override string getImmediateString() { result = this.getBuiltInOperation().toString() }
}
/**
@@ -2135,7 +2162,7 @@ class BuiltInInstruction extends BuiltInOperationInstruction {
* to the `...` parameter.
*/
class VarArgsStartInstruction extends UnaryInstruction {
- VarArgsStartInstruction() { getOpcode() instanceof Opcode::VarArgsStart }
+ VarArgsStartInstruction() { this.getOpcode() instanceof Opcode::VarArgsStart }
}
/**
@@ -2145,7 +2172,7 @@ class VarArgsStartInstruction extends UnaryInstruction {
* a result.
*/
class VarArgsEndInstruction extends UnaryInstruction {
- VarArgsEndInstruction() { getOpcode() instanceof Opcode::VarArgsEnd }
+ VarArgsEndInstruction() { this.getOpcode() instanceof Opcode::VarArgsEnd }
}
/**
@@ -2155,7 +2182,7 @@ class VarArgsEndInstruction extends UnaryInstruction {
* argument.
*/
class VarArgInstruction extends UnaryInstruction {
- VarArgInstruction() { getOpcode() instanceof Opcode::VarArg }
+ VarArgInstruction() { this.getOpcode() instanceof Opcode::VarArg }
}
/**
@@ -2166,7 +2193,7 @@ class VarArgInstruction extends UnaryInstruction {
* argument of the `...` parameter.
*/
class NextVarArgInstruction extends UnaryInstruction {
- NextVarArgInstruction() { getOpcode() instanceof Opcode::NextVarArg }
+ NextVarArgInstruction() { this.getOpcode() instanceof Opcode::NextVarArg }
}
/**
@@ -2180,5 +2207,5 @@ class NextVarArgInstruction extends UnaryInstruction {
* The result is the address of the newly allocated object.
*/
class NewObjInstruction extends Instruction {
- NewObjInstruction() { getOpcode() instanceof Opcode::NewObj }
+ NewObjInstruction() { this.getOpcode() instanceof Opcode::NewObj }
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/Operand.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/Operand.qll
index d7cf89ca9aa..85d217bd361 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/Operand.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/Operand.qll
@@ -46,12 +46,12 @@ class Operand extends TStageOperand {
/**
* Gets the location of the source code for this operand.
*/
- final Language::Location getLocation() { result = getUse().getLocation() }
+ final Language::Location getLocation() { result = this.getUse().getLocation() }
/**
* Gets the function that contains this operand.
*/
- final IRFunction getEnclosingIRFunction() { result = getUse().getEnclosingIRFunction() }
+ final IRFunction getEnclosingIRFunction() { result = this.getUse().getEnclosingIRFunction() }
/**
* Gets the `Instruction` that consumes this operand.
@@ -74,7 +74,7 @@ class Operand extends TStageOperand {
*/
final Instruction getDef() {
result = this.getAnyDef() and
- getDefinitionOverlap() instanceof MustExactlyOverlap
+ this.getDefinitionOverlap() instanceof MustExactlyOverlap
}
/**
@@ -82,7 +82,7 @@ class Operand extends TStageOperand {
*
* Gets the `Instruction` that consumes this operand.
*/
- deprecated final Instruction getUseInstruction() { result = getUse() }
+ deprecated final Instruction getUseInstruction() { result = this.getUse() }
/**
* DEPRECATED: use `getAnyDef` or `getDef`. The exact replacement for this
@@ -91,7 +91,7 @@ class Operand extends TStageOperand {
*
* Gets the `Instruction` whose result is the value of the operand.
*/
- deprecated final Instruction getDefinitionInstruction() { result = getAnyDef() }
+ deprecated final Instruction getDefinitionInstruction() { result = this.getAnyDef() }
/**
* Gets the overlap relationship between the operand's definition and its use.
@@ -101,7 +101,9 @@ class Operand extends TStageOperand {
/**
* Holds if the result of the definition instruction does not exactly overlap this use.
*/
- final predicate isDefinitionInexact() { not getDefinitionOverlap() instanceof MustExactlyOverlap }
+ final predicate isDefinitionInexact() {
+ not this.getDefinitionOverlap() instanceof MustExactlyOverlap
+ }
/**
* Gets a prefix to use when dumping the operand in an operand list.
@@ -121,7 +123,7 @@ class Operand extends TStageOperand {
* For example: `this:r3_5`
*/
final string getDumpString() {
- result = getDumpLabel() + getInexactSpecifier() + getDefinitionId()
+ result = this.getDumpLabel() + this.getInexactSpecifier() + this.getDefinitionId()
}
/**
@@ -129,9 +131,9 @@ class Operand extends TStageOperand {
* definition is not modeled in SSA.
*/
private string getDefinitionId() {
- result = getAnyDef().getResultId()
+ result = this.getAnyDef().getResultId()
or
- not exists(getAnyDef()) and result = "m?"
+ not exists(this.getAnyDef()) and result = "m?"
}
/**
@@ -140,7 +142,7 @@ class Operand extends TStageOperand {
* the empty string.
*/
private string getInexactSpecifier() {
- if isDefinitionInexact() then result = "~" else result = ""
+ if this.isDefinitionInexact() then result = "~" else result = ""
}
/**
@@ -155,7 +157,7 @@ class Operand extends TStageOperand {
* the definition type, such as in the case of a partial read or a read from a pointer that
* has been cast to a different type.
*/
- Language::LanguageType getLanguageType() { result = getAnyDef().getResultLanguageType() }
+ Language::LanguageType getLanguageType() { result = this.getAnyDef().getResultLanguageType() }
/**
* Gets the language-neutral type of the value consumed by this operand. This is usually the same
@@ -164,7 +166,7 @@ class Operand extends TStageOperand {
* from the definition type, such as in the case of a partial read or a read from a pointer that
* has been cast to a different type.
*/
- final IRType getIRType() { result = getLanguageType().getIRType() }
+ final IRType getIRType() { result = this.getLanguageType().getIRType() }
/**
* Gets the type of the value consumed by this operand. This is usually the same as the
@@ -173,7 +175,7 @@ class Operand extends TStageOperand {
* the definition type, such as in the case of a partial read or a read from a pointer that
* has been cast to a different type.
*/
- final Language::Type getType() { getLanguageType().hasType(result, _) }
+ final Language::Type getType() { this.getLanguageType().hasType(result, _) }
/**
* Holds if the value consumed by this operand is a glvalue. If this
@@ -182,13 +184,13 @@ class Operand extends TStageOperand {
* not hold, the value of the operand represents a value whose type is
* given by `getType()`.
*/
- final predicate isGLValue() { getLanguageType().hasType(_, true) }
+ final predicate isGLValue() { this.getLanguageType().hasType(_, true) }
/**
* Gets the size of the value consumed by this operand, in bytes. If the operand does not have
* a known constant size, this predicate does not hold.
*/
- final int getSize() { result = getLanguageType().getByteSize() }
+ final int getSize() { result = this.getLanguageType().getByteSize() }
}
/**
@@ -205,7 +207,7 @@ class MemoryOperand extends Operand {
/**
* Gets the kind of memory access performed by the operand.
*/
- MemoryAccessKind getMemoryAccess() { result = getUse().getOpcode().getReadMemoryAccess() }
+ MemoryAccessKind getMemoryAccess() { result = this.getUse().getOpcode().getReadMemoryAccess() }
/**
* Holds if the memory access performed by this operand will not always read from every bit in the
@@ -215,7 +217,7 @@ class MemoryOperand extends Operand {
* conservative estimate of the memory that might actually be accessed at runtime (for example,
* the global side effects of a function call).
*/
- predicate hasMayReadMemoryAccess() { getUse().getOpcode().hasMayReadMemoryAccess() }
+ predicate hasMayReadMemoryAccess() { this.getUse().getOpcode().hasMayReadMemoryAccess() }
/**
* Returns the operand that holds the memory address from which the current operand loads its
@@ -223,8 +225,8 @@ class MemoryOperand extends Operand {
* is `r1`.
*/
final AddressOperand getAddressOperand() {
- getMemoryAccess().usesAddressOperand() and
- result.getUse() = getUse()
+ this.getMemoryAccess().usesAddressOperand() and
+ result.getUse() = this.getUse()
}
}
@@ -294,7 +296,7 @@ class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand, TNonPhiMemoryOpe
result = unique(Instruction defInstr | hasDefinition(defInstr, _))
}
- final override Overlap getDefinitionOverlap() { hasDefinition(_, result) }
+ final override Overlap getDefinitionOverlap() { this.hasDefinition(_, result) }
pragma[noinline]
private predicate hasDefinition(Instruction defInstr, Overlap overlap) {
@@ -449,13 +451,17 @@ class PhiInputOperand extends MemoryOperand, TPhiOperand {
final override Overlap getDefinitionOverlap() { result = overlap }
- final override int getDumpSortOrder() { result = 11 + getPredecessorBlock().getDisplayIndex() }
-
- final override string getDumpLabel() {
- result = "from " + getPredecessorBlock().getDisplayIndex().toString() + ":"
+ final override int getDumpSortOrder() {
+ result = 11 + this.getPredecessorBlock().getDisplayIndex()
}
- final override string getDumpId() { result = getPredecessorBlock().getDisplayIndex().toString() }
+ final override string getDumpLabel() {
+ result = "from " + this.getPredecessorBlock().getDisplayIndex().toString() + ":"
+ }
+
+ final override string getDumpId() {
+ result = this.getPredecessorBlock().getDisplayIndex().toString()
+ }
/**
* Gets the predecessor block from which this value comes.
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedExpr.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedExpr.qll
index a9f408bf161..d3f70b94db7 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedExpr.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedExpr.qll
@@ -55,7 +55,7 @@ abstract class TranslatedExpr extends TranslatedElement {
abstract predicate producesExprResult();
final CppType getResultType() {
- if isResultGLValue()
+ if this.isResultGLValue()
then result = getTypeForGLValue(expr.getType())
else result = getTypeForPRValue(expr.getType())
}
@@ -128,9 +128,9 @@ class TranslatedConditionValue extends TranslatedCoreExpr, ConditionContext,
TTranslatedConditionValue {
TranslatedConditionValue() { this = TTranslatedConditionValue(expr) }
- override TranslatedElement getChild(int id) { id = 0 and result = getCondition() }
+ override TranslatedElement getChild(int id) { id = 0 and result = this.getCondition() }
- override Instruction getFirstInstruction() { result = getCondition().getFirstInstruction() }
+ override Instruction getFirstInstruction() { result = this.getCondition().getFirstInstruction() }
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
(
@@ -146,46 +146,46 @@ class TranslatedConditionValue extends TranslatedCoreExpr, ConditionContext,
tag = ConditionValueFalseConstantTag()
) and
opcode instanceof Opcode::Constant and
- resultType = getResultType()
+ resultType = this.getResultType()
or
(
tag = ConditionValueTrueStoreTag() or
tag = ConditionValueFalseStoreTag()
) and
opcode instanceof Opcode::Store and
- resultType = getResultType()
+ resultType = this.getResultType()
or
tag = ConditionValueResultLoadTag() and
opcode instanceof Opcode::Load and
- resultType = getResultType()
+ resultType = this.getResultType()
}
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
kind instanceof GotoEdge and
(
tag = ConditionValueTrueTempAddressTag() and
- result = getInstruction(ConditionValueTrueConstantTag())
+ result = this.getInstruction(ConditionValueTrueConstantTag())
or
tag = ConditionValueTrueConstantTag() and
- result = getInstruction(ConditionValueTrueStoreTag())
+ result = this.getInstruction(ConditionValueTrueStoreTag())
or
tag = ConditionValueTrueStoreTag() and
- result = getInstruction(ConditionValueResultTempAddressTag())
+ result = this.getInstruction(ConditionValueResultTempAddressTag())
or
tag = ConditionValueFalseTempAddressTag() and
- result = getInstruction(ConditionValueFalseConstantTag())
+ result = this.getInstruction(ConditionValueFalseConstantTag())
or
tag = ConditionValueFalseConstantTag() and
- result = getInstruction(ConditionValueFalseStoreTag())
+ result = this.getInstruction(ConditionValueFalseStoreTag())
or
tag = ConditionValueFalseStoreTag() and
- result = getInstruction(ConditionValueResultTempAddressTag())
+ result = this.getInstruction(ConditionValueResultTempAddressTag())
or
tag = ConditionValueResultTempAddressTag() and
- result = getInstruction(ConditionValueResultLoadTag())
+ result = this.getInstruction(ConditionValueResultLoadTag())
or
tag = ConditionValueResultLoadTag() and
- result = getParent().getChildSuccessor(this)
+ result = this.getParent().getChildSuccessor(this)
)
}
@@ -193,25 +193,25 @@ class TranslatedConditionValue extends TranslatedCoreExpr, ConditionContext,
tag = ConditionValueTrueStoreTag() and
(
operandTag instanceof AddressOperandTag and
- result = getInstruction(ConditionValueTrueTempAddressTag())
+ result = this.getInstruction(ConditionValueTrueTempAddressTag())
or
operandTag instanceof StoreValueOperandTag and
- result = getInstruction(ConditionValueTrueConstantTag())
+ result = this.getInstruction(ConditionValueTrueConstantTag())
)
or
tag = ConditionValueFalseStoreTag() and
(
operandTag instanceof AddressOperandTag and
- result = getInstruction(ConditionValueFalseTempAddressTag())
+ result = this.getInstruction(ConditionValueFalseTempAddressTag())
or
operandTag instanceof StoreValueOperandTag and
- result = getInstruction(ConditionValueFalseConstantTag())
+ result = this.getInstruction(ConditionValueFalseConstantTag())
)
or
tag = ConditionValueResultLoadTag() and
(
operandTag instanceof AddressOperandTag and
- result = getInstruction(ConditionValueResultTempAddressTag())
+ result = this.getInstruction(ConditionValueResultTempAddressTag())
)
}
@@ -226,7 +226,7 @@ class TranslatedConditionValue extends TranslatedCoreExpr, ConditionContext,
tag = ConditionValueFalseTempAddressTag() or
tag = ConditionValueResultTempAddressTag()
) and
- result = getTempVariable(ConditionValueTempVar())
+ result = this.getTempVariable(ConditionValueTempVar())
}
override string getInstructionConstantValue(InstructionTag tag) {
@@ -235,18 +235,18 @@ class TranslatedConditionValue extends TranslatedCoreExpr, ConditionContext,
tag = ConditionValueFalseConstantTag() and result = "0"
}
- override Instruction getResult() { result = getInstruction(ConditionValueResultLoadTag()) }
+ override Instruction getResult() { result = this.getInstruction(ConditionValueResultLoadTag()) }
override Instruction getChildSuccessor(TranslatedElement child) { none() }
override Instruction getChildTrueSuccessor(TranslatedCondition child) {
- child = getCondition() and
- result = getInstruction(ConditionValueTrueTempAddressTag())
+ child = this.getCondition() and
+ result = this.getInstruction(ConditionValueTrueTempAddressTag())
}
override Instruction getChildFalseSuccessor(TranslatedCondition child) {
- child = getCondition() and
- result = getInstruction(ConditionValueFalseTempAddressTag())
+ child = this.getCondition() and
+ result = this.getInstruction(ConditionValueFalseTempAddressTag())
}
private TranslatedCondition getCondition() { result = getTranslatedCondition(expr) }
@@ -260,9 +260,11 @@ class TranslatedConditionValue extends TranslatedCoreExpr, ConditionContext,
* temporary variable.
*/
abstract class TranslatedValueCategoryAdjustment extends TranslatedExpr {
- final override Instruction getFirstInstruction() { result = getOperand().getFirstInstruction() }
+ final override Instruction getFirstInstruction() {
+ result = this.getOperand().getFirstInstruction()
+ }
- final override TranslatedElement getChild(int id) { id = 0 and result = getOperand() }
+ final override TranslatedElement getChild(int id) { id = 0 and result = this.getOperand() }
final override predicate producesExprResult() {
// A temp object always produces the result of the expression.
@@ -284,28 +286,28 @@ class TranslatedLoad extends TranslatedValueCategoryAdjustment, TTranslatedLoad
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
tag = LoadTag() and
opcode instanceof Opcode::Load and
- resultType = getResultType()
+ resultType = this.getResultType()
}
override predicate isResultGLValue() { none() }
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = LoadTag() and
- result = getParent().getChildSuccessor(this) and
+ result = this.getParent().getChildSuccessor(this) and
kind instanceof GotoEdge
}
override Instruction getChildSuccessor(TranslatedElement child) {
- child = getOperand() and result = getInstruction(LoadTag())
+ child = this.getOperand() and result = this.getInstruction(LoadTag())
}
- override Instruction getResult() { result = getInstruction(LoadTag()) }
+ override Instruction getResult() { result = this.getInstruction(LoadTag()) }
override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag = LoadTag() and
(
operandTag instanceof AddressOperandTag and
- result = getOperand().getResult()
+ result = this.getOperand().getResult()
)
}
}
@@ -337,28 +339,28 @@ class TranslatedSyntheticTemporaryObject extends TranslatedValueCategoryAdjustme
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = InitializerVariableAddressTag() and
- result = getInstruction(InitializerStoreTag()) and
+ result = this.getInstruction(InitializerStoreTag()) and
kind instanceof GotoEdge
or
tag = InitializerStoreTag() and
- result = getParent().getChildSuccessor(this) and
+ result = this.getParent().getChildSuccessor(this) and
kind instanceof GotoEdge
}
override Instruction getChildSuccessor(TranslatedElement child) {
- child = getOperand() and result = getInstruction(InitializerVariableAddressTag())
+ child = this.getOperand() and result = this.getInstruction(InitializerVariableAddressTag())
}
- override Instruction getResult() { result = getInstruction(InitializerVariableAddressTag()) }
+ override Instruction getResult() { result = this.getInstruction(InitializerVariableAddressTag()) }
override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag = InitializerStoreTag() and
(
operandTag instanceof AddressOperandTag and
- result = getInstruction(InitializerVariableAddressTag())
+ result = this.getInstruction(InitializerVariableAddressTag())
or
operandTag instanceof StoreValueOperandTag and
- result = getOperand().getResult()
+ result = this.getOperand().getResult()
)
}
@@ -383,32 +385,32 @@ class TranslatedResultCopy extends TranslatedExpr, TTranslatedResultCopy {
override string toString() { result = "Result of " + expr.toString() }
- override Instruction getFirstInstruction() { result = getOperand().getFirstInstruction() }
+ override Instruction getFirstInstruction() { result = this.getOperand().getFirstInstruction() }
- override TranslatedElement getChild(int id) { id = 0 and result = getOperand() }
+ override TranslatedElement getChild(int id) { id = 0 and result = this.getOperand() }
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
tag = ResultCopyTag() and
opcode instanceof Opcode::CopyValue and
- resultType = getOperand().getResultType()
+ resultType = this.getOperand().getResultType()
}
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = ResultCopyTag() and
- result = getParent().getChildSuccessor(this) and
+ result = this.getParent().getChildSuccessor(this) and
kind instanceof GotoEdge
}
override Instruction getChildSuccessor(TranslatedElement child) {
- child = getOperand() and result = getInstruction(ResultCopyTag())
+ child = this.getOperand() and result = this.getInstruction(ResultCopyTag())
}
- override Instruction getResult() { result = getInstruction(ResultCopyTag()) }
+ override Instruction getResult() { result = this.getInstruction(ResultCopyTag()) }
override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag = ResultCopyTag() and
operandTag instanceof UnaryOperandTag and
- result = getOperand().getResult()
+ result = this.getOperand().getResult()
}
final override predicate producesExprResult() { any() }
@@ -419,23 +421,25 @@ class TranslatedResultCopy extends TranslatedExpr, TTranslatedResultCopy {
class TranslatedCommaExpr extends TranslatedNonConstantExpr {
override CommaExpr expr;
- override Instruction getFirstInstruction() { result = getLeftOperand().getFirstInstruction() }
-
- override TranslatedElement getChild(int id) {
- id = 0 and result = getLeftOperand()
- or
- id = 1 and result = getRightOperand()
+ override Instruction getFirstInstruction() {
+ result = this.getLeftOperand().getFirstInstruction()
}
- override Instruction getResult() { result = getRightOperand().getResult() }
+ override TranslatedElement getChild(int id) {
+ id = 0 and result = this.getLeftOperand()
+ or
+ id = 1 and result = this.getRightOperand()
+ }
+
+ override Instruction getResult() { result = this.getRightOperand().getResult() }
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) { none() }
override Instruction getChildSuccessor(TranslatedElement child) {
- child = getLeftOperand() and
- result = getRightOperand().getFirstInstruction()
+ child = this.getLeftOperand() and
+ result = this.getRightOperand().getFirstInstruction()
or
- child = getRightOperand() and result = getParent().getChildSuccessor(this)
+ child = this.getRightOperand() and result = this.getParent().getChildSuccessor(this)
}
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
@@ -462,7 +466,7 @@ private int getElementSize(Type type) {
abstract class TranslatedCrementOperation extends TranslatedNonConstantExpr {
override CrementOperation expr;
- final override TranslatedElement getChild(int id) { id = 0 and result = getLoadedOperand() }
+ final override TranslatedElement getChild(int id) { id = 0 and result = this.getLoadedOperand() }
final override string getInstructionConstantValue(InstructionTag tag) {
tag = CrementConstantTag() and
@@ -493,10 +497,10 @@ abstract class TranslatedCrementOperation extends TranslatedNonConstantExpr {
final override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
tag = CrementConstantTag() and
opcode instanceof Opcode::Constant and
- resultType = getConstantType()
+ resultType = this.getConstantType()
or
tag = CrementOpTag() and
- opcode = getOpcode() and
+ opcode = this.getOpcode() and
resultType = getTypeForPRValue(expr.getType())
or
tag = CrementStoreTag() and
@@ -508,49 +512,49 @@ abstract class TranslatedCrementOperation extends TranslatedNonConstantExpr {
tag = CrementOpTag() and
(
operandTag instanceof LeftOperandTag and
- result = getLoadedOperand().getResult()
+ result = this.getLoadedOperand().getResult()
or
operandTag instanceof RightOperandTag and
- result = getInstruction(CrementConstantTag())
+ result = this.getInstruction(CrementConstantTag())
)
or
tag = CrementStoreTag() and
(
operandTag instanceof AddressOperandTag and
- result = getUnloadedOperand().getResult()
+ result = this.getUnloadedOperand().getResult()
or
operandTag instanceof StoreValueOperandTag and
- result = getInstruction(CrementOpTag())
+ result = this.getInstruction(CrementOpTag())
)
}
final override Instruction getFirstInstruction() {
- result = getLoadedOperand().getFirstInstruction()
+ result = this.getLoadedOperand().getFirstInstruction()
}
final override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
kind instanceof GotoEdge and
(
tag = CrementConstantTag() and
- result = getInstruction(CrementOpTag())
+ result = this.getInstruction(CrementOpTag())
or
tag = CrementOpTag() and
- result = getInstruction(CrementStoreTag())
+ result = this.getInstruction(CrementStoreTag())
or
tag = CrementStoreTag() and
- result = getParent().getChildSuccessor(this)
+ result = this.getParent().getChildSuccessor(this)
)
}
final override Instruction getChildSuccessor(TranslatedElement child) {
- child = getLoadedOperand() and result = getInstruction(CrementConstantTag())
+ child = this.getLoadedOperand() and result = this.getInstruction(CrementConstantTag())
}
final override int getInstructionElementSize(InstructionTag tag) {
tag = CrementOpTag() and
(
- getOpcode() instanceof Opcode::PointerAdd or
- getOpcode() instanceof Opcode::PointerSub
+ this.getOpcode() instanceof Opcode::PointerAdd or
+ this.getOpcode() instanceof Opcode::PointerSub
) and
result = getElementSize(expr.getType())
}
@@ -567,7 +571,7 @@ abstract class TranslatedCrementOperation extends TranslatedNonConstantExpr {
/**
* Gets the address to which the result of this crement will be stored.
*/
- final TranslatedExpr getUnloadedOperand() { result = getLoadedOperand().getOperand() }
+ final TranslatedExpr getUnloadedOperand() { result = this.getLoadedOperand().getOperand() }
final Opcode getOpcode() {
exists(Type resultType |
@@ -601,18 +605,18 @@ class TranslatedPrefixCrementOperation extends TranslatedCrementOperation {
// new value assigned to the operand. If this is C++, then the result is
// an lvalue, but that lvalue is being loaded as part of this expression.
// EDG doesn't mark this as a load.
- result = getInstruction(CrementOpTag())
+ result = this.getInstruction(CrementOpTag())
else
// This is C++, where the result is an lvalue for the operand, and that
// lvalue is not being loaded as part of this expression.
- result = getUnloadedOperand().getResult()
+ result = this.getUnloadedOperand().getResult()
}
}
class TranslatedPostfixCrementOperation extends TranslatedCrementOperation {
override PostfixCrementOperation expr;
- override Instruction getResult() { result = getLoadedOperand().getResult() }
+ override Instruction getResult() { result = this.getLoadedOperand().getResult() }
}
/**
@@ -624,30 +628,30 @@ class TranslatedArrayExpr extends TranslatedNonConstantExpr {
override ArrayExpr expr;
final override Instruction getFirstInstruction() {
- result = getBaseOperand().getFirstInstruction()
+ result = this.getBaseOperand().getFirstInstruction()
}
final override TranslatedElement getChild(int id) {
- id = 0 and result = getBaseOperand()
+ id = 0 and result = this.getBaseOperand()
or
- id = 1 and result = getOffsetOperand()
+ id = 1 and result = this.getOffsetOperand()
}
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = OnlyInstructionTag() and
- result = getParent().getChildSuccessor(this) and
+ result = this.getParent().getChildSuccessor(this) and
kind instanceof GotoEdge
}
override Instruction getChildSuccessor(TranslatedElement child) {
- child = getBaseOperand() and
- result = getOffsetOperand().getFirstInstruction()
+ child = this.getBaseOperand() and
+ result = this.getOffsetOperand().getFirstInstruction()
or
- child = getOffsetOperand() and
- result = getInstruction(OnlyInstructionTag())
+ child = this.getOffsetOperand() and
+ result = this.getInstruction(OnlyInstructionTag())
}
- override Instruction getResult() { result = getInstruction(OnlyInstructionTag()) }
+ override Instruction getResult() { result = this.getInstruction(OnlyInstructionTag()) }
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
tag = OnlyInstructionTag() and
@@ -659,10 +663,10 @@ class TranslatedArrayExpr extends TranslatedNonConstantExpr {
tag = OnlyInstructionTag() and
(
operandTag instanceof LeftOperandTag and
- result = getBaseOperand().getResult()
+ result = this.getBaseOperand().getResult()
or
operandTag instanceof RightOperandTag and
- result = getOffsetOperand().getResult()
+ result = this.getOffsetOperand().getResult()
)
}
@@ -681,21 +685,23 @@ class TranslatedArrayExpr extends TranslatedNonConstantExpr {
}
abstract class TranslatedTransparentExpr extends TranslatedNonConstantExpr {
- final override Instruction getFirstInstruction() { result = getOperand().getFirstInstruction() }
+ final override Instruction getFirstInstruction() {
+ result = this.getOperand().getFirstInstruction()
+ }
- final override TranslatedElement getChild(int id) { id = 0 and result = getOperand() }
+ final override TranslatedElement getChild(int id) { id = 0 and result = this.getOperand() }
final override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) { none() }
final override Instruction getChildSuccessor(TranslatedElement child) {
- child = getOperand() and result = getParent().getChildSuccessor(this)
+ child = this.getOperand() and result = this.getParent().getChildSuccessor(this)
}
final override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
none()
}
- final override Instruction getResult() { result = getOperand().getResult() }
+ final override Instruction getResult() { result = this.getOperand().getResult() }
final override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
none()
@@ -749,21 +755,23 @@ class TranslatedThisExpr extends TranslatedNonConstantExpr {
or
tag = ThisLoadTag() and
opcode instanceof Opcode::Load and
- resultType = getResultType()
+ resultType = this.getResultType()
}
- final override Instruction getResult() { result = getInstruction(ThisLoadTag()) }
+ final override Instruction getResult() { result = this.getInstruction(ThisLoadTag()) }
- final override Instruction getFirstInstruction() { result = getInstruction(ThisAddressTag()) }
+ final override Instruction getFirstInstruction() {
+ result = this.getInstruction(ThisAddressTag())
+ }
final override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
kind instanceof GotoEdge and
tag = ThisAddressTag() and
- result = getInstruction(ThisLoadTag())
+ result = this.getInstruction(ThisLoadTag())
or
kind instanceof GotoEdge and
tag = ThisLoadTag() and
- result = getParent().getChildSuccessor(this)
+ result = this.getParent().getChildSuccessor(this)
}
final override Instruction getChildSuccessor(TranslatedElement child) { none() }
@@ -771,7 +779,7 @@ class TranslatedThisExpr extends TranslatedNonConstantExpr {
final override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag = ThisLoadTag() and
operandTag instanceof AddressOperandTag and
- result = getInstruction(ThisAddressTag())
+ result = this.getInstruction(ThisAddressTag())
}
override IRVariable getInstructionVariable(InstructionTag tag) {
@@ -784,23 +792,23 @@ abstract class TranslatedVariableAccess extends TranslatedNonConstantExpr {
override VariableAccess expr;
final override TranslatedElement getChild(int id) {
- id = 0 and result = getQualifier() // Might not exist
+ id = 0 and result = this.getQualifier() // Might not exist
}
final TranslatedExpr getQualifier() {
result = getTranslatedExpr(expr.getQualifier().getFullyConverted())
}
- override Instruction getResult() { result = getInstruction(OnlyInstructionTag()) }
+ override Instruction getResult() { result = this.getInstruction(OnlyInstructionTag()) }
final override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = OnlyInstructionTag() and
- result = getParent().getChildSuccessor(this) and
+ result = this.getParent().getChildSuccessor(this) and
kind instanceof GotoEdge
}
final override Instruction getChildSuccessor(TranslatedElement child) {
- child = getQualifier() and result = getInstruction(OnlyInstructionTag())
+ child = this.getQualifier() and result = this.getInstruction(OnlyInstructionTag())
}
}
@@ -808,9 +816,9 @@ class TranslatedNonFieldVariableAccess extends TranslatedVariableAccess {
TranslatedNonFieldVariableAccess() { not expr instanceof FieldAccess }
override Instruction getFirstInstruction() {
- if exists(getQualifier())
- then result = getQualifier().getFirstInstruction()
- else result = getInstruction(OnlyInstructionTag())
+ if exists(this.getQualifier())
+ then result = this.getQualifier().getFirstInstruction()
+ else result = this.getInstruction(OnlyInstructionTag())
}
override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
@@ -832,12 +840,12 @@ class TranslatedNonFieldVariableAccess extends TranslatedVariableAccess {
class TranslatedFieldAccess extends TranslatedVariableAccess {
override FieldAccess expr;
- override Instruction getFirstInstruction() { result = getQualifier().getFirstInstruction() }
+ override Instruction getFirstInstruction() { result = this.getQualifier().getFirstInstruction() }
override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag = OnlyInstructionTag() and
operandTag instanceof UnaryOperandTag and
- result = getQualifier().getResult()
+ result = this.getQualifier().getResult()
}
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
@@ -857,20 +865,20 @@ class TranslatedFunctionAccess extends TranslatedNonConstantExpr {
override TranslatedElement getChild(int id) { none() }
- override Instruction getFirstInstruction() { result = getInstruction(OnlyInstructionTag()) }
+ override Instruction getFirstInstruction() { result = this.getInstruction(OnlyInstructionTag()) }
- override Instruction getResult() { result = getInstruction(OnlyInstructionTag()) }
+ override Instruction getResult() { result = this.getInstruction(OnlyInstructionTag()) }
final override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = OnlyInstructionTag() and
- result = getParent().getChildSuccessor(this) and
+ result = this.getParent().getChildSuccessor(this) and
kind instanceof GotoEdge
}
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
tag = OnlyInstructionTag() and
opcode instanceof Opcode::FunctionAddress and
- resultType = getResultType()
+ resultType = this.getResultType()
}
override Function getInstructionFunction(InstructionTag tag) {
@@ -902,11 +910,13 @@ abstract class TranslatedConstantExpr extends TranslatedCoreExpr, TTranslatedVal
isIRConstant(expr)
}
- final override Instruction getFirstInstruction() { result = getInstruction(OnlyInstructionTag()) }
+ final override Instruction getFirstInstruction() {
+ result = this.getInstruction(OnlyInstructionTag())
+ }
final override TranslatedElement getChild(int id) { none() }
- final override Instruction getResult() { result = getInstruction(OnlyInstructionTag()) }
+ final override Instruction getResult() { result = this.getInstruction(OnlyInstructionTag()) }
final override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
none()
@@ -914,13 +924,13 @@ abstract class TranslatedConstantExpr extends TranslatedCoreExpr, TTranslatedVal
final override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
tag = OnlyInstructionTag() and
- opcode = getOpcode() and
- resultType = getResultType()
+ opcode = this.getOpcode() and
+ resultType = this.getResultType()
}
final override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = OnlyInstructionTag() and
- result = getParent().getChildSuccessor(this) and
+ result = this.getParent().getChildSuccessor(this) and
kind instanceof GotoEdge
}
@@ -962,12 +972,12 @@ abstract class TranslatedSingleInstructionExpr extends TranslatedNonConstantExpr
abstract Opcode getOpcode();
final override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
- opcode = getOpcode() and
+ opcode = this.getOpcode() and
tag = OnlyInstructionTag() and
- resultType = getResultType()
+ resultType = this.getResultType()
}
- final override Instruction getResult() { result = getInstruction(OnlyInstructionTag()) }
+ final override Instruction getResult() { result = this.getInstruction(OnlyInstructionTag()) }
}
class TranslatedUnaryExpr extends TranslatedSingleInstructionExpr {
@@ -978,23 +988,25 @@ class TranslatedUnaryExpr extends TranslatedSingleInstructionExpr {
expr instanceof UnaryMinusExpr
}
- final override Instruction getFirstInstruction() { result = getOperand().getFirstInstruction() }
+ final override Instruction getFirstInstruction() {
+ result = this.getOperand().getFirstInstruction()
+ }
- final override TranslatedElement getChild(int id) { id = 0 and result = getOperand() }
+ final override TranslatedElement getChild(int id) { id = 0 and result = this.getOperand() }
final override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = OnlyInstructionTag() and
- result = getParent().getChildSuccessor(this) and
+ result = this.getParent().getChildSuccessor(this) and
kind instanceof GotoEdge
}
final override Instruction getChildSuccessor(TranslatedElement child) {
- child = getOperand() and result = getInstruction(OnlyInstructionTag())
+ child = this.getOperand() and result = this.getInstruction(OnlyInstructionTag())
}
final override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag = OnlyInstructionTag() and
- result = getOperand().getResult() and
+ result = this.getOperand().getResult() and
operandTag instanceof UnaryOperandTag
}
@@ -1016,11 +1028,11 @@ class TranslatedUnaryExpr extends TranslatedSingleInstructionExpr {
abstract class TranslatedConversion extends TranslatedNonConstantExpr {
override Conversion expr;
- override Instruction getFirstInstruction() { result = getOperand().getFirstInstruction() }
+ override Instruction getFirstInstruction() { result = this.getOperand().getFirstInstruction() }
- final override TranslatedElement getChild(int id) { id = 0 and result = getOperand() }
+ final override TranslatedElement getChild(int id) { id = 0 and result = this.getOperand() }
- final TranslatedExpr getOperand() { result = getTranslatedExpr(expr.(Conversion).getExpr()) }
+ final TranslatedExpr getOperand() { result = getTranslatedExpr(expr.getExpr()) }
}
/**
@@ -1030,26 +1042,26 @@ abstract class TranslatedConversion extends TranslatedNonConstantExpr {
abstract class TranslatedSingleInstructionConversion extends TranslatedConversion {
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = OnlyInstructionTag() and
- result = getParent().getChildSuccessor(this) and
+ result = this.getParent().getChildSuccessor(this) and
kind instanceof GotoEdge
}
override Instruction getChildSuccessor(TranslatedElement child) {
- child = getOperand() and result = getInstruction(OnlyInstructionTag())
+ child = this.getOperand() and result = this.getInstruction(OnlyInstructionTag())
}
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
tag = OnlyInstructionTag() and
- opcode = getOpcode() and
- resultType = getResultType()
+ opcode = this.getOpcode() and
+ resultType = this.getResultType()
}
- override Instruction getResult() { result = getInstruction(OnlyInstructionTag()) }
+ override Instruction getResult() { result = this.getInstruction(OnlyInstructionTag()) }
override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag = OnlyInstructionTag() and
operandTag instanceof UnaryOperandTag and
- result = getOperand().getResult()
+ result = this.getOperand().getResult()
}
/**
@@ -1133,37 +1145,37 @@ class TranslatedBoolConversion extends TranslatedConversion {
kind instanceof GotoEdge and
(
tag = BoolConversionConstantTag() and
- result = getInstruction(BoolConversionCompareTag())
+ result = this.getInstruction(BoolConversionCompareTag())
or
tag = BoolConversionCompareTag() and
- result = getParent().getChildSuccessor(this)
+ result = this.getParent().getChildSuccessor(this)
)
}
override Instruction getChildSuccessor(TranslatedElement child) {
- child = getOperand() and result = getInstruction(BoolConversionConstantTag())
+ child = this.getOperand() and result = this.getInstruction(BoolConversionConstantTag())
}
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
tag = BoolConversionConstantTag() and
opcode instanceof Opcode::Constant and
- resultType = getOperand().getResultType()
+ resultType = this.getOperand().getResultType()
or
tag = BoolConversionCompareTag() and
opcode instanceof Opcode::CompareNE and
resultType = getBoolType()
}
- override Instruction getResult() { result = getInstruction(BoolConversionCompareTag()) }
+ override Instruction getResult() { result = this.getInstruction(BoolConversionCompareTag()) }
override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag = BoolConversionCompareTag() and
(
operandTag instanceof LeftOperandTag and
- result = getOperand().getResult()
+ result = this.getOperand().getResult()
or
operandTag instanceof RightOperandTag and
- result = getInstruction(BoolConversionConstantTag())
+ result = this.getInstruction(BoolConversionConstantTag())
)
}
@@ -1250,67 +1262,71 @@ class TranslatedBinaryOperation extends TranslatedSingleInstructionExpr {
expr instanceof ComparisonOperation
}
- override Instruction getFirstInstruction() { result = getLeftOperand().getFirstInstruction() }
+ override Instruction getFirstInstruction() {
+ result = this.getLeftOperand().getFirstInstruction()
+ }
final override TranslatedElement getChild(int id) {
- id = 0 and result = getLeftOperand()
+ id = 0 and result = this.getLeftOperand()
or
- id = 1 and result = getRightOperand()
+ id = 1 and result = this.getRightOperand()
}
final override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag = OnlyInstructionTag() and
- if swapOperandsOnOp()
+ if this.swapOperandsOnOp()
then (
operandTag instanceof RightOperandTag and
- result = getLeftOperand().getResult()
+ result = this.getLeftOperand().getResult()
or
operandTag instanceof LeftOperandTag and
- result = getRightOperand().getResult()
+ result = this.getRightOperand().getResult()
) else (
operandTag instanceof LeftOperandTag and
- result = getLeftOperand().getResult()
+ result = this.getLeftOperand().getResult()
or
operandTag instanceof RightOperandTag and
- result = getRightOperand().getResult()
+ result = this.getRightOperand().getResult()
)
}
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = OnlyInstructionTag() and
- result = getParent().getChildSuccessor(this) and
+ result = this.getParent().getChildSuccessor(this) and
kind instanceof GotoEdge
}
override Instruction getChildSuccessor(TranslatedElement child) {
- child = getLeftOperand() and
- result = getRightOperand().getFirstInstruction()
+ child = this.getLeftOperand() and
+ result = this.getRightOperand().getFirstInstruction()
or
- child = getRightOperand() and
- result = getInstruction(OnlyInstructionTag())
+ child = this.getRightOperand() and
+ result = this.getInstruction(OnlyInstructionTag())
}
override Opcode getOpcode() {
- result = binaryArithmeticOpcode(expr.(BinaryArithmeticOperation)) or
- result = binaryBitwiseOpcode(expr.(BinaryBitwiseOperation)) or
- result = comparisonOpcode(expr.(ComparisonOperation))
+ result = binaryArithmeticOpcode(expr) or
+ result = binaryBitwiseOpcode(expr) or
+ result = comparisonOpcode(expr)
}
override int getInstructionElementSize(InstructionTag tag) {
tag = OnlyInstructionTag() and
exists(Opcode opcode |
- opcode = getOpcode() and
+ opcode = this.getOpcode() and
(
opcode instanceof Opcode::PointerAdd or
opcode instanceof Opcode::PointerSub or
opcode instanceof Opcode::PointerDiff
) and
- result = getElementSize(getPointerOperand().getExpr().getType())
+ result = getElementSize(this.getPointerOperand().getExpr().getType())
)
}
private TranslatedExpr getPointerOperand() {
- if swapOperandsOnOp() then result = getRightOperand() else result = getLeftOperand()
+ if this.swapOperandsOnOp()
+ then result = this.getRightOperand()
+ else result = this.getLeftOperand()
}
private predicate swapOperandsOnOp() {
@@ -1337,14 +1353,14 @@ class TranslatedAssignExpr extends TranslatedNonConstantExpr {
override AssignExpr expr;
final override TranslatedElement getChild(int id) {
- id = 0 and result = getLeftOperand()
+ id = 0 and result = this.getLeftOperand()
or
- id = 1 and result = getRightOperand()
+ id = 1 and result = this.getRightOperand()
}
final override Instruction getFirstInstruction() {
// Evaluation is right-to-left
- result = getRightOperand().getFirstInstruction()
+ result = this.getRightOperand().getFirstInstruction()
}
final override Instruction getResult() {
@@ -1354,11 +1370,11 @@ class TranslatedAssignExpr extends TranslatedNonConstantExpr {
// value assigned to the left operand. If this is C++, then the result is
// an lvalue, but that lvalue is being loaded as part of this expression.
// EDG doesn't mark this as a load.
- result = getRightOperand().getResult()
+ result = this.getRightOperand().getResult()
else
// This is C++, where the result is an lvalue for the left operand,
// and that lvalue is not being loaded as part of this expression.
- result = getLeftOperand().getResult()
+ result = this.getLeftOperand().getResult()
}
abstract Instruction getStoredValue();
@@ -1373,17 +1389,17 @@ class TranslatedAssignExpr extends TranslatedNonConstantExpr {
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = AssignmentStoreTag() and
- result = getParent().getChildSuccessor(this) and
+ result = this.getParent().getChildSuccessor(this) and
kind instanceof GotoEdge
}
override Instruction getChildSuccessor(TranslatedElement child) {
// Operands are evaluated right-to-left.
- child = getRightOperand() and
- result = getLeftOperand().getFirstInstruction()
+ child = this.getRightOperand() and
+ result = this.getLeftOperand().getFirstInstruction()
or
- child = getLeftOperand() and
- result = getInstruction(AssignmentStoreTag())
+ child = this.getLeftOperand() and
+ result = this.getInstruction(AssignmentStoreTag())
}
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
@@ -1396,10 +1412,10 @@ class TranslatedAssignExpr extends TranslatedNonConstantExpr {
tag = AssignmentStoreTag() and
(
operandTag instanceof AddressOperandTag and
- result = getLeftOperand().getResult()
+ result = this.getLeftOperand().getResult()
or
operandTag instanceof StoreValueOperandTag and
- result = getRightOperand().getResult()
+ result = this.getRightOperand().getResult()
)
}
}
@@ -1408,14 +1424,14 @@ class TranslatedAssignOperation extends TranslatedNonConstantExpr {
override AssignOperation expr;
final override TranslatedElement getChild(int id) {
- id = 0 and result = getLoadedLeftOperand()
+ id = 0 and result = this.getLoadedLeftOperand()
or
- id = 1 and result = getRightOperand()
+ id = 1 and result = this.getRightOperand()
}
final override Instruction getFirstInstruction() {
// Evaluation is right-to-left
- result = getRightOperand().getFirstInstruction()
+ result = this.getRightOperand().getFirstInstruction()
}
final override Instruction getResult() {
@@ -1425,14 +1441,16 @@ class TranslatedAssignOperation extends TranslatedNonConstantExpr {
// value assigned to the left operand. If this is C++, then the result is
// an lvalue, but that lvalue is being loaded as part of this expression.
// EDG doesn't mark this as a load.
- result = getStoredValue()
+ result = this.getStoredValue()
else
// This is C++, where the result is an lvalue for the left operand,
// and that lvalue is not being loaded as part of this expression.
- result = getUnloadedLeftOperand().getResult()
+ result = this.getUnloadedLeftOperand().getResult()
}
- final TranslatedExpr getUnloadedLeftOperand() { result = getLoadedLeftOperand().getOperand() }
+ final TranslatedExpr getUnloadedLeftOperand() {
+ result = this.getLoadedLeftOperand().getOperand()
+ }
/**
* Gets the `TranslatedLoad` on the `e` in this `e += ...` which is the
@@ -1454,38 +1472,38 @@ class TranslatedAssignOperation extends TranslatedNonConstantExpr {
kind instanceof GotoEdge and
(
tag = AssignOperationConvertLeftTag() and
- result = getInstruction(AssignOperationOpTag())
+ result = this.getInstruction(AssignOperationOpTag())
or
(
tag = AssignOperationOpTag() and
- if leftOperandNeedsConversion()
- then result = getInstruction(AssignOperationConvertResultTag())
- else result = getInstruction(AssignmentStoreTag())
+ if this.leftOperandNeedsConversion()
+ then result = this.getInstruction(AssignOperationConvertResultTag())
+ else result = this.getInstruction(AssignmentStoreTag())
)
or
tag = AssignOperationConvertResultTag() and
- result = getInstruction(AssignmentStoreTag())
+ result = this.getInstruction(AssignmentStoreTag())
or
tag = AssignmentStoreTag() and
- result = getParent().getChildSuccessor(this)
+ result = this.getParent().getChildSuccessor(this)
)
}
override Instruction getChildSuccessor(TranslatedElement child) {
// Operands are evaluated right-to-left.
- child = getRightOperand() and
- result = getLoadedLeftOperand().getFirstInstruction()
+ child = this.getRightOperand() and
+ result = this.getLoadedLeftOperand().getFirstInstruction()
or
- child = getLoadedLeftOperand() and
- if leftOperandNeedsConversion()
- then result = getInstruction(AssignOperationConvertLeftTag())
- else result = getInstruction(AssignOperationOpTag())
+ child = this.getLoadedLeftOperand() and
+ if this.leftOperandNeedsConversion()
+ then result = this.getInstruction(AssignOperationConvertLeftTag())
+ else result = this.getInstruction(AssignOperationOpTag())
}
private Instruction getStoredValue() {
- if leftOperandNeedsConversion()
- then result = getInstruction(AssignOperationConvertResultTag())
- else result = getInstruction(AssignOperationOpTag())
+ if this.leftOperandNeedsConversion()
+ then result = this.getInstruction(AssignOperationConvertResultTag())
+ else result = this.getInstruction(AssignOperationOpTag())
}
private Type getConvertedLeftOperandType() {
@@ -1502,15 +1520,15 @@ class TranslatedAssignOperation extends TranslatedNonConstantExpr {
// anyway. If we really want to model this case perfectly, we'll need the
// extractor to tell us what the promoted type of the left operand would
// be.
- result = getLoadedLeftOperand().getExpr().getType()
+ result = this.getLoadedLeftOperand().getExpr().getType()
else
// The right operand has already been converted to the type of the op.
- result = getRightOperand().getExpr().getType()
+ result = this.getRightOperand().getExpr().getType()
}
private predicate leftOperandNeedsConversion() {
- getConvertedLeftOperandType().getUnspecifiedType() !=
- getLoadedLeftOperand().getExpr().getUnspecifiedType()
+ this.getConvertedLeftOperandType().getUnspecifiedType() !=
+ this.getLoadedLeftOperand().getExpr().getUnspecifiedType()
}
private Opcode getOpcode() {
@@ -1541,64 +1559,64 @@ class TranslatedAssignOperation extends TranslatedNonConstantExpr {
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
tag = AssignOperationOpTag() and
- opcode = getOpcode() and
- resultType = getTypeForPRValue(getConvertedLeftOperandType())
+ opcode = this.getOpcode() and
+ resultType = getTypeForPRValue(this.getConvertedLeftOperandType())
or
tag = AssignmentStoreTag() and
opcode instanceof Opcode::Store and
resultType = getTypeForPRValue(expr.getType()) // Always a prvalue
or
- leftOperandNeedsConversion() and
+ this.leftOperandNeedsConversion() and
opcode instanceof Opcode::Convert and
(
tag = AssignOperationConvertLeftTag() and
- resultType = getTypeForPRValue(getConvertedLeftOperandType())
+ resultType = getTypeForPRValue(this.getConvertedLeftOperandType())
or
tag = AssignOperationConvertResultTag() and
- resultType = getTypeForPRValue(getLoadedLeftOperand().getExpr().getType())
+ resultType = getTypeForPRValue(this.getLoadedLeftOperand().getExpr().getType())
)
}
override int getInstructionElementSize(InstructionTag tag) {
tag = AssignOperationOpTag() and
exists(Opcode opcode |
- opcode = getOpcode() and
+ opcode = this.getOpcode() and
(opcode instanceof Opcode::PointerAdd or opcode instanceof Opcode::PointerSub)
) and
result = getElementSize(expr.getType())
}
override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
- leftOperandNeedsConversion() and
+ this.leftOperandNeedsConversion() and
tag = AssignOperationConvertLeftTag() and
operandTag instanceof UnaryOperandTag and
- result = getLoadedLeftOperand().getResult()
+ result = this.getLoadedLeftOperand().getResult()
or
tag = AssignOperationOpTag() and
(
(
operandTag instanceof LeftOperandTag and
- if leftOperandNeedsConversion()
- then result = getInstruction(AssignOperationConvertLeftTag())
- else result = getLoadedLeftOperand().getResult()
+ if this.leftOperandNeedsConversion()
+ then result = this.getInstruction(AssignOperationConvertLeftTag())
+ else result = this.getLoadedLeftOperand().getResult()
)
or
operandTag instanceof RightOperandTag and
- result = getRightOperand().getResult()
+ result = this.getRightOperand().getResult()
)
or
- leftOperandNeedsConversion() and
+ this.leftOperandNeedsConversion() and
tag = AssignOperationConvertResultTag() and
operandTag instanceof UnaryOperandTag and
- result = getInstruction(AssignOperationOpTag())
+ result = this.getInstruction(AssignOperationOpTag())
or
tag = AssignmentStoreTag() and
(
operandTag instanceof AddressOperandTag and
- result = getUnloadedLeftOperand().getResult()
+ result = this.getUnloadedLeftOperand().getResult()
or
operandTag instanceof StoreValueOperandTag and
- result = getStoredValue()
+ result = this.getStoredValue()
)
}
}
@@ -1619,7 +1637,7 @@ abstract class TranslatedAllocationSize extends TranslatedExpr, TTranslatedAlloc
final override predicate producesExprResult() { none() }
- final override Instruction getResult() { result = getInstruction(AllocationSizeTag()) }
+ final override Instruction getResult() { result = this.getInstruction(AllocationSizeTag()) }
}
TranslatedAllocationSize getTranslatedAllocationSize(NewOrNewArrayExpr newExpr) {
@@ -1636,7 +1654,9 @@ TranslatedAllocationSize getTranslatedAllocationSize(NewOrNewArrayExpr newExpr)
class TranslatedConstantAllocationSize extends TranslatedAllocationSize {
TranslatedConstantAllocationSize() { not exists(expr.(NewArrayExpr).getExtent()) }
- final override Instruction getFirstInstruction() { result = getInstruction(AllocationSizeTag()) }
+ final override Instruction getFirstInstruction() {
+ result = this.getInstruction(AllocationSizeTag())
+ }
final override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
tag = AllocationSizeTag() and
@@ -1647,7 +1667,7 @@ class TranslatedConstantAllocationSize extends TranslatedAllocationSize {
final override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = AllocationSizeTag() and
kind instanceof GotoEdge and
- result = getParent().getChildSuccessor(this)
+ result = this.getParent().getChildSuccessor(this)
}
final override TranslatedElement getChild(int id) { none() }
@@ -1672,7 +1692,9 @@ class TranslatedNonConstantAllocationSize extends TranslatedAllocationSize {
TranslatedNonConstantAllocationSize() { exists(expr.getExtent()) }
- final override Instruction getFirstInstruction() { result = getExtent().getFirstInstruction() }
+ final override Instruction getFirstInstruction() {
+ result = this.getExtent().getFirstInstruction()
+ }
final override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
resultType = getTypeForPRValue(expr.getAllocator().getParameter(0).getType()) and
@@ -1690,21 +1712,21 @@ class TranslatedNonConstantAllocationSize extends TranslatedAllocationSize {
kind instanceof GotoEdge and
(
tag = AllocationExtentConvertTag() and
- result = getInstruction(AllocationElementSizeTag())
+ result = this.getInstruction(AllocationElementSizeTag())
or
tag = AllocationElementSizeTag() and
- result = getInstruction(AllocationSizeTag())
+ result = this.getInstruction(AllocationSizeTag())
or
tag = AllocationSizeTag() and
- result = getParent().getChildSuccessor(this)
+ result = this.getParent().getChildSuccessor(this)
)
}
- final override TranslatedElement getChild(int id) { id = 0 and result = getExtent() }
+ final override TranslatedElement getChild(int id) { id = 0 and result = this.getExtent() }
final override Instruction getChildSuccessor(TranslatedElement child) {
- child = getExtent() and
- result = getInstruction(AllocationExtentConvertTag())
+ child = this.getExtent() and
+ result = this.getInstruction(AllocationExtentConvertTag())
}
final override string getInstructionConstantValue(InstructionTag tag) {
@@ -1715,14 +1737,16 @@ class TranslatedNonConstantAllocationSize extends TranslatedAllocationSize {
final override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag = AllocationSizeTag() and
(
- operandTag instanceof LeftOperandTag and result = getInstruction(AllocationExtentConvertTag())
+ operandTag instanceof LeftOperandTag and
+ result = this.getInstruction(AllocationExtentConvertTag())
or
- operandTag instanceof RightOperandTag and result = getInstruction(AllocationElementSizeTag())
+ operandTag instanceof RightOperandTag and
+ result = this.getInstruction(AllocationElementSizeTag())
)
or
tag = AllocationExtentConvertTag() and
operandTag instanceof UnaryOperandTag and
- result = getExtent().getResult()
+ result = this.getExtent().getResult()
}
private TranslatedExpr getExtent() {
@@ -1806,7 +1830,7 @@ abstract class StructorCallContext extends TranslatedElement {
class TranslatedDestructorFieldDestruction extends TranslatedNonConstantExpr, StructorCallContext {
override DestructorFieldDestruction expr;
- final override TranslatedElement getChild(int id) { id = 0 and result = getDestructorCall() }
+ final override TranslatedElement getChild(int id) { id = 0 and result = this.getDestructorCall() }
final override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
tag = OnlyInstructionTag() and
@@ -1817,17 +1841,19 @@ class TranslatedDestructorFieldDestruction extends TranslatedNonConstantExpr, St
final override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = OnlyInstructionTag() and
kind instanceof GotoEdge and
- result = getDestructorCall().getFirstInstruction()
+ result = this.getDestructorCall().getFirstInstruction()
}
final override Instruction getChildSuccessor(TranslatedElement child) {
- child = getDestructorCall() and
- result = getParent().getChildSuccessor(this)
+ child = this.getDestructorCall() and
+ result = this.getParent().getChildSuccessor(this)
}
final override Instruction getResult() { none() }
- final override Instruction getFirstInstruction() { result = getInstruction(OnlyInstructionTag()) }
+ final override Instruction getFirstInstruction() {
+ result = this.getInstruction(OnlyInstructionTag())
+ }
final override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag = OnlyInstructionTag() and
@@ -1840,7 +1866,7 @@ class TranslatedDestructorFieldDestruction extends TranslatedNonConstantExpr, St
result = expr.getTarget()
}
- final override Instruction getReceiver() { result = getInstruction(OnlyInstructionTag()) }
+ final override Instruction getReceiver() { result = this.getInstruction(OnlyInstructionTag()) }
private TranslatedExpr getDestructorCall() { result = getTranslatedExpr(expr.getExpr()) }
}
@@ -1859,12 +1885,12 @@ abstract class TranslatedConditionalExpr extends TranslatedNonConstantExpr {
// the condition directly to the appropriate then/else block via
// `getChild[True|False]Successor()`.
// The binary flavor will override this predicate to add the `ConditionalBranch`.
- not resultIsVoid() and
+ not this.resultIsVoid() and
(
(
- not thenIsVoid() and tag = ConditionValueTrueTempAddressTag()
+ not this.thenIsVoid() and tag = ConditionValueTrueTempAddressTag()
or
- not elseIsVoid() and tag = ConditionValueFalseTempAddressTag()
+ not this.elseIsVoid() and tag = ConditionValueFalseTempAddressTag()
or
tag = ConditionValueResultTempAddressTag()
) and
@@ -1876,106 +1902,106 @@ abstract class TranslatedConditionalExpr extends TranslatedNonConstantExpr {
)
or
(
- not thenIsVoid() and tag = ConditionValueTrueStoreTag()
+ not this.thenIsVoid() and tag = ConditionValueTrueStoreTag()
or
- not elseIsVoid() and tag = ConditionValueFalseStoreTag()
+ not this.elseIsVoid() and tag = ConditionValueFalseStoreTag()
) and
opcode instanceof Opcode::Store and
- resultType = getResultType()
+ resultType = this.getResultType()
or
tag = ConditionValueResultLoadTag() and
opcode instanceof Opcode::Load and
- resultType = getResultType()
+ resultType = this.getResultType()
)
}
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
- not resultIsVoid() and
+ not this.resultIsVoid() and
kind instanceof GotoEdge and
(
- not thenIsVoid() and
+ not this.thenIsVoid() and
(
tag = ConditionValueTrueTempAddressTag() and
- result = getInstruction(ConditionValueTrueStoreTag())
+ result = this.getInstruction(ConditionValueTrueStoreTag())
or
tag = ConditionValueTrueStoreTag() and
- result = getInstruction(ConditionValueResultTempAddressTag())
+ result = this.getInstruction(ConditionValueResultTempAddressTag())
)
or
- not elseIsVoid() and
+ not this.elseIsVoid() and
(
tag = ConditionValueFalseTempAddressTag() and
- result = getInstruction(ConditionValueFalseStoreTag())
+ result = this.getInstruction(ConditionValueFalseStoreTag())
or
tag = ConditionValueFalseStoreTag() and
- result = getInstruction(ConditionValueResultTempAddressTag())
+ result = this.getInstruction(ConditionValueResultTempAddressTag())
)
or
tag = ConditionValueResultTempAddressTag() and
- result = getInstruction(ConditionValueResultLoadTag())
+ result = this.getInstruction(ConditionValueResultLoadTag())
or
tag = ConditionValueResultLoadTag() and
- result = getParent().getChildSuccessor(this)
+ result = this.getParent().getChildSuccessor(this)
)
}
override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
- not resultIsVoid() and
+ not this.resultIsVoid() and
(
- not thenIsVoid() and
+ not this.thenIsVoid() and
tag = ConditionValueTrueStoreTag() and
(
operandTag instanceof AddressOperandTag and
- result = getInstruction(ConditionValueTrueTempAddressTag())
+ result = this.getInstruction(ConditionValueTrueTempAddressTag())
or
operandTag instanceof StoreValueOperandTag and
- result = getThen().getResult()
+ result = this.getThen().getResult()
)
or
- not elseIsVoid() and
+ not this.elseIsVoid() and
tag = ConditionValueFalseStoreTag() and
(
operandTag instanceof AddressOperandTag and
- result = getInstruction(ConditionValueFalseTempAddressTag())
+ result = this.getInstruction(ConditionValueFalseTempAddressTag())
or
operandTag instanceof StoreValueOperandTag and
- result = getElse().getResult()
+ result = this.getElse().getResult()
)
or
tag = ConditionValueResultLoadTag() and
(
operandTag instanceof AddressOperandTag and
- result = getInstruction(ConditionValueResultTempAddressTag())
+ result = this.getInstruction(ConditionValueResultTempAddressTag())
)
)
}
final override predicate hasTempVariable(TempVariableTag tag, CppType type) {
- not resultIsVoid() and
+ not this.resultIsVoid() and
tag = ConditionValueTempVar() and
- type = getResultType()
+ type = this.getResultType()
}
final override IRVariable getInstructionVariable(InstructionTag tag) {
- not resultIsVoid() and
+ not this.resultIsVoid() and
(
tag = ConditionValueTrueTempAddressTag() or
tag = ConditionValueFalseTempAddressTag() or
tag = ConditionValueResultTempAddressTag()
) and
- result = getTempVariable(ConditionValueTempVar())
+ result = this.getTempVariable(ConditionValueTempVar())
}
final override Instruction getResult() {
- not resultIsVoid() and
- result = getInstruction(ConditionValueResultLoadTag())
+ not this.resultIsVoid() and
+ result = this.getInstruction(ConditionValueResultLoadTag())
}
override Instruction getChildSuccessor(TranslatedElement child) {
- child = getElse() and
- if elseIsVoid()
- then result = getParent().getChildSuccessor(this)
- else result = getInstruction(ConditionValueFalseTempAddressTag())
+ child = this.getElse() and
+ if this.elseIsVoid()
+ then result = this.getParent().getChildSuccessor(this)
+ else result = this.getInstruction(ConditionValueFalseTempAddressTag())
}
/**
@@ -1990,7 +2016,7 @@ abstract class TranslatedConditionalExpr extends TranslatedNonConstantExpr {
final TranslatedExpr getElse() { result = getTranslatedExpr(expr.getElse().getFullyConverted()) }
final predicate thenIsVoid() {
- getThen().getResultType().getIRType() instanceof IRVoidType
+ this.getThen().getResultType().getIRType() instanceof IRVoidType
or
// A `ThrowExpr.getType()` incorrectly returns the type of exception being
// thrown, rather than `void`. Handle that case here.
@@ -1998,14 +2024,14 @@ abstract class TranslatedConditionalExpr extends TranslatedNonConstantExpr {
}
private predicate elseIsVoid() {
- getElse().getResultType().getIRType() instanceof IRVoidType
+ this.getElse().getResultType().getIRType() instanceof IRVoidType
or
// A `ThrowExpr.getType()` incorrectly returns the type of exception being
// thrown, rather than `void`. Handle that case here.
expr.getElse() instanceof ThrowExpr
}
- private predicate resultIsVoid() { getResultType().getIRType() instanceof IRVoidType }
+ private predicate resultIsVoid() { this.getResultType().getIRType() instanceof IRVoidType }
}
/**
@@ -2017,34 +2043,34 @@ class TranslatedTernaryConditionalExpr extends TranslatedConditionalExpr, Condit
TranslatedTernaryConditionalExpr() { not expr.isTwoOperand() }
final override TranslatedElement getChild(int id) {
- id = 0 and result = getCondition()
+ id = 0 and result = this.getCondition()
or
- id = 1 and result = getThen()
+ id = 1 and result = this.getThen()
or
- id = 2 and result = getElse()
+ id = 2 and result = this.getElse()
}
- override Instruction getFirstInstruction() { result = getCondition().getFirstInstruction() }
+ override Instruction getFirstInstruction() { result = this.getCondition().getFirstInstruction() }
override Instruction getChildSuccessor(TranslatedElement child) {
result = TranslatedConditionalExpr.super.getChildSuccessor(child)
or
(
- child = getThen() and
- if thenIsVoid()
- then result = getParent().getChildSuccessor(this)
- else result = getInstruction(ConditionValueTrueTempAddressTag())
+ child = this.getThen() and
+ if this.thenIsVoid()
+ then result = this.getParent().getChildSuccessor(this)
+ else result = this.getInstruction(ConditionValueTrueTempAddressTag())
)
}
override Instruction getChildTrueSuccessor(TranslatedCondition child) {
- child = getCondition() and
- result = getThen().getFirstInstruction()
+ child = this.getCondition() and
+ result = this.getThen().getFirstInstruction()
}
override Instruction getChildFalseSuccessor(TranslatedCondition child) {
- child = getCondition() and
- result = getElse().getFirstInstruction()
+ child = this.getCondition() and
+ result = this.getElse().getFirstInstruction()
}
private TranslatedCondition getCondition() {
@@ -2069,12 +2095,12 @@ class TranslatedBinaryConditionalExpr extends TranslatedConditionalExpr {
final override TranslatedElement getChild(int id) {
// We only truly have two children, because our "condition" and "then" are the same as far as
// the extractor is concerned.
- id = 0 and result = getCondition()
+ id = 0 and result = this.getCondition()
or
- id = 1 and result = getElse()
+ id = 1 and result = this.getElse()
}
- override Instruction getFirstInstruction() { result = getCondition().getFirstInstruction() }
+ override Instruction getFirstInstruction() { result = this.getCondition().getFirstInstruction() }
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
super.hasInstruction(opcode, tag, resultType)
@@ -2091,10 +2117,10 @@ class TranslatedBinaryConditionalExpr extends TranslatedConditionalExpr {
tag = ValueConditionConditionalBranchTag() and
(
kind instanceof TrueEdge and
- result = getInstruction(ConditionValueTrueTempAddressTag())
+ result = this.getInstruction(ConditionValueTrueTempAddressTag())
or
kind instanceof FalseEdge and
- result = getElse().getFirstInstruction()
+ result = this.getElse().getFirstInstruction()
)
}
@@ -2103,13 +2129,14 @@ class TranslatedBinaryConditionalExpr extends TranslatedConditionalExpr {
or
tag = ValueConditionConditionalBranchTag() and
operandTag instanceof ConditionOperandTag and
- result = getCondition().getResult()
+ result = this.getCondition().getResult()
}
override Instruction getChildSuccessor(TranslatedElement child) {
result = super.getChildSuccessor(child)
or
- child = getCondition() and result = getInstruction(ValueConditionConditionalBranchTag())
+ child = this.getCondition() and
+ result = this.getInstruction(ValueConditionConditionalBranchTag())
}
private TranslatedExpr getCondition() {
@@ -2154,10 +2181,10 @@ class TranslatedTemporaryObjectExpr extends TranslatedNonConstantExpr,
}
final override Instruction getInitializationSuccessor() {
- result = getParent().getChildSuccessor(this)
+ result = this.getParent().getChildSuccessor(this)
}
- final override Instruction getResult() { result = getTargetAddress() }
+ final override Instruction getResult() { result = this.getTargetAddress() }
}
/**
@@ -2168,14 +2195,14 @@ abstract class TranslatedThrowExpr extends TranslatedNonConstantExpr {
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
tag = ThrowTag() and
- opcode = getThrowOpcode() and
+ opcode = this.getThrowOpcode() and
resultType = getVoidType()
}
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = ThrowTag() and
kind instanceof ExceptionEdge and
- result = getParent().getExceptionSuccessorInstruction()
+ result = this.getParent().getExceptionSuccessorInstruction()
}
override Instruction getResult() { none() }
@@ -2202,11 +2229,13 @@ class TranslatedThrowValueExpr extends TranslatedThrowExpr, TranslatedVariableIn
result = TranslatedVariableInitialization.super.getInstructionSuccessor(tag, kind)
}
- final override Instruction getInitializationSuccessor() { result = getInstruction(ThrowTag()) }
+ final override Instruction getInitializationSuccessor() {
+ result = this.getInstruction(ThrowTag())
+ }
final override predicate hasTempVariable(TempVariableTag tag, CppType type) {
tag = ThrowTempVar() and
- type = getTypeForPRValue(getExceptionType())
+ type = getTypeForPRValue(this.getExceptionType())
}
final override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
@@ -2215,7 +2244,7 @@ class TranslatedThrowValueExpr extends TranslatedThrowExpr, TranslatedVariableIn
tag = ThrowTag() and
(
operandTag instanceof AddressOperandTag and
- result = getInstruction(InitializerVariableAddressTag())
+ result = this.getInstruction(InitializerVariableAddressTag())
)
}
@@ -2224,10 +2253,10 @@ class TranslatedThrowValueExpr extends TranslatedThrowExpr, TranslatedVariableIn
) {
tag = ThrowTag() and
operandTag instanceof LoadOperandTag and
- result = getTypeForPRValue(getExceptionType())
+ result = getTypeForPRValue(this.getExceptionType())
}
- override Type getTargetType() { result = getExceptionType() }
+ override Type getTargetType() { result = this.getExceptionType() }
final override TranslatedInitialization getInitialization() {
result = getTranslatedInitialization(expr.getExpr().getFullyConverted())
@@ -2248,7 +2277,7 @@ class TranslatedReThrowExpr extends TranslatedThrowExpr {
override TranslatedElement getChild(int id) { none() }
- override Instruction getFirstInstruction() { result = getInstruction(ThrowTag()) }
+ override Instruction getFirstInstruction() { result = this.getInstruction(ThrowTag()) }
override Instruction getChildSuccessor(TranslatedElement child) { none() }
@@ -2271,12 +2300,12 @@ class TranslatedBuiltInOperation extends TranslatedNonConstantExpr {
not expr instanceof BuiltInVarArgCopy
}
- final override Instruction getResult() { result = getInstruction(OnlyInstructionTag()) }
+ final override Instruction getResult() { result = this.getInstruction(OnlyInstructionTag()) }
final override Instruction getFirstInstruction() {
- if exists(getChild(0))
- then result = getChild(0).getFirstInstruction()
- else result = getInstruction(OnlyInstructionTag())
+ if exists(this.getChild(0))
+ then result = this.getChild(0).getFirstInstruction()
+ else result = this.getInstruction(OnlyInstructionTag())
}
final override TranslatedElement getChild(int id) {
@@ -2286,31 +2315,31 @@ class TranslatedBuiltInOperation extends TranslatedNonConstantExpr {
final override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = OnlyInstructionTag() and
kind instanceof GotoEdge and
- result = getParent().getChildSuccessor(this)
+ result = this.getParent().getChildSuccessor(this)
}
final override Instruction getChildSuccessor(TranslatedElement child) {
exists(int id |
- child = getChild(id) and
+ child = this.getChild(id) and
(
- result = getChild(id + 1).getFirstInstruction()
+ result = this.getChild(id + 1).getFirstInstruction()
or
- not exists(getChild(id + 1)) and result = getInstruction(OnlyInstructionTag())
+ not exists(this.getChild(id + 1)) and result = this.getInstruction(OnlyInstructionTag())
)
)
}
final override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
tag = OnlyInstructionTag() and
- opcode = getOpcode() and
- resultType = getResultType()
+ opcode = this.getOpcode() and
+ resultType = this.getResultType()
}
final override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag = OnlyInstructionTag() and
exists(int index |
operandTag = positionalArgumentOperand(index) and
- result = getChild(index).(TranslatedExpr).getResult()
+ result = this.getChild(index).(TranslatedExpr).getResult()
)
}
@@ -2391,12 +2420,12 @@ class TranslatedVarArgsStart extends TranslatedNonConstantExpr {
}
final override Instruction getFirstInstruction() {
- result = getInstruction(VarArgsStartEllipsisAddressTag())
+ result = this.getInstruction(VarArgsStartEllipsisAddressTag())
}
final override Instruction getResult() { none() }
- final override TranslatedElement getChild(int id) { id = 0 and result = getVAList() }
+ final override TranslatedElement getChild(int id) { id = 0 and result = this.getVAList() }
private TranslatedExpr getVAList() {
result = getTranslatedExpr(expr.getVAList().getFullyConverted())
@@ -2405,37 +2434,37 @@ class TranslatedVarArgsStart extends TranslatedNonConstantExpr {
final override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = VarArgsStartEllipsisAddressTag() and
kind instanceof GotoEdge and
- result = getInstruction(VarArgsStartTag())
+ result = this.getInstruction(VarArgsStartTag())
or
tag = VarArgsStartTag() and
kind instanceof GotoEdge and
- result = getVAList().getFirstInstruction()
+ result = this.getVAList().getFirstInstruction()
or
tag = VarArgsVAListStoreTag() and
kind instanceof GotoEdge and
- result = getParent().getChildSuccessor(this)
+ result = this.getParent().getChildSuccessor(this)
}
final override Instruction getChildSuccessor(TranslatedElement child) {
- child = getVAList() and
- result = getInstruction(VarArgsVAListStoreTag())
+ child = this.getVAList() and
+ result = this.getInstruction(VarArgsVAListStoreTag())
}
final override IRVariable getInstructionVariable(InstructionTag tag) {
tag = VarArgsStartEllipsisAddressTag() and
- result = getEnclosingFunction().getEllipsisVariable()
+ result = this.getEnclosingFunction().getEllipsisVariable()
}
final override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag = VarArgsStartTag() and
operandTag instanceof UnaryOperandTag and
- result = getInstruction(VarArgsStartEllipsisAddressTag())
+ result = this.getInstruction(VarArgsStartEllipsisAddressTag())
or
tag = VarArgsVAListStoreTag() and
(
- operandTag instanceof AddressOperandTag and result = getVAList().getResult()
+ operandTag instanceof AddressOperandTag and result = this.getVAList().getResult()
or
- operandTag instanceof StoreValueOperandTag and result = getInstruction(VarArgsStartTag())
+ operandTag instanceof StoreValueOperandTag and result = this.getInstruction(VarArgsStartTag())
)
}
}
@@ -2453,7 +2482,7 @@ class TranslatedVarArg extends TranslatedNonConstantExpr {
or
tag = VarArgsArgAddressTag() and
opcode instanceof Opcode::VarArg and
- resultType = getResultType()
+ resultType = this.getResultType()
or
tag = VarArgsMoveNextTag() and
opcode instanceof Opcode::NextVarArg and
@@ -2464,11 +2493,13 @@ class TranslatedVarArg extends TranslatedNonConstantExpr {
resultType = getTypeForPRValue(getVAListType(expr.getVAList().getFullyConverted()))
}
- final override Instruction getFirstInstruction() { result = getVAList().getFirstInstruction() }
+ final override Instruction getFirstInstruction() {
+ result = this.getVAList().getFirstInstruction()
+ }
- final override Instruction getResult() { result = getInstruction(VarArgsArgAddressTag()) }
+ final override Instruction getResult() { result = this.getInstruction(VarArgsArgAddressTag()) }
- final override TranslatedElement getChild(int id) { id = 0 and result = getVAList() }
+ final override TranslatedElement getChild(int id) { id = 0 and result = this.getVAList() }
private TranslatedExpr getVAList() {
result = getTranslatedExpr(expr.getVAList().getFullyConverted())
@@ -2477,46 +2508,47 @@ class TranslatedVarArg extends TranslatedNonConstantExpr {
final override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = VarArgsVAListLoadTag() and
kind instanceof GotoEdge and
- result = getInstruction(VarArgsArgAddressTag())
+ result = this.getInstruction(VarArgsArgAddressTag())
or
tag = VarArgsArgAddressTag() and
kind instanceof GotoEdge and
- result = getInstruction(VarArgsMoveNextTag())
+ result = this.getInstruction(VarArgsMoveNextTag())
or
tag = VarArgsMoveNextTag() and
kind instanceof GotoEdge and
- result = getInstruction(VarArgsVAListStoreTag())
+ result = this.getInstruction(VarArgsVAListStoreTag())
or
tag = VarArgsVAListStoreTag() and
kind instanceof GotoEdge and
- result = getParent().getChildSuccessor(this)
+ result = this.getParent().getChildSuccessor(this)
}
final override Instruction getChildSuccessor(TranslatedElement child) {
- child = getVAList() and
- result = getInstruction(VarArgsVAListLoadTag())
+ child = this.getVAList() and
+ result = this.getInstruction(VarArgsVAListLoadTag())
}
final override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag = VarArgsVAListLoadTag() and
(
operandTag instanceof AddressOperandTag and
- result = getVAList().getResult()
+ result = this.getVAList().getResult()
)
or
tag = VarArgsArgAddressTag() and
operandTag instanceof UnaryOperandTag and
- result = getInstruction(VarArgsVAListLoadTag())
+ result = this.getInstruction(VarArgsVAListLoadTag())
or
tag = VarArgsMoveNextTag() and
operandTag instanceof UnaryOperandTag and
- result = getInstruction(VarArgsVAListLoadTag())
+ result = this.getInstruction(VarArgsVAListLoadTag())
or
tag = VarArgsVAListStoreTag() and
(
- operandTag instanceof AddressOperandTag and result = getVAList().getResult()
+ operandTag instanceof AddressOperandTag and result = this.getVAList().getResult()
or
- operandTag instanceof StoreValueOperandTag and result = getInstruction(VarArgsMoveNextTag())
+ operandTag instanceof StoreValueOperandTag and
+ result = this.getInstruction(VarArgsMoveNextTag())
)
}
}
@@ -2533,11 +2565,13 @@ class TranslatedVarArgsEnd extends TranslatedNonConstantExpr {
resultType = getVoidType()
}
- final override Instruction getFirstInstruction() { result = getVAList().getFirstInstruction() }
+ final override Instruction getFirstInstruction() {
+ result = this.getVAList().getFirstInstruction()
+ }
final override Instruction getResult() { none() }
- final override TranslatedElement getChild(int id) { id = 0 and result = getVAList() }
+ final override TranslatedElement getChild(int id) { id = 0 and result = this.getVAList() }
private TranslatedExpr getVAList() {
result = getTranslatedExpr(expr.getVAList().getFullyConverted())
@@ -2546,18 +2580,18 @@ class TranslatedVarArgsEnd extends TranslatedNonConstantExpr {
final override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = OnlyInstructionTag() and
kind instanceof GotoEdge and
- result = getParent().getChildSuccessor(this)
+ result = this.getParent().getChildSuccessor(this)
}
final override Instruction getChildSuccessor(TranslatedElement child) {
- child = getVAList() and
- result = getInstruction(OnlyInstructionTag())
+ child = this.getVAList() and
+ result = this.getInstruction(OnlyInstructionTag())
}
final override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag = OnlyInstructionTag() and
operandTag instanceof UnaryOperandTag and
- result = getVAList().getResult()
+ result = this.getVAList().getResult()
}
}
@@ -2578,15 +2612,15 @@ class TranslatedVarArgCopy extends TranslatedNonConstantExpr {
}
final override Instruction getFirstInstruction() {
- result = getSourceVAList().getFirstInstruction()
+ result = this.getSourceVAList().getFirstInstruction()
}
- final override Instruction getResult() { result = getInstruction(VarArgsVAListStoreTag()) }
+ final override Instruction getResult() { result = this.getInstruction(VarArgsVAListStoreTag()) }
final override TranslatedElement getChild(int id) {
- id = 0 and result = getDestinationVAList()
+ id = 0 and result = this.getDestinationVAList()
or
- id = 1 and result = getSourceVAList()
+ id = 1 and result = this.getSourceVAList()
}
private TranslatedExpr getDestinationVAList() {
@@ -2600,33 +2634,34 @@ class TranslatedVarArgCopy extends TranslatedNonConstantExpr {
final override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = VarArgsVAListLoadTag() and
kind instanceof GotoEdge and
- result = getDestinationVAList().getFirstInstruction()
+ result = this.getDestinationVAList().getFirstInstruction()
or
tag = VarArgsVAListStoreTag() and
kind instanceof GotoEdge and
- result = getParent().getChildSuccessor(this)
+ result = this.getParent().getChildSuccessor(this)
}
final override Instruction getChildSuccessor(TranslatedElement child) {
- child = getSourceVAList() and
- result = getInstruction(VarArgsVAListLoadTag())
+ child = this.getSourceVAList() and
+ result = this.getInstruction(VarArgsVAListLoadTag())
or
- child = getDestinationVAList() and
- result = getInstruction(VarArgsVAListStoreTag())
+ child = this.getDestinationVAList() and
+ result = this.getInstruction(VarArgsVAListStoreTag())
}
final override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag = VarArgsVAListLoadTag() and
(
operandTag instanceof AddressOperandTag and
- result = getSourceVAList().getResult()
+ result = this.getSourceVAList().getResult()
)
or
tag = VarArgsVAListStoreTag() and
(
- operandTag instanceof AddressOperandTag and result = getDestinationVAList().getResult()
+ operandTag instanceof AddressOperandTag and result = this.getDestinationVAList().getResult()
or
- operandTag instanceof StoreValueOperandTag and result = getInstruction(VarArgsVAListLoadTag())
+ operandTag instanceof StoreValueOperandTag and
+ result = this.getInstruction(VarArgsVAListLoadTag())
)
}
}
@@ -2638,44 +2673,46 @@ abstract class TranslatedNewOrNewArrayExpr extends TranslatedNonConstantExpr, In
override NewOrNewArrayExpr expr;
final override TranslatedElement getChild(int id) {
- id = 0 and result = getAllocatorCall()
+ id = 0 and result = this.getAllocatorCall()
or
- id = 1 and result = getInitialization()
+ id = 1 and result = this.getInitialization()
}
final override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
tag = OnlyInstructionTag() and
opcode instanceof Opcode::Convert and
- resultType = getResultType()
+ resultType = this.getResultType()
}
final override Instruction getFirstInstruction() {
- result = getAllocatorCall().getFirstInstruction()
+ result = this.getAllocatorCall().getFirstInstruction()
}
- final override Instruction getResult() { result = getInstruction(OnlyInstructionTag()) }
+ final override Instruction getResult() { result = this.getInstruction(OnlyInstructionTag()) }
final override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
kind instanceof GotoEdge and
tag = OnlyInstructionTag() and
- if exists(getInitialization())
- then result = getInitialization().getFirstInstruction()
- else result = getParent().getChildSuccessor(this)
+ if exists(this.getInitialization())
+ then result = this.getInitialization().getFirstInstruction()
+ else result = this.getParent().getChildSuccessor(this)
}
final override Instruction getChildSuccessor(TranslatedElement child) {
- child = getAllocatorCall() and result = getInstruction(OnlyInstructionTag())
+ child = this.getAllocatorCall() and result = this.getInstruction(OnlyInstructionTag())
or
- child = getInitialization() and result = getParent().getChildSuccessor(this)
+ child = this.getInitialization() and result = this.getParent().getChildSuccessor(this)
}
final override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag = OnlyInstructionTag() and
operandTag instanceof UnaryOperandTag and
- result = getAllocatorCall().getResult()
+ result = this.getAllocatorCall().getResult()
}
- final override Instruction getTargetAddress() { result = getInstruction(OnlyInstructionTag()) }
+ final override Instruction getTargetAddress() {
+ result = this.getInstruction(OnlyInstructionTag())
+ }
private TranslatedAllocatorCall getAllocatorCall() { result = getTranslatedAllocatorCall(expr) }
@@ -2718,18 +2755,20 @@ class TranslatedNewArrayExpr extends TranslatedNewOrNewArrayExpr {
class TranslatedDeleteArrayExprPlaceHolder extends TranslatedSingleInstructionExpr {
override DeleteArrayExpr expr;
- final override Instruction getFirstInstruction() { result = getOperand().getFirstInstruction() }
+ final override Instruction getFirstInstruction() {
+ result = this.getOperand().getFirstInstruction()
+ }
- final override TranslatedElement getChild(int id) { id = 0 and result = getOperand() }
+ final override TranslatedElement getChild(int id) { id = 0 and result = this.getOperand() }
final override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = OnlyInstructionTag() and
- result = getParent().getChildSuccessor(this) and
+ result = this.getParent().getChildSuccessor(this) and
kind instanceof GotoEdge
}
final override Instruction getChildSuccessor(TranslatedElement child) {
- child = getOperand() and result = getInstruction(OnlyInstructionTag())
+ child = this.getOperand() and result = this.getInstruction(OnlyInstructionTag())
}
final override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
@@ -2752,18 +2791,20 @@ class TranslatedDeleteArrayExprPlaceHolder extends TranslatedSingleInstructionEx
class TranslatedDeleteExprPlaceHolder extends TranslatedSingleInstructionExpr {
override DeleteExpr expr;
- final override Instruction getFirstInstruction() { result = getOperand().getFirstInstruction() }
+ final override Instruction getFirstInstruction() {
+ result = this.getOperand().getFirstInstruction()
+ }
- final override TranslatedElement getChild(int id) { id = 0 and result = getOperand() }
+ final override TranslatedElement getChild(int id) { id = 0 and result = this.getOperand() }
final override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = OnlyInstructionTag() and
- result = getParent().getChildSuccessor(this) and
+ result = this.getParent().getChildSuccessor(this) and
kind instanceof GotoEdge
}
final override Instruction getChildSuccessor(TranslatedElement child) {
- child = getOperand() and result = getInstruction(OnlyInstructionTag())
+ child = this.getOperand() and result = this.getInstruction(OnlyInstructionTag())
}
final override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
@@ -2788,23 +2829,23 @@ class TranslatedDeleteExprPlaceHolder extends TranslatedSingleInstructionExpr {
class TranslatedConditionDeclExpr extends TranslatedNonConstantExpr {
override ConditionDeclExpr expr;
- final override Instruction getFirstInstruction() { result = getDecl().getFirstInstruction() }
+ final override Instruction getFirstInstruction() { result = this.getDecl().getFirstInstruction() }
final override TranslatedElement getChild(int id) {
- id = 0 and result = getDecl()
+ id = 0 and result = this.getDecl()
or
- id = 1 and result = getConditionExpr()
+ id = 1 and result = this.getConditionExpr()
}
- override Instruction getResult() { result = getConditionExpr().getResult() }
+ override Instruction getResult() { result = this.getConditionExpr().getResult() }
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) { none() }
override Instruction getChildSuccessor(TranslatedElement child) {
- child = getDecl() and
- result = getConditionExpr().getFirstInstruction()
+ child = this.getDecl() and
+ result = this.getConditionExpr().getFirstInstruction()
or
- child = getConditionExpr() and result = getParent().getChildSuccessor(this)
+ child = this.getConditionExpr() and result = this.getParent().getChildSuccessor(this)
}
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
@@ -2826,34 +2867,34 @@ class TranslatedLambdaExpr extends TranslatedNonConstantExpr, InitializationCont
override LambdaExpression expr;
final override Instruction getFirstInstruction() {
- result = getInstruction(InitializerVariableAddressTag())
+ result = this.getInstruction(InitializerVariableAddressTag())
}
- final override TranslatedElement getChild(int id) { id = 0 and result = getInitialization() }
+ final override TranslatedElement getChild(int id) { id = 0 and result = this.getInitialization() }
- override Instruction getResult() { result = getInstruction(LoadTag()) }
+ override Instruction getResult() { result = this.getInstruction(LoadTag()) }
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = InitializerVariableAddressTag() and
kind instanceof GotoEdge and
- result = getInstruction(InitializerStoreTag())
+ result = this.getInstruction(InitializerStoreTag())
or
tag = InitializerStoreTag() and
kind instanceof GotoEdge and
(
- result = getInitialization().getFirstInstruction()
+ result = this.getInitialization().getFirstInstruction()
or
- not hasInitializer() and result = getInstruction(LoadTag())
+ not this.hasInitializer() and result = this.getInstruction(LoadTag())
)
or
tag = LoadTag() and
kind instanceof GotoEdge and
- result = getParent().getChildSuccessor(this)
+ result = this.getParent().getChildSuccessor(this)
}
override Instruction getChildSuccessor(TranslatedElement child) {
- child = getInitialization() and
- result = getInstruction(LoadTag())
+ child = this.getInitialization() and
+ result = this.getInstruction(LoadTag())
}
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
@@ -2873,12 +2914,12 @@ class TranslatedLambdaExpr extends TranslatedNonConstantExpr, InitializationCont
override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag = InitializerStoreTag() and
operandTag instanceof AddressOperandTag and
- result = getInstruction(InitializerVariableAddressTag())
+ result = this.getInstruction(InitializerVariableAddressTag())
or
tag = LoadTag() and
(
operandTag instanceof AddressOperandTag and
- result = getInstruction(InitializerVariableAddressTag())
+ result = this.getInstruction(InitializerVariableAddressTag())
)
}
@@ -2887,7 +2928,7 @@ class TranslatedLambdaExpr extends TranslatedNonConstantExpr, InitializationCont
tag = InitializerVariableAddressTag() or
tag = InitializerStoreTag()
) and
- result = getTempVariable(LambdaTempVar())
+ result = this.getTempVariable(LambdaTempVar())
}
override predicate hasTempVariable(TempVariableTag tag, CppType type) {
@@ -2896,12 +2937,12 @@ class TranslatedLambdaExpr extends TranslatedNonConstantExpr, InitializationCont
}
final override Instruction getTargetAddress() {
- result = getInstruction(InitializerVariableAddressTag())
+ result = this.getInstruction(InitializerVariableAddressTag())
}
final override Type getTargetType() { result = expr.getType() }
- private predicate hasInitializer() { exists(getInitialization()) }
+ private predicate hasInitializer() { exists(this.getInitialization()) }
private TranslatedInitialization getInitialization() {
result = getTranslatedInitialization(expr.getChild(0).getFullyConverted())
@@ -2915,28 +2956,28 @@ class TranslatedLambdaExpr extends TranslatedNonConstantExpr, InitializationCont
class TranslatedStmtExpr extends TranslatedNonConstantExpr {
override StmtExpr expr;
- final override Instruction getFirstInstruction() { result = getStmt().getFirstInstruction() }
+ final override Instruction getFirstInstruction() { result = this.getStmt().getFirstInstruction() }
- final override TranslatedElement getChild(int id) { id = 0 and result = getStmt() }
+ final override TranslatedElement getChild(int id) { id = 0 and result = this.getStmt() }
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag instanceof OnlyInstructionTag and
kind instanceof GotoEdge and
- result = getParent().getChildSuccessor(this)
+ result = this.getParent().getChildSuccessor(this)
}
override Instruction getChildSuccessor(TranslatedElement child) {
- child = getStmt() and
- result = getInstruction(OnlyInstructionTag())
+ child = this.getStmt() and
+ result = this.getInstruction(OnlyInstructionTag())
}
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
opcode instanceof Opcode::CopyValue and
tag instanceof OnlyInstructionTag and
- resultType = getResultType()
+ resultType = this.getResultType()
}
- override Instruction getResult() { result = getInstruction(OnlyInstructionTag()) }
+ override Instruction getResult() { result = this.getInstruction(OnlyInstructionTag()) }
override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag instanceof OnlyInstructionTag and
@@ -2950,13 +2991,15 @@ class TranslatedStmtExpr extends TranslatedNonConstantExpr {
class TranslatedErrorExpr extends TranslatedSingleInstructionExpr {
override ErrorExpr expr;
- final override Instruction getFirstInstruction() { result = getInstruction(OnlyInstructionTag()) }
+ final override Instruction getFirstInstruction() {
+ result = this.getInstruction(OnlyInstructionTag())
+ }
final override TranslatedElement getChild(int id) { none() }
final override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = OnlyInstructionTag() and
- result = getParent().getChildSuccessor(this) and
+ result = this.getParent().getChildSuccessor(this) and
kind instanceof GotoEdge
}
@@ -3034,13 +3077,15 @@ class TranslatedAssumeExpr extends TranslatedSingleInstructionExpr {
final override Opcode getOpcode() { result instanceof Opcode::NoOp }
- final override Instruction getFirstInstruction() { result = getInstruction(OnlyInstructionTag()) }
+ final override Instruction getFirstInstruction() {
+ result = this.getInstruction(OnlyInstructionTag())
+ }
final override TranslatedElement getChild(int id) { none() }
final override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = OnlyInstructionTag() and
- result = getParent().getChildSuccessor(this) and
+ result = this.getParent().getChildSuccessor(this) and
kind instanceof GotoEdge
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedStmt.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedStmt.qll
index ce08fc9367f..2bc3b5bc3ef 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedStmt.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedStmt.qll
@@ -103,9 +103,7 @@ class TranslatedDeclStmt extends TranslatedStmt {
class TranslatedExprStmt extends TranslatedStmt {
override ExprStmt stmt;
- TranslatedExpr getExpr() {
- result = getTranslatedExpr(stmt.(ExprStmt).getExpr().getFullyConverted())
- }
+ TranslatedExpr getExpr() { result = getTranslatedExpr(stmt.getExpr().getFullyConverted()) }
override TranslatedElement getChild(int id) { id = 0 and result = getExpr() }
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/IRBlock.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/IRBlock.qll
index 4b86f9a7cec..bb8630a5e0c 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/IRBlock.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/IRBlock.qll
@@ -24,7 +24,7 @@ class IRBlockBase extends TIRBlock {
final string toString() { result = getFirstInstruction(this).toString() }
/** Gets the source location of the first non-`Phi` instruction in this block. */
- final Language::Location getLocation() { result = getFirstInstruction().getLocation() }
+ final Language::Location getLocation() { result = this.getFirstInstruction().getLocation() }
/**
* INTERNAL: Do not use.
@@ -39,7 +39,7 @@ class IRBlockBase extends TIRBlock {
) and
this =
rank[result + 1](IRBlock funcBlock, int sortOverride, int sortKey1, int sortKey2 |
- funcBlock.getEnclosingFunction() = getEnclosingFunction() and
+ funcBlock.getEnclosingFunction() = this.getEnclosingFunction() and
funcBlock.getFirstInstruction().hasSortKeys(sortKey1, sortKey2) and
// Ensure that the block containing `EnterFunction` always comes first.
if funcBlock.getFirstInstruction() instanceof EnterFunctionInstruction
@@ -59,15 +59,15 @@ class IRBlockBase extends TIRBlock {
* Get the `Phi` instructions that appear at the start of this block.
*/
final PhiInstruction getAPhiInstruction() {
- Construction::getPhiInstructionBlockStart(result) = getFirstInstruction()
+ Construction::getPhiInstructionBlockStart(result) = this.getFirstInstruction()
}
/**
* Gets an instruction in this block. This includes `Phi` instructions.
*/
final Instruction getAnInstruction() {
- result = getInstruction(_) or
- result = getAPhiInstruction()
+ result = this.getInstruction(_) or
+ result = this.getAPhiInstruction()
}
/**
@@ -78,7 +78,9 @@ class IRBlockBase extends TIRBlock {
/**
* Gets the last instruction in this block.
*/
- final Instruction getLastInstruction() { result = getInstruction(getInstructionCount() - 1) }
+ final Instruction getLastInstruction() {
+ result = this.getInstruction(this.getInstructionCount() - 1)
+ }
/**
* Gets the number of non-`Phi` instructions in this block.
@@ -149,7 +151,7 @@ class IRBlock extends IRBlockBase {
* Block `A` dominates block `B` if any control flow path from the entry block of the function to
* block `B` must pass through block `A`. A block always dominates itself.
*/
- final predicate dominates(IRBlock block) { strictlyDominates(block) or this = block }
+ final predicate dominates(IRBlock block) { this.strictlyDominates(block) or this = block }
/**
* Gets a block on the dominance frontier of this block.
@@ -159,8 +161,8 @@ class IRBlock extends IRBlockBase {
*/
pragma[noinline]
final IRBlock dominanceFrontier() {
- dominates(result.getAPredecessor()) and
- not strictlyDominates(result)
+ this.dominates(result.getAPredecessor()) and
+ not this.strictlyDominates(result)
}
/**
@@ -189,7 +191,7 @@ class IRBlock extends IRBlockBase {
* Block `A` post-dominates block `B` if any control flow path from `B` to the exit block of the
* function must pass through block `A`. A block always post-dominates itself.
*/
- final predicate postDominates(IRBlock block) { strictlyPostDominates(block) or this = block }
+ final predicate postDominates(IRBlock block) { this.strictlyPostDominates(block) or this = block }
/**
* Gets a block on the post-dominance frontier of this block.
@@ -199,16 +201,16 @@ class IRBlock extends IRBlockBase {
*/
pragma[noinline]
final IRBlock postPominanceFrontier() {
- postDominates(result.getASuccessor()) and
- not strictlyPostDominates(result)
+ this.postDominates(result.getASuccessor()) and
+ not this.strictlyPostDominates(result)
}
/**
* Holds if this block is reachable from the entry block of its function.
*/
final predicate isReachableFromFunctionEntry() {
- this = getEnclosingIRFunction().getEntryBlock() or
- getAPredecessor().isReachableFromFunctionEntry()
+ this = this.getEnclosingIRFunction().getEntryBlock() or
+ this.getAPredecessor().isReachableFromFunctionEntry()
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll
index 6f471d8a7e8..1c2cc493338 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll
@@ -41,7 +41,7 @@ class Instruction extends Construction::TStageInstruction {
}
/** Gets a textual representation of this element. */
- final string toString() { result = getOpcode().toString() + ": " + getAST().toString() }
+ final string toString() { result = this.getOpcode().toString() + ": " + this.getAST().toString() }
/**
* Gets a string showing the result, opcode, and operands of the instruction, equivalent to what
@@ -50,7 +50,8 @@ class Instruction extends Construction::TStageInstruction {
* `mu0_28(int) = Store r0_26, r0_27`
*/
final string getDumpString() {
- result = getResultString() + " = " + getOperationString() + " " + getOperandsString()
+ result =
+ this.getResultString() + " = " + this.getOperationString() + " " + this.getOperandsString()
}
private predicate shouldGenerateDumpStrings() {
@@ -66,10 +67,13 @@ class Instruction extends Construction::TStageInstruction {
* VariableAddress[x]
*/
final string getOperationString() {
- shouldGenerateDumpStrings() and
- if exists(getImmediateString())
- then result = getOperationPrefix() + getOpcode().toString() + "[" + getImmediateString() + "]"
- else result = getOperationPrefix() + getOpcode().toString()
+ this.shouldGenerateDumpStrings() and
+ if exists(this.getImmediateString())
+ then
+ result =
+ this.getOperationPrefix() + this.getOpcode().toString() + "[" + this.getImmediateString() +
+ "]"
+ else result = this.getOperationPrefix() + this.getOpcode().toString()
}
/**
@@ -78,17 +82,17 @@ class Instruction extends Construction::TStageInstruction {
string getImmediateString() { none() }
private string getOperationPrefix() {
- shouldGenerateDumpStrings() and
+ this.shouldGenerateDumpStrings() and
if this instanceof SideEffectInstruction then result = "^" else result = ""
}
private string getResultPrefix() {
- shouldGenerateDumpStrings() and
- if getResultIRType() instanceof IRVoidType
+ this.shouldGenerateDumpStrings() and
+ if this.getResultIRType() instanceof IRVoidType
then result = "v"
else
- if hasMemoryResult()
- then if isResultModeled() then result = "m" else result = "mu"
+ if this.hasMemoryResult()
+ then if this.isResultModeled() then result = "m" else result = "mu"
else result = "r"
}
@@ -97,7 +101,7 @@ class Instruction extends Construction::TStageInstruction {
* used by debugging and printing code only.
*/
int getDisplayIndexInBlock() {
- shouldGenerateDumpStrings() and
+ this.shouldGenerateDumpStrings() and
exists(IRBlock block |
this = block.getInstruction(result)
or
@@ -111,12 +115,12 @@ class Instruction extends Construction::TStageInstruction {
}
private int getLineRank() {
- shouldGenerateDumpStrings() and
+ this.shouldGenerateDumpStrings() and
this =
rank[result](Instruction instr |
instr =
- getAnInstructionAtLine(getEnclosingIRFunction(), getLocation().getFile(),
- getLocation().getStartLine())
+ getAnInstructionAtLine(this.getEnclosingIRFunction(), this.getLocation().getFile(),
+ this.getLocation().getStartLine())
|
instr order by instr.getBlock().getDisplayIndex(), instr.getDisplayIndexInBlock()
)
@@ -130,8 +134,9 @@ class Instruction extends Construction::TStageInstruction {
* Example: `r1_1`
*/
string getResultId() {
- shouldGenerateDumpStrings() and
- result = getResultPrefix() + getAST().getLocation().getStartLine() + "_" + getLineRank()
+ this.shouldGenerateDumpStrings() and
+ result =
+ this.getResultPrefix() + this.getAST().getLocation().getStartLine() + "_" + this.getLineRank()
}
/**
@@ -142,8 +147,8 @@ class Instruction extends Construction::TStageInstruction {
* Example: `r1_1(int*)`
*/
final string getResultString() {
- shouldGenerateDumpStrings() and
- result = getResultId() + "(" + getResultLanguageType().getDumpString() + ")"
+ this.shouldGenerateDumpStrings() and
+ result = this.getResultId() + "(" + this.getResultLanguageType().getDumpString() + ")"
}
/**
@@ -153,10 +158,10 @@ class Instruction extends Construction::TStageInstruction {
* Example: `func:r3_4, this:r3_5`
*/
string getOperandsString() {
- shouldGenerateDumpStrings() and
+ this.shouldGenerateDumpStrings() and
result =
concat(Operand operand |
- operand = getAnOperand()
+ operand = this.getAnOperand()
|
operand.getDumpString(), ", " order by operand.getDumpSortOrder()
)
@@ -190,7 +195,7 @@ class Instruction extends Construction::TStageInstruction {
* Gets the function that contains this instruction.
*/
final Language::Function getEnclosingFunction() {
- result = getEnclosingIRFunction().getFunction()
+ result = this.getEnclosingIRFunction().getFunction()
}
/**
@@ -208,7 +213,7 @@ class Instruction extends Construction::TStageInstruction {
/**
* Gets the location of the source code for this instruction.
*/
- final Language::Location getLocation() { result = getAST().getLocation() }
+ final Language::Location getLocation() { result = this.getAST().getLocation() }
/**
* Gets the `Expr` whose result is computed by this instruction, if any. The `Expr` may be a
@@ -243,7 +248,7 @@ class Instruction extends Construction::TStageInstruction {
* a result, its result type will be `IRVoidType`.
*/
cached
- final IRType getResultIRType() { result = getResultLanguageType().getIRType() }
+ final IRType getResultIRType() { result = this.getResultLanguageType().getIRType() }
/**
* Gets the type of the result produced by this instruction. If the
@@ -254,7 +259,7 @@ class Instruction extends Construction::TStageInstruction {
*/
final Language::Type getResultType() {
exists(Language::LanguageType resultType |
- resultType = getResultLanguageType() and
+ resultType = this.getResultLanguageType() and
(
resultType.hasUnspecifiedType(result, _)
or
@@ -283,7 +288,7 @@ class Instruction extends Construction::TStageInstruction {
* result of the `Load` instruction is a prvalue of type `int`, representing
* the integer value loaded from variable `x`.
*/
- final predicate isGLValue() { getResultLanguageType().hasType(_, true) }
+ final predicate isGLValue() { this.getResultLanguageType().hasType(_, true) }
/**
* Gets the size of the result produced by this instruction, in bytes. If the
@@ -292,7 +297,7 @@ class Instruction extends Construction::TStageInstruction {
* If `this.isGLValue()` holds for this instruction, the value of
* `getResultSize()` will always be the size of a pointer.
*/
- final int getResultSize() { result = getResultLanguageType().getByteSize() }
+ final int getResultSize() { result = this.getResultLanguageType().getByteSize() }
/**
* Gets the opcode that specifies the operation performed by this instruction.
@@ -314,14 +319,16 @@ class Instruction extends Construction::TStageInstruction {
/**
* Holds if this instruction produces a memory result.
*/
- final predicate hasMemoryResult() { exists(getResultMemoryAccess()) }
+ final predicate hasMemoryResult() { exists(this.getResultMemoryAccess()) }
/**
* Gets the kind of memory access performed by this instruction's result.
* Holds only for instructions with a memory result.
*/
pragma[inline]
- final MemoryAccessKind getResultMemoryAccess() { result = getOpcode().getWriteMemoryAccess() }
+ final MemoryAccessKind getResultMemoryAccess() {
+ result = this.getOpcode().getWriteMemoryAccess()
+ }
/**
* Holds if the memory access performed by this instruction's result will not always write to
@@ -332,7 +339,7 @@ class Instruction extends Construction::TStageInstruction {
* (for example, the global side effects of a function call).
*/
pragma[inline]
- final predicate hasResultMayMemoryAccess() { getOpcode().hasMayWriteMemoryAccess() }
+ final predicate hasResultMayMemoryAccess() { this.getOpcode().hasMayWriteMemoryAccess() }
/**
* Gets the operand that holds the memory address to which this instruction stores its
@@ -340,7 +347,7 @@ class Instruction extends Construction::TStageInstruction {
* is `r1`.
*/
final AddressOperand getResultAddressOperand() {
- getResultMemoryAccess().usesAddressOperand() and
+ this.getResultMemoryAccess().usesAddressOperand() and
result.getUse() = this
}
@@ -349,7 +356,7 @@ class Instruction extends Construction::TStageInstruction {
* result, if any. For example, in `m3 = Store r1, r2`, the result of `getResultAddressOperand()`
* is the instruction that defines `r1`.
*/
- final Instruction getResultAddress() { result = getResultAddressOperand().getDef() }
+ final Instruction getResultAddress() { result = this.getResultAddressOperand().getDef() }
/**
* Holds if the result of this instruction is precisely modeled in SSA. Always
@@ -368,7 +375,7 @@ class Instruction extends Construction::TStageInstruction {
*/
final predicate isResultModeled() {
// Register results are always in SSA form.
- not hasMemoryResult() or
+ not this.hasMemoryResult() or
Construction::hasModeledMemoryResult(this)
}
@@ -412,7 +419,7 @@ class Instruction extends Construction::TStageInstruction {
/**
* Gets all direct successors of this instruction.
*/
- final Instruction getASuccessor() { result = getSuccessor(_) }
+ final Instruction getASuccessor() { result = this.getSuccessor(_) }
/**
* Gets a predecessor of this instruction such that the predecessor reaches
@@ -423,7 +430,7 @@ class Instruction extends Construction::TStageInstruction {
/**
* Gets all direct predecessors of this instruction.
*/
- final Instruction getAPredecessor() { result = getPredecessor(_) }
+ final Instruction getAPredecessor() { result = this.getPredecessor(_) }
}
/**
@@ -543,7 +550,7 @@ class IndexedInstruction extends Instruction {
* at this instruction. This instruction has no predecessors.
*/
class EnterFunctionInstruction extends Instruction {
- EnterFunctionInstruction() { getOpcode() instanceof Opcode::EnterFunction }
+ EnterFunctionInstruction() { this.getOpcode() instanceof Opcode::EnterFunction }
}
/**
@@ -554,7 +561,7 @@ class EnterFunctionInstruction extends Instruction {
* struct, or union, see `FieldAddressInstruction`.
*/
class VariableAddressInstruction extends VariableInstruction {
- VariableAddressInstruction() { getOpcode() instanceof Opcode::VariableAddress }
+ VariableAddressInstruction() { this.getOpcode() instanceof Opcode::VariableAddress }
}
/**
@@ -566,7 +573,7 @@ class VariableAddressInstruction extends VariableInstruction {
* The result has an `IRFunctionAddress` type.
*/
class FunctionAddressInstruction extends FunctionInstruction {
- FunctionAddressInstruction() { getOpcode() instanceof Opcode::FunctionAddress }
+ FunctionAddressInstruction() { this.getOpcode() instanceof Opcode::FunctionAddress }
}
/**
@@ -577,7 +584,7 @@ class FunctionAddressInstruction extends FunctionInstruction {
* initializes that parameter.
*/
class InitializeParameterInstruction extends VariableInstruction {
- InitializeParameterInstruction() { getOpcode() instanceof Opcode::InitializeParameter }
+ InitializeParameterInstruction() { this.getOpcode() instanceof Opcode::InitializeParameter }
/**
* Gets the parameter initialized by this instruction.
@@ -603,7 +610,7 @@ class InitializeParameterInstruction extends VariableInstruction {
* initialized elsewhere, would not otherwise have a definition in this function.
*/
class InitializeNonLocalInstruction extends Instruction {
- InitializeNonLocalInstruction() { getOpcode() instanceof Opcode::InitializeNonLocal }
+ InitializeNonLocalInstruction() { this.getOpcode() instanceof Opcode::InitializeNonLocal }
}
/**
@@ -611,7 +618,7 @@ class InitializeNonLocalInstruction extends Instruction {
* with the value of that memory on entry to the function.
*/
class InitializeIndirectionInstruction extends VariableInstruction {
- InitializeIndirectionInstruction() { getOpcode() instanceof Opcode::InitializeIndirection }
+ InitializeIndirectionInstruction() { this.getOpcode() instanceof Opcode::InitializeIndirection }
/**
* Gets the parameter initialized by this instruction.
@@ -635,24 +642,24 @@ class InitializeIndirectionInstruction extends VariableInstruction {
* An instruction that initializes the `this` pointer parameter of the enclosing function.
*/
class InitializeThisInstruction extends Instruction {
- InitializeThisInstruction() { getOpcode() instanceof Opcode::InitializeThis }
+ InitializeThisInstruction() { this.getOpcode() instanceof Opcode::InitializeThis }
}
/**
* An instruction that computes the address of a non-static field of an object.
*/
class FieldAddressInstruction extends FieldInstruction {
- FieldAddressInstruction() { getOpcode() instanceof Opcode::FieldAddress }
+ FieldAddressInstruction() { this.getOpcode() instanceof Opcode::FieldAddress }
/**
* Gets the operand that provides the address of the object containing the field.
*/
- final UnaryOperand getObjectAddressOperand() { result = getAnOperand() }
+ final UnaryOperand getObjectAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the object containing the field.
*/
- final Instruction getObjectAddress() { result = getObjectAddressOperand().getDef() }
+ final Instruction getObjectAddress() { result = this.getObjectAddressOperand().getDef() }
}
/**
@@ -661,17 +668,19 @@ class FieldAddressInstruction extends FieldInstruction {
* This instruction is used for element access to C# arrays.
*/
class ElementsAddressInstruction extends UnaryInstruction {
- ElementsAddressInstruction() { getOpcode() instanceof Opcode::ElementsAddress }
+ ElementsAddressInstruction() { this.getOpcode() instanceof Opcode::ElementsAddress }
/**
* Gets the operand that provides the address of the array object.
*/
- final UnaryOperand getArrayObjectAddressOperand() { result = getAnOperand() }
+ final UnaryOperand getArrayObjectAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the array object.
*/
- final Instruction getArrayObjectAddress() { result = getArrayObjectAddressOperand().getDef() }
+ final Instruction getArrayObjectAddress() {
+ result = this.getArrayObjectAddressOperand().getDef()
+ }
}
/**
@@ -685,7 +694,7 @@ class ElementsAddressInstruction extends UnaryInstruction {
* taken may want to ignore any function that contains an `ErrorInstruction`.
*/
class ErrorInstruction extends Instruction {
- ErrorInstruction() { getOpcode() instanceof Opcode::Error }
+ ErrorInstruction() { this.getOpcode() instanceof Opcode::Error }
}
/**
@@ -695,7 +704,7 @@ class ErrorInstruction extends Instruction {
* an initializer, or whose initializer only partially initializes the variable.
*/
class UninitializedInstruction extends VariableInstruction {
- UninitializedInstruction() { getOpcode() instanceof Opcode::Uninitialized }
+ UninitializedInstruction() { this.getOpcode() instanceof Opcode::Uninitialized }
/**
* Gets the variable that is uninitialized.
@@ -710,7 +719,7 @@ class UninitializedInstruction extends VariableInstruction {
* least one instruction, even when the AST has no semantic effect.
*/
class NoOpInstruction extends Instruction {
- NoOpInstruction() { getOpcode() instanceof Opcode::NoOp }
+ NoOpInstruction() { this.getOpcode() instanceof Opcode::NoOp }
}
/**
@@ -732,32 +741,42 @@ class NoOpInstruction extends Instruction {
* `void`-returning function.
*/
class ReturnInstruction extends Instruction {
- ReturnInstruction() { getOpcode() instanceof ReturnOpcode }
+ ReturnInstruction() { this.getOpcode() instanceof ReturnOpcode }
}
/**
* An instruction that returns control to the caller of the function, without returning a value.
*/
class ReturnVoidInstruction extends ReturnInstruction {
- ReturnVoidInstruction() { getOpcode() instanceof Opcode::ReturnVoid }
+ ReturnVoidInstruction() { this.getOpcode() instanceof Opcode::ReturnVoid }
}
/**
* An instruction that returns control to the caller of the function, including a return value.
*/
class ReturnValueInstruction extends ReturnInstruction {
- ReturnValueInstruction() { getOpcode() instanceof Opcode::ReturnValue }
+ ReturnValueInstruction() { this.getOpcode() instanceof Opcode::ReturnValue }
/**
* Gets the operand that provides the value being returned by the function.
*/
- final LoadOperand getReturnValueOperand() { result = getAnOperand() }
+ final LoadOperand getReturnValueOperand() { result = this.getAnOperand() }
+
+ /**
+ * Gets the operand that provides the address of the value being returned by the function.
+ */
+ final AddressOperand getReturnAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the value being returned by the function, if an
* exact definition is available.
*/
- final Instruction getReturnValue() { result = getReturnValueOperand().getDef() }
+ final Instruction getReturnValue() { result = this.getReturnValueOperand().getDef() }
+
+ /**
+ * Gets the instruction whose result provides the address of the value being returned by the function.
+ */
+ final Instruction getReturnAddress() { result = this.getReturnAddressOperand().getDef() }
}
/**
@@ -770,28 +789,28 @@ class ReturnValueInstruction extends ReturnInstruction {
* that the caller initialized the memory pointed to by the parameter before the call.
*/
class ReturnIndirectionInstruction extends VariableInstruction {
- ReturnIndirectionInstruction() { getOpcode() instanceof Opcode::ReturnIndirection }
+ ReturnIndirectionInstruction() { this.getOpcode() instanceof Opcode::ReturnIndirection }
/**
* Gets the operand that provides the value of the pointed-to memory.
*/
- final SideEffectOperand getSideEffectOperand() { result = getAnOperand() }
+ final SideEffectOperand getSideEffectOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the value of the pointed-to memory, if an exact
* definition is available.
*/
- final Instruction getSideEffect() { result = getSideEffectOperand().getDef() }
+ final Instruction getSideEffect() { result = this.getSideEffectOperand().getDef() }
/**
* Gets the operand that provides the address of the pointed-to memory.
*/
- final AddressOperand getSourceAddressOperand() { result = getAnOperand() }
+ final AddressOperand getSourceAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the pointed-to memory.
*/
- final Instruction getSourceAddress() { result = getSourceAddressOperand().getDef() }
+ final Instruction getSourceAddress() { result = this.getSourceAddressOperand().getDef() }
/**
* Gets the parameter for which this instruction reads the final pointed-to value within the
@@ -826,7 +845,7 @@ class ReturnIndirectionInstruction extends VariableInstruction {
* - `StoreInstruction` - Copies a register operand to a memory result.
*/
class CopyInstruction extends Instruction {
- CopyInstruction() { getOpcode() instanceof CopyOpcode }
+ CopyInstruction() { this.getOpcode() instanceof CopyOpcode }
/**
* Gets the operand that provides the input value of the copy.
@@ -837,16 +856,16 @@ class CopyInstruction extends Instruction {
* Gets the instruction whose result provides the input value of the copy, if an exact definition
* is available.
*/
- final Instruction getSourceValue() { result = getSourceValueOperand().getDef() }
+ final Instruction getSourceValue() { result = this.getSourceValueOperand().getDef() }
}
/**
* An instruction that returns a register result containing a copy of its register operand.
*/
class CopyValueInstruction extends CopyInstruction, UnaryInstruction {
- CopyValueInstruction() { getOpcode() instanceof Opcode::CopyValue }
+ CopyValueInstruction() { this.getOpcode() instanceof Opcode::CopyValue }
- final override UnaryOperand getSourceValueOperand() { result = getAnOperand() }
+ final override UnaryOperand getSourceValueOperand() { result = this.getAnOperand() }
}
/**
@@ -863,47 +882,49 @@ private string getAddressOperandDescription(AddressOperand operand) {
* An instruction that returns a register result containing a copy of its memory operand.
*/
class LoadInstruction extends CopyInstruction {
- LoadInstruction() { getOpcode() instanceof Opcode::Load }
+ LoadInstruction() { this.getOpcode() instanceof Opcode::Load }
final override string getImmediateString() {
- result = getAddressOperandDescription(getSourceAddressOperand())
+ result = getAddressOperandDescription(this.getSourceAddressOperand())
}
/**
* Gets the operand that provides the address of the value being loaded.
*/
- final AddressOperand getSourceAddressOperand() { result = getAnOperand() }
+ final AddressOperand getSourceAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the value being loaded.
*/
- final Instruction getSourceAddress() { result = getSourceAddressOperand().getDef() }
+ final Instruction getSourceAddress() { result = this.getSourceAddressOperand().getDef() }
- final override LoadOperand getSourceValueOperand() { result = getAnOperand() }
+ final override LoadOperand getSourceValueOperand() { result = this.getAnOperand() }
}
/**
* An instruction that returns a memory result containing a copy of its register operand.
*/
class StoreInstruction extends CopyInstruction {
- StoreInstruction() { getOpcode() instanceof Opcode::Store }
+ StoreInstruction() { this.getOpcode() instanceof Opcode::Store }
final override string getImmediateString() {
- result = getAddressOperandDescription(getDestinationAddressOperand())
+ result = getAddressOperandDescription(this.getDestinationAddressOperand())
}
/**
* Gets the operand that provides the address of the location to which the value will be stored.
*/
- final AddressOperand getDestinationAddressOperand() { result = getAnOperand() }
+ final AddressOperand getDestinationAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the location to which the value will
* be stored, if an exact definition is available.
*/
- final Instruction getDestinationAddress() { result = getDestinationAddressOperand().getDef() }
+ final Instruction getDestinationAddress() {
+ result = this.getDestinationAddressOperand().getDef()
+ }
- final override StoreValueOperand getSourceValueOperand() { result = getAnOperand() }
+ final override StoreValueOperand getSourceValueOperand() { result = this.getAnOperand() }
}
/**
@@ -911,27 +932,27 @@ class StoreInstruction extends CopyInstruction {
* operand.
*/
class ConditionalBranchInstruction extends Instruction {
- ConditionalBranchInstruction() { getOpcode() instanceof Opcode::ConditionalBranch }
+ ConditionalBranchInstruction() { this.getOpcode() instanceof Opcode::ConditionalBranch }
/**
* Gets the operand that provides the Boolean condition controlling the branch.
*/
- final ConditionOperand getConditionOperand() { result = getAnOperand() }
+ final ConditionOperand getConditionOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the Boolean condition controlling the branch.
*/
- final Instruction getCondition() { result = getConditionOperand().getDef() }
+ final Instruction getCondition() { result = this.getConditionOperand().getDef() }
/**
* Gets the instruction to which control will flow if the condition is true.
*/
- final Instruction getTrueSuccessor() { result = getSuccessor(EdgeKind::trueEdge()) }
+ final Instruction getTrueSuccessor() { result = this.getSuccessor(EdgeKind::trueEdge()) }
/**
* Gets the instruction to which control will flow if the condition is false.
*/
- final Instruction getFalseSuccessor() { result = getSuccessor(EdgeKind::falseEdge()) }
+ final Instruction getFalseSuccessor() { result = this.getSuccessor(EdgeKind::falseEdge()) }
}
/**
@@ -943,14 +964,14 @@ class ConditionalBranchInstruction extends Instruction {
* successors.
*/
class ExitFunctionInstruction extends Instruction {
- ExitFunctionInstruction() { getOpcode() instanceof Opcode::ExitFunction }
+ ExitFunctionInstruction() { this.getOpcode() instanceof Opcode::ExitFunction }
}
/**
* An instruction whose result is a constant value.
*/
class ConstantInstruction extends ConstantValueInstruction {
- ConstantInstruction() { getOpcode() instanceof Opcode::Constant }
+ ConstantInstruction() { this.getOpcode() instanceof Opcode::Constant }
}
/**
@@ -959,7 +980,7 @@ class ConstantInstruction extends ConstantValueInstruction {
class IntegerConstantInstruction extends ConstantInstruction {
IntegerConstantInstruction() {
exists(IRType resultType |
- resultType = getResultIRType() and
+ resultType = this.getResultIRType() and
(resultType instanceof IRIntegerType or resultType instanceof IRBooleanType)
)
}
@@ -969,7 +990,7 @@ class IntegerConstantInstruction extends ConstantInstruction {
* An instruction whose result is a constant value of floating-point type.
*/
class FloatConstantInstruction extends ConstantInstruction {
- FloatConstantInstruction() { getResultIRType() instanceof IRFloatingPointType }
+ FloatConstantInstruction() { this.getResultIRType() instanceof IRFloatingPointType }
}
/**
@@ -978,7 +999,9 @@ class FloatConstantInstruction extends ConstantInstruction {
class StringConstantInstruction extends VariableInstruction {
override IRStringLiteral var;
- final override string getImmediateString() { result = Language::getStringLiteralText(getValue()) }
+ final override string getImmediateString() {
+ result = Language::getStringLiteralText(this.getValue())
+ }
/**
* Gets the string literal whose address is returned by this instruction.
@@ -990,37 +1013,37 @@ class StringConstantInstruction extends VariableInstruction {
* An instruction whose result is computed from two operands.
*/
class BinaryInstruction extends Instruction {
- BinaryInstruction() { getOpcode() instanceof BinaryOpcode }
+ BinaryInstruction() { this.getOpcode() instanceof BinaryOpcode }
/**
* Gets the left operand of this binary instruction.
*/
- final LeftOperand getLeftOperand() { result = getAnOperand() }
+ final LeftOperand getLeftOperand() { result = this.getAnOperand() }
/**
* Gets the right operand of this binary instruction.
*/
- final RightOperand getRightOperand() { result = getAnOperand() }
+ final RightOperand getRightOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the value of the left operand of this binary
* instruction.
*/
- final Instruction getLeft() { result = getLeftOperand().getDef() }
+ final Instruction getLeft() { result = this.getLeftOperand().getDef() }
/**
* Gets the instruction whose result provides the value of the right operand of this binary
* instruction.
*/
- final Instruction getRight() { result = getRightOperand().getDef() }
+ final Instruction getRight() { result = this.getRightOperand().getDef() }
/**
* Holds if this instruction's operands are `op1` and `op2`, in either order.
*/
final predicate hasOperands(Operand op1, Operand op2) {
- op1 = getLeftOperand() and op2 = getRightOperand()
+ op1 = this.getLeftOperand() and op2 = this.getRightOperand()
or
- op1 = getRightOperand() and op2 = getLeftOperand()
+ op1 = this.getRightOperand() and op2 = this.getLeftOperand()
}
}
@@ -1028,7 +1051,7 @@ class BinaryInstruction extends Instruction {
* An instruction that computes the result of an arithmetic operation.
*/
class ArithmeticInstruction extends Instruction {
- ArithmeticInstruction() { getOpcode() instanceof ArithmeticOpcode }
+ ArithmeticInstruction() { this.getOpcode() instanceof ArithmeticOpcode }
}
/**
@@ -1050,7 +1073,7 @@ class UnaryArithmeticInstruction extends ArithmeticInstruction, UnaryInstruction
* performed according to IEEE-754.
*/
class AddInstruction extends BinaryArithmeticInstruction {
- AddInstruction() { getOpcode() instanceof Opcode::Add }
+ AddInstruction() { this.getOpcode() instanceof Opcode::Add }
}
/**
@@ -1061,7 +1084,7 @@ class AddInstruction extends BinaryArithmeticInstruction {
* according to IEEE-754.
*/
class SubInstruction extends BinaryArithmeticInstruction {
- SubInstruction() { getOpcode() instanceof Opcode::Sub }
+ SubInstruction() { this.getOpcode() instanceof Opcode::Sub }
}
/**
@@ -1072,7 +1095,7 @@ class SubInstruction extends BinaryArithmeticInstruction {
* performed according to IEEE-754.
*/
class MulInstruction extends BinaryArithmeticInstruction {
- MulInstruction() { getOpcode() instanceof Opcode::Mul }
+ MulInstruction() { this.getOpcode() instanceof Opcode::Mul }
}
/**
@@ -1083,7 +1106,7 @@ class MulInstruction extends BinaryArithmeticInstruction {
* to IEEE-754.
*/
class DivInstruction extends BinaryArithmeticInstruction {
- DivInstruction() { getOpcode() instanceof Opcode::Div }
+ DivInstruction() { this.getOpcode() instanceof Opcode::Div }
}
/**
@@ -1093,7 +1116,7 @@ class DivInstruction extends BinaryArithmeticInstruction {
* division by zero or integer overflow is undefined.
*/
class RemInstruction extends BinaryArithmeticInstruction {
- RemInstruction() { getOpcode() instanceof Opcode::Rem }
+ RemInstruction() { this.getOpcode() instanceof Opcode::Rem }
}
/**
@@ -1104,14 +1127,14 @@ class RemInstruction extends BinaryArithmeticInstruction {
* is performed according to IEEE-754.
*/
class NegateInstruction extends UnaryArithmeticInstruction {
- NegateInstruction() { getOpcode() instanceof Opcode::Negate }
+ NegateInstruction() { this.getOpcode() instanceof Opcode::Negate }
}
/**
* An instruction that computes the result of a bitwise operation.
*/
class BitwiseInstruction extends Instruction {
- BitwiseInstruction() { getOpcode() instanceof BitwiseOpcode }
+ BitwiseInstruction() { this.getOpcode() instanceof BitwiseOpcode }
}
/**
@@ -1130,7 +1153,7 @@ class UnaryBitwiseInstruction extends BitwiseInstruction, UnaryInstruction { }
* Both operands must have the same integer type, which will also be the result type.
*/
class BitAndInstruction extends BinaryBitwiseInstruction {
- BitAndInstruction() { getOpcode() instanceof Opcode::BitAnd }
+ BitAndInstruction() { this.getOpcode() instanceof Opcode::BitAnd }
}
/**
@@ -1139,7 +1162,7 @@ class BitAndInstruction extends BinaryBitwiseInstruction {
* Both operands must have the same integer type, which will also be the result type.
*/
class BitOrInstruction extends BinaryBitwiseInstruction {
- BitOrInstruction() { getOpcode() instanceof Opcode::BitOr }
+ BitOrInstruction() { this.getOpcode() instanceof Opcode::BitOr }
}
/**
@@ -1148,7 +1171,7 @@ class BitOrInstruction extends BinaryBitwiseInstruction {
* Both operands must have the same integer type, which will also be the result type.
*/
class BitXorInstruction extends BinaryBitwiseInstruction {
- BitXorInstruction() { getOpcode() instanceof Opcode::BitXor }
+ BitXorInstruction() { this.getOpcode() instanceof Opcode::BitXor }
}
/**
@@ -1159,7 +1182,7 @@ class BitXorInstruction extends BinaryBitwiseInstruction {
* rightmost bits are zero-filled.
*/
class ShiftLeftInstruction extends BinaryBitwiseInstruction {
- ShiftLeftInstruction() { getOpcode() instanceof Opcode::ShiftLeft }
+ ShiftLeftInstruction() { this.getOpcode() instanceof Opcode::ShiftLeft }
}
/**
@@ -1172,7 +1195,7 @@ class ShiftLeftInstruction extends BinaryBitwiseInstruction {
* of the left operand.
*/
class ShiftRightInstruction extends BinaryBitwiseInstruction {
- ShiftRightInstruction() { getOpcode() instanceof Opcode::ShiftRight }
+ ShiftRightInstruction() { this.getOpcode() instanceof Opcode::ShiftRight }
}
/**
@@ -1183,7 +1206,7 @@ class PointerArithmeticInstruction extends BinaryInstruction {
int elementSize;
PointerArithmeticInstruction() {
- getOpcode() instanceof PointerArithmeticOpcode and
+ this.getOpcode() instanceof PointerArithmeticOpcode and
elementSize = Raw::getInstructionElementSize(this)
}
@@ -1206,7 +1229,7 @@ class PointerArithmeticInstruction extends BinaryInstruction {
* An instruction that adds or subtracts an integer offset from a pointer.
*/
class PointerOffsetInstruction extends PointerArithmeticInstruction {
- PointerOffsetInstruction() { getOpcode() instanceof PointerOffsetOpcode }
+ PointerOffsetInstruction() { this.getOpcode() instanceof PointerOffsetOpcode }
}
/**
@@ -1217,7 +1240,7 @@ class PointerOffsetInstruction extends PointerArithmeticInstruction {
* overflow is undefined.
*/
class PointerAddInstruction extends PointerOffsetInstruction {
- PointerAddInstruction() { getOpcode() instanceof Opcode::PointerAdd }
+ PointerAddInstruction() { this.getOpcode() instanceof Opcode::PointerAdd }
}
/**
@@ -1228,7 +1251,7 @@ class PointerAddInstruction extends PointerOffsetInstruction {
* pointer underflow is undefined.
*/
class PointerSubInstruction extends PointerOffsetInstruction {
- PointerSubInstruction() { getOpcode() instanceof Opcode::PointerSub }
+ PointerSubInstruction() { this.getOpcode() instanceof Opcode::PointerSub }
}
/**
@@ -1241,31 +1264,31 @@ class PointerSubInstruction extends PointerOffsetInstruction {
* undefined.
*/
class PointerDiffInstruction extends PointerArithmeticInstruction {
- PointerDiffInstruction() { getOpcode() instanceof Opcode::PointerDiff }
+ PointerDiffInstruction() { this.getOpcode() instanceof Opcode::PointerDiff }
}
/**
* An instruction whose result is computed from a single operand.
*/
class UnaryInstruction extends Instruction {
- UnaryInstruction() { getOpcode() instanceof UnaryOpcode }
+ UnaryInstruction() { this.getOpcode() instanceof UnaryOpcode }
/**
* Gets the sole operand of this instruction.
*/
- final UnaryOperand getUnaryOperand() { result = getAnOperand() }
+ final UnaryOperand getUnaryOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the sole operand of this instruction.
*/
- final Instruction getUnary() { result = getUnaryOperand().getDef() }
+ final Instruction getUnary() { result = this.getUnaryOperand().getDef() }
}
/**
* An instruction that converts the value of its operand to a value of a different type.
*/
class ConvertInstruction extends UnaryInstruction {
- ConvertInstruction() { getOpcode() instanceof Opcode::Convert }
+ ConvertInstruction() { this.getOpcode() instanceof Opcode::Convert }
}
/**
@@ -1279,7 +1302,7 @@ class ConvertInstruction extends UnaryInstruction {
* `as` expression.
*/
class CheckedConvertOrNullInstruction extends UnaryInstruction {
- CheckedConvertOrNullInstruction() { getOpcode() instanceof Opcode::CheckedConvertOrNull }
+ CheckedConvertOrNullInstruction() { this.getOpcode() instanceof Opcode::CheckedConvertOrNull }
}
/**
@@ -1293,7 +1316,7 @@ class CheckedConvertOrNullInstruction extends UnaryInstruction {
* expression.
*/
class CheckedConvertOrThrowInstruction extends UnaryInstruction {
- CheckedConvertOrThrowInstruction() { getOpcode() instanceof Opcode::CheckedConvertOrThrow }
+ CheckedConvertOrThrowInstruction() { this.getOpcode() instanceof Opcode::CheckedConvertOrThrow }
}
/**
@@ -1306,7 +1329,7 @@ class CheckedConvertOrThrowInstruction extends UnaryInstruction {
* the most-derived object.
*/
class CompleteObjectAddressInstruction extends UnaryInstruction {
- CompleteObjectAddressInstruction() { getOpcode() instanceof Opcode::CompleteObjectAddress }
+ CompleteObjectAddressInstruction() { this.getOpcode() instanceof Opcode::CompleteObjectAddress }
}
/**
@@ -1351,7 +1374,7 @@ class InheritanceConversionInstruction extends UnaryInstruction {
* An instruction that converts from the address of a derived class to the address of a base class.
*/
class ConvertToBaseInstruction extends InheritanceConversionInstruction {
- ConvertToBaseInstruction() { getOpcode() instanceof ConvertToBaseOpcode }
+ ConvertToBaseInstruction() { this.getOpcode() instanceof ConvertToBaseOpcode }
}
/**
@@ -1361,7 +1384,9 @@ class ConvertToBaseInstruction extends InheritanceConversionInstruction {
* If the operand holds a null address, the result is a null address.
*/
class ConvertToNonVirtualBaseInstruction extends ConvertToBaseInstruction {
- ConvertToNonVirtualBaseInstruction() { getOpcode() instanceof Opcode::ConvertToNonVirtualBase }
+ ConvertToNonVirtualBaseInstruction() {
+ this.getOpcode() instanceof Opcode::ConvertToNonVirtualBase
+ }
}
/**
@@ -1371,7 +1396,7 @@ class ConvertToNonVirtualBaseInstruction extends ConvertToBaseInstruction {
* If the operand holds a null address, the result is a null address.
*/
class ConvertToVirtualBaseInstruction extends ConvertToBaseInstruction {
- ConvertToVirtualBaseInstruction() { getOpcode() instanceof Opcode::ConvertToVirtualBase }
+ ConvertToVirtualBaseInstruction() { this.getOpcode() instanceof Opcode::ConvertToVirtualBase }
}
/**
@@ -1381,7 +1406,7 @@ class ConvertToVirtualBaseInstruction extends ConvertToBaseInstruction {
* If the operand holds a null address, the result is a null address.
*/
class ConvertToDerivedInstruction extends InheritanceConversionInstruction {
- ConvertToDerivedInstruction() { getOpcode() instanceof Opcode::ConvertToDerived }
+ ConvertToDerivedInstruction() { this.getOpcode() instanceof Opcode::ConvertToDerived }
}
/**
@@ -1390,7 +1415,7 @@ class ConvertToDerivedInstruction extends InheritanceConversionInstruction {
* The operand must have an integer type, which will also be the result type.
*/
class BitComplementInstruction extends UnaryBitwiseInstruction {
- BitComplementInstruction() { getOpcode() instanceof Opcode::BitComplement }
+ BitComplementInstruction() { this.getOpcode() instanceof Opcode::BitComplement }
}
/**
@@ -1399,14 +1424,14 @@ class BitComplementInstruction extends UnaryBitwiseInstruction {
* The operand must have a Boolean type, which will also be the result type.
*/
class LogicalNotInstruction extends UnaryInstruction {
- LogicalNotInstruction() { getOpcode() instanceof Opcode::LogicalNot }
+ LogicalNotInstruction() { this.getOpcode() instanceof Opcode::LogicalNot }
}
/**
* An instruction that compares two numeric operands.
*/
class CompareInstruction extends BinaryInstruction {
- CompareInstruction() { getOpcode() instanceof CompareOpcode }
+ CompareInstruction() { this.getOpcode() instanceof CompareOpcode }
}
/**
@@ -1417,7 +1442,7 @@ class CompareInstruction extends BinaryInstruction {
* unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareEQInstruction extends CompareInstruction {
- CompareEQInstruction() { getOpcode() instanceof Opcode::CompareEQ }
+ CompareEQInstruction() { this.getOpcode() instanceof Opcode::CompareEQ }
}
/**
@@ -1428,14 +1453,14 @@ class CompareEQInstruction extends CompareInstruction {
* `left == right`. Floating-point comparison is performed according to IEEE-754.
*/
class CompareNEInstruction extends CompareInstruction {
- CompareNEInstruction() { getOpcode() instanceof Opcode::CompareNE }
+ CompareNEInstruction() { this.getOpcode() instanceof Opcode::CompareNE }
}
/**
* An instruction that does a relative comparison of two values, such as `<` or `>=`.
*/
class RelationalInstruction extends CompareInstruction {
- RelationalInstruction() { getOpcode() instanceof RelationalOpcode }
+ RelationalInstruction() { this.getOpcode() instanceof RelationalOpcode }
/**
* Gets the operand on the "greater" (or "greater-or-equal") side
@@ -1467,11 +1492,11 @@ class RelationalInstruction extends CompareInstruction {
* are unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareLTInstruction extends RelationalInstruction {
- CompareLTInstruction() { getOpcode() instanceof Opcode::CompareLT }
+ CompareLTInstruction() { this.getOpcode() instanceof Opcode::CompareLT }
- override Instruction getLesser() { result = getLeft() }
+ override Instruction getLesser() { result = this.getLeft() }
- override Instruction getGreater() { result = getRight() }
+ override Instruction getGreater() { result = this.getRight() }
override predicate isStrict() { any() }
}
@@ -1484,11 +1509,11 @@ class CompareLTInstruction extends RelationalInstruction {
* are unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareGTInstruction extends RelationalInstruction {
- CompareGTInstruction() { getOpcode() instanceof Opcode::CompareGT }
+ CompareGTInstruction() { this.getOpcode() instanceof Opcode::CompareGT }
- override Instruction getLesser() { result = getRight() }
+ override Instruction getLesser() { result = this.getRight() }
- override Instruction getGreater() { result = getLeft() }
+ override Instruction getGreater() { result = this.getLeft() }
override predicate isStrict() { any() }
}
@@ -1502,11 +1527,11 @@ class CompareGTInstruction extends RelationalInstruction {
* are unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareLEInstruction extends RelationalInstruction {
- CompareLEInstruction() { getOpcode() instanceof Opcode::CompareLE }
+ CompareLEInstruction() { this.getOpcode() instanceof Opcode::CompareLE }
- override Instruction getLesser() { result = getLeft() }
+ override Instruction getLesser() { result = this.getLeft() }
- override Instruction getGreater() { result = getRight() }
+ override Instruction getGreater() { result = this.getRight() }
override predicate isStrict() { none() }
}
@@ -1520,11 +1545,11 @@ class CompareLEInstruction extends RelationalInstruction {
* are unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareGEInstruction extends RelationalInstruction {
- CompareGEInstruction() { getOpcode() instanceof Opcode::CompareGE }
+ CompareGEInstruction() { this.getOpcode() instanceof Opcode::CompareGE }
- override Instruction getLesser() { result = getRight() }
+ override Instruction getLesser() { result = this.getRight() }
- override Instruction getGreater() { result = getLeft() }
+ override Instruction getGreater() { result = this.getLeft() }
override predicate isStrict() { none() }
}
@@ -1543,78 +1568,78 @@ class CompareGEInstruction extends RelationalInstruction {
* of any case edge.
*/
class SwitchInstruction extends Instruction {
- SwitchInstruction() { getOpcode() instanceof Opcode::Switch }
+ SwitchInstruction() { this.getOpcode() instanceof Opcode::Switch }
/** Gets the operand that provides the integer value controlling the switch. */
- final ConditionOperand getExpressionOperand() { result = getAnOperand() }
+ final ConditionOperand getExpressionOperand() { result = this.getAnOperand() }
/** Gets the instruction whose result provides the integer value controlling the switch. */
- final Instruction getExpression() { result = getExpressionOperand().getDef() }
+ final Instruction getExpression() { result = this.getExpressionOperand().getDef() }
/** Gets the successor instructions along the case edges of the switch. */
- final Instruction getACaseSuccessor() { exists(CaseEdge edge | result = getSuccessor(edge)) }
+ final Instruction getACaseSuccessor() { exists(CaseEdge edge | result = this.getSuccessor(edge)) }
/** Gets the successor instruction along the default edge of the switch, if any. */
- final Instruction getDefaultSuccessor() { result = getSuccessor(EdgeKind::defaultEdge()) }
+ final Instruction getDefaultSuccessor() { result = this.getSuccessor(EdgeKind::defaultEdge()) }
}
/**
* An instruction that calls a function.
*/
class CallInstruction extends Instruction {
- CallInstruction() { getOpcode() instanceof Opcode::Call }
+ CallInstruction() { this.getOpcode() instanceof Opcode::Call }
final override string getImmediateString() {
- result = getStaticCallTarget().toString()
+ result = this.getStaticCallTarget().toString()
or
- not exists(getStaticCallTarget()) and result = "?"
+ not exists(this.getStaticCallTarget()) and result = "?"
}
/**
* Gets the operand the specifies the target function of the call.
*/
- final CallTargetOperand getCallTargetOperand() { result = getAnOperand() }
+ final CallTargetOperand getCallTargetOperand() { result = this.getAnOperand() }
/**
* Gets the `Instruction` that computes the target function of the call. This is usually a
* `FunctionAddress` instruction, but can also be an arbitrary instruction that produces a
* function pointer.
*/
- final Instruction getCallTarget() { result = getCallTargetOperand().getDef() }
+ final Instruction getCallTarget() { result = this.getCallTargetOperand().getDef() }
/**
* Gets all of the argument operands of the call, including the `this` pointer, if any.
*/
- final ArgumentOperand getAnArgumentOperand() { result = getAnOperand() }
+ final ArgumentOperand getAnArgumentOperand() { result = this.getAnOperand() }
/**
* Gets the `Function` that the call targets, if this is statically known.
*/
final Language::Function getStaticCallTarget() {
- result = getCallTarget().(FunctionAddressInstruction).getFunctionSymbol()
+ result = this.getCallTarget().(FunctionAddressInstruction).getFunctionSymbol()
}
/**
* Gets all of the arguments of the call, including the `this` pointer, if any.
*/
- final Instruction getAnArgument() { result = getAnArgumentOperand().getDef() }
+ final Instruction getAnArgument() { result = this.getAnArgumentOperand().getDef() }
/**
* Gets the `this` pointer argument operand of the call, if any.
*/
- final ThisArgumentOperand getThisArgumentOperand() { result = getAnOperand() }
+ final ThisArgumentOperand getThisArgumentOperand() { result = this.getAnOperand() }
/**
* Gets the `this` pointer argument of the call, if any.
*/
- final Instruction getThisArgument() { result = getThisArgumentOperand().getDef() }
+ final Instruction getThisArgument() { result = this.getThisArgumentOperand().getDef() }
/**
* Gets the argument operand at the specified index.
*/
pragma[noinline]
final PositionalArgumentOperand getPositionalArgumentOperand(int index) {
- result = getAnOperand() and
+ result = this.getAnOperand() and
result.getIndex() = index
}
@@ -1623,7 +1648,7 @@ class CallInstruction extends Instruction {
*/
pragma[noinline]
final Instruction getPositionalArgument(int index) {
- result = getPositionalArgumentOperand(index).getDef()
+ result = this.getPositionalArgumentOperand(index).getDef()
}
/**
@@ -1631,16 +1656,16 @@ class CallInstruction extends Instruction {
*/
pragma[noinline]
final ArgumentOperand getArgumentOperand(int index) {
- index >= 0 and result = getPositionalArgumentOperand(index)
+ index >= 0 and result = this.getPositionalArgumentOperand(index)
or
- index = -1 and result = getThisArgumentOperand()
+ index = -1 and result = this.getThisArgumentOperand()
}
/**
* Gets the argument at the specified index, or `this` if `index` is `-1`.
*/
pragma[noinline]
- final Instruction getArgument(int index) { result = getArgumentOperand(index).getDef() }
+ final Instruction getArgument(int index) { result = this.getArgumentOperand(index).getDef() }
/**
* Gets the number of arguments of the call, including the `this` pointer, if any.
@@ -1665,7 +1690,7 @@ class CallInstruction extends Instruction {
* An instruction representing a side effect of a function call.
*/
class SideEffectInstruction extends Instruction {
- SideEffectInstruction() { getOpcode() instanceof SideEffectOpcode }
+ SideEffectInstruction() { this.getOpcode() instanceof SideEffectOpcode }
/**
* Gets the instruction whose execution causes this side effect.
@@ -1680,7 +1705,7 @@ class SideEffectInstruction extends Instruction {
* accessed by that call.
*/
class CallSideEffectInstruction extends SideEffectInstruction {
- CallSideEffectInstruction() { getOpcode() instanceof Opcode::CallSideEffect }
+ CallSideEffectInstruction() { this.getOpcode() instanceof Opcode::CallSideEffect }
}
/**
@@ -1691,7 +1716,7 @@ class CallSideEffectInstruction extends SideEffectInstruction {
* call target cannot write to escaped memory.
*/
class CallReadSideEffectInstruction extends SideEffectInstruction {
- CallReadSideEffectInstruction() { getOpcode() instanceof Opcode::CallReadSideEffect }
+ CallReadSideEffectInstruction() { this.getOpcode() instanceof Opcode::CallReadSideEffect }
}
/**
@@ -1699,33 +1724,33 @@ class CallReadSideEffectInstruction extends SideEffectInstruction {
* specific parameter.
*/
class ReadSideEffectInstruction extends SideEffectInstruction, IndexedInstruction {
- ReadSideEffectInstruction() { getOpcode() instanceof ReadSideEffectOpcode }
+ ReadSideEffectInstruction() { this.getOpcode() instanceof ReadSideEffectOpcode }
/** Gets the operand for the value that will be read from this instruction, if known. */
- final SideEffectOperand getSideEffectOperand() { result = getAnOperand() }
+ final SideEffectOperand getSideEffectOperand() { result = this.getAnOperand() }
/** Gets the value that will be read from this instruction, if known. */
- final Instruction getSideEffect() { result = getSideEffectOperand().getDef() }
+ final Instruction getSideEffect() { result = this.getSideEffectOperand().getDef() }
/** Gets the operand for the address from which this instruction may read. */
- final AddressOperand getArgumentOperand() { result = getAnOperand() }
+ final AddressOperand getArgumentOperand() { result = this.getAnOperand() }
/** Gets the address from which this instruction may read. */
- final Instruction getArgumentDef() { result = getArgumentOperand().getDef() }
+ final Instruction getArgumentDef() { result = this.getArgumentOperand().getDef() }
}
/**
* An instruction representing the read of an indirect parameter within a function call.
*/
class IndirectReadSideEffectInstruction extends ReadSideEffectInstruction {
- IndirectReadSideEffectInstruction() { getOpcode() instanceof Opcode::IndirectReadSideEffect }
+ IndirectReadSideEffectInstruction() { this.getOpcode() instanceof Opcode::IndirectReadSideEffect }
}
/**
* An instruction representing the read of an indirect buffer parameter within a function call.
*/
class BufferReadSideEffectInstruction extends ReadSideEffectInstruction {
- BufferReadSideEffectInstruction() { getOpcode() instanceof Opcode::BufferReadSideEffect }
+ BufferReadSideEffectInstruction() { this.getOpcode() instanceof Opcode::BufferReadSideEffect }
}
/**
@@ -1733,18 +1758,18 @@ class BufferReadSideEffectInstruction extends ReadSideEffectInstruction {
*/
class SizedBufferReadSideEffectInstruction extends ReadSideEffectInstruction {
SizedBufferReadSideEffectInstruction() {
- getOpcode() instanceof Opcode::SizedBufferReadSideEffect
+ this.getOpcode() instanceof Opcode::SizedBufferReadSideEffect
}
/**
* Gets the operand that holds the number of bytes read from the buffer.
*/
- final BufferSizeOperand getBufferSizeOperand() { result = getAnOperand() }
+ final BufferSizeOperand getBufferSizeOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the number of bytes read from the buffer.
*/
- final Instruction getBufferSize() { result = getBufferSizeOperand().getDef() }
+ final Instruction getBufferSize() { result = this.getBufferSizeOperand().getDef() }
}
/**
@@ -1752,17 +1777,17 @@ class SizedBufferReadSideEffectInstruction extends ReadSideEffectInstruction {
* specific parameter.
*/
class WriteSideEffectInstruction extends SideEffectInstruction, IndexedInstruction {
- WriteSideEffectInstruction() { getOpcode() instanceof WriteSideEffectOpcode }
+ WriteSideEffectInstruction() { this.getOpcode() instanceof WriteSideEffectOpcode }
/**
* Get the operand that holds the address of the memory to be written.
*/
- final AddressOperand getDestinationAddressOperand() { result = getAnOperand() }
+ final AddressOperand getDestinationAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the memory to be written.
*/
- Instruction getDestinationAddress() { result = getDestinationAddressOperand().getDef() }
+ Instruction getDestinationAddress() { result = this.getDestinationAddressOperand().getDef() }
}
/**
@@ -1770,7 +1795,7 @@ class WriteSideEffectInstruction extends SideEffectInstruction, IndexedInstructi
*/
class IndirectMustWriteSideEffectInstruction extends WriteSideEffectInstruction {
IndirectMustWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::IndirectMustWriteSideEffect
+ this.getOpcode() instanceof Opcode::IndirectMustWriteSideEffect
}
}
@@ -1780,7 +1805,7 @@ class IndirectMustWriteSideEffectInstruction extends WriteSideEffectInstruction
*/
class BufferMustWriteSideEffectInstruction extends WriteSideEffectInstruction {
BufferMustWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::BufferMustWriteSideEffect
+ this.getOpcode() instanceof Opcode::BufferMustWriteSideEffect
}
}
@@ -1790,18 +1815,18 @@ class BufferMustWriteSideEffectInstruction extends WriteSideEffectInstruction {
*/
class SizedBufferMustWriteSideEffectInstruction extends WriteSideEffectInstruction {
SizedBufferMustWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::SizedBufferMustWriteSideEffect
+ this.getOpcode() instanceof Opcode::SizedBufferMustWriteSideEffect
}
/**
* Gets the operand that holds the number of bytes written to the buffer.
*/
- final BufferSizeOperand getBufferSizeOperand() { result = getAnOperand() }
+ final BufferSizeOperand getBufferSizeOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the number of bytes written to the buffer.
*/
- final Instruction getBufferSize() { result = getBufferSizeOperand().getDef() }
+ final Instruction getBufferSize() { result = this.getBufferSizeOperand().getDef() }
}
/**
@@ -1812,7 +1837,7 @@ class SizedBufferMustWriteSideEffectInstruction extends WriteSideEffectInstructi
*/
class IndirectMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
IndirectMayWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::IndirectMayWriteSideEffect
+ this.getOpcode() instanceof Opcode::IndirectMayWriteSideEffect
}
}
@@ -1822,7 +1847,9 @@ class IndirectMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
* Unlike `BufferWriteSideEffectInstruction`, the buffer might not be completely overwritten.
*/
class BufferMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
- BufferMayWriteSideEffectInstruction() { getOpcode() instanceof Opcode::BufferMayWriteSideEffect }
+ BufferMayWriteSideEffectInstruction() {
+ this.getOpcode() instanceof Opcode::BufferMayWriteSideEffect
+ }
}
/**
@@ -1832,18 +1859,18 @@ class BufferMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
*/
class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
SizedBufferMayWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::SizedBufferMayWriteSideEffect
+ this.getOpcode() instanceof Opcode::SizedBufferMayWriteSideEffect
}
/**
* Gets the operand that holds the number of bytes written to the buffer.
*/
- final BufferSizeOperand getBufferSizeOperand() { result = getAnOperand() }
+ final BufferSizeOperand getBufferSizeOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the number of bytes written to the buffer.
*/
- final Instruction getBufferSize() { result = getBufferSizeOperand().getDef() }
+ final Instruction getBufferSize() { result = this.getBufferSizeOperand().getDef() }
}
/**
@@ -1852,80 +1879,80 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio
*/
class InitializeDynamicAllocationInstruction extends SideEffectInstruction {
InitializeDynamicAllocationInstruction() {
- getOpcode() instanceof Opcode::InitializeDynamicAllocation
+ this.getOpcode() instanceof Opcode::InitializeDynamicAllocation
}
/**
* Gets the operand that represents the address of the allocation this instruction is initializing.
*/
- final AddressOperand getAllocationAddressOperand() { result = getAnOperand() }
+ final AddressOperand getAllocationAddressOperand() { result = this.getAnOperand() }
/**
* Gets the address for the allocation this instruction is initializing.
*/
- final Instruction getAllocationAddress() { result = getAllocationAddressOperand().getDef() }
+ final Instruction getAllocationAddress() { result = this.getAllocationAddressOperand().getDef() }
}
/**
* An instruction representing a GNU or MSVC inline assembly statement.
*/
class InlineAsmInstruction extends Instruction {
- InlineAsmInstruction() { getOpcode() instanceof Opcode::InlineAsm }
+ InlineAsmInstruction() { this.getOpcode() instanceof Opcode::InlineAsm }
}
/**
* An instruction that throws an exception.
*/
class ThrowInstruction extends Instruction {
- ThrowInstruction() { getOpcode() instanceof ThrowOpcode }
+ ThrowInstruction() { this.getOpcode() instanceof ThrowOpcode }
}
/**
* An instruction that throws a new exception.
*/
class ThrowValueInstruction extends ThrowInstruction {
- ThrowValueInstruction() { getOpcode() instanceof Opcode::ThrowValue }
+ ThrowValueInstruction() { this.getOpcode() instanceof Opcode::ThrowValue }
/**
* Gets the address operand of the exception thrown by this instruction.
*/
- final AddressOperand getExceptionAddressOperand() { result = getAnOperand() }
+ final AddressOperand getExceptionAddressOperand() { result = this.getAnOperand() }
/**
* Gets the address of the exception thrown by this instruction.
*/
- final Instruction getExceptionAddress() { result = getExceptionAddressOperand().getDef() }
+ final Instruction getExceptionAddress() { result = this.getExceptionAddressOperand().getDef() }
/**
* Gets the operand for the exception thrown by this instruction.
*/
- final LoadOperand getExceptionOperand() { result = getAnOperand() }
+ final LoadOperand getExceptionOperand() { result = this.getAnOperand() }
/**
* Gets the exception thrown by this instruction.
*/
- final Instruction getException() { result = getExceptionOperand().getDef() }
+ final Instruction getException() { result = this.getExceptionOperand().getDef() }
}
/**
* An instruction that re-throws the current exception.
*/
class ReThrowInstruction extends ThrowInstruction {
- ReThrowInstruction() { getOpcode() instanceof Opcode::ReThrow }
+ ReThrowInstruction() { this.getOpcode() instanceof Opcode::ReThrow }
}
/**
* An instruction that exits the current function by propagating an exception.
*/
class UnwindInstruction extends Instruction {
- UnwindInstruction() { getOpcode() instanceof Opcode::Unwind }
+ UnwindInstruction() { this.getOpcode() instanceof Opcode::Unwind }
}
/**
* An instruction that starts a `catch` handler.
*/
class CatchInstruction extends Instruction {
- CatchInstruction() { getOpcode() instanceof CatchOpcode }
+ CatchInstruction() { this.getOpcode() instanceof CatchOpcode }
}
/**
@@ -1935,7 +1962,7 @@ class CatchByTypeInstruction extends CatchInstruction {
Language::LanguageType exceptionType;
CatchByTypeInstruction() {
- getOpcode() instanceof Opcode::CatchByType and
+ this.getOpcode() instanceof Opcode::CatchByType and
exceptionType = Raw::getInstructionExceptionType(this)
}
@@ -1951,21 +1978,21 @@ class CatchByTypeInstruction extends CatchInstruction {
* An instruction that catches any exception.
*/
class CatchAnyInstruction extends CatchInstruction {
- CatchAnyInstruction() { getOpcode() instanceof Opcode::CatchAny }
+ CatchAnyInstruction() { this.getOpcode() instanceof Opcode::CatchAny }
}
/**
* An instruction that initializes all escaped memory.
*/
class AliasedDefinitionInstruction extends Instruction {
- AliasedDefinitionInstruction() { getOpcode() instanceof Opcode::AliasedDefinition }
+ AliasedDefinitionInstruction() { this.getOpcode() instanceof Opcode::AliasedDefinition }
}
/**
* An instruction that consumes all escaped memory on exit from the function.
*/
class AliasedUseInstruction extends Instruction {
- AliasedUseInstruction() { getOpcode() instanceof Opcode::AliasedUse }
+ AliasedUseInstruction() { this.getOpcode() instanceof Opcode::AliasedUse }
}
/**
@@ -1979,7 +2006,7 @@ class AliasedUseInstruction extends Instruction {
* runtime.
*/
class PhiInstruction extends Instruction {
- PhiInstruction() { getOpcode() instanceof Opcode::Phi }
+ PhiInstruction() { this.getOpcode() instanceof Opcode::Phi }
/**
* Gets all of the instruction's `PhiInputOperand`s, representing the values that flow from each predecessor block.
@@ -2047,29 +2074,29 @@ class PhiInstruction extends Instruction {
* https://link.springer.com/content/pdf/10.1007%2F3-540-61053-7_66.pdf.
*/
class ChiInstruction extends Instruction {
- ChiInstruction() { getOpcode() instanceof Opcode::Chi }
+ ChiInstruction() { this.getOpcode() instanceof Opcode::Chi }
/**
* Gets the operand that represents the previous state of all memory that might be aliased by the
* memory write.
*/
- final ChiTotalOperand getTotalOperand() { result = getAnOperand() }
+ final ChiTotalOperand getTotalOperand() { result = this.getAnOperand() }
/**
* Gets the operand that represents the previous state of all memory that might be aliased by the
* memory write.
*/
- final Instruction getTotal() { result = getTotalOperand().getDef() }
+ final Instruction getTotal() { result = this.getTotalOperand().getDef() }
/**
* Gets the operand that represents the new value written by the memory write.
*/
- final ChiPartialOperand getPartialOperand() { result = getAnOperand() }
+ final ChiPartialOperand getPartialOperand() { result = this.getAnOperand() }
/**
* Gets the operand that represents the new value written by the memory write.
*/
- final Instruction getPartial() { result = getPartialOperand().getDef() }
+ final Instruction getPartial() { result = this.getPartialOperand().getDef() }
/**
* Gets the bit range `[startBit, endBit)` updated by the partial operand of this `ChiInstruction`, relative to the start address of the total operand.
@@ -2093,7 +2120,7 @@ class ChiInstruction extends Instruction {
* or `Switch` instruction where that particular edge is infeasible.
*/
class UnreachedInstruction extends Instruction {
- UnreachedInstruction() { getOpcode() instanceof Opcode::Unreached }
+ UnreachedInstruction() { this.getOpcode() instanceof Opcode::Unreached }
}
/**
@@ -2106,7 +2133,7 @@ class BuiltInOperationInstruction extends Instruction {
Language::BuiltInOperation operation;
BuiltInOperationInstruction() {
- getOpcode() instanceof BuiltInOperationOpcode and
+ this.getOpcode() instanceof BuiltInOperationOpcode and
operation = Raw::getInstructionBuiltInOperation(this)
}
@@ -2122,9 +2149,9 @@ class BuiltInOperationInstruction extends Instruction {
* actual operation is specified by the `getBuiltInOperation()` predicate.
*/
class BuiltInInstruction extends BuiltInOperationInstruction {
- BuiltInInstruction() { getOpcode() instanceof Opcode::BuiltIn }
+ BuiltInInstruction() { this.getOpcode() instanceof Opcode::BuiltIn }
- final override string getImmediateString() { result = getBuiltInOperation().toString() }
+ final override string getImmediateString() { result = this.getBuiltInOperation().toString() }
}
/**
@@ -2135,7 +2162,7 @@ class BuiltInInstruction extends BuiltInOperationInstruction {
* to the `...` parameter.
*/
class VarArgsStartInstruction extends UnaryInstruction {
- VarArgsStartInstruction() { getOpcode() instanceof Opcode::VarArgsStart }
+ VarArgsStartInstruction() { this.getOpcode() instanceof Opcode::VarArgsStart }
}
/**
@@ -2145,7 +2172,7 @@ class VarArgsStartInstruction extends UnaryInstruction {
* a result.
*/
class VarArgsEndInstruction extends UnaryInstruction {
- VarArgsEndInstruction() { getOpcode() instanceof Opcode::VarArgsEnd }
+ VarArgsEndInstruction() { this.getOpcode() instanceof Opcode::VarArgsEnd }
}
/**
@@ -2155,7 +2182,7 @@ class VarArgsEndInstruction extends UnaryInstruction {
* argument.
*/
class VarArgInstruction extends UnaryInstruction {
- VarArgInstruction() { getOpcode() instanceof Opcode::VarArg }
+ VarArgInstruction() { this.getOpcode() instanceof Opcode::VarArg }
}
/**
@@ -2166,7 +2193,7 @@ class VarArgInstruction extends UnaryInstruction {
* argument of the `...` parameter.
*/
class NextVarArgInstruction extends UnaryInstruction {
- NextVarArgInstruction() { getOpcode() instanceof Opcode::NextVarArg }
+ NextVarArgInstruction() { this.getOpcode() instanceof Opcode::NextVarArg }
}
/**
@@ -2180,5 +2207,5 @@ class NextVarArgInstruction extends UnaryInstruction {
* The result is the address of the newly allocated object.
*/
class NewObjInstruction extends Instruction {
- NewObjInstruction() { getOpcode() instanceof Opcode::NewObj }
+ NewObjInstruction() { this.getOpcode() instanceof Opcode::NewObj }
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll
index d7cf89ca9aa..85d217bd361 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll
@@ -46,12 +46,12 @@ class Operand extends TStageOperand {
/**
* Gets the location of the source code for this operand.
*/
- final Language::Location getLocation() { result = getUse().getLocation() }
+ final Language::Location getLocation() { result = this.getUse().getLocation() }
/**
* Gets the function that contains this operand.
*/
- final IRFunction getEnclosingIRFunction() { result = getUse().getEnclosingIRFunction() }
+ final IRFunction getEnclosingIRFunction() { result = this.getUse().getEnclosingIRFunction() }
/**
* Gets the `Instruction` that consumes this operand.
@@ -74,7 +74,7 @@ class Operand extends TStageOperand {
*/
final Instruction getDef() {
result = this.getAnyDef() and
- getDefinitionOverlap() instanceof MustExactlyOverlap
+ this.getDefinitionOverlap() instanceof MustExactlyOverlap
}
/**
@@ -82,7 +82,7 @@ class Operand extends TStageOperand {
*
* Gets the `Instruction` that consumes this operand.
*/
- deprecated final Instruction getUseInstruction() { result = getUse() }
+ deprecated final Instruction getUseInstruction() { result = this.getUse() }
/**
* DEPRECATED: use `getAnyDef` or `getDef`. The exact replacement for this
@@ -91,7 +91,7 @@ class Operand extends TStageOperand {
*
* Gets the `Instruction` whose result is the value of the operand.
*/
- deprecated final Instruction getDefinitionInstruction() { result = getAnyDef() }
+ deprecated final Instruction getDefinitionInstruction() { result = this.getAnyDef() }
/**
* Gets the overlap relationship between the operand's definition and its use.
@@ -101,7 +101,9 @@ class Operand extends TStageOperand {
/**
* Holds if the result of the definition instruction does not exactly overlap this use.
*/
- final predicate isDefinitionInexact() { not getDefinitionOverlap() instanceof MustExactlyOverlap }
+ final predicate isDefinitionInexact() {
+ not this.getDefinitionOverlap() instanceof MustExactlyOverlap
+ }
/**
* Gets a prefix to use when dumping the operand in an operand list.
@@ -121,7 +123,7 @@ class Operand extends TStageOperand {
* For example: `this:r3_5`
*/
final string getDumpString() {
- result = getDumpLabel() + getInexactSpecifier() + getDefinitionId()
+ result = this.getDumpLabel() + this.getInexactSpecifier() + this.getDefinitionId()
}
/**
@@ -129,9 +131,9 @@ class Operand extends TStageOperand {
* definition is not modeled in SSA.
*/
private string getDefinitionId() {
- result = getAnyDef().getResultId()
+ result = this.getAnyDef().getResultId()
or
- not exists(getAnyDef()) and result = "m?"
+ not exists(this.getAnyDef()) and result = "m?"
}
/**
@@ -140,7 +142,7 @@ class Operand extends TStageOperand {
* the empty string.
*/
private string getInexactSpecifier() {
- if isDefinitionInexact() then result = "~" else result = ""
+ if this.isDefinitionInexact() then result = "~" else result = ""
}
/**
@@ -155,7 +157,7 @@ class Operand extends TStageOperand {
* the definition type, such as in the case of a partial read or a read from a pointer that
* has been cast to a different type.
*/
- Language::LanguageType getLanguageType() { result = getAnyDef().getResultLanguageType() }
+ Language::LanguageType getLanguageType() { result = this.getAnyDef().getResultLanguageType() }
/**
* Gets the language-neutral type of the value consumed by this operand. This is usually the same
@@ -164,7 +166,7 @@ class Operand extends TStageOperand {
* from the definition type, such as in the case of a partial read or a read from a pointer that
* has been cast to a different type.
*/
- final IRType getIRType() { result = getLanguageType().getIRType() }
+ final IRType getIRType() { result = this.getLanguageType().getIRType() }
/**
* Gets the type of the value consumed by this operand. This is usually the same as the
@@ -173,7 +175,7 @@ class Operand extends TStageOperand {
* the definition type, such as in the case of a partial read or a read from a pointer that
* has been cast to a different type.
*/
- final Language::Type getType() { getLanguageType().hasType(result, _) }
+ final Language::Type getType() { this.getLanguageType().hasType(result, _) }
/**
* Holds if the value consumed by this operand is a glvalue. If this
@@ -182,13 +184,13 @@ class Operand extends TStageOperand {
* not hold, the value of the operand represents a value whose type is
* given by `getType()`.
*/
- final predicate isGLValue() { getLanguageType().hasType(_, true) }
+ final predicate isGLValue() { this.getLanguageType().hasType(_, true) }
/**
* Gets the size of the value consumed by this operand, in bytes. If the operand does not have
* a known constant size, this predicate does not hold.
*/
- final int getSize() { result = getLanguageType().getByteSize() }
+ final int getSize() { result = this.getLanguageType().getByteSize() }
}
/**
@@ -205,7 +207,7 @@ class MemoryOperand extends Operand {
/**
* Gets the kind of memory access performed by the operand.
*/
- MemoryAccessKind getMemoryAccess() { result = getUse().getOpcode().getReadMemoryAccess() }
+ MemoryAccessKind getMemoryAccess() { result = this.getUse().getOpcode().getReadMemoryAccess() }
/**
* Holds if the memory access performed by this operand will not always read from every bit in the
@@ -215,7 +217,7 @@ class MemoryOperand extends Operand {
* conservative estimate of the memory that might actually be accessed at runtime (for example,
* the global side effects of a function call).
*/
- predicate hasMayReadMemoryAccess() { getUse().getOpcode().hasMayReadMemoryAccess() }
+ predicate hasMayReadMemoryAccess() { this.getUse().getOpcode().hasMayReadMemoryAccess() }
/**
* Returns the operand that holds the memory address from which the current operand loads its
@@ -223,8 +225,8 @@ class MemoryOperand extends Operand {
* is `r1`.
*/
final AddressOperand getAddressOperand() {
- getMemoryAccess().usesAddressOperand() and
- result.getUse() = getUse()
+ this.getMemoryAccess().usesAddressOperand() and
+ result.getUse() = this.getUse()
}
}
@@ -294,7 +296,7 @@ class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand, TNonPhiMemoryOpe
result = unique(Instruction defInstr | hasDefinition(defInstr, _))
}
- final override Overlap getDefinitionOverlap() { hasDefinition(_, result) }
+ final override Overlap getDefinitionOverlap() { this.hasDefinition(_, result) }
pragma[noinline]
private predicate hasDefinition(Instruction defInstr, Overlap overlap) {
@@ -449,13 +451,17 @@ class PhiInputOperand extends MemoryOperand, TPhiOperand {
final override Overlap getDefinitionOverlap() { result = overlap }
- final override int getDumpSortOrder() { result = 11 + getPredecessorBlock().getDisplayIndex() }
-
- final override string getDumpLabel() {
- result = "from " + getPredecessorBlock().getDisplayIndex().toString() + ":"
+ final override int getDumpSortOrder() {
+ result = 11 + this.getPredecessorBlock().getDisplayIndex()
}
- final override string getDumpId() { result = getPredecessorBlock().getDisplayIndex().toString() }
+ final override string getDumpLabel() {
+ result = "from " + this.getPredecessorBlock().getDisplayIndex().toString() + ":"
+ }
+
+ final override string getDumpId() {
+ result = this.getPredecessorBlock().getDisplayIndex().toString()
+ }
/**
* Gets the predecessor block from which this value comes.
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/metrics/MetricClass.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/metrics/MetricClass.qll
index 33a256ce3e5..496e3f4511d 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/metrics/MetricClass.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/metrics/MetricClass.qll
@@ -366,7 +366,7 @@ class MetricClass extends Class {
1 +
count(string s |
exists(Operation op | op = this.getAnEnclosedExpression() and s = op.getOperator())
- ) + count(string s | s = getAUsedHalsteadN1Operator())
+ ) + count(string s | s = this.getAUsedHalsteadN1Operator())
}
/**
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/metrics/MetricFile.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/metrics/MetricFile.qll
index f12d1011865..b3838ce4a5a 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/metrics/MetricFile.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/metrics/MetricFile.qll
@@ -134,7 +134,7 @@ class MetricFile extends File {
result =
// avoid 0 values
1 + count(string s | exists(Operation op | op.getFile() = this and s = op.getOperator())) +
- count(string s | s = getAUsedHalsteadN1Operator())
+ count(string s | s = this.getAUsedHalsteadN1Operator())
}
/**
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/metrics/MetricFunction.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/metrics/MetricFunction.qll
index 45036cfddf3..960e06c8375 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/metrics/MetricFunction.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/metrics/MetricFunction.qll
@@ -41,7 +41,7 @@ class MetricFunction extends Function {
* `&&`, and `||`) plus one.
*/
int getCyclomaticComplexity() {
- result = 1 + cyclomaticComplexityBranches(getBlock()) and
+ result = 1 + cyclomaticComplexityBranches(this.getBlock()) and
not this.isMultiplyDefined()
}
@@ -295,7 +295,7 @@ class MetricFunction extends Function {
int getNestingDepth() {
result =
max(Stmt s, int aDepth | s.getEnclosingFunction() = this and nestingDepth(s, aDepth) | aDepth) and
- not isMultiplyDefined()
+ not this.isMultiplyDefined()
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Allocation.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Allocation.qll
index 25dae1c2fd1..1da6e3b0b3b 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Allocation.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Allocation.qll
@@ -15,10 +15,10 @@ private class MallocAllocationFunction extends AllocationFunction {
MallocAllocationFunction() {
// --- C library allocation
- hasGlobalOrStdOrBslName("malloc") and // malloc(size)
+ this.hasGlobalOrStdOrBslName("malloc") and // malloc(size)
sizeArg = 0
or
- hasGlobalName([
+ this.hasGlobalName([
// --- Windows Memory Management for Windows Drivers
"MmAllocateContiguousMemory", // MmAllocateContiguousMemory(size, maxaddress)
"MmAllocateContiguousNodeMemory", // MmAllocateContiguousNodeMemory(size, minaddress, maxaddress, bound, flag, prefer)
@@ -39,7 +39,7 @@ private class MallocAllocationFunction extends AllocationFunction {
]) and
sizeArg = 0
or
- hasGlobalName([
+ this.hasGlobalName([
// --- Windows Memory Management for Windows Drivers
"ExAllocatePool", // ExAllocatePool(type, size)
"ExAllocatePoolWithTag", // ExAllocatePool(type, size, tag)
@@ -56,10 +56,10 @@ private class MallocAllocationFunction extends AllocationFunction {
]) and
sizeArg = 1
or
- hasGlobalName(["HeapAlloc"]) and // HeapAlloc(heap, flags, size)
+ this.hasGlobalName("HeapAlloc") and // HeapAlloc(heap, flags, size)
sizeArg = 2
or
- hasGlobalName([
+ this.hasGlobalName([
// --- Windows Memory Management for Windows Drivers
"MmAllocatePagesForMdl", // MmAllocatePagesForMdl(minaddress, maxaddress, skip, size)
"MmAllocatePagesForMdlEx", // MmAllocatePagesForMdlEx(minaddress, maxaddress, skip, size, type, flags)
@@ -79,7 +79,7 @@ private class AllocaAllocationFunction extends AllocationFunction {
int sizeArg;
AllocaAllocationFunction() {
- hasGlobalName([
+ this.hasGlobalName([
// --- stack allocation
"alloca", // // alloca(size)
"__builtin_alloca", // __builtin_alloca(size)
@@ -104,7 +104,7 @@ private class CallocAllocationFunction extends AllocationFunction {
CallocAllocationFunction() {
// --- C library allocation
- hasGlobalOrStdOrBslName("calloc") and // calloc(num, size)
+ this.hasGlobalOrStdOrBslName("calloc") and // calloc(num, size)
sizeArg = 1 and
multArg = 0
}
@@ -124,11 +124,11 @@ private class ReallocAllocationFunction extends AllocationFunction {
ReallocAllocationFunction() {
// --- C library allocation
- hasGlobalOrStdOrBslName("realloc") and // realloc(ptr, size)
+ this.hasGlobalOrStdOrBslName("realloc") and // realloc(ptr, size)
sizeArg = 1 and
reallocArg = 0
or
- hasGlobalName([
+ this.hasGlobalName([
// --- Windows Global / Local legacy allocation
"LocalReAlloc", // LocalReAlloc(ptr, size, flags)
"GlobalReAlloc", // GlobalReAlloc(ptr, size, flags)
@@ -140,7 +140,7 @@ private class ReallocAllocationFunction extends AllocationFunction {
sizeArg = 1 and
reallocArg = 0
or
- hasGlobalName("HeapReAlloc") and // HeapReAlloc(heap, flags, ptr, size)
+ this.hasGlobalName("HeapReAlloc") and // HeapReAlloc(heap, flags, ptr, size)
sizeArg = 3 and
reallocArg = 2
}
@@ -156,7 +156,7 @@ private class ReallocAllocationFunction extends AllocationFunction {
*/
private class SizelessAllocationFunction extends AllocationFunction {
SizelessAllocationFunction() {
- hasGlobalName([
+ this.hasGlobalName([
// --- Windows Memory Management for Windows Drivers
"ExAllocateFromLookasideListEx", // ExAllocateFromLookasideListEx(list)
"ExAllocateFromPagedLookasideList", // ExAllocateFromPagedLookasideList(list)
@@ -209,18 +209,18 @@ private class CallAllocationExpr extends AllocationExpr, FunctionCall {
AllocationFunction target;
CallAllocationExpr() {
- target = getTarget() and
+ target = this.getTarget() and
// realloc(ptr, 0) only frees the pointer
not (
exists(target.getReallocPtrArg()) and
- getArgument(target.getSizeArg()).getValue().toInt() = 0
+ this.getArgument(target.getSizeArg()).getValue().toInt() = 0
) and
// these are modelled directly (and more accurately), avoid duplication
not exists(NewOrNewArrayExpr new | new.getAllocatorCall() = this)
}
override Expr getSizeExpr() {
- exists(Expr sizeExpr | sizeExpr = getArgument(target.getSizeArg()) |
+ exists(Expr sizeExpr | sizeExpr = this.getArgument(target.getSizeArg()) |
if exists(target.getSizeMult())
then result = sizeExpr
else
@@ -233,16 +233,18 @@ private class CallAllocationExpr extends AllocationExpr, FunctionCall {
override int getSizeMult() {
// malloc with multiplier argument that is a constant
- result = getArgument(target.getSizeMult()).getValue().toInt()
+ result = this.getArgument(target.getSizeMult()).getValue().toInt()
or
// malloc with no multiplier argument
not exists(target.getSizeMult()) and
- deconstructSizeExpr(getArgument(target.getSizeArg()), _, result)
+ deconstructSizeExpr(this.getArgument(target.getSizeArg()), _, result)
}
- override int getSizeBytes() { result = getSizeExpr().getValue().toInt() * getSizeMult() }
+ override int getSizeBytes() {
+ result = this.getSizeExpr().getValue().toInt() * this.getSizeMult()
+ }
- override Expr getReallocPtr() { result = getArgument(target.getReallocPtrArg()) }
+ override Expr getReallocPtr() { result = this.getArgument(target.getReallocPtrArg()) }
override Type getAllocatedElementType() {
result =
@@ -259,11 +261,11 @@ private class CallAllocationExpr extends AllocationExpr, FunctionCall {
private class NewAllocationExpr extends AllocationExpr, NewExpr {
NewAllocationExpr() { this instanceof NewExpr }
- override int getSizeBytes() { result = getAllocatedType().getSize() }
+ override int getSizeBytes() { result = this.getAllocatedType().getSize() }
- override Type getAllocatedElementType() { result = getAllocatedType() }
+ override Type getAllocatedElementType() { result = this.getAllocatedType() }
- override predicate requiresDealloc() { not exists(getPlacementPointer()) }
+ override predicate requiresDealloc() { not exists(this.getPlacementPointer()) }
}
/**
@@ -274,18 +276,18 @@ private class NewArrayAllocationExpr extends AllocationExpr, NewArrayExpr {
override Expr getSizeExpr() {
// new array expr with variable size
- result = getExtent()
+ result = this.getExtent()
}
override int getSizeMult() {
// new array expr with variable size
- exists(getExtent()) and
- result = getAllocatedElementType().getSize()
+ exists(this.getExtent()) and
+ result = this.getAllocatedElementType().getSize()
}
override Type getAllocatedElementType() { result = NewArrayExpr.super.getAllocatedElementType() }
- override int getSizeBytes() { result = getAllocatedType().getSize() }
+ override int getSizeBytes() { result = this.getAllocatedType().getSize() }
- override predicate requiresDealloc() { not exists(getPlacementPointer()) }
+ override predicate requiresDealloc() { not exists(this.getPlacementPointer()) }
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/GetDelim.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/GetDelim.qll
index e2015406346..67950b6e135 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/GetDelim.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/GetDelim.qll
@@ -8,7 +8,7 @@ import semmle.code.cpp.models.interfaces.FlowSource
*/
private class GetDelimFunction extends TaintFunction, AliasFunction, SideEffectFunction,
RemoteFlowSourceFunction {
- GetDelimFunction() { hasGlobalName(["getdelim", "getwdelim", "__getdelim"]) }
+ GetDelimFunction() { this.hasGlobalName(["getdelim", "getwdelim", "__getdelim"]) }
override predicate hasTaintFlow(FunctionInput i, FunctionOutput o) {
i.isParameter(3) and o.isParameterDeref(0)
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Gets.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Gets.qll
index 08222c2cd6a..407c11834e5 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Gets.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Gets.qll
@@ -19,7 +19,7 @@ private class GetsFunction extends DataFlowFunction, TaintFunction, ArrayFunctio
// gets(str)
// fgets(str, num, stream)
// fgetws(wstr, num, stream)
- hasGlobalOrStdOrBslName(["gets", "fgets", "fgetws"])
+ this.hasGlobalOrStdOrBslName(["gets", "fgets", "fgetws"])
}
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
@@ -54,13 +54,13 @@ private class GetsFunction extends DataFlowFunction, TaintFunction, ArrayFunctio
}
override predicate hasArrayWithVariableSize(int bufParam, int countParam) {
- not hasName("gets") and
+ not this.hasName("gets") and
bufParam = 0 and
countParam = 1
}
override predicate hasArrayWithUnknownSize(int bufParam) {
- hasName("gets") and
+ this.hasName("gets") and
bufParam = 0
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Iterator.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Iterator.qll
index 24d5456293f..2fa9803d053 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Iterator.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Iterator.qll
@@ -34,6 +34,16 @@ private class IteratorByTraits extends Iterator {
IteratorByTraits() { exists(IteratorTraits it | it.getIteratorType() = this) }
}
+/**
+ * The C++ standard includes an `std::iterator_traits` specialization for pointer types. When
+ * this specialization is included in the database, a pointer type `T*` will be an instance
+ * of the `IteratorByTraits` class. However, if the `T*` specialization is not in the database,
+ * we need to explicitly include them with this class.
+ */
+private class IteratorByPointer extends Iterator instanceof PointerType {
+ IteratorByPointer() { not this instanceof IteratorByTraits }
+}
+
/**
* A type which has the typedefs expected for an iterator.
*/
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Memcpy.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Memcpy.qll
index b7d8aed60fa..a8d0e94f43c 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Memcpy.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Memcpy.qll
@@ -44,27 +44,27 @@ private class MemcpyFunction extends ArrayFunction, DataFlowFunction, SideEffect
*/
int getParamSize() { if this.hasGlobalName("memccpy") then result = 3 else result = 2 }
- override predicate hasArrayInput(int bufParam) { bufParam = getParamSrc() }
+ override predicate hasArrayInput(int bufParam) { bufParam = this.getParamSrc() }
- override predicate hasArrayOutput(int bufParam) { bufParam = getParamDest() }
+ override predicate hasArrayOutput(int bufParam) { bufParam = this.getParamDest() }
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
- input.isParameterDeref(getParamSrc()) and
- output.isParameterDeref(getParamDest())
+ input.isParameterDeref(this.getParamSrc()) and
+ output.isParameterDeref(this.getParamDest())
or
- input.isParameterDeref(getParamSrc()) and
+ input.isParameterDeref(this.getParamSrc()) and
output.isReturnValueDeref()
or
- input.isParameter(getParamDest()) and
+ input.isParameter(this.getParamDest()) and
output.isReturnValue()
}
override predicate hasArrayWithVariableSize(int bufParam, int countParam) {
(
- bufParam = getParamDest() or
- bufParam = getParamSrc()
+ bufParam = this.getParamDest() or
+ bufParam = this.getParamSrc()
) and
- countParam = getParamSize()
+ countParam = this.getParamSize()
}
override predicate hasOnlySpecificReadSideEffects() { any() }
@@ -72,37 +72,37 @@ private class MemcpyFunction extends ArrayFunction, DataFlowFunction, SideEffect
override predicate hasOnlySpecificWriteSideEffects() { any() }
override predicate hasSpecificWriteSideEffect(ParameterIndex i, boolean buffer, boolean mustWrite) {
- i = getParamDest() and
+ i = this.getParamDest() and
buffer = true and
// memccpy only writes until a given character `c` is found
(if this.hasGlobalName("memccpy") then mustWrite = false else mustWrite = true)
}
override predicate hasSpecificReadSideEffect(ParameterIndex i, boolean buffer) {
- i = getParamSrc() and buffer = true
+ i = this.getParamSrc() and buffer = true
}
override ParameterIndex getParameterSizeIndex(ParameterIndex i) {
- result = getParamSize() and
+ result = this.getParamSize() and
(
- i = getParamDest() or
- i = getParamSrc()
+ i = this.getParamDest() or
+ i = this.getParamSrc()
)
}
override predicate parameterNeverEscapes(int index) {
- index = getParamSrc()
+ index = this.getParamSrc()
or
- this.hasGlobalName("bcopy") and index = getParamDest()
+ this.hasGlobalName("bcopy") and index = this.getParamDest()
}
override predicate parameterEscapesOnlyViaReturn(int index) {
- not this.hasGlobalName("bcopy") and index = getParamDest()
+ not this.hasGlobalName("bcopy") and index = this.getParamDest()
}
override predicate parameterIsAlwaysReturned(int index) {
not this.hasGlobalName(["bcopy", mempcpy(), "memccpy"]) and
- index = getParamDest()
+ index = this.getParamDest()
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Memset.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Memset.qll
index d646be0363d..11ef853a0bc 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Memset.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Memset.qll
@@ -31,17 +31,17 @@ private class MemsetFunction extends ArrayFunction, DataFlowFunction, AliasFunct
override predicate hasArrayWithVariableSize(int bufParam, int countParam) {
bufParam = 0 and
- (if hasGlobalName(bzero()) then countParam = 1 else countParam = 2)
+ (if this.hasGlobalName(bzero()) then countParam = 1 else countParam = 2)
}
- override predicate parameterNeverEscapes(int index) { hasGlobalName(bzero()) and index = 0 }
+ override predicate parameterNeverEscapes(int index) { this.hasGlobalName(bzero()) and index = 0 }
override predicate parameterEscapesOnlyViaReturn(int index) {
- not hasGlobalName(bzero()) and index = 0
+ not this.hasGlobalName(bzero()) and index = 0
}
override predicate parameterIsAlwaysReturned(int index) {
- not hasGlobalName(bzero()) and index = 0
+ not this.hasGlobalName(bzero()) and index = 0
}
override predicate hasOnlySpecificReadSideEffects() { any() }
@@ -54,7 +54,7 @@ private class MemsetFunction extends ArrayFunction, DataFlowFunction, AliasFunct
override ParameterIndex getParameterSizeIndex(ParameterIndex i) {
i = 0 and
- if hasGlobalName(bzero()) then result = 1 else result = 2
+ if this.hasGlobalName(bzero()) then result = 1 else result = 2
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Pure.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Pure.qll
index d728a66463d..4efab29cabf 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Pure.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Pure.qll
@@ -10,49 +10,49 @@ import semmle.code.cpp.models.interfaces.SideEffect
private class PureStrFunction extends AliasFunction, ArrayFunction, TaintFunction,
SideEffectFunction {
PureStrFunction() {
- hasGlobalOrStdOrBslName([
+ this.hasGlobalOrStdOrBslName([
atoi(), "strcasestr", "strchnul", "strchr", "strchrnul", "strstr", "strpbrk", "strrchr",
"strspn", strtol(), strrev(), strcmp(), strlwr(), strupr()
])
}
override predicate hasArrayInput(int bufParam) {
- getParameter(bufParam).getUnspecifiedType() instanceof PointerType
+ this.getParameter(bufParam).getUnspecifiedType() instanceof PointerType
}
override predicate hasArrayWithNullTerminator(int bufParam) {
- getParameter(bufParam).getUnspecifiedType() instanceof PointerType
+ this.getParameter(bufParam).getUnspecifiedType() instanceof PointerType
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
exists(ParameterIndex i |
(
input.isParameter(i) and
- exists(getParameter(i))
+ exists(this.getParameter(i))
or
input.isParameterDeref(i) and
- getParameter(i).getUnspecifiedType() instanceof PointerType
+ this.getParameter(i).getUnspecifiedType() instanceof PointerType
) and
// Functions that end with _l also take a locale argument (always as the last argument),
// and we don't want taint from those arguments.
- (not this.getName().matches("%\\_l") or exists(getParameter(i + 1)))
+ (not this.getName().matches("%\\_l") or exists(this.getParameter(i + 1)))
) and
(
output.isReturnValueDeref() and
- getUnspecifiedType() instanceof PointerType
+ this.getUnspecifiedType() instanceof PointerType
or
output.isReturnValue()
)
}
override predicate parameterNeverEscapes(int i) {
- getParameter(i).getUnspecifiedType() instanceof PointerType and
- not parameterEscapesOnlyViaReturn(i)
+ this.getParameter(i).getUnspecifiedType() instanceof PointerType and
+ not this.parameterEscapesOnlyViaReturn(i)
}
override predicate parameterEscapesOnlyViaReturn(int i) {
i = 0 and
- getUnspecifiedType() instanceof PointerType
+ this.getUnspecifiedType() instanceof PointerType
}
override predicate parameterIsAlwaysReturned(int i) { none() }
@@ -62,7 +62,7 @@ private class PureStrFunction extends AliasFunction, ArrayFunction, TaintFunctio
override predicate hasOnlySpecificWriteSideEffects() { any() }
override predicate hasSpecificReadSideEffect(ParameterIndex i, boolean buffer) {
- getParameter(i).getUnspecifiedType() instanceof PointerType and
+ this.getParameter(i).getUnspecifiedType() instanceof PointerType and
buffer = true
}
}
@@ -97,21 +97,21 @@ private string strcmp() {
*/
private class StrLenFunction extends AliasFunction, ArrayFunction, SideEffectFunction {
StrLenFunction() {
- hasGlobalOrStdOrBslName(["strlen", "strnlen", "wcslen"])
+ this.hasGlobalOrStdOrBslName(["strlen", "strnlen", "wcslen"])
or
- hasGlobalName(["_mbslen", "_mbslen_l", "_mbstrlen", "_mbstrlen_l"])
+ this.hasGlobalName(["_mbslen", "_mbslen_l", "_mbstrlen", "_mbstrlen_l"])
}
override predicate hasArrayInput(int bufParam) {
- getParameter(bufParam).getUnspecifiedType() instanceof PointerType
+ this.getParameter(bufParam).getUnspecifiedType() instanceof PointerType
}
override predicate hasArrayWithNullTerminator(int bufParam) {
- getParameter(bufParam).getUnspecifiedType() instanceof PointerType
+ this.getParameter(bufParam).getUnspecifiedType() instanceof PointerType
}
override predicate parameterNeverEscapes(int i) {
- getParameter(i).getUnspecifiedType() instanceof PointerType
+ this.getParameter(i).getUnspecifiedType() instanceof PointerType
}
override predicate parameterEscapesOnlyViaReturn(int i) { none() }
@@ -123,7 +123,7 @@ private class StrLenFunction extends AliasFunction, ArrayFunction, SideEffectFun
override predicate hasOnlySpecificWriteSideEffects() { any() }
override predicate hasSpecificReadSideEffect(ParameterIndex i, boolean buffer) {
- getParameter(i).getUnspecifiedType() instanceof PointerType and
+ this.getParameter(i).getUnspecifiedType() instanceof PointerType and
buffer = true
}
}
@@ -133,12 +133,12 @@ private class StrLenFunction extends AliasFunction, ArrayFunction, SideEffectFun
* side-effect free. Excludes functions modeled by `PureStrFunction` and `PureMemFunction`.
*/
private class PureFunction extends TaintFunction, SideEffectFunction {
- PureFunction() { hasGlobalOrStdOrBslName(["abs", "labs"]) }
+ PureFunction() { this.hasGlobalOrStdOrBslName(["abs", "labs"]) }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
exists(ParameterIndex i |
input.isParameter(i) and
- exists(getParameter(i))
+ exists(this.getParameter(i))
) and
output.isReturnValue()
}
@@ -155,44 +155,44 @@ private class PureFunction extends TaintFunction, SideEffectFunction {
private class PureMemFunction extends AliasFunction, ArrayFunction, TaintFunction,
SideEffectFunction {
PureMemFunction() {
- hasGlobalOrStdOrBslName([
+ this.hasGlobalOrStdOrBslName([
"memchr", "__builtin_memchr", "memrchr", "rawmemchr", "memcmp", "__builtin_memcmp", "memmem"
]) or
this.hasGlobalName("memfrob")
}
override predicate hasArrayInput(int bufParam) {
- getParameter(bufParam).getUnspecifiedType() instanceof PointerType
+ this.getParameter(bufParam).getUnspecifiedType() instanceof PointerType
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
exists(ParameterIndex i |
(
input.isParameter(i) and
- exists(getParameter(i))
+ exists(this.getParameter(i))
or
input.isParameterDeref(i) and
- getParameter(i).getUnspecifiedType() instanceof PointerType
+ this.getParameter(i).getUnspecifiedType() instanceof PointerType
) and
// `memfrob` should not have taint from the size argument.
(not this.hasGlobalName("memfrob") or i = 0)
) and
(
output.isReturnValueDeref() and
- getUnspecifiedType() instanceof PointerType
+ this.getUnspecifiedType() instanceof PointerType
or
output.isReturnValue()
)
}
override predicate parameterNeverEscapes(int i) {
- getParameter(i).getUnspecifiedType() instanceof PointerType and
- not parameterEscapesOnlyViaReturn(i)
+ this.getParameter(i).getUnspecifiedType() instanceof PointerType and
+ not this.parameterEscapesOnlyViaReturn(i)
}
override predicate parameterEscapesOnlyViaReturn(int i) {
i = 0 and
- getUnspecifiedType() instanceof PointerType
+ this.getUnspecifiedType() instanceof PointerType
}
override predicate parameterIsAlwaysReturned(int i) { none() }
@@ -202,7 +202,7 @@ private class PureMemFunction extends AliasFunction, ArrayFunction, TaintFunctio
override predicate hasOnlySpecificWriteSideEffects() { any() }
override predicate hasSpecificReadSideEffect(ParameterIndex i, boolean buffer) {
- getParameter(i).getUnspecifiedType() instanceof PointerType and
+ this.getParameter(i).getUnspecifiedType() instanceof PointerType and
buffer = true
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/SmartPointer.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/SmartPointer.qll
index e249a164061..389ce6c5ab0 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/SmartPointer.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/SmartPointer.qll
@@ -72,9 +72,9 @@ private class MakeUniqueOrShared extends TaintFunction {
// since these just take a size argument, which we don't want to propagate taint through.
not this.isArray() and
(
- input.isParameter([0 .. getNumberOfParameters() - 1])
+ input.isParameter([0 .. this.getNumberOfParameters() - 1])
or
- input.isParameterDeref([0 .. getNumberOfParameters() - 1])
+ input.isParameterDeref([0 .. this.getNumberOfParameters() - 1])
) and
output.isReturnValue()
}
@@ -116,14 +116,14 @@ private class SmartPtrSetterFunction extends MemberFunction, AliasFunction, Side
or
// When taking ownership of a smart pointer via an rvalue reference, always overwrite the input
// smart pointer.
- getPointerInput().isParameterDeref(i) and
+ this.getPointerInput().isParameterDeref(i) and
this.getParameter(i).getUnspecifiedType() instanceof RValueReferenceType and
buffer = false and
mustWrite = true
}
override predicate hasSpecificReadSideEffect(ParameterIndex i, boolean buffer) {
- getPointerInput().isParameterDeref(i) and
+ this.getPointerInput().isParameterDeref(i) and
buffer = false
or
not this instanceof Constructor and
@@ -136,7 +136,7 @@ private class SmartPtrSetterFunction extends MemberFunction, AliasFunction, Side
override predicate parameterEscapesOnlyViaReturn(int index) { none() }
override predicate hasAddressFlow(FunctionInput input, FunctionOutput output) {
- input = getPointerInput() and
+ input = this.getPointerInput() and
output.isQualifierObject()
or
// Assignment operator always returns a reference to `*this`.
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Sscanf.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Sscanf.qll
index b6120abf05a..42166ae9baa 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Sscanf.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Sscanf.qll
@@ -23,15 +23,15 @@ private class SscanfModel extends ArrayFunction, TaintFunction, AliasFunction, S
bufParam = this.(ScanfFunction).getInputParameterIndex()
}
- override predicate hasArrayInput(int bufParam) { hasArrayWithNullTerminator(bufParam) }
+ override predicate hasArrayInput(int bufParam) { this.hasArrayWithNullTerminator(bufParam) }
private int getLengthParameterIndex() { result = this.(Snscanf).getInputLengthParameterIndex() }
private int getLocaleParameterIndex() {
this.getName().matches("%\\_l") and
(
- if exists(getLengthParameterIndex())
- then result = getLengthParameterIndex() + 2
+ if exists(this.getLengthParameterIndex())
+ then result = this.getLengthParameterIndex() + 2
else result = 2
)
}
@@ -40,11 +40,11 @@ private class SscanfModel extends ArrayFunction, TaintFunction, AliasFunction, S
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
input.isParameterDeref(this.(ScanfFunction).getInputParameterIndex()) and
- output.isParameterDeref(any(int i | i >= getArgsStartPosition()))
+ output.isParameterDeref(any(int i | i >= this.getArgsStartPosition()))
}
override predicate parameterNeverEscapes(int index) {
- index = [0 .. max(getACallToThisFunction().getNumberOfArguments())]
+ index = [0 .. max(this.getACallToThisFunction().getNumberOfArguments())]
}
override predicate parameterEscapesOnlyViaReturn(int index) { none() }
@@ -56,7 +56,7 @@ private class SscanfModel extends ArrayFunction, TaintFunction, AliasFunction, S
override predicate hasOnlySpecificWriteSideEffects() { any() }
override predicate hasSpecificWriteSideEffect(ParameterIndex i, boolean buffer, boolean mustWrite) {
- i >= getArgsStartPosition() and
+ i >= this.getArgsStartPosition() and
buffer = true and
mustWrite = true
}
@@ -66,7 +66,7 @@ private class SscanfModel extends ArrayFunction, TaintFunction, AliasFunction, S
i =
[
this.(ScanfFunction).getInputParameterIndex(),
- this.(ScanfFunction).getFormatParameterIndex(), getLocaleParameterIndex()
+ this.(ScanfFunction).getFormatParameterIndex(), this.getLocaleParameterIndex()
]
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/StdContainer.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/StdContainer.qll
index 367db1613fc..c93a5ad147b 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/StdContainer.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/StdContainer.qll
@@ -61,20 +61,20 @@ private class StdSequenceContainerConstructor extends Constructor, TaintFunction
* value type of the container.
*/
int getAValueTypeParameterIndex() {
- getParameter(result).getUnspecifiedType().(ReferenceType).getBaseType() =
- getDeclaringType().getTemplateArgument(0).(Type).getUnspecifiedType() // i.e. the `T` of this `std::vector`
+ this.getParameter(result).getUnspecifiedType().(ReferenceType).getBaseType() =
+ this.getDeclaringType().getTemplateArgument(0).(Type).getUnspecifiedType() // i.e. the `T` of this `std::vector`
}
/**
* Gets the index of a parameter to this function that is an iterator.
*/
- int getAnIteratorParameterIndex() { getParameter(result).getType() instanceof Iterator }
+ int getAnIteratorParameterIndex() { this.getParameter(result).getType() instanceof Iterator }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// taint flow from any parameter of the value type to the returned object
(
- input.isParameterDeref(getAValueTypeParameterIndex()) or
- input.isParameter(getAnIteratorParameterIndex())
+ input.isParameterDeref(this.getAValueTypeParameterIndex()) or
+ input.isParameter(this.getAnIteratorParameterIndex())
) and
(
output.isReturnValue() // TODO: this is only needed for AST data flow, which treats constructors as returning the new object
@@ -158,21 +158,21 @@ private class StdSequenceContainerInsert extends TaintFunction {
* value type of the container.
*/
int getAValueTypeParameterIndex() {
- getParameter(result).getUnspecifiedType().(ReferenceType).getBaseType() =
- getDeclaringType().getTemplateArgument(0).(Type).getUnspecifiedType() // i.e. the `T` of this `std::vector`
+ this.getParameter(result).getUnspecifiedType().(ReferenceType).getBaseType() =
+ this.getDeclaringType().getTemplateArgument(0).(Type).getUnspecifiedType() // i.e. the `T` of this `std::vector`
}
/**
* Gets the index of a parameter to this function that is an iterator.
*/
- int getAnIteratorParameterIndex() { getParameter(result).getType() instanceof Iterator }
+ int getAnIteratorParameterIndex() { this.getParameter(result).getType() instanceof Iterator }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from parameter to container itself (qualifier) and return value
(
input.isQualifierObject() or
- input.isParameterDeref(getAValueTypeParameterIndex()) or
- input.isParameter(getAnIteratorParameterIndex())
+ input.isParameterDeref(this.getAValueTypeParameterIndex()) or
+ input.isParameter(this.getAnIteratorParameterIndex())
) and
(
output.isQualifierObject() or
@@ -197,20 +197,20 @@ private class StdSequenceContainerAssign extends TaintFunction {
* value type of the container.
*/
int getAValueTypeParameterIndex() {
- getParameter(result).getUnspecifiedType().(ReferenceType).getBaseType() =
- getDeclaringType().getTemplateArgument(0).(Type).getUnspecifiedType() // i.e. the `T` of this `std::vector`
+ this.getParameter(result).getUnspecifiedType().(ReferenceType).getBaseType() =
+ this.getDeclaringType().getTemplateArgument(0).(Type).getUnspecifiedType() // i.e. the `T` of this `std::vector`
}
/**
* Gets the index of a parameter to this function that is an iterator.
*/
- int getAnIteratorParameterIndex() { getParameter(result).getType() instanceof Iterator }
+ int getAnIteratorParameterIndex() { this.getParameter(result).getType() instanceof Iterator }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from parameter to container itself (qualifier)
(
- input.isParameterDeref(getAValueTypeParameterIndex()) or
- input.isParameter(getAnIteratorParameterIndex())
+ input.isParameterDeref(this.getAValueTypeParameterIndex()) or
+ input.isParameter(this.getAnIteratorParameterIndex())
) and
output.isQualifierObject()
}
@@ -246,7 +246,7 @@ class StdVectorEmplace extends TaintFunction {
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from any parameter except the position iterator to qualifier and return value
// (here we assume taint flow from any constructor parameter to the constructed object)
- input.isParameterDeref([1 .. getNumberOfParameters() - 1]) and
+ input.isParameterDeref([1 .. this.getNumberOfParameters() - 1]) and
(
output.isQualifierObject() or
output.isReturnValue()
@@ -263,7 +263,7 @@ class StdVectorEmplaceBack extends TaintFunction {
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from any parameter to qualifier
// (here we assume taint flow from any constructor parameter to the constructed object)
- input.isParameterDeref([0 .. getNumberOfParameters() - 1]) and
+ input.isParameterDeref([0 .. this.getNumberOfParameters() - 1]) and
output.isQualifierObject()
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/StdMap.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/StdMap.qll
index aecd98981e8..9dc220e79af 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/StdMap.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/StdMap.qll
@@ -22,12 +22,12 @@ private class StdMapConstructor extends Constructor, TaintFunction {
* Gets the index of a parameter to this function that is an iterator.
*/
int getAnIteratorParameterIndex() {
- getParameter(result).getUnspecifiedType() instanceof Iterator
+ this.getParameter(result).getUnspecifiedType() instanceof Iterator
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// taint flow from any parameter of an iterator type to the qualifier
- input.isParameterDeref(getAnIteratorParameterIndex()) and
+ input.isParameterDeref(this.getAnIteratorParameterIndex()) and
(
output.isReturnValue() // TODO: this is only needed for AST data flow, which treats constructors as returning the new object
or
@@ -47,7 +47,7 @@ private class StdMapInsert extends TaintFunction {
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from last parameter to qualifier and return value
// (where the return value is a pair, this should really flow just to the first part of it)
- input.isParameterDeref(getNumberOfParameters() - 1) and
+ input.isParameterDeref(this.getNumberOfParameters() - 1) and
(
output.isQualifierObject() or
output.isReturnValue()
@@ -66,7 +66,7 @@ private class StdMapEmplace extends TaintFunction {
// construct a pair, or a pair to be copied / moved) to the qualifier and
// return value.
// (where the return value is a pair, this should really flow just to the first part of it)
- input.isParameterDeref(getNumberOfParameters() - 1) and
+ input.isParameterDeref(this.getNumberOfParameters() - 1) and
(
output.isQualifierObject() or
output.isReturnValue()
@@ -87,9 +87,9 @@ private class StdMapTryEmplace extends TaintFunction {
// flow from any parameter apart from the key to qualifier and return value
// (here we assume taint flow from any constructor parameter to the constructed object)
// (where the return value is a pair, this should really flow just to the first part of it)
- exists(int arg | arg = [1 .. getNumberOfParameters() - 1] |
+ exists(int arg | arg = [1 .. this.getNumberOfParameters() - 1] |
(
- not getUnspecifiedType() instanceof Iterator or
+ not this.getUnspecifiedType() instanceof Iterator or
arg != 1
) and
input.isParameterDeref(arg)
@@ -154,7 +154,7 @@ private class StdMapErase extends TaintFunction {
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from qualifier to iterator return value
- getType().getUnderlyingType() instanceof Iterator and
+ this.getType().getUnderlyingType() instanceof Iterator and
input.isQualifierObject() and
output.isReturnValue()
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/StdPair.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/StdPair.qll
index 755f6a48520..c6fabcac314 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/StdPair.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/StdPair.qll
@@ -51,13 +51,13 @@ private class StdPairConstructor extends Constructor, TaintFunction {
* either value type of the pair.
*/
int getAValueTypeParameterIndex() {
- getParameter(result).getUnspecifiedType().(ReferenceType).getBaseType() =
- getDeclaringType().getTemplateArgument(_).(Type).getUnspecifiedType() // i.e. the `T1` or `T2` of this `std::pair`
+ this.getParameter(result).getUnspecifiedType().(ReferenceType).getBaseType() =
+ this.getDeclaringType().getTemplateArgument(_).(Type).getUnspecifiedType() // i.e. the `T1` or `T2` of this `std::pair`
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// taint flow from second parameter of a value type to the qualifier
- getAValueTypeParameterIndex() = 1 and
+ this.getAValueTypeParameterIndex() = 1 and
input.isParameterDeref(1) and
(
output.isReturnValue() // TODO: this is only needed for AST data flow, which treats constructors as returning the new object
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/StdSet.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/StdSet.qll
index d2e9892abcb..3e26d80c136 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/StdSet.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/StdSet.qll
@@ -22,12 +22,12 @@ private class StdSetConstructor extends Constructor, TaintFunction {
* Gets the index of a parameter to this function that is an iterator.
*/
int getAnIteratorParameterIndex() {
- getParameter(result).getUnspecifiedType() instanceof Iterator
+ this.getParameter(result).getUnspecifiedType() instanceof Iterator
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// taint flow from any parameter of an iterator type to the qualifier
- input.isParameterDeref(getAnIteratorParameterIndex()) and
+ input.isParameterDeref(this.getAnIteratorParameterIndex()) and
(
output.isReturnValue() // TODO: this is only needed for AST data flow, which treats constructors as returning the new object
or
@@ -45,7 +45,7 @@ private class StdSetInsert extends TaintFunction {
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from last parameter to qualifier and return value
// (where the return value is a pair, this should really flow just to the first part of it)
- input.isParameterDeref(getNumberOfParameters() - 1) and
+ input.isParameterDeref(this.getNumberOfParameters() - 1) and
(
output.isQualifierObject() or
output.isReturnValue()
@@ -63,7 +63,7 @@ private class StdSetEmplace extends TaintFunction {
// flow from any parameter to qualifier and return value
// (here we assume taint flow from any constructor parameter to the constructed object)
// (where the return value is a pair, this should really flow just to the first part of it)
- input.isParameterDeref([0 .. getNumberOfParameters() - 1]) and
+ input.isParameterDeref([0 .. this.getNumberOfParameters() - 1]) and
(
output.isQualifierObject() or
output.isReturnValue()
@@ -107,7 +107,7 @@ private class StdSetErase extends TaintFunction {
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from qualifier to iterator return value
- getType().getUnderlyingType() instanceof Iterator and
+ this.getType().getUnderlyingType() instanceof Iterator and
input.isQualifierObject() and
output.isReturnValue()
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/StdString.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/StdString.qll
index 73a0f6edf26..ae190688b70 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/StdString.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/StdString.qll
@@ -31,31 +31,31 @@ private class StdStringConstructor extends Constructor, TaintFunction {
* character).
*/
int getAStringParameterIndex() {
- exists(Type paramType | paramType = getParameter(result).getUnspecifiedType() |
+ exists(Type paramType | paramType = this.getParameter(result).getUnspecifiedType() |
// e.g. `std::basic_string::CharT *`
paramType instanceof PointerType
or
// e.g. `std::basic_string &`, avoiding `const Allocator&`
paramType instanceof ReferenceType and
not paramType.(ReferenceType).getBaseType() =
- getDeclaringType().getTemplateArgument(2).(Type).getUnspecifiedType()
+ this.getDeclaringType().getTemplateArgument(2).(Type).getUnspecifiedType()
or
// i.e. `std::basic_string::CharT`
- getParameter(result).getUnspecifiedType() =
- getDeclaringType().getTemplateArgument(0).(Type).getUnspecifiedType()
+ this.getParameter(result).getUnspecifiedType() =
+ this.getDeclaringType().getTemplateArgument(0).(Type).getUnspecifiedType()
)
}
/**
* Gets the index of a parameter to this function that is an iterator.
*/
- int getAnIteratorParameterIndex() { getParameter(result).getType() instanceof Iterator }
+ int getAnIteratorParameterIndex() { this.getParameter(result).getType() instanceof Iterator }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// taint flow from any parameter of the value type to the returned object
(
- input.isParameterDeref(getAStringParameterIndex()) or
- input.isParameter(getAnIteratorParameterIndex())
+ input.isParameterDeref(this.getAStringParameterIndex()) or
+ input.isParameter(this.getAnIteratorParameterIndex())
) and
(
output.isReturnValue() // TODO: this is only needed for AST data flow, which treats constructors as returning the new object
@@ -156,23 +156,23 @@ private class StdStringAppend extends TaintFunction {
* character).
*/
int getAStringParameterIndex() {
- getParameter(result).getType() instanceof PointerType or // e.g. `std::basic_string::CharT *`
- getParameter(result).getType() instanceof ReferenceType or // e.g. `std::basic_string &`
- getParameter(result).getUnspecifiedType() =
- getDeclaringType().getTemplateArgument(0).(Type).getUnspecifiedType() // i.e. `std::basic_string::CharT`
+ this.getParameter(result).getType() instanceof PointerType or // e.g. `std::basic_string::CharT *`
+ this.getParameter(result).getType() instanceof ReferenceType or // e.g. `std::basic_string &`
+ this.getParameter(result).getUnspecifiedType() =
+ this.getDeclaringType().getTemplateArgument(0).(Type).getUnspecifiedType() // i.e. `std::basic_string::CharT`
}
/**
* Gets the index of a parameter to this function that is an iterator.
*/
- int getAnIteratorParameterIndex() { getParameter(result).getType() instanceof Iterator }
+ int getAnIteratorParameterIndex() { this.getParameter(result).getType() instanceof Iterator }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from string and parameter to string (qualifier) and return value
(
input.isQualifierObject() or
- input.isParameterDeref(getAStringParameterIndex()) or
- input.isParameter(getAnIteratorParameterIndex())
+ input.isParameterDeref(this.getAStringParameterIndex()) or
+ input.isParameter(this.getAnIteratorParameterIndex())
) and
(
output.isQualifierObject() or
@@ -197,22 +197,22 @@ private class StdStringAssign extends TaintFunction {
* character).
*/
int getAStringParameterIndex() {
- getParameter(result).getType() instanceof PointerType or // e.g. `std::basic_string::CharT *`
- getParameter(result).getType() instanceof ReferenceType or // e.g. `std::basic_string &`
- getParameter(result).getUnspecifiedType() =
- getDeclaringType().getTemplateArgument(0).(Type).getUnspecifiedType() // i.e. `std::basic_string::CharT`
+ this.getParameter(result).getType() instanceof PointerType or // e.g. `std::basic_string::CharT *`
+ this.getParameter(result).getType() instanceof ReferenceType or // e.g. `std::basic_string &`
+ this.getParameter(result).getUnspecifiedType() =
+ this.getDeclaringType().getTemplateArgument(0).(Type).getUnspecifiedType() // i.e. `std::basic_string::CharT`
}
/**
* Gets the index of a parameter to this function that is an iterator.
*/
- int getAnIteratorParameterIndex() { getParameter(result).getType() instanceof Iterator }
+ int getAnIteratorParameterIndex() { this.getParameter(result).getType() instanceof Iterator }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from parameter to string itself (qualifier) and return value
(
- input.isParameterDeref(getAStringParameterIndex()) or
- input.isParameter(getAnIteratorParameterIndex())
+ input.isParameterDeref(this.getAStringParameterIndex()) or
+ input.isParameter(this.getAnIteratorParameterIndex())
) and
(
output.isQualifierObject() or
@@ -574,12 +574,12 @@ private class StdStringStreamConstructor extends Constructor, TaintFunction {
* Gets the index of a parameter to this function that is a string.
*/
int getAStringParameterIndex() {
- getParameter(result).getType() instanceof ReferenceType // `const std::basic_string &`
+ this.getParameter(result).getType() instanceof ReferenceType // `const std::basic_string &`
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// taint flow from any parameter of string type to the returned object
- input.isParameterDeref(getAStringParameterIndex()) and
+ input.isParameterDeref(this.getAStringParameterIndex()) and
(
output.isReturnValue() // TODO: this is only needed for AST data flow, which treats constructors as returning the new object
or
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Strcat.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Strcat.qll
index ee9af547582..1a65e7b6ca4 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Strcat.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Strcat.qll
@@ -32,7 +32,7 @@ class StrcatFunction extends TaintFunction, DataFlowFunction, ArrayFunction, Sid
/**
* Gets the index of the parameter that is the size of the copy (in characters).
*/
- int getParamSize() { exists(getParameter(2)) and result = 2 }
+ int getParamSize() { exists(this.getParameter(2)) and result = 2 }
/**
* Gets the index of the parameter that is the source of the copy.
@@ -50,11 +50,11 @@ class StrcatFunction extends TaintFunction, DataFlowFunction, ArrayFunction, Sid
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
- getName() = ["strncat", "wcsncat", "_mbsncat", "_mbsncat_l"] and
+ this.getName() = ["strncat", "wcsncat", "_mbsncat", "_mbsncat_l"] and
input.isParameter(2) and
output.isParameterDeref(0)
or
- getName() = ["_mbsncat_l", "_mbsnbcat_l"] and
+ this.getName() = ["_mbsncat_l", "_mbsnbcat_l"] and
input.isParameter(3) and
output.isParameterDeref(0)
or
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Strcpy.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Strcpy.qll
index 432fbf999ef..10b160dee47 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Strcpy.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Strcpy.qll
@@ -45,22 +45,22 @@ class StrcpyFunction extends ArrayFunction, DataFlowFunction, TaintFunction, Sid
) and
// exclude the 2-parameter template versions
// that find the size of a fixed size destination buffer.
- getNumberOfParameters() = 3
+ this.getNumberOfParameters() = 3
}
/**
* Holds if this is one of the `strcpy_s` variants.
*/
- private predicate isSVariant() { getName().matches("%\\_s") }
+ private predicate isSVariant() { this.getName().matches("%\\_s") }
/**
* Gets the index of the parameter that is the maximum size of the copy (in characters).
*/
int getParamSize() {
- if isSVariant()
+ if this.isSVariant()
then result = 1
else (
- getName().matches(["%ncpy%", "%nbcpy%", "%xfrm%"]) and
+ this.getName().matches(["%ncpy%", "%nbcpy%", "%xfrm%"]) and
result = 2
)
}
@@ -68,49 +68,49 @@ class StrcpyFunction extends ArrayFunction, DataFlowFunction, TaintFunction, Sid
/**
* Gets the index of the parameter that is the source of the copy.
*/
- int getParamSrc() { if isSVariant() then result = 2 else result = 1 }
+ int getParamSrc() { if this.isSVariant() then result = 2 else result = 1 }
/**
* Gets the index of the parameter that is the destination of the copy.
*/
int getParamDest() { result = 0 }
- override predicate hasArrayInput(int bufParam) { bufParam = getParamSrc() }
+ override predicate hasArrayInput(int bufParam) { bufParam = this.getParamSrc() }
- override predicate hasArrayOutput(int bufParam) { bufParam = getParamDest() }
+ override predicate hasArrayOutput(int bufParam) { bufParam = this.getParamDest() }
- override predicate hasArrayWithNullTerminator(int bufParam) { bufParam = getParamSrc() }
+ override predicate hasArrayWithNullTerminator(int bufParam) { bufParam = this.getParamSrc() }
override predicate hasArrayWithVariableSize(int bufParam, int countParam) {
- bufParam = getParamDest() and
- countParam = getParamSize()
+ bufParam = this.getParamDest() and
+ countParam = this.getParamSize()
}
override predicate hasArrayWithUnknownSize(int bufParam) {
- not exists(getParamSize()) and
- bufParam = getParamDest()
+ not exists(this.getParamSize()) and
+ bufParam = this.getParamDest()
}
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
- not exists(getParamSize()) and
- input.isParameterDeref(getParamSrc()) and
- output.isParameterDeref(getParamDest())
+ not exists(this.getParamSize()) and
+ input.isParameterDeref(this.getParamSrc()) and
+ output.isParameterDeref(this.getParamDest())
or
- not exists(getParamSize()) and
- input.isParameterDeref(getParamSrc()) and
+ not exists(this.getParamSize()) and
+ input.isParameterDeref(this.getParamSrc()) and
output.isReturnValueDeref()
or
- input.isParameter(getParamDest()) and
+ input.isParameter(this.getParamDest()) and
output.isReturnValue()
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// these may do only a partial copy of the input buffer to the output
// buffer
- exists(getParamSize()) and
- input.isParameter(getParamSrc()) and
+ exists(this.getParamSize()) and
+ input.isParameter(this.getParamSrc()) and
(
- output.isParameterDeref(getParamDest()) or
+ output.isParameterDeref(this.getParamDest()) or
output.isReturnValueDeref()
)
}
@@ -120,18 +120,18 @@ class StrcpyFunction extends ArrayFunction, DataFlowFunction, TaintFunction, Sid
override predicate hasOnlySpecificWriteSideEffects() { any() }
override predicate hasSpecificWriteSideEffect(ParameterIndex i, boolean buffer, boolean mustWrite) {
- i = getParamDest() and
+ i = this.getParamDest() and
buffer = true and
mustWrite = false
}
override predicate hasSpecificReadSideEffect(ParameterIndex i, boolean buffer) {
- i = getParamSrc() and
+ i = this.getParamSrc() and
buffer = true
}
override ParameterIndex getParameterSizeIndex(ParameterIndex i) {
- i = getParamDest() and
- result = getParamSize()
+ i = this.getParamDest() and
+ result = this.getParamSize()
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Strcrement.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Strcrement.qll
index 4c335c8581e..8f6c17aae54 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Strcrement.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Strcrement.qll
@@ -29,10 +29,10 @@ private class Strcrement extends ArrayFunction, TaintFunction, SideEffectFunctio
this.getParameter(bufParam).getUnspecifiedType() instanceof PointerType
}
- override predicate hasArrayInput(int bufParam) { hasArrayWithNullTerminator(bufParam) }
+ override predicate hasArrayInput(int bufParam) { this.hasArrayWithNullTerminator(bufParam) }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
- exists(int index | hasArrayInput(index) |
+ exists(int index | this.hasArrayInput(index) |
input.isParameter(index) and output.isReturnValue()
or
input.isParameterDeref(index) and output.isReturnValueDeref()
@@ -44,6 +44,6 @@ private class Strcrement extends ArrayFunction, TaintFunction, SideEffectFunctio
override predicate hasOnlySpecificWriteSideEffects() { any() }
override predicate hasSpecificReadSideEffect(ParameterIndex i, boolean buffer) {
- hasArrayInput(i) and buffer = true
+ this.hasArrayInput(i) and buffer = true
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Swap.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Swap.qll
index b79f7afe5d9..446e659fac5 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Swap.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/implementations/Swap.qll
@@ -31,7 +31,7 @@ private class MemberSwap extends TaintFunction, MemberFunction, AliasFunction {
this.hasName("swap") and
this.getNumberOfParameters() = 1 and
this.getParameter(0).getType().(ReferenceType).getBaseType().getUnspecifiedType() =
- getDeclaringType()
+ this.getDeclaringType()
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/interfaces/FunctionInputsAndOutputs.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/interfaces/FunctionInputsAndOutputs.qll
index 4ab55ee5b3f..5e899be68d4 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/interfaces/FunctionInputsAndOutputs.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/models/interfaces/FunctionInputsAndOutputs.qll
@@ -44,7 +44,7 @@ class FunctionInput extends TFunctionInput {
* Holds if this is the input value of the parameter with index `index`.
* DEPRECATED: Use `isParameter(index)` instead.
*/
- deprecated final predicate isInParameter(ParameterIndex index) { isParameter(index) }
+ deprecated final predicate isInParameter(ParameterIndex index) { this.isParameter(index) }
/**
* Holds if this is the input value pointed to by a pointer parameter to a function, or the input
@@ -70,7 +70,9 @@ class FunctionInput extends TFunctionInput {
* `index`.
* DEPRECATED: Use `isParameterDeref(index)` instead.
*/
- deprecated final predicate isInParameterPointer(ParameterIndex index) { isParameterDeref(index) }
+ deprecated final predicate isInParameterPointer(ParameterIndex index) {
+ this.isParameterDeref(index)
+ }
/**
* Holds if this is the input value pointed to by the `this` pointer of an instance member
@@ -92,7 +94,7 @@ class FunctionInput extends TFunctionInput {
* function.
* DEPRECATED: Use `isQualifierObject()` instead.
*/
- deprecated final predicate isInQualifier() { isQualifierObject() }
+ deprecated final predicate isInQualifier() { this.isQualifierObject() }
/**
* Holds if this is the input value of the `this` pointer of an instance member function.
@@ -314,7 +316,9 @@ class FunctionOutput extends TFunctionOutput {
* index `index`.
* DEPRECATED: Use `isParameterDeref(index)` instead.
*/
- deprecated final predicate isOutParameterPointer(ParameterIndex index) { isParameterDeref(index) }
+ deprecated final predicate isOutParameterPointer(ParameterIndex index) {
+ this.isParameterDeref(index)
+ }
/**
* Holds if this is the output value pointed to by the `this` pointer of an instance member
@@ -336,7 +340,7 @@ class FunctionOutput extends TFunctionOutput {
* function.
* DEPRECATED: Use `isQualifierObject()` instead.
*/
- deprecated final predicate isOutQualifier() { isQualifierObject() }
+ deprecated final predicate isOutQualifier() { this.isQualifierObject() }
/**
* Holds if this is the value returned by a function.
@@ -361,7 +365,7 @@ class FunctionOutput extends TFunctionOutput {
* Holds if this is the value returned by a function.
* DEPRECATED: Use `isReturnValue()` instead.
*/
- deprecated final predicate isOutReturnValue() { isReturnValue() }
+ deprecated final predicate isOutReturnValue() { this.isReturnValue() }
/**
* Holds if this is the output value pointed to by the return value of a function, if the function
@@ -389,7 +393,7 @@ class FunctionOutput extends TFunctionOutput {
* function returns a reference.
* DEPRECATED: Use `isReturnValueDeref()` instead.
*/
- deprecated final predicate isOutReturnPointer() { isReturnValueDeref() }
+ deprecated final predicate isOutReturnPointer() { this.isReturnValueDeref() }
/**
* Holds if `i >= 0` and `isParameterDeref(i)` holds for this is the value, or
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/padding/Padding.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/padding/Padding.qll
index 7446569451d..c5c1903b4d1 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/padding/Padding.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/padding/Padding.qll
@@ -72,7 +72,7 @@ abstract class Architecture extends string {
or
t instanceof CharType and result = 8
or
- t instanceof WideCharType and result = wideCharSize()
+ t instanceof WideCharType and result = this.wideCharSize()
or
t instanceof Char8Type and result = 8
or
@@ -84,22 +84,22 @@ abstract class Architecture extends string {
or
t instanceof IntType and result = 32
or
- t instanceof LongType and result = longSize()
+ t instanceof LongType and result = this.longSize()
or
- t instanceof LongLongType and result = longLongSize()
+ t instanceof LongLongType and result = this.longLongSize()
or
- result = enumBitSize(t.(Enum))
+ result = this.enumBitSize(t)
or
- result = integralBitSize(t.(SpecifiedType).getBaseType())
+ result = this.integralBitSize(t.(SpecifiedType).getBaseType())
or
- result = integralBitSize(t.(TypedefType).getBaseType())
+ result = this.integralBitSize(t.(TypedefType).getBaseType())
}
/**
* Gets the bit size of enum type `e`.
*/
int enumBitSize(Enum e) {
- result = integralBitSize(e.getExplicitUnderlyingType())
+ result = this.integralBitSize(e.getExplicitUnderlyingType())
or
not exists(e.getExplicitUnderlyingType()) and result = 32
}
@@ -108,7 +108,7 @@ abstract class Architecture extends string {
* Gets the alignment of enum type `e`.
*/
int enumAlignment(Enum e) {
- result = alignment(e.getExplicitUnderlyingType())
+ result = this.alignment(e.getExplicitUnderlyingType())
or
not exists(e.getExplicitUnderlyingType()) and result = 32
}
@@ -120,26 +120,26 @@ abstract class Architecture extends string {
*/
cached
int bitSize(Type t) {
- result = integralBitSize(t)
+ result = this.integralBitSize(t)
or
t instanceof FloatType and result = 32
or
t instanceof DoubleType and result = 64
or
- t instanceof LongDoubleType and result = longDoubleSize()
+ t instanceof LongDoubleType and result = this.longDoubleSize()
or
- t instanceof PointerType and result = pointerSize()
+ t instanceof PointerType and result = this.pointerSize()
or
- t instanceof ReferenceType and result = pointerSize()
+ t instanceof ReferenceType and result = this.pointerSize()
or
- t instanceof FunctionPointerType and result = pointerSize()
+ t instanceof FunctionPointerType and result = this.pointerSize()
or
- result = bitSize(t.(SpecifiedType).getBaseType())
+ result = this.bitSize(t.(SpecifiedType).getBaseType())
or
- result = bitSize(t.(TypedefType).getBaseType())
+ result = this.bitSize(t.(TypedefType).getBaseType())
or
exists(ArrayType array | array = t |
- result = array.getArraySize() * paddedSize(array.getBaseType())
+ result = array.getArraySize() * this.paddedSize(array.getBaseType())
)
or
result = t.(PaddedType).typeBitSize(this)
@@ -155,7 +155,7 @@ abstract class Architecture extends string {
or
t instanceof CharType and result = 8
or
- t instanceof WideCharType and result = wideCharSize()
+ t instanceof WideCharType and result = this.wideCharSize()
or
t instanceof Char8Type and result = 8
or
@@ -169,27 +169,27 @@ abstract class Architecture extends string {
or
t instanceof FloatType and result = 32
or
- t instanceof DoubleType and result = doubleAlign()
+ t instanceof DoubleType and result = this.doubleAlign()
or
- t instanceof LongType and result = longSize()
+ t instanceof LongType and result = this.longSize()
or
- t instanceof LongDoubleType and result = longDoubleAlign()
+ t instanceof LongDoubleType and result = this.longDoubleAlign()
or
- t instanceof LongLongType and result = longLongAlign()
+ t instanceof LongLongType and result = this.longLongAlign()
or
- t instanceof PointerType and result = pointerSize()
+ t instanceof PointerType and result = this.pointerSize()
or
- t instanceof FunctionPointerType and result = pointerSize()
+ t instanceof FunctionPointerType and result = this.pointerSize()
or
- t instanceof ReferenceType and result = pointerSize()
+ t instanceof ReferenceType and result = this.pointerSize()
or
- result = enumAlignment(t.(Enum))
+ result = this.enumAlignment(t)
or
- result = alignment(t.(SpecifiedType).getBaseType())
+ result = this.alignment(t.(SpecifiedType).getBaseType())
or
- result = alignment(t.(TypedefType).getBaseType())
+ result = this.alignment(t.(TypedefType).getBaseType())
or
- result = alignment(t.(ArrayType).getBaseType())
+ result = this.alignment(t.(ArrayType).getBaseType())
or
result = t.(PaddedType).typeAlignment(this)
}
@@ -203,7 +203,7 @@ abstract class Architecture extends string {
exists(Type realType | realType = stripSpecifiers(t) |
if realType instanceof PaddedType
then result = realType.(PaddedType).paddedSize(this)
- else result = bitSize(realType)
+ else result = this.bitSize(realType)
)
}
@@ -232,14 +232,14 @@ private Field getAnInitialField(PaddedType t) {
result = t.getAField()
or
// Initial field of the type of a field of the union
- result = getAnInitialField(t.getAField().getUnspecifiedType().(PaddedType))
+ result = getAnInitialField(t.getAField().getUnspecifiedType())
else
exists(Field firstField | t.fieldIndex(firstField) = 1 |
// The first field of `t`
result = firstField
or
// Initial field of the first field of `t`
- result = getAnInitialField(firstField.getUnspecifiedType().(PaddedType))
+ result = getAnInitialField(firstField.getUnspecifiedType())
)
}
@@ -429,7 +429,7 @@ class PaddedType extends Class {
* Gets the number of bits wasted by padding at the end of this
* struct.
*/
- int trailingPadding(Architecture arch) { result = paddedSize(arch) - arch.bitSize(this) }
+ int trailingPadding(Architecture arch) { result = this.paddedSize(arch) - arch.bitSize(this) }
/**
* Gets the number of bits wasted in this struct definition; that is.
@@ -440,7 +440,7 @@ class PaddedType extends Class {
* laid out one after another, and hence there is no padding between
* them.
*/
- int wastedSpace(Architecture arch) { result = arch.paddedSize(this) - dataSize(arch) }
+ int wastedSpace(Architecture arch) { result = arch.paddedSize(this) - this.dataSize(arch) }
/**
* Gets the total size of all fields declared in this class, not including any
@@ -448,8 +448,8 @@ class PaddedType extends Class {
*/
private int fieldDataSize(Architecture arch) {
if this instanceof Union
- then result = max(Field f | f = this.getAMember() | fieldSize(f, arch))
- else result = sum(Field f | f = this.getAMember() | fieldSize(f, arch))
+ then result = max(Field f | f = this.getAMember() | this.fieldSize(f, arch))
+ else result = sum(Field f | f = this.getAMember() | this.fieldSize(f, arch))
}
/**
@@ -472,7 +472,7 @@ class PaddedType extends Class {
* reorganizing member structs' field layouts.
*/
int optimalSize(Architecture arch) {
- result = alignUp(dataSize(arch), arch.alignment(this)).maximum(8)
+ result = alignUp(this.dataSize(arch), arch.alignment(this)).maximum(8)
}
/**
@@ -490,11 +490,11 @@ class PaddedType extends Class {
// but that uses a recursive aggregate, which isn't supported in
// QL. We therefore use this slightly more complex implementation
// instead.
- result = biggestFieldSizeUpTo(lastFieldIndex(), arch)
+ result = this.biggestFieldSizeUpTo(this.lastFieldIndex(), arch)
else
// If we're not a union type, the size is the padded
// sum of field sizes, padded.
- result = fieldEnd(lastFieldIndex(), arch)
+ result = this.fieldEnd(this.lastFieldIndex(), arch)
}
/**
@@ -522,8 +522,8 @@ class PaddedType extends Class {
if index = 0
then result = 0
else
- exists(Field f, int fSize | index = fieldIndex(f) and fSize = fieldSize(f, arch) |
- result = fSize.maximum(biggestFieldSizeUpTo(index - 1, arch))
+ exists(Field f, int fSize | index = this.fieldIndex(f) and fSize = this.fieldSize(f, arch) |
+ result = fSize.maximum(this.biggestFieldSizeUpTo(index - 1, arch))
)
}
@@ -536,8 +536,10 @@ class PaddedType extends Class {
if index = 0
then result = 1 // Minimum possible alignment
else
- exists(Field f, int fAlign | index = fieldIndex(f) and fAlign = arch.alignment(f.getType()) |
- result = fAlign.maximum(biggestAlignmentUpTo(index - 1, arch))
+ exists(Field f, int fAlign |
+ index = this.fieldIndex(f) and fAlign = arch.alignment(f.getType())
+ |
+ result = fAlign.maximum(this.biggestAlignmentUpTo(index - 1, arch))
)
}
@@ -545,17 +547,18 @@ class PaddedType extends Class {
* Gets the 1-based index for each field.
*/
int fieldIndex(Field f) {
- memberIndex(f) = rank[result](Field field, int index | memberIndex(field) = index | index)
+ this.memberIndex(f) =
+ rank[result](Field field, int index | this.memberIndex(field) = index | index)
}
- private int memberIndex(Field f) { result = min(int i | getCanonicalMember(i) = f) }
+ private int memberIndex(Field f) { result = min(int i | this.getCanonicalMember(i) = f) }
/**
* Gets the 1-based index for the last field.
*/
int lastFieldIndex() {
- if exists(lastField())
- then result = fieldIndex(lastField())
+ if exists(this.lastField())
+ then result = this.fieldIndex(this.lastField())
else
// Field indices are 1-based, so return 0 to represent the lack of fields.
result = 0
@@ -566,25 +569,27 @@ class PaddedType extends Class {
* `arch`.
*/
int fieldSize(Field f, Architecture arch) {
- exists(fieldIndex(f)) and
+ exists(this.fieldIndex(f)) and
if f instanceof BitField
then result = f.(BitField).getNumBits()
else result = arch.paddedSize(f.getType())
}
/** Gets the last field of this type. */
- Field lastField() { fieldIndex(result) = max(Field other | | fieldIndex(other)) }
+ Field lastField() { this.fieldIndex(result) = max(Field other | | this.fieldIndex(other)) }
/**
* Gets the offset, in bits, of the end of the class' last base class
* subobject, or zero if the class has no base classes.
*/
int baseClassEnd(Architecture arch) {
- if exists(getABaseClass()) then result = arch.baseClassSize(getADerivation()) else result = 0
+ if exists(this.getABaseClass())
+ then result = arch.baseClassSize(this.getADerivation())
+ else result = 0
}
/** Gets the bitfield at field index `index`, if that field is a bitfield. */
- private BitField bitFieldAt(int index) { fieldIndex(result) = index }
+ private BitField bitFieldAt(int index) { this.fieldIndex(result) = index }
/**
* Gets the 0-based offset, in bits, of the first free bit after
@@ -596,13 +601,13 @@ class PaddedType extends Class {
then
// Base case: No fields seen yet, so return the offset of the end of the
// base class subojects.
- result = baseClassEnd(arch)
+ result = this.baseClassEnd(arch)
else
- exists(Field f | index = fieldIndex(f) |
- exists(int fSize | fSize = fieldSize(f, arch) |
+ exists(Field f | index = this.fieldIndex(f) |
+ exists(int fSize | fSize = this.fieldSize(f, arch) |
// Recursive case: Take previous field's end point, pad and add
// this field's size
- exists(int firstFree | firstFree = fieldEnd(index - 1, arch) |
+ exists(int firstFree | firstFree = this.fieldEnd(index - 1, arch) |
if f instanceof BitField
then
// Bitfield packing:
@@ -629,9 +634,11 @@ class PaddedType extends Class {
// No additional restrictions, so just pack it in with no padding.
result = firstFree + fSize
) else (
- if exists(bitFieldAt(index - 1))
+ if exists(this.bitFieldAt(index - 1))
then
- exists(BitField previousBitField | previousBitField = bitFieldAt(index - 1) |
+ exists(BitField previousBitField |
+ previousBitField = this.bitFieldAt(index - 1)
+ |
// Previous field was a bitfield.
if
nextSizeofBoundary >= (firstFree + fSize) and
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/rangeanalysis/RangeSSA.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/rangeanalysis/RangeSSA.qll
index bc66d9b2dd0..d2d2fbd5b3c 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/rangeanalysis/RangeSSA.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/rangeanalysis/RangeSSA.qll
@@ -88,10 +88,10 @@ class RangeSsaDefinition extends ControlFlowNodeBase {
ControlFlowNode getDefinition() { result = this }
/** Gets the basic block containing this definition. */
- BasicBlock getBasicBlock() { result.contains(getDefinition()) }
+ BasicBlock getBasicBlock() { result.contains(this.getDefinition()) }
/** Whether this definition is a phi node for variable `v`. */
- predicate isPhiNode(StackVariable v) { exists(RangeSSA x | x.phi_node(v, this.(BasicBlock))) }
+ predicate isPhiNode(StackVariable v) { exists(RangeSSA x | x.phi_node(v, this)) }
/**
* DEPRECATED: Use isGuardPhi/4 instead
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll
index 289187d4301..0be94ed4e62 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll
@@ -127,17 +127,22 @@ private string getValue(Expr e) {
private class UnsignedBitwiseAndExpr extends BitwiseAndExpr {
UnsignedBitwiseAndExpr() {
(
- getLeftOperand().getFullyConverted().getType().getUnderlyingType().(IntegralType).isUnsigned() or
- getValue(getLeftOperand().getFullyConverted()).toInt() >= 0
- ) and
- (
- getRightOperand()
+ this.getLeftOperand()
.getFullyConverted()
.getType()
.getUnderlyingType()
.(IntegralType)
.isUnsigned() or
- getValue(getRightOperand().getFullyConverted()).toInt() >= 0
+ getValue(this.getLeftOperand().getFullyConverted()).toInt() >= 0
+ ) and
+ (
+ this.getRightOperand()
+ .getFullyConverted()
+ .getType()
+ .getUnderlyingType()
+ .(IntegralType)
+ .isUnsigned() or
+ getValue(this.getRightOperand().getFullyConverted()).toInt() >= 0
)
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/BufferWrite.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/BufferWrite.qll
index e5d892eb4cd..61845654721 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/BufferWrite.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/BufferWrite.qll
@@ -77,21 +77,21 @@ abstract class BufferWrite extends Expr {
* much smaller (8 bytes) than their true maximum length. This can be
* helpful in determining the cause of a buffer overflow issue.
*/
- int getMaxDataLimited() { result = getMaxData() }
+ int getMaxDataLimited() { result = this.getMaxData() }
/**
* Gets the size of a single character of the type this
* operation works with, in bytes.
*/
int getCharSize() {
- result = getBufferType().(PointerType).getBaseType().getSize() or
- result = getBufferType().(ArrayType).getBaseType().getSize()
+ result = this.getBufferType().(PointerType).getBaseType().getSize() or
+ result = this.getBufferType().(ArrayType).getBaseType().getSize()
}
/**
* Gets a description of this buffer write.
*/
- string getBWDesc() { result = toString() }
+ string getBWDesc() { result = this.toString() }
}
/**
@@ -109,7 +109,7 @@ abstract class BufferWriteCall extends BufferWrite, FunctionCall { }
class StrCopyBW extends BufferWriteCall {
StrcpyFunction f;
- StrCopyBW() { getTarget() = f.(TopLevelFunction) }
+ StrCopyBW() { this.getTarget() = f.(TopLevelFunction) }
/**
* Gets the index of the parameter that is the maximum size of the copy (in characters).
@@ -122,21 +122,22 @@ class StrCopyBW extends BufferWriteCall {
int getParamSrc() { result = f.getParamSrc() }
override Type getBufferType() {
- result = this.getTarget().getParameter(getParamSrc()).getUnspecifiedType()
+ result = this.getTarget().getParameter(this.getParamSrc()).getUnspecifiedType()
}
- override Expr getASource() { result = getArgument(getParamSrc()) }
+ override Expr getASource() { result = this.getArgument(this.getParamSrc()) }
- override Expr getDest() { result = getArgument(f.getParamDest()) }
+ override Expr getDest() { result = this.getArgument(f.getParamDest()) }
- override predicate hasExplicitLimit() { exists(getParamSize()) }
+ override predicate hasExplicitLimit() { exists(this.getParamSize()) }
override int getExplicitLimit() {
- result = getArgument(getParamSize()).getValue().toInt() * getCharSize()
+ result = this.getArgument(this.getParamSize()).getValue().toInt() * this.getCharSize()
}
override int getMaxData() {
- result = getArgument(getParamSrc()).(AnalysedString).getMaxLength() * getCharSize()
+ result =
+ this.getArgument(this.getParamSrc()).(AnalysedString).getMaxLength() * this.getCharSize()
}
}
@@ -146,7 +147,7 @@ class StrCopyBW extends BufferWriteCall {
class StrCatBW extends BufferWriteCall {
StrcatFunction f;
- StrCatBW() { getTarget() = f.(TopLevelFunction) }
+ StrCatBW() { this.getTarget() = f.(TopLevelFunction) }
/**
* Gets the index of the parameter that is the maximum size of the copy (in characters).
@@ -159,21 +160,22 @@ class StrCatBW extends BufferWriteCall {
int getParamSrc() { result = f.getParamSrc() }
override Type getBufferType() {
- result = this.getTarget().getParameter(getParamSrc()).getUnspecifiedType()
+ result = this.getTarget().getParameter(this.getParamSrc()).getUnspecifiedType()
}
- override Expr getASource() { result = getArgument(getParamSrc()) }
+ override Expr getASource() { result = this.getArgument(this.getParamSrc()) }
- override Expr getDest() { result = getArgument(f.getParamDest()) }
+ override Expr getDest() { result = this.getArgument(f.getParamDest()) }
- override predicate hasExplicitLimit() { exists(getParamSize()) }
+ override predicate hasExplicitLimit() { exists(this.getParamSize()) }
override int getExplicitLimit() {
- result = getArgument(getParamSize()).getValue().toInt() * getCharSize()
+ result = this.getArgument(this.getParamSize()).getValue().toInt() * this.getCharSize()
}
override int getMaxData() {
- result = getArgument(getParamSrc()).(AnalysedString).getMaxLength() * getCharSize()
+ result =
+ this.getArgument(this.getParamSrc()).(AnalysedString).getMaxLength() * this.getCharSize()
}
}
@@ -184,7 +186,7 @@ class SprintfBW extends BufferWriteCall {
FormattingFunction f;
SprintfBW() {
- exists(string name | f = getTarget().(TopLevelFunction) and name = f.getName() |
+ exists(string name | f = this.getTarget().(TopLevelFunction) and name = f.getName() |
/*
* C sprintf variants:
*/
@@ -229,19 +231,19 @@ class SprintfBW extends BufferWriteCall {
result = this.(FormattingFunctionCall).getFormatArgument(_)
}
- override Expr getDest() { result = getArgument(f.getOutputParameterIndex(false)) }
+ override Expr getDest() { result = this.getArgument(f.getOutputParameterIndex(false)) }
override int getMaxData() {
exists(FormatLiteral fl |
fl = this.(FormattingFunctionCall).getFormat() and
- result = fl.getMaxConvertedLength() * getCharSize()
+ result = fl.getMaxConvertedLength() * this.getCharSize()
)
}
override int getMaxDataLimited() {
exists(FormatLiteral fl |
fl = this.(FormattingFunctionCall).getFormat() and
- result = fl.getMaxConvertedLengthLimited() * getCharSize()
+ result = fl.getMaxConvertedLengthLimited() * this.getCharSize()
)
}
}
@@ -251,7 +253,7 @@ class SprintfBW extends BufferWriteCall {
*/
class SnprintfBW extends BufferWriteCall {
SnprintfBW() {
- exists(TopLevelFunction fn, string name | fn = getTarget() and name = fn.getName() |
+ exists(TopLevelFunction fn, string name | fn = this.getTarget() and name = fn.getName() |
/*
* C snprintf variants:
*/
@@ -326,25 +328,25 @@ class SnprintfBW extends BufferWriteCall {
result = this.(FormattingFunctionCall).getFormatArgument(_)
}
- override Expr getDest() { result = getArgument(0) }
+ override Expr getDest() { result = this.getArgument(0) }
- override predicate hasExplicitLimit() { exists(getParamSize()) }
+ override predicate hasExplicitLimit() { exists(this.getParamSize()) }
override int getExplicitLimit() {
- result = getArgument(getParamSize()).getValue().toInt() * getCharSize()
+ result = this.getArgument(this.getParamSize()).getValue().toInt() * this.getCharSize()
}
override int getMaxData() {
exists(FormatLiteral fl |
fl = this.(FormattingFunctionCall).getFormat() and
- result = fl.getMaxConvertedLength() * getCharSize()
+ result = fl.getMaxConvertedLength() * this.getCharSize()
)
}
override int getMaxDataLimited() {
exists(FormatLiteral fl |
fl = this.(FormattingFunctionCall).getFormat() and
- result = fl.getMaxConvertedLengthLimited() * getCharSize()
+ result = fl.getMaxConvertedLengthLimited() * this.getCharSize()
)
}
}
@@ -354,7 +356,7 @@ class SnprintfBW extends BufferWriteCall {
*/
class GetsBW extends BufferWriteCall {
GetsBW() {
- getTarget().(TopLevelFunction).getName() =
+ this.getTarget().(TopLevelFunction).getName() =
[
"gets", // gets(dst)
"fgets", // fgets(dst, max_amount, src_stream)
@@ -365,24 +367,24 @@ class GetsBW extends BufferWriteCall {
/**
* Gets the index of the parameter that is the maximum number of characters to be read.
*/
- int getParamSize() { exists(getArgument(1)) and result = 1 }
+ int getParamSize() { exists(this.getArgument(1)) and result = 1 }
override Type getBufferType() { result = this.getTarget().getParameter(0).getUnspecifiedType() }
override Expr getASource() {
- if exists(getArgument(2))
- then result = getArgument(2)
+ if exists(this.getArgument(2))
+ then result = this.getArgument(2)
else
// the source is input inside the 'gets' call itself
result = this
}
- override Expr getDest() { result = getArgument(0) }
+ override Expr getDest() { result = this.getArgument(0) }
- override predicate hasExplicitLimit() { exists(getParamSize()) }
+ override predicate hasExplicitLimit() { exists(this.getParamSize()) }
override int getExplicitLimit() {
- result = getArgument(getParamSize()).getValue().toInt() * getCharSize()
+ result = this.getArgument(this.getParamSize()).getValue().toInt() * this.getCharSize()
}
}
@@ -438,7 +440,7 @@ class ScanfBW extends BufferWrite {
exists(ScanfFunctionCall fc, ScanfFormatLiteral fl, int arg |
this = fc.getArgument(arg) and
fl = fc.getFormat() and
- result = (fl.getMaxConvertedLength(arg - getParamArgs()) + 1) * getCharSize() // +1 is for the terminating null
+ result = (fl.getMaxConvertedLength(arg - this.getParamArgs()) + 1) * this.getCharSize() // +1 is for the terminating null
)
}
@@ -463,14 +465,14 @@ private int path_max() {
class RealpathBW extends BufferWriteCall {
RealpathBW() {
exists(path_max()) and // Ignore realpath() calls if PATH_MAX cannot be determined
- getTarget().hasGlobalName("realpath") // realpath(path, resolved_path);
+ this.getTarget().hasGlobalName("realpath") // realpath(path, resolved_path);
}
override Type getBufferType() { result = this.getTarget().getParameter(0).getUnspecifiedType() }
- override Expr getDest() { result = getArgument(1) }
+ override Expr getDest() { result = this.getArgument(1) }
- override Expr getASource() { result = getArgument(0) }
+ override Expr getASource() { result = this.getArgument(0) }
override int getMaxData() {
result = path_max() and
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/CommandExecution.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/CommandExecution.qll
index f8f7c6c476f..063c7300031 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/CommandExecution.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/CommandExecution.qll
@@ -28,35 +28,19 @@ class SystemFunction extends FunctionWithWrappers instanceof CommandExecutionFun
*/
class VarargsExecFunctionCall extends FunctionCall {
VarargsExecFunctionCall() {
- getTarget().hasGlobalName("execl") or
- getTarget().hasGlobalName("execle") or
- getTarget().hasGlobalName("execlp") or
- // Windows
- getTarget().hasGlobalName("_execl") or
- getTarget().hasGlobalName("_execle") or
- getTarget().hasGlobalName("_execlp") or
- getTarget().hasGlobalName("_execlpe") or
- getTarget().hasGlobalName("_spawnl") or
- getTarget().hasGlobalName("_spawnle") or
- getTarget().hasGlobalName("_spawnlp") or
- getTarget().hasGlobalName("_spawnlpe") or
- getTarget().hasGlobalName("_wexecl") or
- getTarget().hasGlobalName("_wexecle") or
- getTarget().hasGlobalName("_wexeclp") or
- getTarget().hasGlobalName("_wexeclpe") or
- getTarget().hasGlobalName("_wspawnl") or
- getTarget().hasGlobalName("_wspawnle") or
- getTarget().hasGlobalName("_wspawnlp") or
- getTarget().hasGlobalName("_wspawnlpe")
+ getTarget()
+ .hasGlobalName([
+ "execl", "execle", "execlp",
+ // Windows
+ "_execl", "_execle", "_execlp", "_execlpe", "_spawnl", "_spawnle", "_spawnlp",
+ "_spawnlpe", "_wexecl", "_wexecle", "_wexeclp", "_wexeclpe", "_wspawnl", "_wspawnle",
+ "_wspawnlp", "_wspawnlpe"
+ ])
}
/** Whether the last argument to the function is an environment pointer */
predicate hasEnvironmentArgument() {
- getTarget().hasGlobalName("execle") or
- getTarget().hasGlobalName("_execle") or
- getTarget().hasGlobalName("_execlpe") or
- getTarget().hasGlobalName("_wexecle") or
- getTarget().hasGlobalName("_wexeclpe")
+ getTarget().hasGlobalName(["execle", "_execle", "_execlpe", "_wexecle", "_wexeclpe"])
}
/**
@@ -83,11 +67,7 @@ class VarargsExecFunctionCall extends FunctionCall {
* all the other ones start with the command.
*/
private int getCommandIdx() {
- if
- getTarget().getName().matches("\\_spawn%") or
- getTarget().getName().matches("\\_wspawn%")
- then result = 1
- else result = 0
+ if getTarget().getName().matches(["\\_spawn%", "\\_wspawn%"]) then result = 1 else result = 0
}
}
@@ -98,28 +78,14 @@ class VarargsExecFunctionCall extends FunctionCall {
*/
class ArrayExecFunctionCall extends FunctionCall {
ArrayExecFunctionCall() {
- getTarget().hasGlobalName("execv") or
- getTarget().hasGlobalName("execvp") or
- getTarget().hasGlobalName("execvpe") or
- getTarget().hasGlobalName("execve") or
- getTarget().hasGlobalName("fexecve") or
- // Windows variants
- getTarget().hasGlobalName("_execv") or
- getTarget().hasGlobalName("_execve") or
- getTarget().hasGlobalName("_execvp") or
- getTarget().hasGlobalName("_execvpe") or
- getTarget().hasGlobalName("_spawnv") or
- getTarget().hasGlobalName("_spawnve") or
- getTarget().hasGlobalName("_spawnvp") or
- getTarget().hasGlobalName("_spawnvpe") or
- getTarget().hasGlobalName("_wexecv") or
- getTarget().hasGlobalName("_wexecve") or
- getTarget().hasGlobalName("_wexecvp") or
- getTarget().hasGlobalName("_wexecvpe") or
- getTarget().hasGlobalName("_wspawnv") or
- getTarget().hasGlobalName("_wspawnve") or
- getTarget().hasGlobalName("_wspawnvp") or
- getTarget().hasGlobalName("_wspawnvpe")
+ getTarget()
+ .hasGlobalName([
+ "execv", "execvp", "execvpe", "execve", "fexecve",
+ // Windows variants
+ "_execv", "_execve", "_execvp", "_execvpe", "_spawnv", "_spawnve", "_spawnvp",
+ "_spawnvpe", "_wexecv", "_wexecve", "_wexecvp", "_wexecvpe", "_wspawnv", "_wspawnve",
+ "_wspawnvp", "_wspawnvpe"
+ ])
}
/** The argument with the array of command arguments */
@@ -133,11 +99,7 @@ class ArrayExecFunctionCall extends FunctionCall {
* all the other ones start with the command.
*/
private int getCommandIdx() {
- if
- getTarget().getName().matches("\\_spawn%") or
- getTarget().getName().matches("\\_wspawn%")
- then result = 1
- else result = 0
+ if getTarget().getName().matches(["\\_spawn%", "\\_wspawn%"]) then result = 1 else result = 0
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/FileWrite.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/FileWrite.qll
index 7c3d893b471..dc421e8f3ae 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/FileWrite.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/FileWrite.qll
@@ -52,9 +52,9 @@ class BasicOStreamClass extends Type {
*/
class BasicOStreamCall extends FunctionCall {
BasicOStreamCall() {
- if getTarget() instanceof MemberFunction
- then getQualifier().getType() instanceof BasicOStreamClass
- else getArgument(0).getType() instanceof BasicOStreamClass
+ if this.getTarget() instanceof MemberFunction
+ then this.getQualifier().getType() instanceof BasicOStreamClass
+ else this.getArgument(0).getType() instanceof BasicOStreamClass
}
}
@@ -77,10 +77,10 @@ abstract class ChainedOutputCall extends BasicOStreamCall {
*/
Expr getEndDest() {
// recurse into the destination
- result = getDest().(ChainedOutputCall).getEndDest()
+ result = this.getDest().(ChainedOutputCall).getEndDest()
or
// or return something other than a ChainedOutputCall
- result = getDest() and
+ result = this.getDest() and
not result instanceof ChainedOutputCall
}
}
@@ -89,18 +89,18 @@ abstract class ChainedOutputCall extends BasicOStreamCall {
* A call to `operator<<` on an output stream.
*/
class OperatorLShiftCall extends ChainedOutputCall {
- OperatorLShiftCall() { getTarget().(Operator).hasName("operator<<") }
+ OperatorLShiftCall() { this.getTarget().(Operator).hasName("operator<<") }
override Expr getSource() {
- if getTarget() instanceof MemberFunction
- then result = getArgument(0)
- else result = getArgument(1)
+ if this.getTarget() instanceof MemberFunction
+ then result = this.getArgument(0)
+ else result = this.getArgument(1)
}
override Expr getDest() {
- if getTarget() instanceof MemberFunction
- then result = getQualifier()
- else result = getArgument(0)
+ if this.getTarget() instanceof MemberFunction
+ then result = this.getQualifier()
+ else result = this.getArgument(0)
}
}
@@ -108,22 +108,22 @@ class OperatorLShiftCall extends ChainedOutputCall {
* A call to 'put'.
*/
class PutFunctionCall extends ChainedOutputCall {
- PutFunctionCall() { getTarget().(MemberFunction).hasName("put") }
+ PutFunctionCall() { this.getTarget().(MemberFunction).hasName("put") }
- override Expr getSource() { result = getArgument(0) }
+ override Expr getSource() { result = this.getArgument(0) }
- override Expr getDest() { result = getQualifier() }
+ override Expr getDest() { result = this.getQualifier() }
}
/**
* A call to 'write'.
*/
class WriteFunctionCall extends ChainedOutputCall {
- WriteFunctionCall() { getTarget().(MemberFunction).hasName("write") }
+ WriteFunctionCall() { this.getTarget().(MemberFunction).hasName("write") }
- override Expr getSource() { result = getArgument(0) }
+ override Expr getSource() { result = this.getArgument(0) }
- override Expr getDest() { result = getQualifier() }
+ override Expr getDest() { result = this.getQualifier() }
}
/**
@@ -173,6 +173,6 @@ private predicate fileWriteWithConvChar(FormattingFunctionCall ffc, Expr source,
source = ffc.getFormatArgument(n)
|
exists(f.getOutputParameterIndex(true)) and
- conv = ffc.(FormattingFunctionCall).getFormat().(FormatLiteral).getConversionChar(n)
+ conv = ffc.getFormat().(FormatLiteral).getConversionChar(n)
)
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/FlowSources.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/FlowSources.qll
index b080651951f..d2c90a38075 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/FlowSources.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/FlowSources.qll
@@ -24,7 +24,7 @@ private class RemoteReturnSource extends RemoteFlowSource {
RemoteReturnSource() {
exists(RemoteFlowSourceFunction func, CallInstruction instr, FunctionOutput output |
- asInstruction() = instr and
+ this.asInstruction() = instr and
instr.getStaticCallTarget() = func and
func.hasRemoteFlowSource(output, sourceType) and
(
@@ -43,7 +43,7 @@ private class RemoteParameterSource extends RemoteFlowSource {
RemoteParameterSource() {
exists(RemoteFlowSourceFunction func, WriteSideEffectInstruction instr, FunctionOutput output |
- asInstruction() = instr and
+ this.asInstruction() = instr and
instr.getPrimaryInstruction().(CallInstruction).getStaticCallTarget() = func and
func.hasRemoteFlowSource(output, sourceType) and
output.isParameterDerefOrQualifierObject(instr.getIndex())
@@ -58,7 +58,7 @@ private class LocalReturnSource extends LocalFlowSource {
LocalReturnSource() {
exists(LocalFlowSourceFunction func, CallInstruction instr, FunctionOutput output |
- asInstruction() = instr and
+ this.asInstruction() = instr and
instr.getStaticCallTarget() = func and
func.hasLocalFlowSource(output, sourceType) and
(
@@ -77,7 +77,7 @@ private class LocalParameterSource extends LocalFlowSource {
LocalParameterSource() {
exists(LocalFlowSourceFunction func, WriteSideEffectInstruction instr, FunctionOutput output |
- asInstruction() = instr and
+ this.asInstruction() = instr and
instr.getPrimaryInstruction().(CallInstruction).getStaticCallTarget() = func and
func.hasLocalFlowSource(output, sourceType) and
output.isParameterDerefOrQualifierObject(instr.getIndex())
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/FunctionWithWrappers.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/FunctionWithWrappers.qll
index 654e9d92451..b7a7a95a427 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/FunctionWithWrappers.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/FunctionWithWrappers.qll
@@ -77,7 +77,7 @@ abstract class FunctionWithWrappers extends Function {
) {
// base case
func = this and
- interestingArg(paramIndex) and
+ this.interestingArg(paramIndex) and
callChain = toCause(func, paramIndex) and
depth = 0
or
@@ -101,7 +101,7 @@ abstract class FunctionWithWrappers extends Function {
private predicate wrapperFunctionAnyDepth(Function func, int paramIndex, string cause) {
// base case
func = this and
- interestingArg(paramIndex) and
+ this.interestingArg(paramIndex) and
cause = toCause(func, paramIndex)
or
// recursive step
@@ -147,7 +147,7 @@ abstract class FunctionWithWrappers extends Function {
)
or
not this.wrapperFunctionLimitedDepth(func, paramIndex, _, _) and
- cause = wrapperFunctionAnyDepthUnique(func, paramIndex)
+ cause = this.wrapperFunctionAnyDepthUnique(func, paramIndex)
}
/**
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/OutputWrite.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/OutputWrite.qll
index 9ed22aa970f..affb9954926 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/OutputWrite.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/OutputWrite.qll
@@ -21,14 +21,12 @@ class OutputWrite extends Expr {
* A standard output or standard error variable.
*/
private predicate outputVariable(Variable v) {
- // standard output
- v.hasName("cout") or
- v.hasName("wcout") or
- // standard error
- v.hasName("cerr") or
- v.hasName("clog") or
- v.hasName("wcerr") or
- v.hasName("wclog")
+ v.hasName([
+ // standard output
+ "cout", "wcout",
+ // standard error
+ "cerr", "clog", "wcerr", "wclog"
+ ])
}
/**
@@ -64,10 +62,7 @@ private predicate outputWrite(Expr write, Expr source) {
arg >= f.(FormattingFunction).getFormatParameterIndex()
or
// puts, putchar
- (
- f.hasGlobalOrStdName("puts") or
- f.hasGlobalOrStdName("putchar")
- ) and
+ f.hasGlobalOrStdName(["puts", "putchar"]) and
arg = 0
or
exists(Call wrappedCall, Expr wrappedSource |
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/Security.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/Security.qll
index da808592b3e..7a73144f5fa 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/Security.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/Security.qll
@@ -78,7 +78,7 @@ class SecurityOptions extends string {
functionCall.getTarget().getName() = fname and
(
fname = ["fgets", "gets"] or
- userInputReturn(fname)
+ this.userInputReturn(fname)
)
)
or
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/SensitiveExprs.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/SensitiveExprs.qll
index 22e0ee71b66..389129835cb 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/SensitiveExprs.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/SensitiveExprs.qll
@@ -11,17 +11,8 @@ import cpp
*/
bindingset[s]
private predicate suspicious(string s) {
- (
- s.matches("%password%") or
- s.matches("%passwd%") or
- s.matches("%trusted%")
- ) and
- not (
- s.matches("%hash%") or
- s.matches("%crypt%") or
- s.matches("%file%") or
- s.matches("%path%")
- )
+ s.matches(["%password%", "%passwd%", "%trusted%"]) and
+ not s.matches(["%hash%", "%crypt%", "%file%", "%path%"])
}
/**
@@ -29,7 +20,7 @@ private predicate suspicious(string s) {
*/
class SensitiveVariable extends Variable {
SensitiveVariable() {
- suspicious(getName().toLowerCase()) and
+ suspicious(this.getName().toLowerCase()) and
not this.getUnspecifiedType() instanceof IntegralType
}
}
@@ -39,7 +30,7 @@ class SensitiveVariable extends Variable {
*/
class SensitiveFunction extends Function {
SensitiveFunction() {
- suspicious(getName().toLowerCase()) and
+ suspicious(this.getName().toLowerCase()) and
not this.getUnspecifiedType() instanceof IntegralType
}
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/boostorg/asio/protocols.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/boostorg/asio/protocols.qll
index e113d5e5745..c9d6b6613d8 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/boostorg/asio/protocols.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/security/boostorg/asio/protocols.qll
@@ -113,7 +113,7 @@ module BoostorgAsio {
result.getName() = "tls_server"
)
or
- result = getASslv23ProtocolConstant()
+ result = this.getASslv23ProtocolConstant()
}
/**
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/stmts/Block.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/stmts/Block.qll
index 3bebc660456..5fe8798cebb 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/stmts/Block.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/stmts/Block.qll
@@ -76,9 +76,9 @@ class BlockStmt extends Stmt, @stmt_block {
* the result is the expression statement `a = b`.
*/
Stmt getLastStmtIn() {
- if getLastStmt() instanceof BlockStmt
- then result = getLastStmt().(BlockStmt).getLastStmtIn()
- else result = getLastStmt()
+ if this.getLastStmt() instanceof BlockStmt
+ then result = this.getLastStmt().(BlockStmt).getLastStmtIn()
+ else result = this.getLastStmt()
}
/**
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/stmts/Stmt.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/stmts/Stmt.qll
index ed1fb4fbb50..af68daf025c 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/stmts/Stmt.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/stmts/Stmt.qll
@@ -27,10 +27,10 @@ class Stmt extends StmtParent, @stmt {
*/
BlockStmt getEnclosingBlock() {
if
- getParentStmt() instanceof BlockStmt and
- not getParentStmt().(BlockStmt).getLocation() instanceof UnknownLocation
- then result = getParentStmt()
- else result = getParentStmt().getEnclosingBlock()
+ this.getParentStmt() instanceof BlockStmt and
+ not this.getParentStmt().(BlockStmt).getLocation() instanceof UnknownLocation
+ then result = this.getParentStmt()
+ else result = this.getParentStmt().getEnclosingBlock()
}
/** Gets a child of this statement. */
@@ -438,7 +438,7 @@ class WhileStmt extends Loop, @stmt_while {
* while(1) { ...; if(b) break; ...; }
* ```
*/
- predicate conditionAlwaysTrue() { conditionAlwaysTrue(getCondition()) }
+ predicate conditionAlwaysTrue() { conditionAlwaysTrue(this.getCondition()) }
/**
* Holds if the loop condition is provably `false`.
@@ -448,7 +448,7 @@ class WhileStmt extends Loop, @stmt_while {
* while(0) { ...; }
* ```
*/
- predicate conditionAlwaysFalse() { conditionAlwaysFalse(getCondition()) }
+ predicate conditionAlwaysFalse() { conditionAlwaysFalse(this.getCondition()) }
/**
* Holds if the loop condition is provably `true` upon entry,
@@ -857,7 +857,7 @@ class RangeBasedForStmt extends Loop, @stmt_range_based_for {
* ```
* the result is `int x`.
*/
- LocalVariable getVariable() { result = getChild(4).(DeclStmt).getADeclaration() }
+ LocalVariable getVariable() { result = this.getChild(4).(DeclStmt).getADeclaration() }
/**
* Gets the expression giving the range to iterate over.
@@ -868,10 +868,10 @@ class RangeBasedForStmt extends Loop, @stmt_range_based_for {
* ```
* the result is `xs`.
*/
- Expr getRange() { result = getRangeVariable().getInitializer().getExpr() }
+ Expr getRange() { result = this.getRangeVariable().getInitializer().getExpr() }
/** Gets the compiler-generated `__range` variable after desugaring. */
- LocalVariable getRangeVariable() { result = getChild(0).(DeclStmt).getADeclaration() }
+ LocalVariable getRangeVariable() { result = this.getChild(0).(DeclStmt).getADeclaration() }
/**
* Gets the compiler-generated `__begin != __end` which is the
@@ -891,10 +891,10 @@ class RangeBasedForStmt extends Loop, @stmt_range_based_for {
DeclStmt getBeginEndDeclaration() { result = this.getChild(1) }
/** Gets the compiler-generated `__begin` variable after desugaring. */
- LocalVariable getBeginVariable() { result = getBeginEndDeclaration().getDeclaration(0) }
+ LocalVariable getBeginVariable() { result = this.getBeginEndDeclaration().getDeclaration(0) }
/** Gets the compiler-generated `__end` variable after desugaring. */
- LocalVariable getEndVariable() { result = getBeginEndDeclaration().getDeclaration(1) }
+ LocalVariable getEndVariable() { result = this.getBeginEndDeclaration().getDeclaration(1) }
/**
* Gets the compiler-generated `++__begin` which is the update
@@ -905,7 +905,7 @@ class RangeBasedForStmt extends Loop, @stmt_range_based_for {
Expr getUpdate() { result = this.getChild(3) }
/** Gets the compiler-generated `__begin` variable after desugaring. */
- LocalVariable getAnIterationVariable() { result = getBeginVariable() }
+ LocalVariable getAnIterationVariable() { result = this.getBeginVariable() }
}
/**
@@ -1067,7 +1067,7 @@ class ForStmt extends Loop, @stmt_for {
* for(x = 0; 1; ++x) { sum += x; }
* ```
*/
- predicate conditionAlwaysTrue() { conditionAlwaysTrue(getCondition()) }
+ predicate conditionAlwaysTrue() { conditionAlwaysTrue(this.getCondition()) }
/**
* Holds if the loop condition is provably `false`.
@@ -1077,7 +1077,7 @@ class ForStmt extends Loop, @stmt_for {
* for(x = 0; 0; ++x) { sum += x; }
* ```
*/
- predicate conditionAlwaysFalse() { conditionAlwaysFalse(getCondition()) }
+ predicate conditionAlwaysFalse() { conditionAlwaysFalse(this.getCondition()) }
/**
* Holds if the loop condition is provably `true` upon entry,
@@ -1723,10 +1723,10 @@ class Handler extends Stmt, @stmt_handler {
/**
* Gets the block containing the implementation of this handler.
*/
- CatchBlock getBlock() { result = getChild(0) }
+ CatchBlock getBlock() { result = this.getChild(0) }
/** Gets the 'try' statement corresponding to this 'catch block'. */
- TryStmt getTryStmt() { result = getParent() }
+ TryStmt getTryStmt() { result = this.getParent() }
/**
* Gets the parameter introduced by this 'catch block', if any.
@@ -1734,7 +1734,7 @@ class Handler extends Stmt, @stmt_handler {
* For example, `catch(std::exception& e)` introduces a
* parameter `e`, whereas `catch(...)` does not introduce a parameter.
*/
- Parameter getParameter() { result = getBlock().getParameter() }
+ Parameter getParameter() { result = this.getBlock().getParameter() }
override predicate mayBeImpure() { none() }
@@ -1921,15 +1921,15 @@ class MicrosoftTryStmt extends Stmt, @stmt_microsoft_try {
* This is a Microsoft C/C++ extension.
*/
class MicrosoftTryExceptStmt extends MicrosoftTryStmt {
- MicrosoftTryExceptStmt() { getChild(1) instanceof Expr }
+ MicrosoftTryExceptStmt() { this.getChild(1) instanceof Expr }
override string toString() { result = "__try { ... } __except( ... ) { ... }" }
/** Gets the expression guarding the `__except` statement. */
- Expr getCondition() { result = getChild(1) }
+ Expr getCondition() { result = this.getChild(1) }
/** Gets the `__except` statement (usually a `BlockStmt`). */
- Stmt getExcept() { result = getChild(2) }
+ Stmt getExcept() { result = this.getChild(2) }
override string getAPrimaryQlClass() { result = "MicrosoftTryExceptStmt" }
}
@@ -1948,12 +1948,12 @@ class MicrosoftTryExceptStmt extends MicrosoftTryStmt {
* This is a Microsoft C/C++ extension.
*/
class MicrosoftTryFinallyStmt extends MicrosoftTryStmt {
- MicrosoftTryFinallyStmt() { not getChild(1) instanceof Expr }
+ MicrosoftTryFinallyStmt() { not this.getChild(1) instanceof Expr }
override string toString() { result = "__try { ... } __finally { ... }" }
/** Gets the `__finally` statement (usually a `BlockStmt`). */
- Stmt getFinally() { result = getChild(1) }
+ Stmt getFinally() { result = this.getChild(1) }
override string getAPrimaryQlClass() { result = "MicrosoftTryFinallyStmt" }
}
diff --git a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/valuenumbering/HashCons.qll b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/valuenumbering/HashCons.qll
index c16389ce9bf..0073154dd3c 100644
--- a/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/valuenumbering/HashCons.qll
+++ b/repo-tests/codeql/cpp/ql/lib/semmle/code/cpp/valuenumbering/HashCons.qll
@@ -589,7 +589,7 @@ private predicate mk_HasAlloc(HashCons hc, NewOrNewArrayExpr new) {
}
private predicate mk_HasExtent(HashCons hc, NewArrayExpr new) {
- hc = hashCons(new.(NewArrayExpr).getExtent().getFullyConverted())
+ hc = hashCons(new.getExtent().getFullyConverted())
}
private predicate analyzableNewExpr(NewExpr new) {
@@ -619,7 +619,7 @@ private predicate analyzableNewArrayExpr(NewArrayExpr new) {
strictcount(new.getAllocatedType().getUnspecifiedType()) = 1 and
count(new.getAllocatorCall().getFullyConverted()) <= 1 and
count(new.getInitializer().getFullyConverted()) <= 1 and
- count(new.(NewArrayExpr).getExtent().getFullyConverted()) <= 1
+ count(new.getExtent().getFullyConverted()) <= 1
}
private predicate mk_NewArrayExpr(
diff --git a/repo-tests/codeql/cpp/ql/src/Architecture/Refactoring Opportunities/ClassesWithManyFields.ql b/repo-tests/codeql/cpp/ql/src/Architecture/Refactoring Opportunities/ClassesWithManyFields.ql
index 5f11a9e0830..d2c69ba5fff 100644
--- a/repo-tests/codeql/cpp/ql/src/Architecture/Refactoring Opportunities/ClassesWithManyFields.ql
+++ b/repo-tests/codeql/cpp/ql/src/Architecture/Refactoring Opportunities/ClassesWithManyFields.ql
@@ -68,12 +68,12 @@ class VariableDeclarationLine extends TVariableDeclarationInfo {
/**
* Gets the start column of the first `VariableDeclarationEntry` on this line.
*/
- int getStartColumn() { result = min(getAVDE().getLocation().getStartColumn()) }
+ int getStartColumn() { result = min(this.getAVDE().getLocation().getStartColumn()) }
/**
* Gets the end column of the last `VariableDeclarationEntry` on this line.
*/
- int getEndColumn() { result = max(getAVDE().getLocation().getEndColumn()) }
+ int getEndColumn() { result = max(this.getAVDE().getLocation().getEndColumn()) }
/**
* Gets the rank of this `VariableDeclarationLine` in its file and class
@@ -89,14 +89,14 @@ class VariableDeclarationLine extends TVariableDeclarationInfo {
*/
VariableDeclarationLine getNext() {
result = TVariableDeclarationLine(c, f, _) and
- result.getRank() = getRank() + 1
+ result.getRank() = this.getRank() + 1
}
/**
* Gets the `VariableDeclarationLine` following this one, if it is nearby.
*/
VariableDeclarationLine getProximateNext() {
- result = getNext() and
+ result = this.getNext() and
result.getLine() <= this.getLine() + 3
}
@@ -114,14 +114,14 @@ class VariableDeclarationGroup extends VariableDeclarationLine {
// there is no `VariableDeclarationLine` within three lines previously
not any(VariableDeclarationLine prev).getProximateNext() = this and
// `end` is the last transitively proximate line
- end = getProximateNext*() and
+ end = this.getProximateNext*() and
not exists(end.getProximateNext())
}
predicate hasLocationInfo(string path, int startline, int startcol, int endline, int endcol) {
path = f.getAbsolutePath() and
- startline = getLine() and
- startcol = getStartColumn() and
+ startline = this.getLine() and
+ startcol = this.getStartColumn() and
endline = end.getLine() and
endcol = end.getEndColumn()
}
@@ -132,18 +132,18 @@ class VariableDeclarationGroup extends VariableDeclarationLine {
int getCount() {
result =
count(VariableDeclarationLine l |
- l = getProximateNext*()
+ l = this.getProximateNext*()
|
l.getAVDE().getVariable().getName()
)
}
override string toString() {
- getCount() = 1 and
- result = "declaration of " + getAVDE().getVariable().getName()
+ this.getCount() = 1 and
+ result = "declaration of " + this.getAVDE().getVariable().getName()
or
- getCount() > 1 and
- result = "group of " + getCount() + " fields here"
+ this.getCount() > 1 and
+ result = "group of " + this.getCount() + " fields here"
}
}
diff --git a/repo-tests/codeql/cpp/ql/src/Best Practices/Likely Errors/EmptyBlock.ql b/repo-tests/codeql/cpp/ql/src/Best Practices/Likely Errors/EmptyBlock.ql
index 9fa8c4e5e3f..6e434c85c95 100644
--- a/repo-tests/codeql/cpp/ql/src/Best Practices/Likely Errors/EmptyBlock.ql
+++ b/repo-tests/codeql/cpp/ql/src/Best Practices/Likely Errors/EmptyBlock.ql
@@ -81,9 +81,8 @@ class BlockOrNonChild extends Element {
predicate emptyBlockContainsNonchild(BlockStmt b) {
emptyBlock(_, b) and
exists(BlockOrNonChild c, AffectedFile file |
- c.(BlockOrNonChild).getStartRankIn(file) = 1 + b.(BlockOrNonChild).getStartRankIn(file) and
- c.(BlockOrNonChild).getNonContiguousEndRankIn(file) <
- b.(BlockOrNonChild).getNonContiguousEndRankIn(file)
+ c.getStartRankIn(file) = 1 + b.(BlockOrNonChild).getStartRankIn(file) and
+ c.getNonContiguousEndRankIn(file) < b.(BlockOrNonChild).getNonContiguousEndRankIn(file)
)
}
diff --git a/repo-tests/codeql/cpp/ql/src/Best Practices/Magic Constants/MagicConstants.qll b/repo-tests/codeql/cpp/ql/src/Best Practices/Magic Constants/MagicConstants.qll
index 587b64b60b3..fce3d286a5f 100644
--- a/repo-tests/codeql/cpp/ql/src/Best Practices/Magic Constants/MagicConstants.qll
+++ b/repo-tests/codeql/cpp/ql/src/Best Practices/Magic Constants/MagicConstants.qll
@@ -58,15 +58,7 @@ predicate intTrivial(Literal lit) { exists(string v | trivialIntValue(v) and v =
predicate longTrivial(Literal lit) { exists(string v | trivialLongValue(v) and v = lit.getValue()) }
predicate powerOfTen(float f) {
- f = 10 or
- f = 100 or
- f = 1000 or
- f = 10000 or
- f = 100000 or
- f = 1000000 or
- f = 10000000 or
- f = 100000000 or
- f = 1000000000
+ f = [10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000]
}
predicate floatTrivial(Literal lit) {
diff --git a/repo-tests/codeql/cpp/ql/src/Best Practices/Unused Entities/UnusedStaticFunctions.ql b/repo-tests/codeql/cpp/ql/src/Best Practices/Unused Entities/UnusedStaticFunctions.ql
index 2d7649d534e..4a08ce4fb4b 100644
--- a/repo-tests/codeql/cpp/ql/src/Best Practices/Unused Entities/UnusedStaticFunctions.ql
+++ b/repo-tests/codeql/cpp/ql/src/Best Practices/Unused Entities/UnusedStaticFunctions.ql
@@ -62,7 +62,7 @@ class Thing extends Locatable {
}
Thing callsOrAccesses() {
- this.(Function).calls(result.(Function))
+ this.(Function).calls(result)
or
this.(Function).accesses(result.(Function))
or
diff --git a/repo-tests/codeql/cpp/ql/src/JPL_C/LOC-2/Rule 09/Semaphores.qll b/repo-tests/codeql/cpp/ql/src/JPL_C/LOC-2/Rule 09/Semaphores.qll
index 0b60a3b9877..543c516e4bf 100644
--- a/repo-tests/codeql/cpp/ql/src/JPL_C/LOC-2/Rule 09/Semaphores.qll
+++ b/repo-tests/codeql/cpp/ql/src/JPL_C/LOC-2/Rule 09/Semaphores.qll
@@ -50,7 +50,7 @@ class SemaphoreTake extends LockOperation {
result.(SemaphoreGive).getLocked() = this.getLocked()
}
- override string say() { result = "semaphore take of " + getLocked().getName() }
+ override string say() { result = "semaphore take of " + this.getLocked().getName() }
}
class SemaphoreGive extends UnlockOperation {
@@ -76,13 +76,13 @@ class LockingPrimitive extends FunctionCall, LockOperation {
this.getTarget().getName().replaceAll("Lock", "Unlock")
}
- override string say() { result = "call to " + getLocked().getName() }
+ override string say() { result = "call to " + this.getLocked().getName() }
}
class UnlockingPrimitive extends FunctionCall, UnlockOperation {
UnlockingPrimitive() { this.getTarget().getName() = ["taskUnlock", "intUnlock", "taskRtpUnlock"] }
- Function getLocked() { result = getMatchingLock().getLocked() }
+ Function getLocked() { result = this.getMatchingLock().getLocked() }
override LockOperation getMatchingLock() { this = result.getMatchingUnlock() }
}
diff --git a/repo-tests/codeql/cpp/ql/src/Likely Bugs/Conversion/NonzeroValueCastToPointer.ql b/repo-tests/codeql/cpp/ql/src/Likely Bugs/Conversion/NonzeroValueCastToPointer.ql
index 87f52198b0d..47842118874 100644
--- a/repo-tests/codeql/cpp/ql/src/Likely Bugs/Conversion/NonzeroValueCastToPointer.ql
+++ b/repo-tests/codeql/cpp/ql/src/Likely Bugs/Conversion/NonzeroValueCastToPointer.ql
@@ -13,14 +13,15 @@
import cpp
predicate commonErrorCode(string value) {
- value = "0" or
- value = "1" or
- value = "-1" or
- value = "18446744073709551615" or // 2^64-1, i.e. -1 as an unsigned int64
- value = "4294967295" or // 2^32-1, i.e. -1 as an unsigned int32
- value = "3735928559" or // 0xdeadbeef
- value = "3735929054" or // 0xdeadc0de
- value = "3405691582" // 0xcafebabe
+ value =
+ [
+ "0", "1", "-1", // common error codes
+ "18446744073709551615", // 2^64-1, i.e. -1 as an unsigned int64
+ "4294967295", // 2^32-1, i.e. -1 as an unsigned int32
+ "3735928559", // 0xdeadbeef
+ "3735929054", // 0xdeadc0de
+ "3405691582" // 0xcafebabe
+ ]
}
from Expr e
diff --git a/repo-tests/codeql/cpp/ql/src/Likely Bugs/Format/NonConstantFormat.ql b/repo-tests/codeql/cpp/ql/src/Likely Bugs/Format/NonConstantFormat.ql
index f00dfa2213b..2a04669e85b 100644
--- a/repo-tests/codeql/cpp/ql/src/Likely Bugs/Format/NonConstantFormat.ql
+++ b/repo-tests/codeql/cpp/ql/src/Likely Bugs/Format/NonConstantFormat.ql
@@ -63,14 +63,14 @@ predicate cannotContainString(Type t) {
predicate isNonConst(DataFlow::Node node) {
exists(Expr e | e = node.asExpr() |
- exists(FunctionCall fc | fc = e.(FunctionCall) |
+ exists(FunctionCall fc | fc = e |
not (
whitelistFunction(fc.getTarget(), _) or
fc.getTarget().hasDefinition()
)
)
or
- exists(Parameter p | p = e.(VariableAccess).getTarget().(Parameter) |
+ exists(Parameter p | p = e.(VariableAccess).getTarget() |
p.getFunction().getName() = "main" and p.getType() instanceof PointerType
)
or
diff --git a/repo-tests/codeql/cpp/ql/src/Likely Bugs/Leap Year/LeapYear.qll b/repo-tests/codeql/cpp/ql/src/Likely Bugs/Leap Year/LeapYear.qll
index 23b66bd94a6..e164523700b 100644
--- a/repo-tests/codeql/cpp/ql/src/Likely Bugs/Leap Year/LeapYear.qll
+++ b/repo-tests/codeql/cpp/ql/src/Likely Bugs/Leap Year/LeapYear.qll
@@ -10,7 +10,7 @@ import semmle.code.cpp.commons.DateTime
* Get the top-level `BinaryOperation` enclosing the expression e.
*/
private BinaryOperation getATopLevelBinaryOperationExpression(Expr e) {
- result = e.getEnclosingElement().(BinaryOperation)
+ result = e.getEnclosingElement()
or
result = getATopLevelBinaryOperationExpression(e.getEnclosingElement())
}
diff --git a/repo-tests/codeql/cpp/ql/src/Likely Bugs/Likely Typos/ExprHasNoEffect.ql b/repo-tests/codeql/cpp/ql/src/Likely Bugs/Likely Typos/ExprHasNoEffect.ql
index d3a7dcb8939..6f240fa2d2a 100644
--- a/repo-tests/codeql/cpp/ql/src/Likely Bugs/Likely Typos/ExprHasNoEffect.ql
+++ b/repo-tests/codeql/cpp/ql/src/Likely Bugs/Likely Typos/ExprHasNoEffect.ql
@@ -66,7 +66,7 @@ predicate functionDefinedInIfDefRecursive(Function f) {
*/
predicate baseCall(FunctionCall call) {
call.getNameQualifier().getQualifyingElement() =
- call.getEnclosingFunction().getDeclaringType().(Class).getABaseClass+()
+ call.getEnclosingFunction().getDeclaringType().getABaseClass+()
}
from PureExprInVoidContext peivc, Locatable parent, Locatable info, string info_text, string tail
diff --git a/repo-tests/codeql/cpp/ql/src/Likely Bugs/Memory Management/ImproperNullTermination.ql b/repo-tests/codeql/cpp/ql/src/Likely Bugs/Memory Management/ImproperNullTermination.ql
index 5c92b0a3db7..ed378dce60a 100644
--- a/repo-tests/codeql/cpp/ql/src/Likely Bugs/Memory Management/ImproperNullTermination.ql
+++ b/repo-tests/codeql/cpp/ql/src/Likely Bugs/Memory Management/ImproperNullTermination.ql
@@ -5,7 +5,6 @@
* @kind problem
* @id cpp/improper-null-termination
* @problem.severity warning
- * @precision medium
* @security-severity 7.8
* @tags security
* external/cwe/cwe-170
diff --git a/repo-tests/codeql/cpp/ql/src/Likely Bugs/Memory Management/NtohlArrayNoBound.qll b/repo-tests/codeql/cpp/ql/src/Likely Bugs/Memory Management/NtohlArrayNoBound.qll
index 9606fb968ec..37d5a5e5c1b 100644
--- a/repo-tests/codeql/cpp/ql/src/Likely Bugs/Memory Management/NtohlArrayNoBound.qll
+++ b/repo-tests/codeql/cpp/ql/src/Likely Bugs/Memory Management/NtohlArrayNoBound.qll
@@ -126,13 +126,7 @@ class MallocSizeExpr extends BufferAccess, FunctionCall {
}
class NetworkFunctionCall extends FunctionCall {
- NetworkFunctionCall() {
- getTarget().hasName("ntohd") or
- getTarget().hasName("ntohf") or
- getTarget().hasName("ntohl") or
- getTarget().hasName("ntohll") or
- getTarget().hasName("ntohs")
- }
+ NetworkFunctionCall() { getTarget().hasName(["ntohd", "ntohf", "ntohl", "ntohll", "ntohs"]) }
}
class NetworkToBufferSizeConfiguration extends DataFlow::Configuration {
diff --git a/repo-tests/codeql/cpp/ql/src/Likely Bugs/Memory Management/StrncpyFlippedArgs.ql b/repo-tests/codeql/cpp/ql/src/Likely Bugs/Memory Management/StrncpyFlippedArgs.ql
index 8e7bc5bfcf4..f7eca2304b3 100644
--- a/repo-tests/codeql/cpp/ql/src/Likely Bugs/Memory Management/StrncpyFlippedArgs.ql
+++ b/repo-tests/codeql/cpp/ql/src/Likely Bugs/Memory Management/StrncpyFlippedArgs.ql
@@ -43,23 +43,25 @@ predicate isSizePlus(Expr e, BufferSizeExpr baseSize, int plus) {
predicate strncpyFunction(Function f, int argDest, int argSrc, int argLimit) {
exists(string name | name = f.getName() |
- (
- name = "strcpy_s" or // strcpy_s(dst, max_amount, src)
- name = "wcscpy_s" or // wcscpy_s(dst, max_amount, src)
- name = "_mbscpy_s" // _mbscpy_s(dst, max_amount, src)
- ) and
+ name =
+ [
+ "strcpy_s", // strcpy_s(dst, max_amount, src)
+ "wcscpy_s", // wcscpy_s(dst, max_amount, src)
+ "_mbscpy_s" // _mbscpy_s(dst, max_amount, src)
+ ] and
argDest = 0 and
argSrc = 2 and
argLimit = 1
or
- (
- name = "strncpy" or // strncpy(dst, src, max_amount)
- name = "strncpy_l" or // strncpy_l(dst, src, max_amount, locale)
- name = "wcsncpy" or // wcsncpy(dst, src, max_amount)
- name = "_wcsncpy_l" or // _wcsncpy_l(dst, src, max_amount, locale)
- name = "_mbsncpy" or // _mbsncpy(dst, src, max_amount)
- name = "_mbsncpy_l" // _mbsncpy_l(dst, src, max_amount, locale)
- ) and
+ name =
+ [
+ "strncpy", // strncpy(dst, src, max_amount)
+ "strncpy_l", // strncpy_l(dst, src, max_amount, locale)
+ "wcsncpy", // wcsncpy(dst, src, max_amount)
+ "_wcsncpy_l", // _wcsncpy_l(dst, src, max_amount, locale)
+ "_mbsncpy", // _mbsncpy(dst, src, max_amount)
+ "_mbsncpy_l" // _mbsncpy_l(dst, src, max_amount, locale)
+ ] and
argDest = 0 and
argSrc = 1 and
argLimit = 2
diff --git a/repo-tests/codeql/cpp/ql/src/Metrics/Files/FCyclomaticComplexity.ql b/repo-tests/codeql/cpp/ql/src/Metrics/Files/FCyclomaticComplexity.ql
index 8aae5042ca4..3c6953caa5a 100644
--- a/repo-tests/codeql/cpp/ql/src/Metrics/Files/FCyclomaticComplexity.ql
+++ b/repo-tests/codeql/cpp/ql/src/Metrics/Files/FCyclomaticComplexity.ql
@@ -15,7 +15,7 @@ import cpp
from File f, float complexity, float loc
where
f.fromSource() and
- loc = sum(FunctionDeclarationEntry fde | fde.getFile() = f | fde.getNumberOfLines()).(float) and
+ loc = sum(FunctionDeclarationEntry fde | fde.getFile() = f | fde.getNumberOfLines()) and
if loc > 0
then
// Weighted average of complexity by function length
diff --git a/repo-tests/codeql/cpp/ql/src/Power of 10/Rule 1/UseOfJmp.ql b/repo-tests/codeql/cpp/ql/src/Power of 10/Rule 1/UseOfJmp.ql
index 9a6d143bfb6..0a98eafc70f 100644
--- a/repo-tests/codeql/cpp/ql/src/Power of 10/Rule 1/UseOfJmp.ql
+++ b/repo-tests/codeql/cpp/ql/src/Power of 10/Rule 1/UseOfJmp.ql
@@ -15,10 +15,7 @@ import cpp
class ForbiddenFunction extends Function {
ForbiddenFunction() {
exists(string name | name = this.getName() |
- name = "setjmp" or
- name = "longjmp" or
- name = "sigsetjmp" or
- name = "siglongjmp"
+ name = ["setjmp", "longjmp", "sigsetjmp", "siglongjmp"]
)
}
}
diff --git a/repo-tests/codeql/cpp/ql/src/Security/CWE/CWE-022/TaintedPath.ql b/repo-tests/codeql/cpp/ql/src/Security/CWE/CWE-022/TaintedPath.ql
index 5e22506d03a..fc8023053b0 100644
--- a/repo-tests/codeql/cpp/ql/src/Security/CWE/CWE-022/TaintedPath.ql
+++ b/repo-tests/codeql/cpp/ql/src/Security/CWE/CWE-022/TaintedPath.ql
@@ -26,12 +26,8 @@ import TaintedWithPath
class FileFunction extends FunctionWithWrappers {
FileFunction() {
exists(string nme | this.hasGlobalName(nme) |
- nme = "fopen" or
- nme = "_fopen" or
- nme = "_wfopen" or
- nme = "open" or
- nme = "_open" or
- nme = "_wopen" or
+ nme = ["fopen", "_fopen", "_wfopen", "open", "_open", "_wopen"]
+ or
// create file function on windows
nme.matches("CreateFile%")
)
@@ -40,10 +36,7 @@ class FileFunction extends FunctionWithWrappers {
or
// on any of the fstream classes, or filebuf
exists(string nme | this.getDeclaringType().hasQualifiedName("std", nme) |
- nme = "basic_fstream" or
- nme = "basic_ifstream" or
- nme = "basic_ofstream" or
- nme = "basic_filebuf"
+ nme = ["basic_fstream", "basic_ifstream", "basic_ofstream", "basic_filebuf"]
) and
// we look for either the open method or the constructor
(this.getName() = "open" or this instanceof Constructor)
diff --git a/repo-tests/codeql/cpp/ql/src/Security/CWE/CWE-170/ImproperNullTerminationTainted.ql b/repo-tests/codeql/cpp/ql/src/Security/CWE/CWE-170/ImproperNullTerminationTainted.ql
index 31ce1037b27..3b0e3f5198d 100644
--- a/repo-tests/codeql/cpp/ql/src/Security/CWE/CWE-170/ImproperNullTerminationTainted.ql
+++ b/repo-tests/codeql/cpp/ql/src/Security/CWE/CWE-170/ImproperNullTerminationTainted.ql
@@ -5,7 +5,6 @@
* @kind problem
* @id cpp/user-controlled-null-termination-tainted
* @problem.severity warning
- * @precision medium
* @security-severity 10.0
* @tags security
* external/cwe/cwe-170
@@ -22,11 +21,7 @@ class TaintSource extends VariableAccess {
this.getTarget() instanceof SemanticStackVariable and
x.isUserInput(this, cause)
|
- cause = "read" or
- cause = "fread" or
- cause = "recv" or
- cause = "recvfrom" or
- cause = "recvmsg"
+ cause = ["read", "fread", "recv", "recvfrom", "recvmsg"]
)
}
diff --git a/repo-tests/codeql/cpp/ql/src/Security/CWE/CWE-319/UseOfHttp.ql b/repo-tests/codeql/cpp/ql/src/Security/CWE/CWE-319/UseOfHttp.ql
new file mode 100644
index 00000000000..0ae7e12f90e
--- /dev/null
+++ b/repo-tests/codeql/cpp/ql/src/Security/CWE/CWE-319/UseOfHttp.ql
@@ -0,0 +1,90 @@
+/**
+ * @name Failure to use HTTPS URLs
+ * @description Non-HTTPS connections can be intercepted by third parties.
+ * @kind path-problem
+ * @problem.severity warning
+ * @precision medium
+ * @id cpp/non-https-url
+ * @tags security
+ * external/cwe/cwe-319
+ * external/cwe/cwe-345
+ */
+
+import cpp
+import semmle.code.cpp.dataflow.TaintTracking
+import DataFlow::PathGraph
+
+/**
+ * A string matching private host names of IPv4 and IPv6, which only matches
+ * the host portion therefore checking for port is not necessary.
+ * Several examples are localhost, reserved IPv4 IP addresses including
+ * 127.0.0.1, 10.x.x.x, 172.16.x,x, 192.168.x,x, and reserved IPv6 addresses
+ * including [0:0:0:0:0:0:0:1] and [::1]
+ */
+class PrivateHostName extends string {
+ bindingset[this]
+ PrivateHostName() {
+ this.regexpMatch("(?i)localhost(?:[:/?#].*)?|127\\.0\\.0\\.1(?:[:/?#].*)?|10(?:\\.[0-9]+){3}(?:[:/?#].*)?|172\\.16(?:\\.[0-9]+){2}(?:[:/?#].*)?|192.168(?:\\.[0-9]+){2}(?:[:/?#].*)?|\\[?0:0:0:0:0:0:0:1\\]?(?:[:/?#].*)?|\\[?::1\\]?(?:[:/?#].*)?")
+ }
+}
+
+/**
+ * A string containing an HTTP URL not in a private domain.
+ */
+class HttpStringLiteral extends StringLiteral {
+ HttpStringLiteral() {
+ exists(string s | this.getValue() = s |
+ s = "http"
+ or
+ exists(string tail |
+ tail = s.regexpCapture("http://(.*)", 1) and not tail instanceof PrivateHostName
+ ) and
+ not TaintTracking::localExprTaint(any(StringLiteral p |
+ p.getValue() instanceof PrivateHostName
+ ), this.getParent*())
+ )
+ }
+}
+
+/**
+ * Taint tracking configuration for HTTP connections.
+ */
+class HttpStringToUrlOpenConfig extends TaintTracking::Configuration {
+ HttpStringToUrlOpenConfig() { this = "HttpStringToUrlOpenConfig" }
+
+ override predicate isSource(DataFlow::Node src) {
+ // Sources are strings containing an HTTP URL not in a private domain.
+ src.asExpr() instanceof HttpStringLiteral
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ // Sinks can be anything that demonstrates the string is likely to be
+ // accessed as a URL, for example using it in a network access. Some
+ // URLs are only ever displayed or used for data processing.
+ exists(FunctionCall fc |
+ fc.getTarget()
+ .hasGlobalOrStdName([
+ "system", "gethostbyname", "gethostbyname2", "gethostbyname_r", "getaddrinfo",
+ "X509_load_http", "X509_CRL_load_http"
+ ]) and
+ sink.asExpr() = fc.getArgument(0)
+ or
+ fc.getTarget().hasGlobalOrStdName(["send", "URLDownloadToFile", "URLDownloadToCacheFile"]) and
+ sink.asExpr() = fc.getArgument(1)
+ or
+ fc.getTarget().hasGlobalOrStdName(["curl_easy_setopt", "getnameinfo"]) and
+ sink.asExpr() = fc.getArgument(2)
+ or
+ fc.getTarget().hasGlobalOrStdName(["ShellExecute", "ShellExecuteA", "ShellExecuteW"]) and
+ sink.asExpr() = fc.getArgument(3)
+ )
+ }
+}
+
+from
+ HttpStringToUrlOpenConfig config, DataFlow::PathNode source, DataFlow::PathNode sink,
+ HttpStringLiteral str
+where
+ config.hasFlowPath(source, sink) and
+ str = source.getNode().asExpr()
+select str, source, sink, "A URL may be constructed with the HTTP protocol."
diff --git a/repo-tests/codeql/cpp/ql/src/Security/CWE/CWE-497/ExposedSystemData.ql b/repo-tests/codeql/cpp/ql/src/Security/CWE/CWE-497/ExposedSystemData.ql
index bbe3b0805e1..b9176af1c68 100644
--- a/repo-tests/codeql/cpp/ql/src/Security/CWE/CWE-497/ExposedSystemData.ql
+++ b/repo-tests/codeql/cpp/ql/src/Security/CWE/CWE-497/ExposedSystemData.ql
@@ -103,12 +103,7 @@ private predicate posixSystemInfo(FunctionCall source, Element use) {
// - various filesystem parameters
// int uname(struct utsname *buf)
// - OS name and version
- (
- source.getTarget().hasName("confstr") or
- source.getTarget().hasName("statvfs") or
- source.getTarget().hasName("fstatvfs") or
- source.getTarget().hasName("uname")
- ) and
+ source.getTarget().hasName(["confstr", "statvfs", "fstatvfs", "uname"]) and
use = source.getArgument(1)
}
@@ -128,14 +123,9 @@ private predicate posixPWInfo(FunctionCall source, Element use) {
// struct group *getgrnam(const char *name);
// struct group *getgrgid(gid_t);
// struct group *getgrent(void);
- (
- source.getTarget().hasName("getpwnam") or
- source.getTarget().hasName("getpwuid") or
- source.getTarget().hasName("getpwent") or
- source.getTarget().hasName("getgrnam") or
- source.getTarget().hasName("getgrgid") or
- source.getTarget().hasName("getgrent")
- ) and
+ source
+ .getTarget()
+ .hasName(["getpwnam", "getpwuid", "getpwent", "getgrnam", "getgrgid", "getgrent"]) and
use = source
or
// int getpwnam_r(const char *name, struct passwd *pwd,
@@ -146,31 +136,15 @@ private predicate posixPWInfo(FunctionCall source, Element use) {
// char *buf, size_t buflen, struct group **result);
// int getgrnam_r(const char *name, struct group *grp,
// char *buf, size_t buflen, struct group **result);
- (
- source.getTarget().hasName("getpwnam_r") or
- source.getTarget().hasName("getpwuid_r") or
- source.getTarget().hasName("getgrgid_r") or
- source.getTarget().hasName("getgrnam_r")
- ) and
- (
- use = source.getArgument(1) or
- use = source.getArgument(2) or
- use = source.getArgument(4)
- )
+ source.getTarget().hasName(["getpwnam_r", "getpwuid_r", "getgrgid_r", "getgrnam_r"]) and
+ use = source.getArgument([1, 2, 4])
or
// int getpwent_r(struct passwd *pwd, char *buffer, size_t bufsize,
// struct passwd **result);
// int getgrent_r(struct group *gbuf, char *buf,
// size_t buflen, struct group **gbufp);
- (
- source.getTarget().hasName("getpwent_r") or
- source.getTarget().hasName("getgrent_r")
- ) and
- (
- use = source.getArgument(0) or
- use = source.getArgument(1) or
- use = source.getArgument(3)
- )
+ source.getTarget().hasName(["getpwent_r", "getgrent_r"]) and
+ use = source.getArgument([0, 1, 3])
}
/**
@@ -190,13 +164,11 @@ private predicate windowsSystemInfo(FunctionCall source, Element use) {
// BOOL WINAPI GetVersionEx(_Inout_ LPOSVERSIONINFO lpVersionInfo);
// void WINAPI GetSystemInfo(_Out_ LPSYSTEM_INFO lpSystemInfo);
// void WINAPI GetNativeSystemInfo(_Out_ LPSYSTEM_INFO lpSystemInfo);
- (
- source.getTarget().hasGlobalName("GetVersionEx") or
- source.getTarget().hasGlobalName("GetVersionExA") or
- source.getTarget().hasGlobalName("GetVersionExW") or
- source.getTarget().hasGlobalName("GetSystemInfo") or
- source.getTarget().hasGlobalName("GetNativeSystemInfo")
- ) and
+ source
+ .getTarget()
+ .hasGlobalName([
+ "GetVersionEx", "GetVersionExA", "GetVersionExW", "GetSystemInfo", "GetNativeSystemInfo"
+ ]) and
use = source.getArgument(0)
}
@@ -216,11 +188,11 @@ private predicate windowsFolderPath(FunctionCall source, Element use) {
// _In_ int csidl,
// _In_ BOOL fCreate
// );
- (
- source.getTarget().hasGlobalName("SHGetSpecialFolderPath") or
- source.getTarget().hasGlobalName("SHGetSpecialFolderPathA") or
- source.getTarget().hasGlobalName("SHGetSpecialFolderPathW")
- ) and
+ source
+ .getTarget()
+ .hasGlobalName([
+ "SHGetSpecialFolderPath", "SHGetSpecialFolderPathA", "SHGetSpecialFolderPathW"
+ ]) and
use = source.getArgument(1)
or
// HRESULT SHGetKnownFolderPath(
@@ -239,11 +211,7 @@ private predicate windowsFolderPath(FunctionCall source, Element use) {
// _In_ DWORD dwFlags,
// _Out_ LPTSTR pszPath
// );
- (
- source.getTarget().hasGlobalName("SHGetFolderPath") or
- source.getTarget().hasGlobalName("SHGetFolderPathA") or
- source.getTarget().hasGlobalName("SHGetFolderPathW")
- ) and
+ source.getTarget().hasGlobalName(["SHGetFolderPath", "SHGetFolderPathA", "SHGetFolderPathW"]) and
use = source.getArgument(4)
or
// HRESULT SHGetFolderPathAndSubDir(
@@ -254,11 +222,11 @@ private predicate windowsFolderPath(FunctionCall source, Element use) {
// _In_ LPCTSTR pszSubDir,
// _Out_ LPTSTR pszPath
// );
- (
- source.getTarget().hasGlobalName("SHGetFolderPathAndSubDir") or
- source.getTarget().hasGlobalName("SHGetFolderPathAndSubDirA") or
- source.getTarget().hasGlobalName("SHGetFolderPathAndSubDirW")
- ) and
+ source
+ .getTarget()
+ .hasGlobalName([
+ "SHGetFolderPathAndSubDir", "SHGetFolderPathAndSubDirA", "SHGetFolderPathAndSubDirW"
+ ]) and
use = source.getArgument(5)
}
@@ -273,11 +241,7 @@ class WindowsFolderPath extends SystemData {
}
private predicate logonUser(FunctionCall source, VariableAccess use) {
- (
- source.getTarget().hasGlobalName("LogonUser") or
- source.getTarget().hasGlobalName("LogonUserW") or
- source.getTarget().hasGlobalName("LogonUserA")
- ) and
+ source.getTarget().hasGlobalName(["LogonUser", "LogonUserW", "LogonUserA"]) and
use = source.getAnArgument()
}
@@ -297,11 +261,7 @@ private predicate regQuery(FunctionCall source, VariableAccess use) {
// _Out_opt_ LPTSTR lpValue,
// _Inout_opt_ PLONG lpcbValue
// );
- (
- source.getTarget().hasGlobalName("RegQueryValue") or
- source.getTarget().hasGlobalName("RegQueryValueA") or
- source.getTarget().hasGlobalName("RegQueryValueW")
- ) and
+ source.getTarget().hasGlobalName(["RegQueryValue", "RegQueryValueA", "RegQueryValueW"]) and
use = source.getArgument(2)
or
// LONG WINAPI RegQueryMultipleValues(
@@ -311,11 +271,11 @@ private predicate regQuery(FunctionCall source, VariableAccess use) {
// _Out_opt_ LPTSTR lpValueBuf,
// _Inout_opt_ LPDWORD ldwTotsize
// );
- (
- source.getTarget().hasGlobalName("RegQueryMultipleValues") or
- source.getTarget().hasGlobalName("RegQueryMultipleValuesA") or
- source.getTarget().hasGlobalName("RegQueryMultipleValuesW")
- ) and
+ source
+ .getTarget()
+ .hasGlobalName([
+ "RegQueryMultipleValues", "RegQueryMultipleValuesA", "RegQueryMultipleValuesW"
+ ]) and
use = source.getArgument(3)
or
// LONG WINAPI RegQueryValueEx(
@@ -326,11 +286,7 @@ private predicate regQuery(FunctionCall source, VariableAccess use) {
// _Out_opt_ LPBYTE lpData,
// _Inout_opt_ LPDWORD lpcbData
// );
- (
- source.getTarget().hasGlobalName("RegQueryValueEx") or
- source.getTarget().hasGlobalName("RegQueryValueExA") or
- source.getTarget().hasGlobalName("RegQueryValueExW")
- ) and
+ source.getTarget().hasGlobalName(["RegQueryValueEx", "RegQueryValueExA", "RegQueryValueExW"]) and
use = source.getArgument(4)
or
// LONG WINAPI RegGetValue(
@@ -342,11 +298,7 @@ private predicate regQuery(FunctionCall source, VariableAccess use) {
// _Out_opt_ PVOID pvData,
// _Inout_opt_ LPDWORD pcbData
// );
- (
- source.getTarget().hasGlobalName("RegGetValue") or
- source.getTarget().hasGlobalName("RegGetValueA") or
- source.getTarget().hasGlobalName("RegGetValueW")
- ) and
+ source.getTarget().hasGlobalName(["RegGetValue", "RegGetValueA", "RegGetValueW"]) and
use = source.getArgument(5)
}
@@ -372,10 +324,8 @@ abstract class DataOutput extends Element {
/**
* Data that is output via standard output or standard error.
*/
-class StandardOutput extends DataOutput {
- StandardOutput() { this instanceof OutputWrite }
-
- override Expr getASource() { result = this.(OutputWrite).getASource() }
+class StandardOutput extends DataOutput instanceof OutputWrite {
+ override Expr getASource() { result = OutputWrite.super.getASource() }
}
private predicate socketCallOrIndirect(FunctionCall call) {
@@ -408,12 +358,7 @@ private predicate socketOutput(FunctionCall call, Expr data) {
// const struct sockaddr *dest_addr, socklen_t addrlen);
// ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
// int write(int handle, void *buffer, int nbyte);
- (
- call.getTarget().hasGlobalName("send") or
- call.getTarget().hasGlobalName("sendto") or
- call.getTarget().hasGlobalName("sendmsg") or
- call.getTarget().hasGlobalName("write")
- ) and
+ call.getTarget().hasGlobalName(["send", "sendto", "sendmsg", "write"]) and
data = call.getArgument(1) and
socketFileDescriptor(call.getArgument(0))
)
@@ -431,5 +376,5 @@ class SocketOutput extends DataOutput {
from SystemData sd, DataOutput ow
where
sd.getAnExprIndirect() = ow.getASource() or
- sd.getAnExprIndirect() = ow.getASource().(Expr).getAChild*()
+ sd.getAnExprIndirect() = ow.getASource().getAChild*()
select ow, "This operation exposes system data from $@.", sd, sd.toString()
diff --git a/repo-tests/codeql/cpp/ql/src/experimental/Security/CWE/CWE-243/IncorrectChangingWorkingDirectory.ql b/repo-tests/codeql/cpp/ql/src/experimental/Security/CWE/CWE-243/IncorrectChangingWorkingDirectory.ql
new file mode 100644
index 00000000000..02d57ee3c3f
--- /dev/null
+++ b/repo-tests/codeql/cpp/ql/src/experimental/Security/CWE/CWE-243/IncorrectChangingWorkingDirectory.ql
@@ -0,0 +1,69 @@
+/**
+ * @name Find work with changing working directories, with security errors.
+ * @description Not validating the return value or pinning the directory can be unsafe.
+ * @kind problem
+ * @id cpp/work-with-changing-working-directories
+ * @problem.severity warning
+ * @precision medium
+ * @tags correctness
+ * security
+ * external/cwe/cwe-243
+ * external/cwe/cwe-252
+ */
+
+import cpp
+import semmle.code.cpp.commons.Exclusions
+
+/** Holds if a `fc` function call is available before or after a `chdir` function call. */
+predicate inExistsChdir(FunctionCall fcp) {
+ exists(FunctionCall fctmp |
+ (
+ fctmp.getTarget().hasGlobalOrStdName("chdir") or
+ fctmp.getTarget().hasGlobalOrStdName("fchdir")
+ ) and
+ (
+ fcp.getBasicBlock().getASuccessor*() = fctmp.getBasicBlock() or
+ fctmp.getBasicBlock().getASuccessor*() = fcp.getBasicBlock()
+ )
+ )
+}
+
+/** Holds if a `fc` function call is available before or after a function call containing a `chdir` call. */
+predicate outExistsChdir(FunctionCall fcp) {
+ exists(FunctionCall fctmp |
+ exists(FunctionCall fctmp2 |
+ (
+ fctmp2.getTarget().hasGlobalOrStdName("chdir") or
+ fctmp2.getTarget().hasGlobalOrStdName("fchdir")
+ ) and
+ // we are looking for a call containing calls chdir and fchdir
+ fctmp2.getEnclosingStmt().getParentStmt*() = fctmp.getTarget().getEntryPoint().getChildStmt*()
+ ) and
+ (
+ fcp.getBasicBlock().getASuccessor*() = fctmp.getBasicBlock() or
+ fctmp.getBasicBlock().getASuccessor*() = fcp.getBasicBlock()
+ )
+ )
+}
+
+from FunctionCall fc, string msg
+where
+ fc.getTarget().hasGlobalOrStdName("chroot") and
+ not inExistsChdir(fc) and
+ not outExistsChdir(fc) and
+ // in this section I want to exclude calls to functions containing chroot that have a direct path to chdir, or to a function containing chdir
+ exists(FunctionCall fctmp |
+ fc.getEnclosingStmt().getParentStmt*() = fctmp.getTarget().getEntryPoint().getChildStmt*() and
+ not inExistsChdir(fctmp) and
+ not outExistsChdir(fctmp)
+ ) and
+ msg = "Creation of 'chroot' jail without changing the working directory"
+ or
+ (
+ fc.getTarget().hasGlobalOrStdName("chdir") or
+ fc.getTarget().hasGlobalOrStdName("fchdir")
+ ) and
+ fc instanceof ExprInVoidContext and
+ not isFromMacroDefinition(fc) and
+ msg = "Unchecked return value for call to '" + fc.getTarget().getName() + "'."
+select fc, msg
diff --git a/repo-tests/codeql/cpp/ql/src/experimental/Security/CWE/CWE-273/PrivilegeDroppingOutoforder.ql b/repo-tests/codeql/cpp/ql/src/experimental/Security/CWE/CWE-273/PrivilegeDroppingOutoforder.ql
index 7798203205a..9d2faf793c5 100644
--- a/repo-tests/codeql/cpp/ql/src/experimental/Security/CWE/CWE-273/PrivilegeDroppingOutoforder.ql
+++ b/repo-tests/codeql/cpp/ql/src/experimental/Security/CWE/CWE-273/PrivilegeDroppingOutoforder.ql
@@ -44,14 +44,13 @@ class SetuidLikeWrapperCall extends FunctionCall {
class CallBeforeSetuidFunctionCall extends FunctionCall {
CallBeforeSetuidFunctionCall() {
- (
- getTarget().hasGlobalName("setgid") or
- getTarget().hasGlobalName("setresgid") or
- // Compatibility may require skipping initgroups and setgroups return checks.
- // A stricter best practice is to check the result and errnor for EPERM.
- getTarget().hasGlobalName("initgroups") or
- getTarget().hasGlobalName("setgroups")
- ) and
+ getTarget()
+ .hasGlobalName([
+ "setgid", "setresgid",
+ // Compatibility may require skipping initgroups and setgroups return checks.
+ // A stricter best practice is to check the result and errnor for EPERM.
+ "initgroups", "setgroups"
+ ]) and
// setgid/setresgid/etc with the root group are false positives.
not argumentMayBeRoot(getArgument(0))
}
diff --git a/repo-tests/codeql/cpp/ql/src/experimental/Security/CWE/CWE-377/InsecureTemporaryFile.ql b/repo-tests/codeql/cpp/ql/src/experimental/Security/CWE/CWE-377/InsecureTemporaryFile.ql
new file mode 100644
index 00000000000..f08f7ef61f5
--- /dev/null
+++ b/repo-tests/codeql/cpp/ql/src/experimental/Security/CWE/CWE-377/InsecureTemporaryFile.ql
@@ -0,0 +1,112 @@
+/**
+ * @name Insecure generation of filenames.
+ * @description Using a predictable filename when creating a temporary file can lead to an attacker-controlled input.
+ * @kind problem
+ * @id cpp/insecure-generation-of-filename
+ * @problem.severity warning
+ * @precision medium
+ * @tags correctness
+ * security
+ * external/cwe/cwe-377
+ */
+
+import cpp
+import semmle.code.cpp.valuenumbering.GlobalValueNumbering
+
+/** Holds for a function `f` that has an argument at index `apos` used to read the file. */
+predicate numberArgumentRead(Function f, int apos) {
+ f.hasGlobalOrStdName("fgets") and apos = 2
+ or
+ f.hasGlobalOrStdName("fread") and apos = 3
+ or
+ f.hasGlobalOrStdName("read") and apos = 0
+ or
+ f.hasGlobalOrStdName("fscanf") and apos = 0
+}
+
+/** Holds for a function `f` that has an argument at index `apos` used to write to file */
+predicate numberArgumentWrite(Function f, int apos) {
+ f.hasGlobalOrStdName("fprintf") and apos = 0
+ or
+ f.hasGlobalOrStdName("fputs") and apos = 1
+ or
+ f.hasGlobalOrStdName("write") and apos = 0
+ or
+ f.hasGlobalOrStdName("fwrite") and apos = 3
+ or
+ f.hasGlobalOrStdName("fflush") and apos = 0
+}
+
+from FunctionCall fc, string msg
+where
+ // search for functions for generating a name, without a guarantee of the absence of a file during the period of work with it.
+ (
+ fc.getTarget().hasGlobalOrStdName("tmpnam") or
+ fc.getTarget().hasGlobalOrStdName("tmpnam_s") or
+ fc.getTarget().hasGlobalOrStdName("tmpnam_r")
+ ) and
+ not exists(FunctionCall fctmp |
+ (
+ fctmp.getTarget().hasGlobalOrStdName("mktemp") or
+ fctmp.getTarget().hasGlobalOrStdName("mkstemp") or
+ fctmp.getTarget().hasGlobalOrStdName("mkstemps") or
+ fctmp.getTarget().hasGlobalOrStdName("mkdtemp")
+ ) and
+ (
+ fc.getBasicBlock().getASuccessor*() = fctmp.getBasicBlock() or
+ fctmp.getBasicBlock().getASuccessor*() = fc.getBasicBlock()
+ )
+ ) and
+ msg =
+ "Finding the name of a file that does not exist does not mean that it will not be exist at the next operation."
+ or
+ // finding places to work with a file without setting permissions, but with predictable names.
+ (
+ fc.getTarget().hasGlobalOrStdName("fopen") or
+ fc.getTarget().hasGlobalOrStdName("open")
+ ) and
+ fc.getNumberOfArguments() = 2 and
+ exists(FunctionCall fctmp, int i |
+ numberArgumentWrite(fctmp.getTarget(), i) and
+ globalValueNumber(fc) = globalValueNumber(fctmp.getArgument(i))
+ ) and
+ not exists(FunctionCall fctmp, int i |
+ numberArgumentRead(fctmp.getTarget(), i) and
+ globalValueNumber(fc) = globalValueNumber(fctmp.getArgument(i))
+ ) and
+ exists(FunctionCall fctmp |
+ (
+ fctmp.getTarget().hasGlobalOrStdName("strcat") or
+ fctmp.getTarget().hasGlobalOrStdName("strcpy")
+ ) and
+ globalValueNumber(fc.getArgument(0)) = globalValueNumber(fctmp.getAnArgument())
+ or
+ fctmp.getTarget().hasGlobalOrStdName("getenv") and
+ globalValueNumber(fc.getArgument(0)) = globalValueNumber(fctmp)
+ or
+ (
+ fctmp.getTarget().hasGlobalOrStdName("asprintf") or
+ fctmp.getTarget().hasGlobalOrStdName("vasprintf") or
+ fctmp.getTarget().hasGlobalOrStdName("xasprintf") or
+ fctmp.getTarget().hasGlobalOrStdName("xvasprintf ")
+ ) and
+ exists(Variable vrtmp |
+ vrtmp = fc.getArgument(0).(VariableAccess).getTarget() and
+ vrtmp = fctmp.getArgument(0).(AddressOfExpr).getAddressable().(Variable) and
+ not vrtmp instanceof Field
+ )
+ ) and
+ not exists(FunctionCall fctmp |
+ (
+ fctmp.getTarget().hasGlobalOrStdName("umask") or
+ fctmp.getTarget().hasGlobalOrStdName("fchmod") or
+ fctmp.getTarget().hasGlobalOrStdName("chmod")
+ ) and
+ (
+ fc.getBasicBlock().getASuccessor*() = fctmp.getBasicBlock() or
+ fctmp.getBasicBlock().getASuccessor*() = fc.getBasicBlock()
+ )
+ ) and
+ msg =
+ "Creating a file for writing without evaluating its existence and setting permissions can be unsafe."
+select fc, msg
diff --git a/repo-tests/codeql/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBitwiseOrLogicalOperations.ql b/repo-tests/codeql/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBitwiseOrLogicalOperations.ql
index eae74f76749..78f539aae8b 100644
--- a/repo-tests/codeql/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBitwiseOrLogicalOperations.ql
+++ b/repo-tests/codeql/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBitwiseOrLogicalOperations.ql
@@ -188,8 +188,7 @@ where
isBitwiseandBitwise(exp) and
isDifferentResults(exp.(BinaryBitwiseOperation).getLeftOperand(),
exp.(BinaryBitwiseOperation).getRightOperand().(BinaryBitwiseOperation).getLeftOperand(),
- exp.(BinaryBitwiseOperation).getRightOperand().(BinaryBitwiseOperation).getRightOperand(),
- exp.(BinaryBitwiseOperation),
- exp.(BinaryBitwiseOperation).getRightOperand().(BinaryBitwiseOperation)) and
+ exp.(BinaryBitwiseOperation).getRightOperand().(BinaryBitwiseOperation).getRightOperand(), exp,
+ exp.(BinaryBitwiseOperation).getRightOperand()) and
msg = "specify the priority with parentheses."
select exp, msg
diff --git a/repo-tests/codeql/cpp/ql/src/external/CodeDuplication.qll b/repo-tests/codeql/cpp/ql/src/external/CodeDuplication.qll
index 1550ca697a3..2656378bf62 100644
--- a/repo-tests/codeql/cpp/ql/src/external/CodeDuplication.qll
+++ b/repo-tests/codeql/cpp/ql/src/external/CodeDuplication.qll
@@ -38,10 +38,10 @@ class Copy extends @duplication_or_similarity {
int sourceStartColumn() { tokens(this, 0, _, result, _, _) }
/** Gets the line on which the last token in this block ends. */
- int sourceEndLine() { tokens(this, lastToken(), _, _, result, _) }
+ int sourceEndLine() { tokens(this, this.lastToken(), _, _, result, _) }
/** Gets the column on which the last token in this block ends. */
- int sourceEndColumn() { tokens(this, lastToken(), _, _, _, result) }
+ int sourceEndColumn() { tokens(this, this.lastToken(), _, _, _, result) }
/** Gets the number of lines containing at least (part of) one token in this block. */
int sourceLines() { result = this.sourceEndLine() + 1 - this.sourceStartLine() }
@@ -66,11 +66,11 @@ class Copy extends @duplication_or_similarity {
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
- sourceFile().getAbsolutePath() = filepath and
- startline = sourceStartLine() and
- startcolumn = sourceStartColumn() and
- endline = sourceEndLine() and
- endcolumn = sourceEndColumn()
+ this.sourceFile().getAbsolutePath() = filepath and
+ startline = this.sourceStartLine() and
+ startcolumn = this.sourceStartColumn() and
+ endline = this.sourceEndLine() and
+ endcolumn = this.sourceEndColumn()
}
/** Gets a textual representation of this element. */
@@ -79,13 +79,15 @@ class Copy extends @duplication_or_similarity {
/** A block of duplicated code. */
class DuplicateBlock extends Copy, @duplication {
- override string toString() { result = "Duplicate code: " + sourceLines() + " duplicated lines." }
+ override string toString() {
+ result = "Duplicate code: " + this.sourceLines() + " duplicated lines."
+ }
}
/** A block of similar code. */
class SimilarBlock extends Copy, @similarity {
override string toString() {
- result = "Similar code: " + sourceLines() + " almost duplicated lines."
+ result = "Similar code: " + this.sourceLines() + " almost duplicated lines."
}
}
diff --git a/repo-tests/codeql/cpp/ql/src/external/MetricFilter.qll b/repo-tests/codeql/cpp/ql/src/external/MetricFilter.qll
index 58e8bf154e9..0315cd23c8d 100644
--- a/repo-tests/codeql/cpp/ql/src/external/MetricFilter.qll
+++ b/repo-tests/codeql/cpp/ql/src/external/MetricFilter.qll
@@ -67,7 +67,7 @@ class MetricResult extends int {
/** Gets the URL corresponding to the location of this query result. */
string getURL() {
result =
- "file://" + getFile().getAbsolutePath() + ":" + getStartLine() + ":" + getStartColumn() + ":" +
- getEndLine() + ":" + getEndColumn()
+ "file://" + this.getFile().getAbsolutePath() + ":" + this.getStartLine() + ":" +
+ this.getStartColumn() + ":" + this.getEndLine() + ":" + this.getEndColumn()
}
}
diff --git a/repo-tests/codeql/cpp/ql/src/jsf/4.09 Style/AV Rule 53.1.ql b/repo-tests/codeql/cpp/ql/src/jsf/4.09 Style/AV Rule 53.1.ql
index 608374b241c..91b5e4e8b98 100644
--- a/repo-tests/codeql/cpp/ql/src/jsf/4.09 Style/AV Rule 53.1.ql
+++ b/repo-tests/codeql/cpp/ql/src/jsf/4.09 Style/AV Rule 53.1.ql
@@ -14,12 +14,5 @@ import cpp
from Include i, string name
where
name = i.getIncludeText() and
- (
- name.matches("%'%") or
- name.matches("%\\\\%") or
- name.matches("%/*%") or
- name.matches("%//%") or
- name.matches("%\"%\"%\"%") or
- name.matches("%<%\"%>%")
- )
+ name.matches(["%'%", "%\\\\%", "%/*%", "%//%", "%\"%\"%\"%", "%<%\"%>%"])
select i, "AV Rule 53.1: Invalid character sequence in header file name '" + name + "'"
diff --git a/repo-tests/codeql/cpp/ql/src/jsf/4.10 Classes/AV Rule 79.ql b/repo-tests/codeql/cpp/ql/src/jsf/4.10 Classes/AV Rule 79.ql
index adeb54746df..b1eab42af37 100644
--- a/repo-tests/codeql/cpp/ql/src/jsf/4.10 Classes/AV Rule 79.ql
+++ b/repo-tests/codeql/cpp/ql/src/jsf/4.10 Classes/AV Rule 79.ql
@@ -142,7 +142,7 @@ class Resource extends MemberVariable {
predicate acquisitionWithRequiredKind(Assignment acquireAssign, string kind) {
// acquireAssign is an assignment to this resource
- acquireAssign.(Assignment).getLValue() = this.getAnAccess() and
+ acquireAssign.getLValue() = this.getAnAccess() and
// Should be in this class, but *any* member method will do
this.inSameClass(acquireAssign) and
// Check that it is an acquisition function and return the corresponding kind
diff --git a/repo-tests/codeql/cpp/ql/src/jsf/4.15 Declarations and Definitions/AV Rule 135.ql b/repo-tests/codeql/cpp/ql/src/jsf/4.15 Declarations and Definitions/AV Rule 135.ql
index d3e23c9a7f6..aa4cc984170 100644
--- a/repo-tests/codeql/cpp/ql/src/jsf/4.15 Declarations and Definitions/AV Rule 135.ql
+++ b/repo-tests/codeql/cpp/ql/src/jsf/4.15 Declarations and Definitions/AV Rule 135.ql
@@ -31,7 +31,7 @@ from Variable v, Variable shadowed
where
not v.getParentScope().(BlockStmt).isInMacroExpansion() and
(
- v.(LocalVariableOrParameter).shadowsGlobal(shadowed.(GlobalVariable)) or
+ v.(LocalVariableOrParameter).shadowsGlobal(shadowed) or
localShadowsParameter(v, shadowed) or
shadowing(v, shadowed)
)
diff --git a/repo-tests/codeql/cpp/ql/src/jsf/4.22 Pointers and References/AV Rule 173.ql b/repo-tests/codeql/cpp/ql/src/jsf/4.22 Pointers and References/AV Rule 173.ql
index 49ed13f1b48..1b1e92bd4a7 100644
--- a/repo-tests/codeql/cpp/ql/src/jsf/4.22 Pointers and References/AV Rule 173.ql
+++ b/repo-tests/codeql/cpp/ql/src/jsf/4.22 Pointers and References/AV Rule 173.ql
@@ -26,7 +26,7 @@ import cpp
from Assignment a, Variable global, Variable local
where
a.fromSource() and
- global.getAnAccess() = a.getLValue().(VariableAccess) and
+ global.getAnAccess() = a.getLValue() and
local.getAnAccess() = a.getRValue().(AddressOfExpr).getOperand() and
local.hasSpecifier("auto") and
(
diff --git a/repo-tests/codeql/cpp/ql/src/jsf/4.28 Portable Code/AV Rule 209.ql b/repo-tests/codeql/cpp/ql/src/jsf/4.28 Portable Code/AV Rule 209.ql
index b881c5ed601..64a130f46e0 100644
--- a/repo-tests/codeql/cpp/ql/src/jsf/4.28 Portable Code/AV Rule 209.ql
+++ b/repo-tests/codeql/cpp/ql/src/jsf/4.28 Portable Code/AV Rule 209.ql
@@ -15,13 +15,7 @@ import cpp
from Element u, ArithmeticType at
where
- (
- at.hasName("int") or
- at.hasName("short") or
- at.hasName("long") or
- at.hasName("float") or
- at.hasName("double")
- ) and
+ at.hasName(["int", "short", "long", "float", "double"]) and
u = at.getATypeNameUse() and
not at instanceof WideCharType
select u, "AV Rule 209: The basic types of int, short, long, float and double shall not be used."
diff --git a/repo-tests/codeql/cpp/ql/src/jsf/4.28 Portable Code/AV Rule 210.ql b/repo-tests/codeql/cpp/ql/src/jsf/4.28 Portable Code/AV Rule 210.ql
index c12edf0c02a..a30509d9beb 100644
--- a/repo-tests/codeql/cpp/ql/src/jsf/4.28 Portable Code/AV Rule 210.ql
+++ b/repo-tests/codeql/cpp/ql/src/jsf/4.28 Portable Code/AV Rule 210.ql
@@ -49,11 +49,11 @@ class ExposingIntegralUnion extends Union {
exists(MemberVariable mv1, MemberVariable mv2, IntegralType mv1tp, IntegralType mv2tp |
mv1 = this.getAMemberVariable() and
mv2 = this.getAMemberVariable() and
- mv1tp = mv1.getUnderlyingType().(IntegralType) and
+ mv1tp = mv1.getUnderlyingType() and
(
- mv2tp = mv2.getUnderlyingType().(IntegralType)
+ mv2tp = mv2.getUnderlyingType()
or
- mv2tp = mv2.getUnderlyingType().(ArrayType).getBaseType().getUnderlyingType().(IntegralType)
+ mv2tp = mv2.getUnderlyingType().(ArrayType).getBaseType().getUnderlyingType()
) and
mv1tp.getSize() > mv2tp.getSize()
)
diff --git a/repo-tests/codeql/csharp/ql/consistency-queries/CfgConsistency.ql b/repo-tests/codeql/csharp/ql/consistency-queries/CfgConsistency.ql
new file mode 100644
index 00000000000..fe45e5a96d8
--- /dev/null
+++ b/repo-tests/codeql/csharp/ql/consistency-queries/CfgConsistency.ql
@@ -0,0 +1,64 @@
+import csharp
+import semmle.code.csharp.controlflow.internal.Completion
+import semmle.code.csharp.controlflow.internal.PreBasicBlocks
+import ControlFlow
+import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl
+import semmle.code.csharp.controlflow.internal.Splitting
+import Consistency
+
+private predicate splitBB(ControlFlow::BasicBlock bb) {
+ exists(ControlFlow::Node first |
+ first = bb.getFirstNode() and
+ first.isJoin() and
+ strictcount(first.getAPredecessor().getElement()) = 1
+ )
+}
+
+private class RelevantBasicBlock extends ControlFlow::BasicBlock {
+ RelevantBasicBlock() { not splitBB(this) }
+}
+
+predicate bbStartInconsistency(ControlFlowElement cfe) {
+ exists(RelevantBasicBlock bb | bb.getFirstNode() = cfe.getAControlFlowNode()) and
+ not cfe = any(PreBasicBlock bb).getFirstElement()
+}
+
+predicate bbSuccInconsistency(ControlFlowElement pred, ControlFlowElement succ) {
+ exists(RelevantBasicBlock predBB, RelevantBasicBlock succBB |
+ predBB.getLastNode() = pred.getAControlFlowNode() and
+ succBB = predBB.getASuccessor() and
+ succBB.getFirstNode() = succ.getAControlFlowNode()
+ ) and
+ not exists(PreBasicBlock predBB, PreBasicBlock succBB |
+ predBB.getLastElement() = pred and
+ succBB = predBB.getASuccessor() and
+ succBB.getFirstElement() = succ
+ )
+}
+
+predicate bbIntraSuccInconsistency(ControlFlowElement pred, ControlFlowElement succ) {
+ exists(ControlFlow::BasicBlock bb, int i |
+ pred.getAControlFlowNode() = bb.getNode(i) and
+ succ.getAControlFlowNode() = bb.getNode(i + 1)
+ ) and
+ not exists(PreBasicBlock bb |
+ bb.getLastElement() = pred and
+ bb.getASuccessor().getFirstElement() = succ
+ ) and
+ not exists(PreBasicBlock bb, int i |
+ bb.getElement(i) = pred and
+ bb.getElement(i + 1) = succ
+ )
+}
+
+query predicate preBasicBlockConsistency(ControlFlowElement cfe1, ControlFlowElement cfe2, string s) {
+ bbStartInconsistency(cfe1) and
+ cfe2 = cfe1 and
+ s = "start inconsistency"
+ or
+ bbSuccInconsistency(cfe1, cfe2) and
+ s = "succ inconsistency"
+ or
+ bbIntraSuccInconsistency(cfe1, cfe2) and
+ s = "intra succ inconsistency"
+}
diff --git a/repo-tests/codeql/csharp/ql/consistency-queries/qlpack.yml b/repo-tests/codeql/csharp/ql/consistency-queries/qlpack.yml
new file mode 100644
index 00000000000..ca83245a97f
--- /dev/null
+++ b/repo-tests/codeql/csharp/ql/consistency-queries/qlpack.yml
@@ -0,0 +1,6 @@
+name: codeql-csharp-consistency-queries
+version: 0.0.0
+libraryPathDependencies:
+ - codeql/csharp-all
+ - codeql/csharp-queries
+extractor: csharp
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/asp/WebConfig.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/asp/WebConfig.qll
index ed0f9aef451..16d5393afc2 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/asp/WebConfig.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/asp/WebConfig.qll
@@ -8,7 +8,7 @@ import csharp
* A `Web.config` file.
*/
class WebConfigXML extends XMLFile {
- WebConfigXML() { getName().matches("%Web.config") }
+ WebConfigXML() { this.getName().matches("%Web.config") }
}
/** A `` tag in an ASP.NET configuration file. */
@@ -73,12 +73,14 @@ class FormsElement extends XMLElement {
/**
* Gets attribute's `requireSSL` value.
*/
- string getRequireSSL() { result = getAttribute("requireSSL").getValue().trim().toLowerCase() }
+ string getRequireSSL() {
+ result = this.getAttribute("requireSSL").getValue().trim().toLowerCase()
+ }
/**
* Holds if `requireSSL` value is true.
*/
- predicate isRequireSSL() { getRequireSSL() = "true" }
+ predicate isRequireSSL() { this.getRequireSSL() = "true" }
}
/** A `` tag in an ASP.NET configuration file. */
@@ -89,26 +91,28 @@ class HttpCookiesElement extends XMLElement {
* Gets attribute's `httpOnlyCookies` value.
*/
string getHttpOnlyCookies() {
- result = getAttribute("httpOnlyCookies").getValue().trim().toLowerCase()
+ result = this.getAttribute("httpOnlyCookies").getValue().trim().toLowerCase()
}
/**
* Holds if there is any chance that `httpOnlyCookies` is set to `true`.
*/
- predicate isHttpOnlyCookies() { getHttpOnlyCookies() = "true" }
+ predicate isHttpOnlyCookies() { this.getHttpOnlyCookies() = "true" }
/**
* Gets attribute's `requireSSL` value.
*/
- string getRequireSSL() { result = getAttribute("requireSSL").getValue().trim().toLowerCase() }
+ string getRequireSSL() {
+ result = this.getAttribute("requireSSL").getValue().trim().toLowerCase()
+ }
/**
* Holds if there is any chance that `requireSSL` is set to `true` either globally or for Forms.
*/
predicate isRequireSSL() {
- getRequireSSL() = "true"
+ this.getRequireSSL() = "true"
or
- not getRequireSSL() = "false" and // not set all, i.e. default
- exists(FormsElement forms | forms.getFile() = getFile() | forms.isRequireSSL())
+ not this.getRequireSSL() = "false" and // not set all, i.e. default
+ exists(FormsElement forms | forms.getFile() = this.getFile() | forms.isRequireSSL())
}
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Access.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Access.qll
index 6d72a48ff1b..5fecd8acb10 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Access.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Access.qll
@@ -20,7 +20,7 @@ class VariableAccess extends Access, @cil_access { }
/** An instruction that reads a variable. */
class ReadAccess extends VariableAccess, Expr, @cil_read_access {
- override Type getType() { result = getTarget().getType() }
+ override Type getType() { result = this.getTarget().getType() }
}
/** An instruction yielding an address. */
@@ -49,7 +49,7 @@ class ParameterReadAccess extends ParameterAccess, ReadAccess {
class ParameterWriteAccess extends ParameterAccess, WriteAccess {
override int getPopCount() { result = 1 }
- override Expr getExpr() { result = getOperand(0) }
+ override Expr getExpr() { result = this.getOperand(0) }
}
/** An access to the `this` parameter. */
@@ -71,9 +71,9 @@ class LocalVariableAccess extends StackVariableAccess, @cil_local_access {
class LocalVariableWriteAccess extends LocalVariableAccess, WriteAccess {
override int getPopCount() { result = 1 }
- override Expr getExpr() { result = getOperand(0) }
+ override Expr getExpr() { result = this.getOperand(0) }
- override string getExtra() { result = "L" + getTarget().getIndex() }
+ override string getExtra() { result = "L" + this.getTarget().getIndex() }
}
/** An instruction that reads a local variable. */
@@ -85,7 +85,7 @@ class LocalVariableReadAccess extends LocalVariableAccess, ReadAccess {
class FieldAccess extends VariableAccess, @cil_field_access {
override Field getTarget() { result = VariableAccess.super.getTarget() }
- override string getExtra() { result = getTarget().getName() }
+ override string getExtra() { result = this.getTarget().getName() }
/** Gets the qualifier of the access, if any. */
abstract Expr getQualifier();
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/BasicBlock.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/BasicBlock.qll
index 0c9c0b8ad07..2680cb0a769 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/BasicBlock.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/BasicBlock.qll
@@ -10,7 +10,7 @@ private import CIL
*/
class BasicBlock extends Cached::TBasicBlockStart {
/** Gets an immediate successor of this basic block, if any. */
- BasicBlock getASuccessor() { result.getFirstNode() = getLastNode().getASuccessor() }
+ BasicBlock getASuccessor() { result.getFirstNode() = this.getLastNode().getASuccessor() }
/** Gets an immediate predecessor of this basic block, if any. */
BasicBlock getAPredecessor() { result.getASuccessor() = this }
@@ -31,7 +31,7 @@ class BasicBlock extends Cached::TBasicBlockStart {
* The basic block on line 2 is an immediate `true` successor of the
* basic block on line 1.
*/
- BasicBlock getATrueSuccessor() { result.getFirstNode() = getLastNode().getTrueSuccessor() }
+ BasicBlock getATrueSuccessor() { result.getFirstNode() = this.getLastNode().getTrueSuccessor() }
/**
* Gets an immediate `false` successor, if any.
@@ -49,22 +49,22 @@ class BasicBlock extends Cached::TBasicBlockStart {
* The basic block on line 2 is an immediate `false` successor of the
* basic block on line 1.
*/
- BasicBlock getAFalseSuccessor() { result.getFirstNode() = getLastNode().getFalseSuccessor() }
+ BasicBlock getAFalseSuccessor() { result.getFirstNode() = this.getLastNode().getFalseSuccessor() }
/** Gets the control flow node at a specific (zero-indexed) position in this basic block. */
- ControlFlowNode getNode(int pos) { Cached::bbIndex(getFirstNode(), result, pos) }
+ ControlFlowNode getNode(int pos) { Cached::bbIndex(this.getFirstNode(), result, pos) }
/** Gets a control flow node in this basic block. */
- ControlFlowNode getANode() { result = getNode(_) }
+ ControlFlowNode getANode() { result = this.getNode(_) }
/** Gets the first control flow node in this basic block. */
ControlFlowNode getFirstNode() { this = Cached::TBasicBlockStart(result) }
/** Gets the last control flow node in this basic block. */
- ControlFlowNode getLastNode() { result = getNode(length() - 1) }
+ ControlFlowNode getLastNode() { result = this.getNode(this.length() - 1) }
/** Gets the length of this basic block. */
- int length() { result = strictcount(getANode()) }
+ int length() { result = strictcount(this.getANode()) }
/**
* Holds if this basic block strictly dominates basic block `bb`.
@@ -114,7 +114,7 @@ class BasicBlock extends Cached::TBasicBlockStart {
*/
predicate dominates(BasicBlock bb) {
bb = this or
- strictlyDominates(bb)
+ this.strictlyDominates(bb)
}
/**
@@ -140,14 +140,14 @@ class BasicBlock extends Cached::TBasicBlockStart {
* does not dominate the basic block on line 6.
*/
predicate inDominanceFrontier(BasicBlock df) {
- dominatesPredecessor(df) and
- not strictlyDominates(df)
+ this.dominatesPredecessor(df) and
+ not this.strictlyDominates(df)
}
/**
* Holds if this basic block dominates a predecessor of `df`.
*/
- private predicate dominatesPredecessor(BasicBlock df) { dominates(df.getAPredecessor()) }
+ private predicate dominatesPredecessor(BasicBlock df) { this.dominates(df.getAPredecessor()) }
/**
* Gets the basic block that immediately dominates this basic block, if any.
@@ -226,7 +226,7 @@ class BasicBlock extends Cached::TBasicBlockStart {
* post-dominates itself.
*/
predicate postDominates(BasicBlock bb) {
- strictlyPostDominates(bb) or
+ this.strictlyPostDominates(bb) or
this = bb
}
@@ -239,7 +239,7 @@ class BasicBlock extends Cached::TBasicBlockStart {
predicate inLoop() { this.getASuccessor+() = this }
/** Gets a textual representation of this basic block. */
- string toString() { result = getFirstNode().toString() }
+ string toString() { result = this.getFirstNode().toString() }
/** Gets the location of this basic block. */
Location getLocation() { result = this.getFirstNode().getLocation() }
@@ -325,16 +325,16 @@ private predicate exitBB(BasicBlock bb) { not exists(bb.getLastNode().getASucces
* A basic block with more than one predecessor.
*/
class JoinBlock extends BasicBlock {
- JoinBlock() { getFirstNode().isJoin() }
+ JoinBlock() { this.getFirstNode().isJoin() }
}
/** A basic block that terminates in a condition, splitting the subsequent control flow. */
class ConditionBlock extends BasicBlock {
ConditionBlock() {
exists(BasicBlock succ |
- succ = getATrueSuccessor()
+ succ = this.getATrueSuccessor()
or
- succ = getAFalseSuccessor()
+ succ = this.getAFalseSuccessor()
)
}
@@ -380,16 +380,16 @@ class ConditionBlock extends BasicBlock {
*/
exists(BasicBlock succ |
- isCandidateSuccessor(succ, testIsTrue) and
+ this.isCandidateSuccessor(succ, testIsTrue) and
succ.dominates(controlled)
)
}
private predicate isCandidateSuccessor(BasicBlock succ, boolean testIsTrue) {
(
- testIsTrue = true and succ = getATrueSuccessor()
+ testIsTrue = true and succ = this.getATrueSuccessor()
or
- testIsTrue = false and succ = getAFalseSuccessor()
+ testIsTrue = false and succ = this.getAFalseSuccessor()
) and
forall(BasicBlock pred | pred = succ.getAPredecessor() and pred != this | succ.dominates(pred))
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/ConsistencyChecks.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/ConsistencyChecks.qll
index 02cfd149886..262bb58ab9c 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/ConsistencyChecks.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/ConsistencyChecks.qll
@@ -62,7 +62,7 @@ abstract class InstructionViolation extends CfgViolation, CfgCheck {
override string toString() {
result =
instruction.getImplementation().getMethod().toStringWithTypes() + ": " +
- instruction.toString() + ", " + getInstructionsUpTo()
+ instruction.toString() + ", " + this.getInstructionsUpTo()
}
}
@@ -126,7 +126,7 @@ class MissingOperand extends InstructionViolation {
}
override string getMessage() {
- result = "This instruction is missing operand " + getMissingOperand()
+ result = "This instruction is missing operand " + this.getMissingOperand()
}
}
@@ -364,7 +364,7 @@ class TypeViolation extends ConsistencyViolation, TypeCheck {
/** Gets the type containing the violation. */
Type getType() { this = TypeCheck(result) }
- override string toString() { result = getType().toString() }
+ override string toString() { result = this.getType().toString() }
abstract override string getMessage();
}
@@ -374,7 +374,7 @@ class TypeViolation extends ConsistencyViolation, TypeCheck {
*/
class TypeIsBothConstructedAndUnbound extends TypeViolation {
TypeIsBothConstructedAndUnbound() {
- getType() instanceof ConstructedGeneric and getType() instanceof UnboundGeneric
+ this.getType() instanceof ConstructedGeneric and this.getType() instanceof UnboundGeneric
}
override string getMessage() { result = "Type is both constructed and unbound" }
@@ -397,16 +397,16 @@ class InconsistentTypeLocation extends TypeViolation {
*/
class TypeParameterMismatch extends TypeViolation {
TypeParameterMismatch() {
- getType().(ConstructedGeneric).getNumberOfTypeArguments() !=
- getType().getUnboundType().(UnboundGeneric).getNumberOfTypeParameters()
+ this.getType().(ConstructedGeneric).getNumberOfTypeArguments() !=
+ this.getType().getUnboundType().(UnboundGeneric).getNumberOfTypeParameters()
}
override string getMessage() {
result =
- "Constructed type (" + getType().toStringWithTypes() + ") has " +
- getType().(ConstructedGeneric).getNumberOfTypeArguments() +
- " type arguments and unbound type (" + getType().getUnboundType().toStringWithTypes() +
- ") has " + getType().getUnboundType().(UnboundGeneric).getNumberOfTypeParameters() +
+ "Constructed type (" + this.getType().toStringWithTypes() + ") has " +
+ this.getType().(ConstructedGeneric).getNumberOfTypeArguments() +
+ " type arguments and unbound type (" + this.getType().getUnboundType().toStringWithTypes() +
+ ") has " + this.getType().getUnboundType().(UnboundGeneric).getNumberOfTypeParameters() +
" type parameters"
}
}
@@ -418,7 +418,7 @@ class MethodViolation extends ConsistencyViolation, DeclarationCheck {
/** Gets the method containing the violation. */
Method getMethod() { this = DeclarationCheck(result) }
- override string toString() { result = getMethod().toString() }
+ override string toString() { result = this.getMethod().toString() }
override string getMessage() { none() }
}
@@ -440,14 +440,15 @@ class InconsistentMethodLocation extends MethodViolation {
*/
class ConstructedMethodTypeParams extends MethodViolation {
ConstructedMethodTypeParams() {
- getMethod().(ConstructedGeneric).getNumberOfTypeArguments() !=
- getMethod().getUnboundDeclaration().(UnboundGeneric).getNumberOfTypeParameters()
+ this.getMethod().(ConstructedGeneric).getNumberOfTypeArguments() !=
+ this.getMethod().getUnboundDeclaration().(UnboundGeneric).getNumberOfTypeParameters()
}
override string getMessage() {
result =
- "The constructed method " + getMethod().toStringWithTypes() +
- " does not match unbound method " + getMethod().getUnboundDeclaration().toStringWithTypes()
+ "The constructed method " + this.getMethod().toStringWithTypes() +
+ " does not match unbound method " +
+ this.getMethod().getUnboundDeclaration().toStringWithTypes()
}
}
@@ -477,8 +478,8 @@ class InvalidOverride extends MethodViolation {
private Method base;
InvalidOverride() {
- base = getMethod().getOverriddenMethod() and
- not getMethod().getDeclaringType().getABaseType+() = base.getDeclaringType() and
+ base = this.getMethod().getOverriddenMethod() and
+ not this.getMethod().getDeclaringType().getABaseType+() = base.getDeclaringType() and
base.getDeclaringType().isUnboundDeclaration() // Bases classes of constructed types aren't extracted properly.
}
@@ -493,7 +494,9 @@ class InvalidOverride extends MethodViolation {
* A pointer type that does not have a pointee type.
*/
class InvalidPointerType extends TypeViolation {
- InvalidPointerType() { exists(PointerType p | p = getType() | count(p.getReferentType()) != 1) }
+ InvalidPointerType() {
+ exists(PointerType p | p = this.getType() | count(p.getReferentType()) != 1)
+ }
override string getMessage() { result = "Invalid Pointertype.getPointeeType()" }
}
@@ -502,7 +505,9 @@ class InvalidPointerType extends TypeViolation {
* An array with an invalid `getElementType`.
*/
class ArrayTypeMissingElement extends TypeViolation {
- ArrayTypeMissingElement() { exists(ArrayType t | t = getType() | count(t.getElementType()) != 1) }
+ ArrayTypeMissingElement() {
+ exists(ArrayType t | t = this.getType() | count(t.getElementType()) != 1)
+ }
override string getMessage() { result = "Invalid ArrayType.getElementType()" }
}
@@ -511,7 +516,7 @@ class ArrayTypeMissingElement extends TypeViolation {
* An array with an invalid `getRank`.
*/
class ArrayTypeInvalidRank extends TypeViolation {
- ArrayTypeInvalidRank() { exists(ArrayType t | t = getType() | not t.getRank() > 0) }
+ ArrayTypeInvalidRank() { exists(ArrayType t | t = this.getType() | not t.getRank() > 0) }
override string getMessage() { result = "Invalid ArrayType.getRank()" }
}
@@ -564,7 +569,7 @@ abstract class DeclarationViolation extends ConsistencyViolation, DeclarationChe
/** Gets the member containing the potential violation. */
Declaration getDeclaration() { this = DeclarationCheck(result) }
- override string toString() { result = getDeclaration().toString() }
+ override string toString() { result = this.getDeclaration().toString() }
}
/**
@@ -572,7 +577,7 @@ abstract class DeclarationViolation extends ConsistencyViolation, DeclarationChe
*/
class PropertyWithNoAccessors extends DeclarationViolation {
PropertyWithNoAccessors() {
- exists(Property p | p = getDeclaration() | not exists(p.getAnAccessor()))
+ exists(Property p | p = this.getDeclaration() | not exists(p.getAnAccessor()))
}
override string getMessage() { result = "Property has no accessors" }
@@ -646,7 +651,7 @@ class TypeMultiplyDefined extends TypeViolation, DisabledCheck {
override string getMessage() {
result =
- "This type (" + getType().toStringWithTypes() + ") has " +
+ "This type (" + this.getType().toStringWithTypes() + ") has " +
count(Type t |
not t instanceof ConstructedGeneric and
t.toStringWithTypes() = this.getType().toStringWithTypes()
@@ -669,11 +674,11 @@ class MissingCilDeclaration extends ConsistencyViolation, MissingCSharpCheck {
override string getMessage() {
result =
- "Cannot locate CIL for " + getDeclaration().toStringWithTypes() + " of class " +
- getDeclaration().getPrimaryQlClasses()
+ "Cannot locate CIL for " + this.getDeclaration().toStringWithTypes() + " of class " +
+ this.getDeclaration().getPrimaryQlClasses()
}
- override string toString() { result = getDeclaration().toStringWithTypes() }
+ override string toString() { result = this.getDeclaration().toStringWithTypes() }
}
/**
@@ -717,21 +722,23 @@ private predicate expectedCilDeclaration(CS::Declaration decl) {
/** A member with an invalid name. */
class MemberWithInvalidName extends DeclarationViolation {
MemberWithInvalidName() {
- exists(string name | name = getDeclaration().(Member).getName() |
+ exists(string name | name = this.getDeclaration().(Member).getName() |
exists(name.indexOf(".")) and
not name = ".ctor" and
not name = ".cctor"
)
}
- override string getMessage() { result = "Invalid name " + getDeclaration().(Member).getName() }
+ override string getMessage() {
+ result = "Invalid name " + this.getDeclaration().(Member).getName()
+ }
}
class ConstructedSourceDeclarationMethod extends MethodViolation {
Method method;
ConstructedSourceDeclarationMethod() {
- method = getMethod() and
+ method = this.getMethod() and
method = method.getUnboundDeclaration() and
(
method instanceof ConstructedGeneric or
@@ -751,7 +758,7 @@ class DeclarationWithMultipleLabels extends DeclarationViolation {
}
override string getMessage() {
- result = "Multiple labels " + concat(getDeclaration().getLabel(), ", ")
+ result = "Multiple labels " + concat(this.getDeclaration().getLabel(), ", ")
}
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/ControlFlow.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/ControlFlow.qll
index 52a2ddc3376..8b6d6c70a05 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/ControlFlow.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/ControlFlow.qll
@@ -23,13 +23,13 @@ class ControlFlowNode extends @cil_controlflow_node {
int getPopCount() { result = 0 }
/** Gets a successor of this node, if any. */
- final Instruction getASuccessor() { result = getASuccessorType(_) }
+ final Instruction getASuccessor() { result = this.getASuccessorType(_) }
/** Gets a true successor of this node, if any. */
- final Instruction getTrueSuccessor() { result = getASuccessorType(any(TrueFlow f)) }
+ final Instruction getTrueSuccessor() { result = this.getASuccessorType(any(TrueFlow f)) }
/** Gets a false successor of this node, if any. */
- final Instruction getFalseSuccessor() { result = getASuccessorType(any(FalseFlow f)) }
+ final Instruction getFalseSuccessor() { result = this.getASuccessorType(any(FalseFlow f)) }
/** Gets a successor to this node, of type `type`, if any. */
cached
@@ -57,7 +57,7 @@ class ControlFlowNode extends @cil_controlflow_node {
}
/** Gets an operand of this instruction, if any. */
- ControlFlowNode getAnOperand() { result = getOperand(_) }
+ ControlFlowNode getAnOperand() { result = this.getOperand(_) }
/** Gets an expression that consumes the output of this instruction on the stack. */
Instruction getParentExpr() { this = result.getAnOperand() }
@@ -86,17 +86,17 @@ class ControlFlowNode extends @cil_controlflow_node {
)
}
- private int getStackDelta() { result = getPushCount() - getPopCount() }
+ private int getStackDelta() { result = this.getPushCount() - this.getPopCount() }
/** Gets the stack size before this instruction. */
- int getStackSizeBefore() { result = getAPredecessor().getStackSizeAfter() }
+ int getStackSizeBefore() { result = this.getAPredecessor().getStackSizeAfter() }
/** Gets the stack size after this instruction. */
final int getStackSizeAfter() {
// This is a guard to prevent ill formed programs
// and other logic errors going into an infinite loop.
- result in [0 .. getImplementation().getStackSize()] and
- result = getStackSizeBefore() + getStackDelta()
+ result in [0 .. this.getImplementation().getStackSize()] and
+ result = this.getStackSizeBefore() + this.getStackDelta()
}
/** Gets the method containing this control flow node. */
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Declaration.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Declaration.qll
index a747d4a6d80..178b5c9966e 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Declaration.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Declaration.qll
@@ -68,7 +68,7 @@ class Member extends DotNet::Member, Declaration, @cil_member {
/** Holds if this member has a security attribute. */
predicate hasSecurity() { cil_security(this) }
- override Location getLocation() { result = getDeclaringType().getLocation() }
+ override Location getLocation() { result = this.getDeclaringType().getLocation() }
}
/** A property. */
@@ -87,24 +87,25 @@ class Property extends DotNet::Property, Member, CustomModifierReceiver, @cil_pr
override Setter getSetter() { this = result.getProperty() }
/** Gets an accessor of this property. */
- Accessor getAnAccessor() { result = getGetter() or result = getSetter() }
+ Accessor getAnAccessor() { result = this.getGetter() or result = this.getSetter() }
- override string toString() { result = "property " + getName() }
+ override string toString() { result = "property " + this.getName() }
override string toStringWithTypes() {
result =
- getType().toStringWithTypes() + " " + getDeclaringType().toStringWithTypes() + "." + getName()
+ this.getType().toStringWithTypes() + " " + this.getDeclaringType().toStringWithTypes() + "." +
+ this.getName()
}
}
/** A property that is trivial (wraps a field). */
class TrivialProperty extends Property {
TrivialProperty() {
- getGetter().(TrivialGetter).getField() = getSetter().(TrivialSetter).getField()
+ this.getGetter().(TrivialGetter).getField() = this.getSetter().(TrivialSetter).getField()
}
/** Gets the underlying field of this property. */
- Field getField() { result = getGetter().(TrivialGetter).getField() }
+ Field getField() { result = this.getGetter().(TrivialGetter).getField() }
}
/** An event. */
@@ -125,9 +126,9 @@ class Event extends DotNet::Event, Member, @cil_event {
/** Gets the raiser. */
Method getRaiser() { cil_raiser(this, result) }
- override string toString() { result = "event " + getName() }
+ override string toString() { result = "event " + this.getName() }
override string toStringWithTypes() {
- result = getDeclaringType().toStringWithTypes() + "." + getName()
+ result = this.getDeclaringType().toStringWithTypes() + "." + this.getName()
}
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Generics.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Generics.qll
index a742a142cc4..2e702e68ffe 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Generics.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Generics.qll
@@ -45,5 +45,5 @@ class ConstructedType extends ConstructedGeneric, Type {
/** A constructed generic method. */
class ConstructedMethod extends ConstructedGeneric, Method {
- final override UnboundGenericMethod getUnboundGeneric() { result = getUnboundMethod() }
+ final override UnboundGenericMethod getUnboundGeneric() { result = this.getUnboundMethod() }
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Instruction.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Instruction.qll
index 3e620031264..fa9753e1f0c 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Instruction.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Instruction.qll
@@ -4,15 +4,17 @@ private import CIL
/** An instruction. */
class Instruction extends Element, ControlFlowNode, DataFlowNode, @cil_instruction {
- override string toString() { result = getOpcodeName() }
+ override string toString() { result = this.getOpcodeName() }
/** Gets a more verbose textual representation of this instruction. */
- string toStringExtra() { result = getIndex() + ": " + getOpcodeName() + getExtraStr() }
+ string toStringExtra() {
+ result = this.getIndex() + ": " + this.getOpcodeName() + this.getExtraStr()
+ }
/** Gets the method containing this instruction. */
override MethodImplementation getImplementation() { cil_instruction(this, _, _, result) }
- override Method getMethod() { result = getImplementation().getMethod() }
+ override Method getMethod() { result = this.getImplementation().getMethod() }
/**
* Gets the index of this instruction.
@@ -30,7 +32,7 @@ class Instruction extends Element, ControlFlowNode, DataFlowNode, @cil_instructi
string getExtra() { none() }
private string getExtraStr() {
- if exists(getExtra()) then result = " " + getExtra() else result = ""
+ if exists(this.getExtra()) then result = " " + this.getExtra() else result = ""
}
/** Gets the declaration accessed by this instruction, if any. */
@@ -39,8 +41,8 @@ class Instruction extends Element, ControlFlowNode, DataFlowNode, @cil_instructi
/** Gets a successor instruction to this instruction. */
override Instruction getASuccessorType(FlowType t) {
t instanceof NormalFlow and
- canFlowNext() and
- result = this.getImplementation().getInstruction(getIndex() + 1)
+ this.canFlowNext() and
+ result = this.getImplementation().getInstruction(this.getIndex() + 1)
}
/** Holds if this instruction passes control flow into the next instruction. */
@@ -61,7 +63,7 @@ class Instruction extends Element, ControlFlowNode, DataFlowNode, @cil_instructi
override Location getALocation() {
cil_instruction_location(this, result) // The source code, if available
or
- result = getImplementation().getLocation() // The containing assembly
+ result = this.getImplementation().getLocation() // The containing assembly
}
override Location getLocation() { result = Element.super.getLocation() }
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/InstructionGroups.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/InstructionGroups.qll
index e4aeb05a839..5dac4bf7291 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/InstructionGroups.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/InstructionGroups.qll
@@ -14,7 +14,7 @@ class Expr extends DotNet::Expr, Instruction, @cil_expr {
override Type getType() { result = Instruction.super.getType() }
- override Method getEnclosingCallable() { result = getImplementation().getMethod() }
+ override Method getEnclosingCallable() { result = this.getImplementation().getMethod() }
/**
* The "parent" of a CIL expression is taken to be the instruction
@@ -28,13 +28,13 @@ class Branch extends Instruction, @cil_jump {
/** Gets the instruction that is jumped to. */
Instruction getTarget() { cil_jump(this, result) }
- override string getExtra() { result = getTarget().getIndex() + ":" }
+ override string getExtra() { result = this.getTarget().getIndex() + ":" }
}
/** An instruction that unconditionally jumps to another instruction. */
class UnconditionalBranch extends Branch, @cil_unconditional_jump {
override Instruction getASuccessorType(FlowType t) {
- t instanceof NormalFlow and result = getTarget()
+ t instanceof NormalFlow and result = this.getTarget()
}
override predicate canFlowNext() { none() }
@@ -43,9 +43,9 @@ class UnconditionalBranch extends Branch, @cil_unconditional_jump {
/** An instruction that jumps to a target based on a condition. */
class ConditionalBranch extends Branch, @cil_conditional_jump {
override Instruction getASuccessorType(FlowType t) {
- t instanceof TrueFlow and result = getTarget()
+ t instanceof TrueFlow and result = this.getTarget()
or
- t instanceof FalseFlow and result = getImplementation().getInstruction(getIndex() + 1)
+ t instanceof FalseFlow and result = this.getImplementation().getInstruction(this.getIndex() + 1)
}
override int getPushCount() { result = 0 }
@@ -61,7 +61,7 @@ class UnaryExpr extends Expr, @cil_unary_expr {
override int getPopCount() { result = 1 }
/** Gets the operand of this unary expression. */
- Expr getOperand() { result = getOperand(0) }
+ Expr getOperand() { result = this.getOperand(0) }
}
/** A binary expression that compares two values. */
@@ -73,8 +73,8 @@ class ComparisonOperation extends BinaryExpr, @cil_comparison_operation {
class BinaryArithmeticExpr extends BinaryExpr, @cil_binary_arithmetic_operation {
override Type getType() {
exists(Type t0, Type t1 |
- t0 = getOperand(0).getType().getUnderlyingType() and
- t1 = getOperand(1).getType().getUnderlyingType()
+ t0 = this.getOperand(0).getType().getUnderlyingType() and
+ t1 = this.getOperand(1).getType().getUnderlyingType()
|
t0 = t1 and result = t0
or
@@ -100,7 +100,7 @@ class UnaryBitwiseOperation extends UnaryExpr, @cil_unary_bitwise_operation {
/** A unary expression that converts a value from one primitive type to another. */
class Conversion extends UnaryExpr, @cil_conversion_operation {
/** Gets the expression being converted. */
- Expr getExpr() { result = getOperand(0) }
+ Expr getExpr() { result = this.getOperand(0) }
}
/** A branch that leaves the scope of a `Handler`. */
@@ -111,7 +111,7 @@ class Literal extends DotNet::Literal, Expr, @cil_literal {
/** Gets the pushed value. */
override string getValue() { cil_value(this, result) }
- override string getExtra() { result = getValue() }
+ override string getExtra() { result = this.getValue() }
}
/** An integer literal. */
@@ -149,44 +149,44 @@ class Call extends Expr, DotNet::Call, @cil_call_any {
/** Gets the method that is called. */
override Method getTarget() { cil_access(this, result) }
- override Method getARuntimeTarget() { result = getTarget().getAnOverrider*() }
+ override Method getARuntimeTarget() { result = this.getTarget().getAnOverrider*() }
- override string getExtra() { result = getTarget().getQualifiedName() }
+ override string getExtra() { result = this.getTarget().getQualifiedName() }
/**
* Gets the return type of the call. Methods that do not return a value
* return the `void` type, `System.Void`, although the value of `getPushCount` is
* 0 in this case.
*/
- override Type getType() { result = getTarget().getReturnType() }
+ override Type getType() { result = this.getTarget().getReturnType() }
// The number of items popped/pushed from the stack
// depends on the target of the call.
- override int getPopCount() { result = getTarget().getCallPopCount() }
+ override int getPopCount() { result = this.getTarget().getCallPopCount() }
- override int getPushCount() { result = getTarget().getCallPushCount() }
+ override int getPushCount() { result = this.getTarget().getCallPushCount() }
/**
* Holds if this is a "tail call", meaning that control does not return to the
* calling method.
*/
predicate isTailCall() {
- getImplementation().getInstruction(getIndex() - 1) instanceof Opcodes::Tail
+ this.getImplementation().getInstruction(this.getIndex() - 1) instanceof Opcodes::Tail
}
/** Holds if this call is virtual and could go to an overriding method. */
predicate isVirtual() { none() }
- override Expr getRawArgument(int i) { result = getOperand(getPopCount() - i - 1) }
+ override Expr getRawArgument(int i) { result = this.getOperand(this.getPopCount() - i - 1) }
/** Gets the qualifier of this call, if any. */
- Expr getQualifier() { result = getRawArgument(0) and not getTarget().isStatic() }
+ Expr getQualifier() { result = this.getRawArgument(0) and not this.getTarget().isStatic() }
override Expr getArgument(int i) {
- if getTarget().isStatic()
- then result = getRawArgument(i)
+ if this.getTarget().isStatic()
+ then result = this.getRawArgument(i)
else (
- result = getRawArgument(i + 1) and i >= 0
+ result = this.getRawArgument(i + 1) and i >= 0
)
}
@@ -217,10 +217,10 @@ class VirtualCall extends Call {
/** A read of an array element. */
class ReadArrayElement extends BinaryExpr, @cil_read_array {
/** Gets the array being read. */
- Expr getArray() { result = getOperand(1) }
+ Expr getArray() { result = this.getOperand(1) }
/** Gets the index into the array. */
- Expr getArrayIndex() { result = getOperand(0) }
+ Expr getArrayIndex() { result = this.getOperand(0) }
}
/** A write of an array element. */
@@ -233,14 +233,14 @@ class WriteArrayElement extends Instruction, @cil_write_array {
/** A `return` statement. */
class Return extends Instruction, @cil_ret {
/** Gets the expression being returned, if any. */
- Expr getExpr() { result = getOperand(0) }
+ Expr getExpr() { result = this.getOperand(0) }
override predicate canFlowNext() { none() }
}
/** A `throw` statement. */
class Throw extends Instruction, DotNet::Throw, @cil_throw_any {
- override Expr getExpr() { result = getOperand(0) }
+ override Expr getExpr() { result = this.getOperand(0) }
override predicate canFlowNext() { none() }
}
@@ -250,10 +250,10 @@ class StoreIndirect extends Instruction, @cil_stind {
override int getPopCount() { result = 2 }
/** Gets the location to store the value at. */
- Expr getAddress() { result = getOperand(1) }
+ Expr getAddress() { result = this.getOperand(1) }
/** Gets the value to store. */
- Expr getExpr() { result = getOperand(0) }
+ Expr getExpr() { result = this.getOperand(0) }
}
/** Loads a value from an address/location. */
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Instructions.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Instructions.qll
index e385ceced31..5752ae45b20 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Instructions.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Instructions.qll
@@ -83,21 +83,21 @@ module Opcodes {
class Ldc_i4 extends IntLiteral, @cil_ldc_i4 {
override string getOpcodeName() { result = "ldc.i4" }
- override string getExtra() { result = getValue() }
+ override string getExtra() { result = this.getValue() }
}
/** An `ldc.i8` instruction. */
class Ldc_i8 extends IntLiteral, @cil_ldc_i8 {
override string getOpcodeName() { result = "ldc.i8" }
- override string getExtra() { result = getValue() }
+ override string getExtra() { result = this.getValue() }
}
/** An `ldc.i4.s` instruction. */
class Ldc_i4_s extends IntLiteral, @cil_ldc_i4_s {
override string getOpcodeName() { result = "ldc.i4.s" }
- override string getExtra() { result = getValue() }
+ override string getExtra() { result = this.getValue() }
}
/** An `ldnull` instruction. */
@@ -115,7 +115,7 @@ module Opcodes {
class Ldc_r4 extends FloatLiteral, @cil_ldc_r4 {
override string getOpcodeName() { result = "ldc.r4" }
- override string getExtra() { result = getValue() }
+ override string getExtra() { result = this.getValue() }
override Type getType() { result instanceof FloatType }
}
@@ -124,7 +124,7 @@ module Opcodes {
class Ldc_r8 extends FloatLiteral, @cil_ldc_r8 {
override string getOpcodeName() { result = "ldc.r8" }
- override string getExtra() { result = getValue() }
+ override string getExtra() { result = this.getValue() }
override Type getType() { result instanceof DoubleType }
}
@@ -199,9 +199,9 @@ module Opcodes {
override string getOpcodeName() { result = "neg" }
override NumericType getType() {
- result = getOperand().getType()
+ result = this.getOperand().getType()
or
- getOperand().getType() instanceof Enum and result instanceof IntType
+ this.getOperand().getType() instanceof Enum and result instanceof IntType
}
}
@@ -260,7 +260,7 @@ module Opcodes {
override int getPushCount() { result = 2 } // This is the only instruction that pushes 2 items
- override Type getType() { result = getOperand(0).getType() }
+ override Type getType() { result = this.getOperand(0).getType() }
}
/** A `ret` instruction. */
@@ -270,7 +270,7 @@ module Opcodes {
override predicate canFlowNext() { none() }
override int getPopCount() {
- if getImplementation().getMethod().returnsVoid() then result = 0 else result = 1
+ if this.getImplementation().getMethod().returnsVoid() then result = 0 else result = 1
}
}
@@ -283,7 +283,7 @@ module Opcodes {
class Ldstr extends StringLiteral, @cil_ldstr {
override string getOpcodeName() { result = "ldstr" }
- override string getExtra() { result = "\"" + getValue() + "\"" }
+ override string getExtra() { result = "\"" + this.getValue() + "\"" }
override Type getType() { result instanceof StringType }
}
@@ -427,11 +427,14 @@ module Opcodes {
override Instruction getASuccessorType(FlowType t) {
t instanceof NormalFlow and
- (result = getTarget(_) or result = getImplementation().getInstruction(getIndex() + 1))
+ (
+ result = this.getTarget(_) or
+ result = this.getImplementation().getInstruction(this.getIndex() + 1)
+ )
}
override string getExtra() {
- result = concat(int n | exists(getTarget(n)) | getTarget(n).getIndex() + ":", " ")
+ result = concat(int n | exists(this.getTarget(n)) | this.getTarget(n).getIndex() + ":", " ")
}
}
@@ -493,9 +496,9 @@ module Opcodes {
// The number of items popped/pushed from the stack depends on the target of
// the call. Also, we need to pop the function pointer itself too.
- override int getPopCount() { result = getTargetType().getCallPopCount() + 1 }
+ override int getPopCount() { result = this.getTargetType().getCallPopCount() + 1 }
- override int getPushCount() { result = getTargetType().getCallPushCount() }
+ override int getPushCount() { result = this.getTargetType().getCallPushCount() }
}
/** A `callvirt` instruction. */
@@ -524,49 +527,49 @@ module Opcodes {
override BoolType getType() { exists(result) }
/** Gets the type that is being tested against. */
- Type getTestedType() { result = getAccess() }
+ Type getTestedType() { result = this.getAccess() }
- override string getExtra() { result = getTestedType().getQualifiedName() }
+ override string getExtra() { result = this.getTestedType().getQualifiedName() }
}
/** A `castclass` instruction. */
class Castclass extends UnaryExpr, @cil_castclass {
override string getOpcodeName() { result = "castclass" }
- override Type getType() { result = getAccess() }
+ override Type getType() { result = this.getAccess() }
/** Gets the type that is being cast to. */
- Type getTestedType() { result = getAccess() }
+ Type getTestedType() { result = this.getAccess() }
- override string getExtra() { result = getTestedType().getQualifiedName() }
+ override string getExtra() { result = this.getTestedType().getQualifiedName() }
}
/** An `stloc.0` instruction. */
class Stloc_0 extends LocalVariableWriteAccess, @cil_stloc_0 {
override string getOpcodeName() { result = "stloc.0" }
- override LocalVariable getTarget() { result = getImplementation().getLocalVariable(0) }
+ override LocalVariable getTarget() { result = this.getImplementation().getLocalVariable(0) }
}
/** An `stloc.1` instruction. */
class Stloc_1 extends LocalVariableWriteAccess, @cil_stloc_1 {
override string getOpcodeName() { result = "stloc.1" }
- override LocalVariable getTarget() { result = getImplementation().getLocalVariable(1) }
+ override LocalVariable getTarget() { result = this.getImplementation().getLocalVariable(1) }
}
/** An `stloc.2` instruction. */
class Stloc_2 extends LocalVariableWriteAccess, @cil_stloc_2 {
override string getOpcodeName() { result = "stloc.2" }
- override LocalVariable getTarget() { result = getImplementation().getLocalVariable(2) }
+ override LocalVariable getTarget() { result = this.getImplementation().getLocalVariable(2) }
}
/** An `stloc.3` instruction. */
class Stloc_3 extends LocalVariableWriteAccess, @cil_stloc_3 {
override string getOpcodeName() { result = "stloc.3" }
- override LocalVariable getTarget() { result = getImplementation().getLocalVariable(3) }
+ override LocalVariable getTarget() { result = this.getImplementation().getLocalVariable(3) }
}
/** An `stloc.s` instruction. */
@@ -587,28 +590,28 @@ module Opcodes {
class Ldloc_0 extends LocalVariableReadAccess, @cil_ldloc_0 {
override string getOpcodeName() { result = "ldloc.0" }
- override LocalVariable getTarget() { result = getImplementation().getLocalVariable(0) }
+ override LocalVariable getTarget() { result = this.getImplementation().getLocalVariable(0) }
}
/** An `ldloc.1` instruction. */
class Ldloc_1 extends LocalVariableReadAccess, @cil_ldloc_1 {
override string getOpcodeName() { result = "ldloc.1" }
- override LocalVariable getTarget() { result = getImplementation().getLocalVariable(1) }
+ override LocalVariable getTarget() { result = this.getImplementation().getLocalVariable(1) }
}
/** An `ldloc.2` instruction. */
class Ldloc_2 extends LocalVariableReadAccess, @cil_ldloc_2 {
override string getOpcodeName() { result = "ldloc.2" }
- override LocalVariable getTarget() { result = getImplementation().getLocalVariable(2) }
+ override LocalVariable getTarget() { result = this.getImplementation().getLocalVariable(2) }
}
/** An `ldloc.3` instruction. */
class Ldloc_3 extends LocalVariableReadAccess, @cil_ldloc_3 {
override string getOpcodeName() { result = "ldloc.3" }
- override LocalVariable getTarget() { result = getImplementation().getLocalVariable(3) }
+ override LocalVariable getTarget() { result = this.getImplementation().getLocalVariable(3) }
}
/** An `ldloc.s` instruction. */
@@ -617,7 +620,7 @@ module Opcodes {
override LocalVariable getTarget() { cil_access(this, result) }
- override string getExtra() { result = "L" + getTarget().getIndex() }
+ override string getExtra() { result = "L" + this.getTarget().getIndex() }
}
/** An `ldloca.s` instruction. */
@@ -626,7 +629,7 @@ module Opcodes {
override LocalVariable getTarget() { cil_access(this, result) }
- override string getExtra() { result = "L" + getTarget().getIndex() }
+ override string getExtra() { result = "L" + this.getTarget().getIndex() }
}
/** An `ldloc` instruction. */
@@ -635,7 +638,7 @@ module Opcodes {
override LocalVariable getTarget() { cil_access(this, result) }
- override string getExtra() { result = "L" + getTarget().getIndex() }
+ override string getExtra() { result = "L" + this.getTarget().getIndex() }
}
/** An `ldarg.0` instruction. */
@@ -643,7 +646,7 @@ module Opcodes {
override string getOpcodeName() { result = "ldarg.0" }
override MethodParameter getTarget() {
- result = getImplementation().getMethod().getRawParameter(0)
+ result = this.getImplementation().getMethod().getRawParameter(0)
}
}
@@ -652,7 +655,7 @@ module Opcodes {
override string getOpcodeName() { result = "ldarg.1" }
override MethodParameter getTarget() {
- result = getImplementation().getMethod().getRawParameter(1)
+ result = this.getImplementation().getMethod().getRawParameter(1)
}
}
@@ -661,7 +664,7 @@ module Opcodes {
override string getOpcodeName() { result = "ldarg.2" }
override MethodParameter getTarget() {
- result = getImplementation().getMethod().getRawParameter(2)
+ result = this.getImplementation().getMethod().getRawParameter(2)
}
}
@@ -670,7 +673,7 @@ module Opcodes {
override string getOpcodeName() { result = "ldarg.3" }
override MethodParameter getTarget() {
- result = getImplementation().getMethod().getRawParameter(3)
+ result = this.getImplementation().getMethod().getRawParameter(3)
}
}
@@ -710,7 +713,7 @@ module Opcodes {
override int getPopCount() { result = 1 }
- override Expr getQualifier() { result = getOperand(0) }
+ override Expr getQualifier() { result = this.getOperand(0) }
}
/** An `ldflda` instruction. */
@@ -719,7 +722,7 @@ module Opcodes {
override int getPopCount() { result = 1 }
- override Expr getQualifier() { result = getOperand(0) }
+ override Expr getQualifier() { result = this.getOperand(0) }
}
/** An `ldsfld` instruction. */
@@ -746,9 +749,9 @@ module Opcodes {
override int getPopCount() { result = 2 }
- override Expr getQualifier() { result = getOperand(1) }
+ override Expr getQualifier() { result = this.getOperand(1) }
- override Expr getExpr() { result = getOperand(0) }
+ override Expr getExpr() { result = this.getOperand(0) }
}
/** An `stsfld` instruction. */
@@ -759,7 +762,7 @@ module Opcodes {
override Expr getQualifier() { none() }
- override Expr getExpr() { result = getOperand(0) }
+ override Expr getExpr() { result = this.getOperand(0) }
}
/** A `newobj` instruction. */
@@ -772,7 +775,7 @@ module Opcodes {
override Type getType() { result = this.getTarget().getDeclaringType() }
- override Expr getArgument(int i) { result = getRawArgument(i) }
+ override Expr getArgument(int i) { result = this.getRawArgument(i) }
pragma[noinline]
private Parameter getARawTargetParameter() { result = this.getTarget().getARawParameter() }
@@ -796,21 +799,21 @@ module Opcodes {
class Box extends UnaryExpr, @cil_box {
override string getOpcodeName() { result = "box" }
- override Type getType() { result = getAccess() }
+ override Type getType() { result = this.getAccess() }
}
/** An `unbox.any` instruction. */
class Unbox_any extends UnaryExpr, @cil_unbox_any {
override string getOpcodeName() { result = "unbox.any" }
- override Type getType() { result = getAccess() }
+ override Type getType() { result = this.getAccess() }
}
/** An `unbox` instruction. */
class Unbox extends UnaryExpr, @cil_unbox {
override string getOpcodeName() { result = "unbox" }
- override Type getType() { result = getAccess() }
+ override Type getType() { result = this.getAccess() }
}
/** An `ldobj` instruction. */
@@ -820,7 +823,7 @@ module Opcodes {
/** Gets the type of the object. */
Type getTarget() { cil_access(this, result) }
- override Type getType() { result = getAccess() }
+ override Type getType() { result = this.getAccess() }
}
/** An `ldtoken` instruction. */
@@ -867,31 +870,31 @@ module Opcodes {
// Note that this is technically wrong - it should be
// result.(ArrayType).getElementType() = getAccess()
// However the (ArrayType) may not be in the database.
- result = getAccess()
+ result = this.getAccess()
}
- override string getExtra() { result = getType().getQualifiedName() }
+ override string getExtra() { result = this.getType().getQualifiedName() }
}
/** An `ldelem` instruction. */
class Ldelem extends ReadArrayElement, @cil_ldelem {
override string getOpcodeName() { result = "ldelem" }
- override Type getType() { result = getAccess() }
+ override Type getType() { result = this.getAccess() }
}
/** An `ldelem.ref` instruction. */
class Ldelem_ref extends ReadArrayElement, @cil_ldelem_ref {
override string getOpcodeName() { result = "ldelem.ref" }
- override Type getType() { result = getArray().getType() }
+ override Type getType() { result = this.getArray().getType() }
}
/** An `ldelema` instruction. */
class Ldelema extends ReadArrayElement, ReadRef, @cil_ldelema {
override string getOpcodeName() { result = "ldelema" }
- override Type getType() { result = getAccess() }
+ override Type getType() { result = this.getAccess() }
}
/** An `stelem.ref` instruction. */
@@ -1410,7 +1413,7 @@ module Opcodes {
override int getPopCount() { result = 1 }
- override Type getType() { result = getAccess() }
+ override Type getType() { result = this.getAccess() }
}
/** A `refanytype` instruction. */
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Method.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Method.qll
index 82bde17a477..461a020972b 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Method.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Method.qll
@@ -28,13 +28,13 @@ class MethodImplementation extends EntryPoint, @cil_method_implementation {
LocalVariable getLocalVariable(int n) { cil_local_variable(result, this, n, _) }
/** Gets a local variable of this implementation, if any. */
- LocalVariable getALocalVariable() { result = getLocalVariable(_) }
+ LocalVariable getALocalVariable() { result = this.getLocalVariable(_) }
/** Gets an instruction in this implementation, if any. */
- Instruction getAnInstruction() { result = getInstruction(_) }
+ Instruction getAnInstruction() { result = this.getInstruction(_) }
/** Gets the total number of instructions in this implementation. */
- int getNumberOfInstructions() { result = count(getAnInstruction()) }
+ int getNumberOfInstructions() { result = count(this.getAnInstruction()) }
/** Gets the `i`th handler in this implementation. */
Handler getHandler(int i) { result.getImplementation() = this and result.getIndex() = i }
@@ -49,7 +49,7 @@ class MethodImplementation extends EntryPoint, @cil_method_implementation {
/** Gets the maximum stack size of this implementation. */
int getStackSize() { cil_method_stack_size(this, result) }
- override string toString() { result = getMethod().toString() }
+ override string toString() { result = this.getMethod().toString() }
/** Gets a string representing the disassembly of this implementation. */
string getDisassembly() {
@@ -75,13 +75,13 @@ class Method extends DotNet::Callable, Element, Member, TypeContainer, DataFlowN
MethodImplementation getAnImplementation() { result.getMethod() = this }
/** Gets the "best" implementation of this method, if any. */
- BestImplementation getImplementation() { result = getAnImplementation() }
+ BestImplementation getImplementation() { result = this.getAnImplementation() }
override Method getMethod() { result = this }
override string getName() { cil_method(this, result, _, _) }
- override string getUndecoratedName() { result = getName() }
+ override string getUndecoratedName() { result = this.getName() }
override string toString() { result = this.getName() }
@@ -92,25 +92,29 @@ class Method extends DotNet::Callable, Element, Member, TypeContainer, DataFlowN
override Location getALocation() { cil_method_location(this.getUnboundDeclaration(), result) }
override MethodParameter getParameter(int n) {
- if isStatic() then result = getRawParameter(n) else (result = getRawParameter(n + 1) and n >= 0)
+ if this.isStatic()
+ then result = this.getRawParameter(n)
+ else (
+ result = this.getRawParameter(n + 1) and n >= 0
+ )
}
- override Type getType() { result = getReturnType() }
+ override Type getType() { result = this.getReturnType() }
/** Gets the return type of this method. */
override Type getReturnType() { cil_method(this, _, _, result) }
/** Holds if the return type is `void`. */
- predicate returnsVoid() { getReturnType() instanceof VoidType }
+ predicate returnsVoid() { this.getReturnType() instanceof VoidType }
/** Gets the number of stack items pushed in a call to this method. */
- int getCallPushCount() { if returnsVoid() then result = 0 else result = 1 }
+ int getCallPushCount() { if this.returnsVoid() then result = 0 else result = 1 }
/** Gets the number of stack items popped in a call to this method. */
- int getCallPopCount() { result = count(getRawParameter(_)) }
+ int getCallPopCount() { result = count(this.getRawParameter(_)) }
/** Gets a method called by this method. */
- Method getACallee() { result = getImplementation().getAnInstruction().(Call).getTarget() }
+ Method getACallee() { result = this.getImplementation().getAnInstruction().(Call).getTarget() }
/** Holds if this method is `virtual`. */
predicate isVirtual() { cil_virtual(this) }
@@ -129,43 +133,45 @@ class Method extends DotNet::Callable, Element, Member, TypeContainer, DataFlowN
/** Gets the unbound declaration of this method, or the method itself. */
Method getUnboundMethod() { cil_method_source_declaration(this, result) }
- override Method getUnboundDeclaration() { result = getUnboundMethod() }
+ override Method getUnboundDeclaration() { result = this.getUnboundMethod() }
/** Holds if this method is an instance constructor. */
- predicate isInstanceConstructor() { isSpecial() and getName() = ".ctor" }
+ predicate isInstanceConstructor() { this.isSpecial() and this.getName() = ".ctor" }
/** Holds if this method is a static class constructor. */
- predicate isStaticConstructor() { isSpecial() and getName() = ".cctor" }
+ predicate isStaticConstructor() { this.isSpecial() and this.getName() = ".cctor" }
/** Holds if this method is a constructor (static or instance). */
- predicate isConstructor() { isStaticConstructor() or isInstanceConstructor() }
+ predicate isConstructor() { this.isStaticConstructor() or this.isInstanceConstructor() }
/** Holds if this method is a destructor/finalizer. */
- predicate isFinalizer() { getOverriddenMethod*().getQualifiedName() = "System.Object.Finalize" }
+ predicate isFinalizer() {
+ this.getOverriddenMethod*().getQualifiedName() = "System.Object.Finalize"
+ }
/** Holds if this method is an operator. */
- predicate isOperator() { isSpecial() and getName().matches("op\\_%") }
+ predicate isOperator() { this.isSpecial() and this.getName().matches("op\\_%") }
/** Holds if this method is a getter. */
- predicate isGetter() { isSpecial() and getName().matches("get\\_%") }
+ predicate isGetter() { this.isSpecial() and this.getName().matches("get\\_%") }
/** Holds if this method is a setter. */
- predicate isSetter() { isSpecial() and getName().matches("set\\_%") }
+ predicate isSetter() { this.isSpecial() and this.getName().matches("set\\_%") }
/** Holds if this method is an adder/add event accessor. */
- predicate isAdder() { isSpecial() and getName().matches("add\\_%") }
+ predicate isAdder() { this.isSpecial() and this.getName().matches("add\\_%") }
/** Holds if this method is a remover/remove event accessor. */
- predicate isRemove() { isSpecial() and getName().matches("remove\\_%") }
+ predicate isRemove() { this.isSpecial() and this.getName().matches("remove\\_%") }
/** Holds if this method is an implicit conversion operator. */
- predicate isImplicitConversion() { isSpecial() and getName() = "op_Implicit" }
+ predicate isImplicitConversion() { this.isSpecial() and this.getName() = "op_Implicit" }
/** Holds if this method is an explicit conversion operator. */
- predicate isExplicitConversion() { isSpecial() and getName() = "op_Explicit" }
+ predicate isExplicitConversion() { this.isSpecial() and this.getName() = "op_Explicit" }
/** Holds if this method is a conversion operator. */
- predicate isConversion() { isImplicitConversion() or isExplicitConversion() }
+ predicate isConversion() { this.isImplicitConversion() or this.isExplicitConversion() }
/**
* Gets a method that is overridden, either in a base class
@@ -176,7 +182,7 @@ class Method extends DotNet::Callable, Element, Member, TypeContainer, DataFlowN
/** Gets a method that overrides this method, if any. */
final Method getAnOverrider() { result.getOverriddenMethod() = this }
- override predicate hasBody() { exists(getImplementation()) }
+ override predicate hasBody() { exists(this.getImplementation()) }
override predicate canReturn(DotNet::Expr expr) {
exists(Return ret | ret.getImplementation() = this.getImplementation() and expr = ret.getExpr())
@@ -206,7 +212,7 @@ class InstanceConstructor extends Constructor {
/** A method that always returns the `this` parameter. */
class ChainingMethod extends Method {
ChainingMethod() {
- forex(Return ret | ret = getImplementation().getAnInstruction() |
+ forex(Return ret | ret = this.getImplementation().getAnInstruction() |
ret.getExpr() instanceof ThisAccess
)
}
@@ -231,7 +237,7 @@ class Getter extends Accessor {
*/
class TrivialGetter extends Method {
TrivialGetter() {
- exists(MethodImplementation impl | impl = getAnImplementation() |
+ exists(MethodImplementation impl | impl = this.getAnImplementation() |
impl.getInstruction(0) instanceof ThisAccess and
impl.getInstruction(1) instanceof FieldReadAccess and
impl.getInstruction(2) instanceof Return
@@ -239,7 +245,9 @@ class TrivialGetter extends Method {
}
/** Gets the underlying field of this getter. */
- Field getField() { getImplementation().getAnInstruction().(FieldReadAccess).getTarget() = result }
+ Field getField() {
+ this.getImplementation().getAnInstruction().(FieldReadAccess).getTarget() = result
+ }
}
/** A setter. */
@@ -262,7 +270,7 @@ class Setter extends Accessor {
*/
class TrivialSetter extends Method {
TrivialSetter() {
- exists(MethodImplementation impl | impl = getImplementation() |
+ exists(MethodImplementation impl | impl = this.getImplementation() |
impl.getInstruction(0) instanceof ThisAccess and
impl.getInstruction(1).(ParameterReadAccess).getTarget().getIndex() = 1 and
impl.getInstruction(2) instanceof FieldWriteAccess
@@ -271,7 +279,7 @@ class TrivialSetter extends Method {
/** Gets the underlying field of this setter. */
Field getField() {
- result = getImplementation().getAnInstruction().(FieldWriteAccess).getTarget()
+ result = this.getImplementation().getAnInstruction().(FieldWriteAccess).getTarget()
}
}
@@ -283,5 +291,5 @@ class Operator extends Method {
Operator() { this.isOperator() }
/** Gets the name of the implementing method (for compatibility with C# data model). */
- string getFunctionName() { result = getName() }
+ string getFunctionName() { result = this.getName() }
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Type.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Type.qll
index a081d62b7ee..7aeaf9a6495 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Type.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Type.qll
@@ -19,7 +19,7 @@ class TypeContainer extends DotNet::NamedElement, @cil_type_container {
/** A namespace. */
class Namespace extends DotNet::Namespace, TypeContainer, @namespace {
- override string toString() { result = getQualifiedName() }
+ override string toString() { result = this.getQualifiedName() }
override Namespace getParent() { result = this.getParentNamespace() }
@@ -39,7 +39,7 @@ class Type extends DotNet::Type, Declaration, TypeContainer, @cil_type {
override string toString() { result = this.getName() }
/** Gets the containing type of this type, if any. */
- override Type getDeclaringType() { result = getParent() }
+ override Type getDeclaringType() { result = this.getParent() }
/** Gets a member of this type, if any. */
Member getAMember() { result.getDeclaringType() = this }
@@ -96,13 +96,13 @@ class Type extends DotNet::Type, Declaration, TypeContainer, @cil_type {
Type getABaseInterface() { cil_base_interface(this, result) }
/** Gets an immediate base type of this type, if any. */
- Type getABaseType() { result = getBaseClass() or result = getABaseInterface() }
+ Type getABaseType() { result = this.getBaseClass() or result = this.getABaseInterface() }
/** Gets an immediate subtype of this type, if any. */
Type getASubtype() { result.getABaseType() = this }
/** Gets the namespace directly containing this type, if any. */
- Namespace getNamespace() { result = getParent() }
+ Namespace getNamespace() { result = this.getParent() }
/**
* Gets an index for implicit conversions. A type can be converted to another numeric type
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Types.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Types.qll
index d4d9342b73d..1dfaa0191a1 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Types.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Types.qll
@@ -12,7 +12,7 @@ class TypeParameter extends DotNet::TypeParameter, Type, @cil_typeparameter {
/** Gets the generic type/method declaring this type parameter. */
TypeContainer getGeneric() { cil_type_parameter(result, _, this) }
- override Location getLocation() { result = getParent().getLocation() }
+ override Location getLocation() { result = this.getParent().getLocation() }
/** Holds if this type parameter has the `new` constraint. */
predicate isDefaultConstructible() { cil_typeparam_new(this) }
@@ -34,11 +34,11 @@ class TypeParameter extends DotNet::TypeParameter, Type, @cil_typeparameter {
/** A value or reference type. */
class ValueOrRefType extends DotNet::ValueOrRefType, Type, @cil_valueorreftype {
- override ValueOrRefType getDeclaringType() { result = getParent() }
+ override ValueOrRefType getDeclaringType() { result = this.getParent() }
override string getUndecoratedName() { cil_type(this, result, _, _, _) }
- override Namespace getDeclaringNamespace() { result = getNamespace() }
+ override Namespace getDeclaringNamespace() { result = this.getNamespace() }
override ValueOrRefType getABaseType() { result = Type.super.getABaseType() }
}
@@ -79,7 +79,7 @@ class ArrayType extends DotNet::ArrayType, Type, @cil_array_type {
override string toStringWithTypes() { result = DotNet::ArrayType.super.toStringWithTypes() }
- override Location getLocation() { result = getElementType().getLocation() }
+ override Location getLocation() { result = this.getElementType().getLocation() }
override ValueOrRefType getABaseType() { result = Type.super.getABaseType() }
}
@@ -92,7 +92,7 @@ class PointerType extends DotNet::PointerType, PrimitiveType, @cil_pointer_type
override string getName() { result = DotNet::PointerType.super.getName() }
- override Location getLocation() { result = getReferentType().getLocation() }
+ override Location getLocation() { result = this.getReferentType().getLocation() }
override string toString() { result = DotNet::PointerType.super.toString() }
@@ -312,13 +312,13 @@ class FunctionPointerType extends Type, CustomModifierReceiver, Parameterizable,
override string toString() { result = Type.super.toString() }
/** Holds if the return type is `void`. */
- predicate returnsVoid() { getReturnType() instanceof VoidType }
+ predicate returnsVoid() { this.getReturnType() instanceof VoidType }
/** Gets the number of stack items pushed in a call to this method. */
- int getCallPushCount() { if returnsVoid() then result = 0 else result = 1 }
+ int getCallPushCount() { if this.returnsVoid() then result = 0 else result = 1 }
/** Gets the number of stack items popped in a call to this method. */
- int getCallPopCount() { result = count(getRawParameter(_)) }
+ int getCallPopCount() { result = count(this.getRawParameter(_)) }
- override string getLabel() { result = getName() }
+ override string getLabel() { result = this.getName() }
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Variable.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Variable.qll
index 3a247e1f0d1..604f2c2b646 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Variable.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/Variable.qll
@@ -17,10 +17,10 @@ class Variable extends DotNet::Variable, Declaration, DataFlowNode, @cil_variabl
VariableAccess getAnAccess() { result.getTarget() = this }
/** Gets a read access to this variable, if any. */
- ReadAccess getARead() { result = getAnAccess() }
+ ReadAccess getARead() { result = this.getAnAccess() }
/** Gets a write access to this variable, if any. */
- WriteAccess getAWrite() { result = getAnAccess() }
+ WriteAccess getAWrite() { result = this.getAnAccess() }
override string toString() { result = Declaration.super.toString() }
@@ -40,20 +40,21 @@ class StackVariable extends Variable, @cil_stack_variable {
class LocalVariable extends StackVariable, @cil_local_variable {
override string toString() {
result =
- "Local variable " + getIndex() + " of method " + getImplementation().getMethod().getName()
+ "Local variable " + this.getIndex() + " of method " +
+ this.getImplementation().getMethod().getName()
}
/** Gets the method implementation defining this local variable. */
MethodImplementation getImplementation() { this = result.getALocalVariable() }
/** Gets the index number of this local variable. This is not usually significant. */
- int getIndex() { this = getImplementation().getLocalVariable(result) }
+ int getIndex() { this = this.getImplementation().getLocalVariable(result) }
override Type getType() { cil_local_variable(this, _, _, result) }
- override Location getLocation() { result = getImplementation().getLocation() }
+ override Location getLocation() { result = this.getImplementation().getLocation() }
- override Method getMethod() { result = getImplementation().getMethod() }
+ override Method getMethod() { result = this.getImplementation().getMethod() }
}
/** A parameter of a `Method` or `FunctionPointerType`. */
@@ -64,7 +65,7 @@ class Parameter extends DotNet::Parameter, CustomModifierReceiver, @cil_paramete
int getIndex() { cil_parameter(this, _, result, _) }
override string toString() {
- result = "Parameter " + getIndex() + " of " + getDeclaringElement().getName()
+ result = "Parameter " + this.getIndex() + " of " + this.getDeclaringElement().getName()
}
override Type getType() { cil_parameter(this, _, _, result) }
@@ -82,23 +83,25 @@ class Parameter extends DotNet::Parameter, CustomModifierReceiver, @cil_paramete
predicate hasInFlag() { cil_parameter_in(this) }
/** Holds if this parameter has C# `out` semantics. */
- override predicate isOut() { hasOutFlag() and not hasInFlag() }
+ override predicate isOut() { this.hasOutFlag() and not this.hasInFlag() }
/** Holds if this parameter has C# `ref` semantics. */
- override predicate isRef() { hasOutFlag() and hasInFlag() }
+ override predicate isRef() { this.hasOutFlag() and this.hasInFlag() }
- override string toStringWithTypes() { result = getPrefix() + getType().toStringWithTypes() }
+ override string toStringWithTypes() {
+ result = this.getPrefix() + this.getType().toStringWithTypes()
+ }
private string getPrefix() {
- if isOut()
+ if this.isOut()
then result = "out "
else
- if isRef()
+ if this.isRef()
then result = "ref "
else result = ""
}
- override Location getLocation() { result = getDeclaringElement().getLocation() }
+ override Location getLocation() { result = this.getDeclaringElement().getLocation() }
}
/** A method parameter. */
@@ -110,11 +113,11 @@ class MethodParameter extends Parameter, StackVariable {
/** Gets a parameter in an overridden method. */
MethodParameter getOverriddenParameter() {
- result = getMethod().getOverriddenMethod().getRawParameter(getRawPosition())
+ result = this.getMethod().getOverriddenMethod().getRawParameter(this.getRawPosition())
}
override MethodParameter getUnboundDeclaration() {
- result = getMethod().getUnboundDeclaration().getRawParameter(getRawPosition())
+ result = this.getMethod().getUnboundDeclaration().getRawParameter(this.getRawPosition())
}
override string toString() { result = Parameter.super.toString() }
@@ -136,10 +139,10 @@ class ThisParameter extends MethodParameter {
/** A field. */
class Field extends DotNet::Field, Variable, Member, CustomModifierReceiver, @cil_field {
- override string toString() { result = getName() }
+ override string toString() { result = this.getName() }
override string toStringWithTypes() {
- result = getDeclaringType().toStringWithTypes() + "." + getName()
+ result = this.getDeclaringType().toStringWithTypes() + "." + this.getName()
}
override string getName() { cil_field(this, _, result, _) }
@@ -148,5 +151,5 @@ class Field extends DotNet::Field, Variable, Member, CustomModifierReceiver, @ci
override ValueOrRefType getDeclaringType() { cil_field(this, result, _, _) }
- override Location getLocation() { result = getDeclaringType().getLocation() }
+ override Location getLocation() { result = this.getDeclaringType().getLocation() }
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/internal/SsaImplCommon.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/internal/SsaImplCommon.qll
index 884f4406d01..eae5d23f544 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/internal/SsaImplCommon.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/cil/internal/SsaImplCommon.qll
@@ -141,24 +141,23 @@ private module Liveness {
private import Liveness
-/** Holds if `bb1` strictly dominates `bb2`. */
-private predicate strictlyDominates(BasicBlock bb1, BasicBlock bb2) {
- bb1 = getImmediateBasicBlockDominator+(bb2)
-}
-
-/** Holds if `bb1` dominates a predecessor of `bb2`. */
-private predicate dominatesPredecessor(BasicBlock bb1, BasicBlock bb2) {
- exists(BasicBlock pred | pred = getABasicBlockPredecessor(bb2) |
- bb1 = pred
- or
- strictlyDominates(bb1, pred)
- )
-}
-
-/** Holds if `df` is in the dominance frontier of `bb`. */
+/**
+ * Holds if `df` is in the dominance frontier of `bb`.
+ *
+ * This is equivalent to:
+ *
+ * ```ql
+ * bb = getImmediateBasicBlockDominator*(getABasicBlockPredecessor(df)) and
+ * not bb = getImmediateBasicBlockDominator+(df)
+ * ```
+ */
private predicate inDominanceFrontier(BasicBlock bb, BasicBlock df) {
- dominatesPredecessor(bb, df) and
- not strictlyDominates(bb, df)
+ bb = getABasicBlockPredecessor(df) and not bb = getImmediateBasicBlockDominator(df)
+ or
+ exists(BasicBlock prev | inDominanceFrontier(prev, df) |
+ bb = getImmediateBasicBlockDominator(prev) and
+ not bb = getImmediateBasicBlockDominator(df)
+ )
}
/**
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/AnnotatedType.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/AnnotatedType.qll
index 37aa2b23410..8afdbd0d4a3 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/AnnotatedType.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/AnnotatedType.qll
@@ -67,7 +67,7 @@ private module Annotations {
Nullability() { this = TNullability(nullability) }
- override string toString() { result = getMemberString() + getSelfNullability() }
+ override string toString() { result = this.getMemberString() + this.getSelfNullability() }
language[monotonicAggregates]
private string getMemberString() {
@@ -125,7 +125,9 @@ private module Annotations {
}
/** Gets a textual representation of this type annotation. */
- string toString() { result = getTypePrefix() + getNullability() + getTypeSuffix() }
+ string toString() {
+ result = this.getTypePrefix() + this.getNullability() + this.getTypeSuffix()
+ }
private int getFlags() { this = TAnnotationFlags(result, _) }
@@ -136,7 +138,7 @@ private module Annotations {
/** Gets an annotation in this set of annotations. */
TypeAnnotation getAnAnnotation() {
- isSet(result.getBit())
+ this.isSet(result.getBit())
or
result = this.getNullability()
}
@@ -298,7 +300,7 @@ class AnnotatedType extends TAnnotatedType {
/** Gets a textual representation of this annotated type. */
string toString() {
result =
- annotations.getTypePrefix() + getUnderlyingType().toStringWithTypes() +
+ annotations.getTypePrefix() + this.getUnderlyingType().toStringWithTypes() +
annotations.getTypeSuffix()
}
@@ -327,7 +329,7 @@ class AnnotatedType extends TAnnotatedType {
/** Gets a type annotation of this annotated type. */
private Annotations::TypeAnnotation getAnAnnotation() {
- result = getAnnotations().getAnAnnotation()
+ result = this.getAnnotations().getAnAnnotation()
}
/** Holds if the type is a non-nullable reference, for example, `string` in a nullable-enabled context. */
@@ -376,7 +378,7 @@ class AnnotatedArrayType extends AnnotatedType {
private string getDimensionString(AnnotatedType elementType) {
exists(AnnotatedType et, string res |
- et = getElementType() and
+ et = this.getElementType() and
res = type.getArraySuffix() and
if et.getUnderlyingType() instanceof ArrayType and not et.isNullableRefType()
then result = res + et.(AnnotatedArrayType).getDimensionString(elementType)
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Attribute.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Attribute.qll
index 06fbda2a150..dae9f8a9fad 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Attribute.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Attribute.qll
@@ -89,7 +89,7 @@ class Attribute extends TopLevelExprParent, @attribute {
override Location getALocation() { attribute_location(this, result) }
override string toString() {
- exists(string type, string name | type = getType().getName() |
+ exists(string type, string name | type = this.getType().getName() |
(if type.matches("%Attribute") then name = type.prefix(type.length() - 9) else name = type) and
result = "[" + name + "(...)]"
)
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Callable.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Callable.qll
index 133ae86d551..41641a9d032 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Callable.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Callable.qll
@@ -117,7 +117,7 @@ class Callable extends DotNet::Callable, Parameterizable, ExprOrStmtParent, @cal
final BlockStmt getAStatementBody() { result = this.getStatementBody() }
/** Holds if this callable has a statement body. */
- final predicate hasStatementBody() { exists(getStatementBody()) }
+ final predicate hasStatementBody() { exists(this.getStatementBody()) }
/**
* Gets the expression body of this callable (if any), specified by `=>`.
@@ -157,7 +157,7 @@ class Callable extends DotNet::Callable, Parameterizable, ExprOrStmtParent, @cal
deprecated final Expr getAnExpressionBody() { result = this.getExpressionBody() }
/** Holds if this callable has an expression body. */
- final predicate hasExpressionBody() { exists(getExpressionBody()) }
+ final predicate hasExpressionBody() { exists(this.getExpressionBody()) }
/** Gets the entry point in the control graph for this callable. */
ControlFlow::Nodes::EntryNode getEntryPoint() { result.getCallable() = this }
@@ -218,7 +218,9 @@ class Callable extends DotNet::Callable, Parameterizable, ExprOrStmtParent, @cal
exists(YieldReturnStmt yield | yield.getEnclosingCallable() = this | e = yield.getExpr())
}
- override string toStringWithTypes() { result = getName() + "(" + parameterTypesToString() + ")" }
+ override string toStringWithTypes() {
+ result = this.getName() + "(" + this.parameterTypesToString() + ")"
+ }
/** Gets a `Call` that has this callable as a target. */
Call getACall() { this = result.getTarget() }
@@ -270,18 +272,18 @@ class Method extends Callable, Virtualizable, Attributable, @method {
override Location getALocation() { method_location(this, result) }
/** Holds if this method is an extension method. */
- predicate isExtensionMethod() { getParameter(0).hasExtensionMethodModifier() }
+ predicate isExtensionMethod() { this.getParameter(0).hasExtensionMethodModifier() }
/** Gets the type of the `params` parameter of this method, if any. */
Type getParamsType() {
- exists(Parameter last | last = getParameter(getNumberOfParameters() - 1) |
+ exists(Parameter last | last = this.getParameter(this.getNumberOfParameters() - 1) |
last.isParams() and
result = last.getType().(ArrayType).getElementType()
)
}
/** Holds if this method has a `params` parameter. */
- predicate hasParams() { exists(getParamsType()) }
+ predicate hasParams() { exists(this.getParamsType()) }
// Remove when `Callable.isOverridden()` is removed
override predicate isOverridden() { Virtualizable.super.isOverridden() }
@@ -316,7 +318,7 @@ class ExtensionMethod extends Method {
/** Gets the type being extended by this method. */
pragma[noinline]
- Type getExtendedType() { result = getParameter(0).getType() }
+ Type getExtendedType() { result = this.getParameter(0).getType() }
override string getAPrimaryQlClass() { result = "ExtensionMethod" }
}
@@ -355,7 +357,7 @@ class Constructor extends DotNet::Constructor, Callable, Member, Attributable, @
ConstructorInitializer getInitializer() { result = this.getChildExpr(-1) }
/** Holds if this constructor has an initializer. */
- predicate hasInitializer() { exists(getInitializer()) }
+ predicate hasInitializer() { exists(this.getInitializer()) }
override ValueOrRefType getDeclaringType() { constructors(this, _, result, _) }
@@ -467,7 +469,7 @@ class Operator extends Callable, Member, Attributable, @operator {
override string toString() { result = Callable.super.toString() }
- override Parameter getRawParameter(int i) { result = getParameter(i) }
+ override Parameter getRawParameter(int i) { result = this.getParameter(i) }
}
/** A clone method on a record. */
@@ -999,10 +1001,10 @@ class LocalFunction extends Callable, Modifiable, Attributable, @local_function
override Type getReturnType() { local_functions(this, _, result, _) }
- override Element getParent() { result = getStatement().getParent() }
+ override Element getParent() { result = this.getStatement().getParent() }
/** Gets the local function statement defining this function. */
- LocalFunctionStmt getStatement() { result.getLocalFunction() = getUnboundDeclaration() }
+ LocalFunctionStmt getStatement() { result.getLocalFunction() = this.getUnboundDeclaration() }
override Callable getEnclosingCallable() { result = this.getStatement().getEnclosingCallable() }
@@ -1011,9 +1013,9 @@ class LocalFunction extends Callable, Modifiable, Attributable, @local_function
name = this.getName()
}
- override Location getALocation() { result = getStatement().getALocation() }
+ override Location getALocation() { result = this.getStatement().getALocation() }
- override Parameter getRawParameter(int i) { result = getParameter(i) }
+ override Parameter getRawParameter(int i) { result = this.getParameter(i) }
override string getAPrimaryQlClass() { result = "LocalFunction" }
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Comments.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Comments.qll
index 41f4e5b0be8..9a611b851e8 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Comments.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Comments.qll
@@ -11,7 +11,7 @@ import Location
/**
* A single line of comment.
*
- * Either a single line comment (`SingleLineComment`), an XML comment (`XmlComment`),
+ * Either a single line comment (`SinglelineComment`), an XML comment (`XmlComment`),
* or a line in a multi-line comment (`MultilineComment`).
*/
class CommentLine extends @commentline {
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Conversion.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Conversion.qll
index 0c560d1d86f..d62055b6a17 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Conversion.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Conversion.qll
@@ -552,11 +552,16 @@ private predicate defaultDynamicConversion(Type fromType, Type toType) {
fromType instanceof RefType and toType instanceof DynamicType
}
+pragma[noinline]
+private predicate systemDelegateBaseType(RefType t) {
+ t = any(SystemDelegateClass c).getABaseType*()
+}
+
// This is a deliberate, small cartesian product, so we have manually lifted it to force the
// evaluator to evaluate it in its entirety, rather than trying to optimize it in context.
pragma[noinline]
private predicate defaultDelegateConversion(RefType fromType, RefType toType) {
- fromType instanceof DelegateType and toType = any(SystemDelegateClass c).getABaseType*()
+ fromType instanceof DelegateType and systemDelegateBaseType(toType)
}
private predicate convRefTypeRefType(RefType fromType, RefType toType) {
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Element.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Element.qll
index fbd96f6086d..390a7b16632 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Element.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Element.qll
@@ -31,7 +31,7 @@ class Element extends DotNet::Element, @element {
Element getParent() { result.getAChild() = this }
/** Gets a child of this element, if any. */
- Element getAChild() { result = getChild(_) }
+ Element getAChild() { result = this.getChild(_) }
/** Gets the `i`th child of this element (zero-based). */
Element getChild(int i) { none() }
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Event.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Event.qll
index 7cbfda76877..810cffa927a 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Event.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Event.qll
@@ -29,10 +29,10 @@ class Event extends DeclarationWithAccessors, @event {
EventAccessor getAnEventAccessor() { result.getDeclaration() = this }
/** Gets the `add` accessor of this event, if any. */
- AddEventAccessor getAddEventAccessor() { result = getAnEventAccessor() }
+ AddEventAccessor getAddEventAccessor() { result = this.getAnEventAccessor() }
/** Gets the `remove` accessor of this event, if any. */
- RemoveEventAccessor getRemoveEventAccessor() { result = getAnEventAccessor() }
+ RemoveEventAccessor getRemoveEventAccessor() { result = this.getAnEventAccessor() }
/**
* Holds if this event can be used like a field within its declaring type
@@ -111,9 +111,9 @@ class EventAccessor extends Accessor, @event_accessor {
* ```
*/
class AddEventAccessor extends EventAccessor, @add_event_accessor {
- override string getName() { result = "add" + "_" + getDeclaration().getName() }
+ override string getName() { result = "add" + "_" + this.getDeclaration().getName() }
- override string getUndecoratedName() { result = "add" + "_" + getDeclaration().getName() }
+ override string getUndecoratedName() { result = "add" + "_" + this.getDeclaration().getName() }
override string getAPrimaryQlClass() { result = "AddEventAccessor" }
}
@@ -132,9 +132,9 @@ class AddEventAccessor extends EventAccessor, @add_event_accessor {
* ```
*/
class RemoveEventAccessor extends EventAccessor, @remove_event_accessor {
- override string getName() { result = "remove" + "_" + getDeclaration().getName() }
+ override string getName() { result = "remove" + "_" + this.getDeclaration().getName() }
- override string getUndecoratedName() { result = "remove" + "_" + getDeclaration().getName() }
+ override string getUndecoratedName() { result = "remove" + "_" + this.getDeclaration().getName() }
override string getAPrimaryQlClass() { result = "RemoveEventAccessor" }
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/File.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/File.qll
index df9ce6f3cf6..55fd2ccdc81 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/File.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/File.qll
@@ -47,7 +47,7 @@ class Container extends @container {
*/
string getRelativePath() {
exists(string absPath, string pref |
- absPath = getAbsolutePath() and sourceLocationPrefix(pref)
+ absPath = this.getAbsolutePath() and sourceLocationPrefix(pref)
|
absPath = pref and result = ""
or
@@ -74,7 +74,7 @@ class Container extends @container {
*
*/
string getBaseName() {
- result = getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1)
+ result = this.getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1)
}
/**
@@ -100,7 +100,9 @@ class Container extends @container {
* | "/tmp/x.tar.gz" | "gz" |
*
*/
- string getExtension() { result = getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3) }
+ string getExtension() {
+ result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3)
+ }
/**
* Gets the stem of this container, that is, the prefix of its base name up to
@@ -119,7 +121,9 @@ class Container extends @container {
* | "/tmp/x.tar.gz" | "x.tar" |
*
*/
- string getStem() { result = getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1) }
+ string getStem() {
+ result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1)
+ }
/** Gets the parent container of this file or folder, if any. */
Container getParentContainer() { containerparent(result, this) }
@@ -128,52 +132,52 @@ class Container extends @container {
Container getAChildContainer() { this = result.getParentContainer() }
/** Gets a file in this container. */
- File getAFile() { result = getAChildContainer() }
+ File getAFile() { result = this.getAChildContainer() }
/** Gets the file in this container that has the given `baseName`, if any. */
File getFile(string baseName) {
- result = getAFile() and
+ result = this.getAFile() and
result.getBaseName() = baseName
}
/** Gets a sub-folder in this container. */
- Folder getAFolder() { result = getAChildContainer() }
+ Folder getAFolder() { result = this.getAChildContainer() }
/** Gets the sub-folder in this container that has the given `baseName`, if any. */
Folder getFolder(string baseName) {
- result = getAFolder() and
+ result = this.getAFolder() and
result.getBaseName() = baseName
}
/** Gets the file or sub-folder in this container that has the given `name`, if any. */
Container getChildContainer(string name) {
- result = getAChildContainer() and
+ result = this.getAChildContainer() and
result.getBaseName() = name
}
/** Gets the file in this container that has the given `stem` and `extension`, if any. */
File getFile(string stem, string extension) {
- result = getAChildContainer() and
+ result = this.getAChildContainer() and
result.getStem() = stem and
result.getExtension() = extension
}
/** Gets a sub-folder contained in this container. */
- Folder getASubFolder() { result = getAChildContainer() }
+ Folder getASubFolder() { result = this.getAChildContainer() }
/**
* Gets a textual representation of the path of this container.
*
* This is the absolute path of the container.
*/
- string toString() { result = getAbsolutePath() }
+ string toString() { result = this.getAbsolutePath() }
}
/** A folder. */
class Folder extends Container, @folder {
override string getAbsolutePath() { folders(this, result) }
- override string getURL() { result = "folder://" + getAbsolutePath() }
+ override string getURL() { result = "folder://" + this.getAbsolutePath() }
}
/** A file. */
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Generics.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Generics.qll
index 25a3679715b..9190523e3c0 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Generics.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Generics.qll
@@ -71,14 +71,14 @@ class ConstructedGeneric extends DotNet::ConstructedGeneric, Generic {
override UnboundGeneric getUnboundGeneric() { constructed_generic(this, result) }
override UnboundGeneric getUnboundDeclaration() {
- result = getUnboundGeneric().getUnboundDeclaration()
+ result = this.getUnboundGeneric().getUnboundDeclaration()
}
override int getNumberOfTypeArguments() { result = count(int i | type_arguments(_, i, this)) }
override Type getTypeArgument(int i) { none() }
- override Type getATypeArgument() { result = getTypeArgument(_) }
+ override Type getATypeArgument() { result = this.getTypeArgument(_) }
/** Gets the annotated type of type argument `i`. */
final AnnotatedType getAnnotatedTypeArgument(int i) { result.appliesToTypeArgument(this, i) }
@@ -141,7 +141,7 @@ class UnboundGenericType extends ValueOrRefType, UnboundGeneric {
result = ValueOrRefType.super.getUnboundDeclaration()
}
- final override Type getChild(int n) { result = getTypeParameter(n) }
+ final override Type getChild(int n) { result = this.getTypeParameter(n) }
override string toStringWithTypes() {
result = this.getUndecoratedName() + "<" + getTypeParametersToString(this) + ">"
@@ -173,7 +173,7 @@ class TypeParameter extends DotNet::TypeParameter, Type, @type_parameter {
TypeParameterConstraints getConstraints() { result.getTypeParameter() = this }
override predicate isRefType() {
- exists(TypeParameterConstraints tpc | tpc = getConstraints() |
+ exists(TypeParameterConstraints tpc | tpc = this.getConstraints() |
tpc.hasRefTypeConstraint() or
tpc.getATypeConstraint() instanceof Class or
tpc.getATypeConstraint().(TypeParameter).isRefType()
@@ -182,7 +182,7 @@ class TypeParameter extends DotNet::TypeParameter, Type, @type_parameter {
}
override predicate isValueType() {
- exists(TypeParameterConstraints tpc | tpc = getConstraints() |
+ exists(TypeParameterConstraints tpc | tpc = this.getConstraints() |
tpc.hasValueTypeConstraint() or
tpc.getATypeConstraint().(TypeParameter).isValueType()
)
@@ -219,9 +219,9 @@ class TypeParameter extends DotNet::TypeParameter, Type, @type_parameter {
/** Gets a non-type-parameter type that was transitively supplied for this parameter. */
Type getAnUltimatelySuppliedType() {
- result = getASuppliedType() and not result instanceof TypeParameter
+ result = this.getASuppliedType() and not result instanceof TypeParameter
or
- result = getASuppliedType().(TypeParameter).getAnUltimatelySuppliedType()
+ result = this.getASuppliedType().(TypeParameter).getAnUltimatelySuppliedType()
}
override int getIndex() { type_parameters(this, result, _, _) }
@@ -376,8 +376,8 @@ class UnboundGenericDelegateType extends DelegateType, UnboundGenericType {
override string toStringWithTypes() {
result =
- getUndecoratedName() + "<" + getTypeParametersToString(this) + ">(" + parameterTypesToString()
- + ")"
+ this.getUndecoratedName() + "<" + getTypeParametersToString(this) + ">(" +
+ this.parameterTypesToString() + ")"
}
}
@@ -404,7 +404,7 @@ class ConstructedType extends ValueOrRefType, ConstructedGeneric {
override UnboundGenericType getUnboundGeneric() { constructed_generic(this, getTypeRef(result)) }
- final override Type getChild(int n) { result = getTypeArgument(n) }
+ final override Type getChild(int n) { result = this.getTypeArgument(n) }
final override string toStringWithTypes() {
result = this.getUndecoratedName() + "<" + getTypeArgumentsToString(this) + ">"
@@ -542,12 +542,12 @@ class UnboundGenericMethod extends Method, UnboundGeneric {
override string toStringWithTypes() {
result =
- getUndecoratedName() + "<" + getTypeParametersToString(this) + ">" + "(" +
- parameterTypesToString() + ")"
+ this.getUndecoratedName() + "<" + getTypeParametersToString(this) + ">" + "(" +
+ this.parameterTypesToString() + ")"
}
final override string getName() {
- result = getUndecoratedName() + "<" + getTypeParameterCommas(this) + ">"
+ result = this.getUndecoratedName() + "<" + getTypeParameterCommas(this) + ">"
}
final override string getUndecoratedName() { methods(this, result, _, _, _) }
@@ -580,8 +580,8 @@ class ConstructedMethod extends Method, ConstructedGeneric {
override string toStringWithTypes() {
result =
- getUndecoratedName() + "<" + getTypeArgumentsToString(this) + ">" + "(" +
- parameterTypesToString() + ")"
+ this.getUndecoratedName() + "<" + getTypeArgumentsToString(this) + ">" + "(" +
+ this.parameterTypesToString() + ")"
}
override UnboundGenericMethod getUnboundDeclaration() {
@@ -589,12 +589,12 @@ class ConstructedMethod extends Method, ConstructedGeneric {
}
final override string getName() {
- result = getUndecoratedName() + "<" + getTypeArgumentsNames(this) + ">"
+ result = this.getUndecoratedName() + "<" + getTypeArgumentsNames(this) + ">"
}
override predicate hasQualifiedName(string qualifier, string name) {
- qualifier = getDeclaringType().getQualifiedName() and
- name = getUndecoratedName() + "<" + getTypeArgumentsQualifiedNames(this) + ">"
+ qualifier = this.getDeclaringType().getQualifiedName() and
+ name = this.getUndecoratedName() + "<" + getTypeArgumentsQualifiedNames(this) + ">"
}
final override string getUndecoratedName() { methods(this, result, _, _, _) }
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Member.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Member.qll
index 9f8408621fc..40b887f052a 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Member.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Member.qll
@@ -155,7 +155,9 @@ class Modifiable extends Declaration, @modifiable {
* Holds if this declaration is effectively `public`, meaning that it can be
* referenced outside the declaring assembly.
*/
- predicate isEffectivelyPublic() { not isEffectivelyPrivate() and not isEffectivelyInternal() }
+ predicate isEffectivelyPublic() {
+ not this.isEffectivelyPrivate() and not this.isEffectivelyInternal()
+ }
}
/** A declaration that is a member of a type. */
@@ -193,12 +195,12 @@ class Virtualizable extends Member, @virtualizable {
override predicate isPublic() {
Member.super.isPublic() or
- implementsExplicitInterface()
+ this.implementsExplicitInterface()
}
override predicate isPrivate() {
super.isPrivate() and
- not implementsExplicitInterface()
+ not this.implementsExplicitInterface()
}
/**
@@ -211,17 +213,17 @@ class Virtualizable extends Member, @virtualizable {
/**
* Holds if this member implements an interface member explicitly.
*/
- predicate implementsExplicitInterface() { exists(getExplicitlyImplementedInterface()) }
+ predicate implementsExplicitInterface() { exists(this.getExplicitlyImplementedInterface()) }
/** Holds if this member can be overridden or implemented. */
predicate isOverridableOrImplementable() {
- not isSealed() and
- not getDeclaringType().isSealed() and
+ not this.isSealed() and
+ not this.getDeclaringType().isSealed() and
(
- isVirtual() or
- isOverride() or
- isAbstract() or
- getDeclaringType() instanceof Interface
+ this.isVirtual() or
+ this.isOverride() or
+ this.isAbstract() or
+ this.getDeclaringType() instanceof Interface
)
}
@@ -243,10 +245,10 @@ class Virtualizable extends Member, @virtualizable {
Virtualizable getAnOverrider() { this = result.getOverridee() }
/** Holds if this member is overridden by some other member. */
- predicate isOverridden() { exists(getAnOverrider()) }
+ predicate isOverridden() { exists(this.getAnOverrider()) }
/** Holds if this member overrides another member. */
- predicate overrides() { exists(getOverridee()) }
+ predicate overrides() { exists(this.getOverridee()) }
/**
* Gets the interface member that is immediately implemented by this member, if any.
@@ -274,7 +276,7 @@ class Virtualizable extends Member, @virtualizable {
Virtualizable getImplementee(ValueOrRefType t) { implements(this, result, t) }
/** Gets the interface member that is immediately implemented by this member, if any. */
- Virtualizable getImplementee() { result = getImplementee(_) }
+ Virtualizable getImplementee() { result = this.getImplementee(_) }
/**
* Gets a member that immediately implements this interface member, if any.
@@ -338,8 +340,8 @@ class Virtualizable extends Member, @virtualizable {
|
this = implementation
or
- getOverridee+() = implementation and
- getDeclaringType().getABaseType+() = implementationType
+ this.getOverridee+() = implementation and
+ this.getDeclaringType().getABaseType+() = implementationType
)
}
@@ -355,10 +357,10 @@ class Virtualizable extends Member, @virtualizable {
Virtualizable getAnUltimateImplementor() { this = result.getAnUltimateImplementee() }
/** Holds if this interface member is implemented by some other member. */
- predicate isImplemented() { exists(getAnImplementor()) }
+ predicate isImplemented() { exists(this.getAnImplementor()) }
/** Holds if this member implements (transitively) an interface member. */
- predicate implements() { exists(getAnUltimateImplementee()) }
+ predicate implements() { exists(this.getAnUltimateImplementee()) }
/**
* Holds if this member overrides or implements (reflexively, transitively)
@@ -366,8 +368,8 @@ class Virtualizable extends Member, @virtualizable {
*/
predicate overridesOrImplementsOrEquals(Virtualizable that) {
this = that or
- getOverridee+() = that or
- getAnUltimateImplementee() = that
+ this.getOverridee+() = that or
+ this.getAnUltimateImplementee() = that
}
}
@@ -386,7 +388,7 @@ class Parameterizable extends DotNet::Parameterizable, Declaration, @parameteriz
*/
private string parameterTypeToString(int i) {
exists(Parameter p, string prefix |
- p = getParameter(i) and
+ p = this.getParameter(i) and
result = prefix + p.getType().toStringWithTypes()
|
if p.isOut()
@@ -407,6 +409,7 @@ class Parameterizable extends DotNet::Parameterizable, Declaration, @parameteriz
*/
language[monotonicAggregates]
string parameterTypesToString() {
- result = concat(int i | exists(getParameter(i)) | parameterTypeToString(i), ", " order by i)
+ result =
+ concat(int i | exists(this.getParameter(i)) | this.parameterTypeToString(i), ", " order by i)
}
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Modifier.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Modifier.qll
index 542598d204e..39652070af3 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Modifier.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Modifier.qll
@@ -19,10 +19,5 @@ class Modifier extends Element, @modifier {
* An access modifier: `public`, `private`, `internal` or `protected`.
*/
class AccessModifier extends Modifier {
- AccessModifier() {
- hasName("public") or
- hasName("private") or
- hasName("internal") or
- hasName("protected")
- }
+ AccessModifier() { this.hasName(["public", "private", "internal", "protected"]) }
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Preprocessor.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Preprocessor.qll
index daf4978da53..3342dd5c59c 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Preprocessor.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Preprocessor.qll
@@ -289,7 +289,7 @@ class IfDirective extends ConditionalDirective, @directive_if {
}
/** Gets a sibling `#elif` or `#else` preprocessor directive. */
- BranchDirective getASiblingDirective() { result = getSiblingDirective(_) }
+ BranchDirective getASiblingDirective() { result = this.getSiblingDirective(_) }
override string toString() { result = "#if ..." }
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/PrintAst.ql b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/PrintAst.ql
deleted file mode 100644
index 3867fd2990f..00000000000
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/PrintAst.ql
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * @name Print AST
- * @description Outputs a representation of the Abstract Syntax Tree.
- * @id csharp/print-ast
- * @kind graph
- */
-
-import csharp
-import PrintAst
-
-/**
- * Temporarily tweak this class or make a copy to control which functions are
- * printed.
- */
-class PrintAstConfigurationOverride extends PrintAstConfiguration {
- /**
- * TWEAK THIS PREDICATE AS NEEDED.
- */
- override predicate shouldPrint(Element e, Location l) { super.shouldPrint(e, l) }
-}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/PrintAst.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/PrintAst.qll
index a701c7bfbf3..a3d36fba69d 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/PrintAst.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/PrintAst.qll
@@ -171,7 +171,7 @@ class PrintAstNode extends TPrintAstNode {
/**
* Gets a child of this node.
*/
- final PrintAstNode getAChild() { result = getChild(_) }
+ final PrintAstNode getAChild() { result = this.getChild(_) }
/**
* Gets the parent of this node, if any.
@@ -189,7 +189,7 @@ class PrintAstNode extends TPrintAstNode {
*/
string getProperty(string key) {
key = "semmle.label" and
- result = toString()
+ result = this.toString()
}
/**
@@ -198,7 +198,7 @@ class PrintAstNode extends TPrintAstNode {
* this.
*/
string getChildEdgeLabel(int childIndex) {
- exists(getChild(childIndex)) and
+ exists(this.getChild(childIndex)) and
result = childIndex.toString()
}
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Property.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Property.qll
index 5464142a085..a91ac6f13a4 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Property.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Property.qll
@@ -53,10 +53,10 @@ class DeclarationWithAccessors extends AssignableMember, Virtualizable, Attribut
class DeclarationWithGetSetAccessors extends DeclarationWithAccessors, TopLevelExprParent,
@assignable_with_accessors {
/** Gets the `get` accessor of this declaration, if any. */
- Getter getGetter() { result = getAnAccessor() }
+ Getter getGetter() { result = this.getAnAccessor() }
/** Gets the `set` accessor of this declaration, if any. */
- Setter getSetter() { result = getAnAccessor() }
+ Setter getSetter() { result = this.getAnAccessor() }
override DeclarationWithGetSetAccessors getOverridee() {
result = DeclarationWithAccessors.super.getOverridee()
@@ -182,10 +182,10 @@ class Property extends DotNet::Property, DeclarationWithGetSetAccessors, @proper
or
// For library types, we don't know about assignments in constructors. We instead assume that
// arguments passed to parameters of constructors with suitable names.
- getDeclaringType().fromLibrary() and
+ this.getDeclaringType().fromLibrary() and
exists(Parameter param, Constructor c, string propertyName |
- propertyName = getName() and
- c = getDeclaringType().getAConstructor() and
+ propertyName = this.getName() and
+ c = this.getDeclaringType().getAConstructor() and
param = c.getAParameter() and
// Find a constructor parameter with the same name, but with a lower case initial letter.
param.hasName(propertyName.charAt(0).toLowerCase() + propertyName.suffix(1))
@@ -256,7 +256,7 @@ class Indexer extends DeclarationWithGetSetAccessors, Parameterizable, @indexer
override string getUndecoratedName() { indexers(this, result, _, _, _) }
/** Gets the dimension of this indexer, that is, its number of parameters. */
- int getDimension() { result = getNumberOfParameters() }
+ int getDimension() { result = this.getNumberOfParameters() }
override ValueOrRefType getDeclaringType() { indexers(this, _, result, _, _) }
@@ -304,7 +304,9 @@ class Indexer extends DeclarationWithGetSetAccessors, Parameterizable, @indexer
override Location getALocation() { indexer_location(this, result) }
- override string toStringWithTypes() { result = getName() + "[" + parameterTypesToString() + "]" }
+ override string toStringWithTypes() {
+ result = this.getName() + "[" + this.parameterTypesToString() + "]"
+ }
override string getAPrimaryQlClass() { result = "Indexer" }
}
@@ -368,17 +370,17 @@ class Accessor extends Callable, Modifiable, Attributable, @callable_accessor {
* ```
*/
override Modifier getAModifier() {
- result = getAnAccessModifier()
+ result = this.getAnAccessModifier()
or
- result = getDeclaration().getAModifier() and
- not (result instanceof AccessModifier and exists(getAnAccessModifier()))
+ result = this.getDeclaration().getAModifier() and
+ not (result instanceof AccessModifier and exists(this.getAnAccessModifier()))
}
override Accessor getUnboundDeclaration() { accessors(this, _, _, _, result) }
override Location getALocation() { accessor_location(this, result) }
- override string toString() { result = getName() }
+ override string toString() { result = this.getName() }
}
/**
@@ -395,11 +397,11 @@ class Accessor extends Callable, Modifiable, Attributable, @callable_accessor {
* ```
*/
class Getter extends Accessor, @getter {
- override string getName() { result = "get" + "_" + getDeclaration().getName() }
+ override string getName() { result = "get" + "_" + this.getDeclaration().getName() }
- override string getUndecoratedName() { result = "get" + "_" + getDeclaration().getName() }
+ override string getUndecoratedName() { result = "get" + "_" + this.getDeclaration().getName() }
- override Type getReturnType() { result = getDeclaration().getType() }
+ override Type getReturnType() { result = this.getDeclaration().getType() }
/**
* Gets the field used in the trival implementation of this getter, if any.
@@ -417,8 +419,8 @@ class Getter extends Accessor, @getter {
*/
Field trivialGetterField() {
exists(ReturnStmt ret |
- getStatementBody().getNumberOfStmts() = 1 and
- getStatementBody().getAChild() = ret and
+ this.getStatementBody().getNumberOfStmts() = 1 and
+ this.getStatementBody().getAChild() = ret and
ret.getExpr() = result.getAnAccess()
)
}
@@ -444,9 +446,9 @@ class Getter extends Accessor, @getter {
* ```
*/
class Setter extends Accessor, @setter {
- override string getName() { result = "set" + "_" + getDeclaration().getName() }
+ override string getName() { result = "set" + "_" + this.getDeclaration().getName() }
- override string getUndecoratedName() { result = "set" + "_" + getDeclaration().getName() }
+ override string getUndecoratedName() { result = "set" + "_" + this.getDeclaration().getName() }
override Type getReturnType() {
exists(this) and // needed to avoid compiler warning
@@ -469,8 +471,8 @@ class Setter extends Accessor, @setter {
*/
Field trivialSetterField() {
exists(AssignExpr assign |
- getStatementBody().getNumberOfStmts() = 1 and
- assign.getParent() = getStatementBody().getAChild() and
+ this.getStatementBody().getNumberOfStmts() = 1 and
+ assign.getParent() = this.getStatementBody().getAChild() and
assign.getLValue() = result.getAnAccess() and
assign.getRValue() = accessToValue()
)
@@ -521,9 +523,9 @@ private ParameterAccess accessToValue() {
*/
class TrivialProperty extends Property {
TrivialProperty() {
- isAutoImplemented()
+ this.isAutoImplemented()
or
- getGetter().trivialGetterField() = getSetter().trivialSetterField()
+ this.getGetter().trivialGetterField() = this.getSetter().trivialSetterField()
or
exists(CIL::TrivialProperty prop | this.matchesHandle(prop))
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Stmt.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Stmt.qll
index 2ccd57078db..be074c176ba 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Stmt.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Stmt.qll
@@ -65,10 +65,10 @@ class BlockStmt extends Stmt, @block_stmt {
int getNumberOfStmts() { result = count(this.getAStmt()) }
/** Gets the first statement in this block, if any. */
- Stmt getFirstStmt() { result = getStmt(0) }
+ Stmt getFirstStmt() { result = this.getStmt(0) }
/** Gets the last statement in this block, if any. */
- Stmt getLastStmt() { result = getStmt(getNumberOfStmts() - 1) }
+ Stmt getLastStmt() { result = this.getStmt(this.getNumberOfStmts() - 1) }
/** Holds if this block is an empty block with no statements. */
predicate isEmpty() { not exists(this.getAStmt()) }
@@ -79,8 +79,8 @@ class BlockStmt extends Stmt, @block_stmt {
}
override Stmt stripSingletonBlocks() {
- if getNumberOfStmts() = 1
- then result = getAChildStmt().stripSingletonBlocks()
+ if this.getNumberOfStmts() = 1
+ then result = this.getAChildStmt().stripSingletonBlocks()
else result = this
}
@@ -420,7 +420,7 @@ class ForStmt extends LoopStmt, @for_stmt {
* }
* ```
*/
- Expr getAnInitializer() { result = getInitializer(_) }
+ Expr getAnInitializer() { result = this.getInitializer(_) }
/**
* Gets the `n`th initializer expression of this `for` loop
@@ -451,7 +451,7 @@ class ForStmt extends LoopStmt, @for_stmt {
* }
* ```
*/
- Expr getAnUpdate() { result = getUpdate(_) }
+ Expr getAnUpdate() { result = this.getUpdate(_) }
/**
* Gets the `n`th update expression of this `for` loop (starting at index 0).
@@ -519,7 +519,7 @@ class ForeachStmt extends LoopStmt, @foreach_stmt {
* ```
*/
LocalVariableDeclExpr getVariableDeclExpr(int i) {
- result = getVariableDeclTuple().getArgument(i)
+ result = this.getVariableDeclTuple().getArgument(i)
or
i = 0 and result = this.getChild(0)
}
@@ -547,7 +547,7 @@ class ForeachStmt extends LoopStmt, @foreach_stmt {
* }
* ```
*/
- LocalVariable getVariable(int i) { result = getVariableDeclExpr(i).getVariable() }
+ LocalVariable getVariable(int i) { result = this.getVariableDeclExpr(i).getVariable() }
/**
* Gets a local variable of this `foreach` loop.
@@ -560,7 +560,7 @@ class ForeachStmt extends LoopStmt, @foreach_stmt {
* }
* ```
*/
- LocalVariable getAVariable() { result = getVariable(_) }
+ LocalVariable getAVariable() { result = this.getVariable(_) }
/**
* Gets a local variable declaration of this `foreach` loop.
@@ -573,7 +573,7 @@ class ForeachStmt extends LoopStmt, @foreach_stmt {
* }
* ```
*/
- LocalVariableDeclExpr getAVariableDeclExpr() { result = getVariableDeclExpr(_) }
+ LocalVariableDeclExpr getAVariableDeclExpr() { result = this.getVariableDeclExpr(_) }
override Expr getCondition() { none() }
@@ -690,8 +690,8 @@ class GotoLabelStmt extends GotoStmt, @goto_stmt {
/** Gets the target statement that this `goto` statement jumps to. */
LabeledStmt getTarget() {
- result.getEnclosingCallable() = getEnclosingCallable() and
- result.getLabel() = getLabel()
+ result.getEnclosingCallable() = this.getEnclosingCallable() and
+ result.getLabel() = this.getLabel()
}
override string getAPrimaryQlClass() { result = "GotoLabelStmt" }
@@ -717,7 +717,7 @@ class GotoCaseStmt extends GotoStmt, @goto_case_stmt {
/** Gets the constant expression that this `goto case` statement jumps to. */
Expr getExpr() { result = this.getChild(0) }
- override string getLabel() { result = getExpr().getValue() }
+ override string getLabel() { result = this.getExpr().getValue() }
override string toString() { result = "goto case ...;" }
@@ -764,14 +764,14 @@ class ThrowStmt extends JumpStmt, ThrowElement, @throw_stmt {
override ExceptionClass getThrownExceptionType() {
result = ThrowElement.super.getThrownExceptionType()
or
- result = getRethrowParent().(CatchClause).getCaughtExceptionType()
+ result = this.getRethrowParent().(CatchClause).getCaughtExceptionType()
}
private ControlFlowElement getRethrowParent() {
- result = this and not exists(getExpr())
+ result = this and not exists(this.getExpr())
or
exists(ControlFlowElement mid |
- mid = getRethrowParent() and
+ mid = this.getRethrowParent() and
not mid instanceof CatchClause and
result = mid.getParent()
)
@@ -785,7 +785,7 @@ class ThrowStmt extends JumpStmt, ThrowElement, @throw_stmt {
* and may be thrown as an exception.
*/
class ExceptionClass extends Class {
- ExceptionClass() { getBaseClass*() instanceof SystemExceptionClass }
+ ExceptionClass() { this.getBaseClass*() instanceof SystemExceptionClass }
}
/**
@@ -897,13 +897,15 @@ class TryStmt extends Stmt, @try_stmt {
override string getAPrimaryQlClass() { result = "TryStmt" }
/** Gets the `catch` clause that handles an exception of type `ex`, if any. */
- CatchClause getAnExceptionHandler(ExceptionClass ex) { result = clauseHandlesException(ex, 0) }
+ CatchClause getAnExceptionHandler(ExceptionClass ex) {
+ result = this.clauseHandlesException(ex, 0)
+ }
/**
* Holds if catch clause `cc` definitely handles exceptions of type `ex`.
*/
predicate definitelyHandles(ExceptionClass ex, CatchClause cc) {
- cc = getACatchClause() and
+ cc = this.getACatchClause() and
not exists(cc.getFilterClause()) and
(
cc.getCaughtExceptionType() = ex.getBaseClass*()
@@ -913,22 +915,22 @@ class TryStmt extends Stmt, @try_stmt {
}
private predicate maybeHandles(ExceptionClass ex, CatchClause cc) {
- cc = getACatchClause() and
+ cc = this.getACatchClause() and
cc.getCaughtExceptionType().getBaseClass*() = ex
}
private CatchClause clauseHandlesException(ExceptionClass ex, int n) {
- exists(CatchClause clause | clause = getCatchClause(n) |
- if definitelyHandles(ex, clause)
+ exists(CatchClause clause | clause = this.getCatchClause(n) |
+ if this.definitelyHandles(ex, clause)
then result = clause
else
- if maybeHandles(ex, clause)
+ if this.maybeHandles(ex, clause)
then
result = clause or
- result = clauseHandlesException(ex, n + 1)
+ result = this.clauseHandlesException(ex, n + 1)
else
// Does not handle
- result = clauseHandlesException(ex, n + 1)
+ result = this.clauseHandlesException(ex, n + 1)
)
}
@@ -939,10 +941,10 @@ class TryStmt extends Stmt, @try_stmt {
* `try` statement.
*/
ControlFlowElement getATriedElement() {
- result = getBlock()
+ result = this.getBlock()
or
exists(ControlFlowElement mid |
- mid = getATriedElement() and
+ mid = this.getATriedElement() and
not mid instanceof TryStmt and
result = getAChild(mid, mid.getEnclosingCallable())
)
@@ -996,10 +998,10 @@ class CatchClause extends Stmt, @catch {
* }
* ```
*/
- Expr getFilterClause() { result = getChild(2) }
+ Expr getFilterClause() { result = this.getChild(2) }
/** Holds if this `catch` clause has a filter. */
- predicate hasFilterClause() { exists(getFilterClause()) }
+ predicate hasFilterClause() { exists(this.getFilterClause()) }
/** Holds if this is the last `catch` clause in the `try` statement that it belongs to. */
predicate isLast() {
@@ -1120,7 +1122,7 @@ class LockStmt extends Stmt, @lock_stmt {
override string toString() { result = "lock (...) {...}" }
/** Gets the variable being locked, if any. */
- Variable getLockVariable() { result.getAnAccess() = getExpr() }
+ Variable getLockVariable() { result.getAnAccess() = this.getExpr() }
/** Gets a statement in the scope of this `lock` statement. */
Stmt getALockedStmt() {
@@ -1128,14 +1130,14 @@ class LockStmt extends Stmt, @lock_stmt {
// delegates and lambdas
result.getParent() = this
or
- exists(Stmt mid | mid = getALockedStmt() and result.getParent() = mid)
+ exists(Stmt mid | mid = this.getALockedStmt() and result.getParent() = mid)
}
/** Holds if this statement is of the form `lock(this) { ... }`. */
- predicate isLockThis() { getExpr() instanceof ThisAccess }
+ predicate isLockThis() { this.getExpr() instanceof ThisAccess }
/** Gets the type `T` if this statement is of the form `lock(typeof(T)) { ... }`. */
- Type getLockTypeObject() { result = getExpr().(TypeofExpr).getTypeAccess().getTarget() }
+ Type getLockTypeObject() { result = this.getExpr().(TypeofExpr).getTypeAccess().getTarget() }
override string getAPrimaryQlClass() { result = "LockStmt" }
}
@@ -1453,7 +1455,7 @@ class LocalFunctionStmt extends Stmt, @local_function_stmt {
/** Gets the local function defined by this statement. */
LocalFunction getLocalFunction() { local_function_stmts(this, result) }
- override string toString() { result = getLocalFunction().getName() + "(...)" }
+ override string toString() { result = this.getLocalFunction().getName() + "(...)" }
override string getAPrimaryQlClass() { result = "LocalFunctionStmt" }
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Type.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Type.qll
index d7a15000bbf..109c1df00c7 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Type.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Type.qll
@@ -37,7 +37,7 @@ class Type extends DotNet::Type, Member, TypeContainer, @type {
predicate containsTypeParameters() {
this instanceof TypeParameter
or
- not this instanceof UnboundGenericType and getAChild().containsTypeParameters()
+ not this instanceof UnboundGenericType and this.getAChild().containsTypeParameters()
}
/** Holds if this type is a reference type, or a type parameter that is a reference type. */
@@ -133,8 +133,8 @@ class ValueOrRefType extends DotNet::ValueOrRefType, Type, Attributable, @value_
/** Gets an immediate base type of this type, if any. */
override ValueOrRefType getABaseType() {
- result = getBaseClass() or
- result = getABaseInterface()
+ result = this.getBaseClass() or
+ result = this.getABaseInterface()
}
/** Gets an immediate subtype of this type, if any. */
@@ -200,9 +200,9 @@ class ValueOrRefType extends DotNet::ValueOrRefType, Type, Attributable, @value_
*/
pragma[inline]
predicate hasCallable(Callable c) {
- hasMethod(c)
+ this.hasMethod(c)
or
- hasMember(c.(Accessor).getDeclaration())
+ this.hasMember(c.(Accessor).getDeclaration())
}
/**
@@ -234,63 +234,63 @@ class ValueOrRefType extends DotNet::ValueOrRefType, Type, Attributable, @value_
or
hasNonOverriddenMember(this.getBaseClass+(), m)
or
- hasOverriddenMember(m)
+ this.hasOverriddenMember(m)
}
cached
private predicate hasOverriddenMember(Virtualizable v) {
v.isOverridden() and
- v = getAMember()
+ v = this.getAMember()
or
- getBaseClass().(ValueOrRefType).hasOverriddenMember(v) and
+ this.getBaseClass().(ValueOrRefType).hasOverriddenMember(v) and
not v.isPrivate() and
- not memberOverrides(v)
+ not this.memberOverrides(v)
}
// Predicate folding for proper join-order
pragma[noinline]
private predicate memberOverrides(Virtualizable v) {
- getAMember().(Virtualizable).getOverridee() = v
+ this.getAMember().(Virtualizable).getOverridee() = v
}
/** Gets a field (or member constant) with the given name. */
- Field getField(string name) { result = getAMember() and result.hasName(name) }
+ Field getField(string name) { result = this.getAMember() and result.hasName(name) }
/** Gets a field (or member constant) of this type, if any. */
Field getAField() { result = this.getField(_) }
/** Gets a member constant of this type, if any. */
- MemberConstant getAConstant() { result = getAMember() }
+ MemberConstant getAConstant() { result = this.getAMember() }
/** Gets a method of this type, if any. */
- Method getAMethod() { result = getAMember() }
+ Method getAMethod() { result = this.getAMember() }
/** Gets a method of this type with the given name. */
- Method getAMethod(string name) { result = getAMember() and result.hasName(name) }
+ Method getAMethod(string name) { result = this.getAMember() and result.hasName(name) }
/** Gets a property of this type, if any. */
- Property getAProperty() { result = getAMember() }
+ Property getAProperty() { result = this.getAMember() }
/** Gets a named property of this type. */
- Property getProperty(string name) { result = getAMember() and result.hasName(name) }
+ Property getProperty(string name) { result = this.getAMember() and result.hasName(name) }
/** Gets an indexer of this type, if any. */
- Indexer getAnIndexer() { result = getAMember() }
+ Indexer getAnIndexer() { result = this.getAMember() }
/** Gets an event of this type, if any. */
- Event getAnEvent() { result = getAMember() }
+ Event getAnEvent() { result = this.getAMember() }
/** Gets a user-defined operator of this type, if any. */
- Operator getAnOperator() { result = getAMember() }
+ Operator getAnOperator() { result = this.getAMember() }
/** Gets a static or instance constructor of this type, if any. */
- Constructor getAConstructor() { result = getAMember() }
+ Constructor getAConstructor() { result = this.getAMember() }
/** Gets the static constructor of this type, if any. */
- StaticConstructor getStaticConstructor() { result = getAMember() }
+ StaticConstructor getStaticConstructor() { result = this.getAMember() }
/** Gets a nested type of this type, if any. */
- NestedType getANestedType() { result = getAMember() }
+ NestedType getANestedType() { result = this.getAMember() }
/** Gets the number of types that directly depend on this type. */
int getAfferentCoupling() { afferentCoupling(this, result) }
@@ -675,10 +675,10 @@ class Enum extends ValueType, @enum_type {
*/
class Struct extends ValueType, @struct_type {
/** Holds if this `struct` has a `ref` modifier. */
- predicate isRef() { hasModifier("ref") }
+ predicate isRef() { this.hasModifier("ref") }
/** Holds if this `struct` has a `readonly` modifier. */
- predicate isReadonly() { hasModifier("readonly") }
+ predicate isReadonly() { this.hasModifier("readonly") }
override string getAPrimaryQlClass() { result = "Struct" }
}
@@ -695,7 +695,7 @@ class RefType extends ValueOrRefType, @ref_type {
/** Gets a member that overrides a non-abstract member in a super type, if any. */
private Virtualizable getAnOverrider() {
- getAMember() = result and
+ this.getAMember() = result and
exists(Virtualizable v | result.getOverridee() = v and not v.isAbstract())
}
@@ -897,14 +897,14 @@ class FunctionPointerType extends Type, Parameterizable, @function_pointer_type
}
/** Gets an unmanaged calling convention. */
- Type getAnUnmanagedCallingConvention() { result = getUnmanagedCallingConvention(_) }
+ Type getAnUnmanagedCallingConvention() { result = this.getUnmanagedCallingConvention(_) }
/** Gets the annotated return type of this function pointer type. */
AnnotatedType getAnnotatedReturnType() { result.appliesTo(this) }
override string getAPrimaryQlClass() { result = "FunctionPointerType" }
- override string getLabel() { result = getName() }
+ override string getLabel() { result = this.getName() }
}
/**
@@ -922,13 +922,15 @@ class NullableType extends ValueType, DotNet::ConstructedGeneric, @nullable_type
*/
Type getUnderlyingType() { nullable_underlying_type(this, getTypeRef(result)) }
- override string toStringWithTypes() { result = getUnderlyingType().toStringWithTypes() + "?" }
+ override string toStringWithTypes() {
+ result = this.getUnderlyingType().toStringWithTypes() + "?"
+ }
- override Type getChild(int n) { result = getUnderlyingType() and n = 0 }
+ override Type getChild(int n) { result = this.getUnderlyingType() and n = 0 }
- override Location getALocation() { result = getUnderlyingType().getALocation() }
+ override Location getALocation() { result = this.getUnderlyingType().getALocation() }
- override Type getTypeArgument(int p) { p = 0 and result = getUnderlyingType() }
+ override Type getTypeArgument(int p) { p = 0 and result = this.getUnderlyingType() }
override string getAPrimaryQlClass() { result = "NullableType" }
@@ -966,8 +968,8 @@ class ArrayType extends DotNet::ArrayType, RefType, @array_type {
/** Holds if this array type has the same shape (dimension and rank) as `that` array type. */
predicate hasSameShapeAs(ArrayType that) {
- getDimension() = that.getDimension() and
- getRank() = that.getRank()
+ this.getDimension() = that.getDimension() and
+ this.getRank() = that.getRank()
}
/**
@@ -981,7 +983,7 @@ class ArrayType extends DotNet::ArrayType, RefType, @array_type {
private string getDimensionString(Type elementType) {
exists(Type et, string res |
et = this.getElementType() and
- res = getArraySuffix() and
+ res = this.getArraySuffix() and
if et instanceof ArrayType
then result = res + et.(ArrayType).getDimensionString(elementType)
else (
@@ -996,7 +998,7 @@ class ArrayType extends DotNet::ArrayType, RefType, @array_type {
)
}
- override Type getChild(int n) { result = getElementType() and n = 0 }
+ override Type getChild(int n) { result = this.getElementType() and n = 0 }
override Location getALocation() {
type_location(this, result)
@@ -1021,13 +1023,15 @@ class PointerType extends DotNet::PointerType, Type, @pointer_type {
override string toStringWithTypes() { result = DotNet::PointerType.super.toStringWithTypes() }
- override Type getChild(int n) { result = getReferentType() and n = 0 }
+ override Type getChild(int n) { result = this.getReferentType() and n = 0 }
final override string getName() { types(this, _, result) }
- final override string getUndecoratedName() { result = getReferentType().getUndecoratedName() }
+ final override string getUndecoratedName() {
+ result = this.getReferentType().getUndecoratedName()
+ }
- override Location getALocation() { result = getReferentType().getALocation() }
+ override Location getALocation() { result = this.getReferentType().getALocation() }
override string toString() { result = DotNet::PointerType.super.toString() }
@@ -1082,10 +1086,10 @@ class TupleType extends ValueType, @tuple_type {
* Gets the type of the `n`th element of this tuple, indexed from 0.
* For example, the 0th (first) element type of `(int, string)` is `int`.
*/
- Type getElementType(int n) { result = getElement(n).getType() }
+ Type getElementType(int n) { result = this.getElement(n).getType() }
/** Gets an element of this tuple. */
- Field getAnElement() { result = getElement(_) }
+ Field getAnElement() { result = this.getElement(_) }
override Location getALocation() { type_location(this, result) }
@@ -1093,23 +1097,27 @@ class TupleType extends ValueType, @tuple_type {
* Gets the arity of this tuple. For example, the arity of
* `(int, int, double)` is 3.
*/
- int getArity() { result = count(getAnElement()) }
+ int getArity() { result = count(this.getAnElement()) }
language[monotonicAggregates]
override string toStringWithTypes() {
result =
"(" +
- concat(Type t, int i | t = getElement(i).getType() | t.toStringWithTypes(), ", " order by i)
- + ")"
+ concat(Type t, int i |
+ t = this.getElement(i).getType()
+ |
+ t.toStringWithTypes(), ", " order by i
+ ) + ")"
}
language[monotonicAggregates]
override string getName() {
result =
- "(" + concat(Type t, int i | t = getElement(i).getType() | t.getName(), "," order by i) + ")"
+ "(" + concat(Type t, int i | t = this.getElement(i).getType() | t.getName(), "," order by i) +
+ ")"
}
- override string getLabel() { result = getUnderlyingType().getLabel() }
+ override string getLabel() { result = this.getUnderlyingType().getLabel() }
override Type getChild(int i) { result = this.getUnderlyingType().getChild(i) }
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Unification.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Unification.qll
index 3a2c6745f45..d9f39cec603 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Unification.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Unification.qll
@@ -297,7 +297,7 @@ module Gvn {
or
result =
strictconcat(int i, int j |
- toStringPart(i, j)
+ this.toStringPart(i, j)
|
this.toStringConstructedPart(i, j) order by i desc, j
)
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Variable.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Variable.qll
index a13175dfeb0..6592320fdd7 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Variable.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/Variable.qll
@@ -149,7 +149,7 @@ class Parameter extends DotNet::Parameter, LocalScopeVariable, Attributable, Top
predicate isIn() { params(this, _, _, _, 5, _, _) }
/** Holds if this parameter is an output or reference parameter. */
- predicate isOutOrRef() { isOut() or isRef() }
+ predicate isOutOrRef() { this.isOut() or this.isRef() }
/**
* Holds if this parameter is a parameter array. For example, `args`
@@ -210,7 +210,7 @@ class Parameter extends DotNet::Parameter, LocalScopeVariable, Attributable, Top
Expr getDefaultValue() { result = this.getUnboundDeclaration().getChildExpr(0) }
/** Holds if this parameter has a default value. */
- predicate hasDefaultValue() { exists(getDefaultValue()) }
+ predicate hasDefaultValue() { exists(this.getDefaultValue()) }
/** Gets the callable to which this parameter belongs, if any. */
override Callable getCallable() { result = this.getDeclaringElement() }
@@ -238,7 +238,9 @@ class Parameter extends DotNet::Parameter, LocalScopeVariable, Attributable, Top
* `y` is `5`, and the assigned arguments to `z` are `3` and `6`, respectively.
*/
pragma[nomagic]
- Expr getAnAssignedArgument() { result = getCallable().getACall().getArgumentForParameter(this) }
+ Expr getAnAssignedArgument() {
+ result = this.getCallable().getACall().getArgumentForParameter(this)
+ }
/** Holds if this parameter is potentially overwritten in the body of its callable. */
predicate isOverwritten() {
@@ -323,7 +325,7 @@ class LocalVariable extends LocalScopeVariable, @local_variable {
/** Gets the enclosing callable of this local variable. */
Callable getEnclosingCallable() { result = this.getVariableDeclExpr().getEnclosingCallable() }
- override Callable getCallable() { result = getEnclosingCallable() }
+ override Callable getCallable() { result = this.getEnclosingCallable() }
override predicate isRef() { localvars(this, 3, _, _, _, _) }
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/XML.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/XML.qll
index 4c762f4bf65..76f3b3cb022 100755
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/XML.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/XML.qll
@@ -108,7 +108,7 @@ class XMLParent extends @xmlparent {
}
/** Gets the text value contained in this XML parent. */
- string getTextValue() { result = allCharactersString() }
+ string getTextValue() { result = this.allCharactersString() }
/** Gets a printable representation of this XML parent. */
string toString() { result = this.getName() }
@@ -119,7 +119,7 @@ class XMLFile extends XMLParent, File {
XMLFile() { xmlEncoding(this, _) }
/** Gets a printable representation of this XML file. */
- override string toString() { result = getName() }
+ override string toString() { result = this.getName() }
/** Gets the name of this XML file. */
override string getName() { result = File.super.getAbsolutePath() }
@@ -129,14 +129,14 @@ class XMLFile extends XMLParent, File {
*
* Gets the path of this XML file.
*/
- deprecated string getPath() { result = getAbsolutePath() }
+ deprecated string getPath() { result = this.getAbsolutePath() }
/**
* DEPRECATED: Use `getParentContainer().getAbsolutePath()` instead.
*
* Gets the path of the folder that contains this XML file.
*/
- deprecated string getFolder() { result = getParentContainer().getAbsolutePath() }
+ deprecated string getFolder() { result = this.getParentContainer().getAbsolutePath() }
/** Gets the encoding of this XML file. */
string getEncoding() { xmlEncoding(this, result) }
@@ -200,7 +200,7 @@ class XMLDTD extends XMLLocatable, @xmldtd {
*/
class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
/** Holds if this XML element has the given `name`. */
- predicate hasName(string name) { name = getName() }
+ predicate hasName(string name) { name = this.getName() }
/** Gets the name of this XML element. */
override string getName() { xmlElements(this, result, _, _, _) }
@@ -239,7 +239,7 @@ class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
string getAttributeValue(string name) { result = this.getAttribute(name).getValue() }
/** Gets a printable representation of this XML element. */
- override string toString() { result = getName() }
+ override string toString() { result = this.getName() }
}
/**
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/commons/Assertions.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/commons/Assertions.qll
index d425ec118ed..4f364147395 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/commons/Assertions.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/commons/Assertions.qll
@@ -42,7 +42,7 @@ abstract class AssertMethod extends Method {
*
* Gets the index of a parameter being asserted.
*/
- deprecated final int getAssertionIndex() { result = getAnAssertionIndex() }
+ deprecated final int getAssertionIndex() { result = this.getAnAssertionIndex() }
/** Gets the parameter at position `i` being asserted. */
final Parameter getAssertedParameter(int i) {
@@ -55,7 +55,7 @@ abstract class AssertMethod extends Method {
*
* Gets a parameter being asserted.
*/
- deprecated final Parameter getAssertedParameter() { result = getAssertedParameter(_) }
+ deprecated final Parameter getAssertedParameter() { result = this.getAssertedParameter(_) }
/** Gets the failure type if the assertion fails for argument `i`, if any. */
abstract AssertionFailure getAssertionFailure(int i);
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/commons/Collections.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/commons/Collections.qll
index cee7b647fe7..0121ef13f8c 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/commons/Collections.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/commons/Collections.qll
@@ -3,23 +3,12 @@
import csharp
private string modifyMethodName() {
- result = "Add" or
- result = "AddFirst" or
- result = "AddLast" or
- result = "Clear" or
- result = "Enqueue" or
- result = "ExceptWith" or
- result = "Insert" or
- result = "IntersectWith" or
- result = "Push" or
- result = "Remove" or
- result = "RemoveAt" or
- result = "RemoveFirst" or
- result = "RemoveLast" or
- result = "Set" or
- result = "SetAll" or
- result = "SymmetricExceptWith" or
- result = "UnionWith"
+ result =
+ [
+ "Add", "AddFirst", "AddLast", "Clear", "Enqueue", "ExceptWith", "Insert", "IntersectWith",
+ "Push", "Remove", "RemoveAt", "RemoveFirst", "RemoveLast", "Set", "SetAll",
+ "SymmetricExceptWith", "UnionWith"
+ ]
}
/** A method call that modifies a collection. */
@@ -39,45 +28,27 @@ class CollectionModificationAccess extends Access {
}
private string collectionTypeName() {
- result = "ArrayList" or
- result = "BitArray" or
- result = "Hashtable" or
- result = "ICollection" or
- result = "IDictionary" or
- result = "IList" or
- result = "Queue" or
- result = "ReadOnlyCollectionBase" or
- result = "SortedList" or
- result = "Stack"
+ result =
+ [
+ "ArrayList", "BitArray", "Hashtable", "ICollection", "IDictionary", "IList", "Queue",
+ "ReadOnlyCollectionBase", "SortedList", "Stack"
+ ]
}
-private string collectionNamespaceName() {
- result = "Mono.Collections" or
- result = "System.Collections"
-}
+private string collectionNamespaceName() { result = ["Mono.Collections", "System.Collections"] }
private string genericCollectionNamespaceName() {
- result = "Mono.Collections.Generic" or
- result = "System.Collections.Generic"
+ result = ["Mono.Collections.Generic", "System.Collections.Generic"]
}
private string genericCollectionTypeName() {
- result = "Dictionary<,>" or
- result = "HashSet<>" or
- result = "ICollection<>" or
- result = "IDictionary<,>" or
- result = "IList<>" or
- result = "ISet<>" or
- result = "LinkedList<>" or
- result = "List<>" or
- result = "Queue<>" or
- result = "SortedDictionary<,>" or
- result = "SortedList<,>" or
- result = "SortedSet<>" or
- result = "Stack<>" or
- result = "SynchronizedCollection<>" or
- result = "SynchronizedKeyedCollection<>" or
- result = "SynchronizedReadOnlyCollection<>"
+ result =
+ [
+ "Dictionary<,>", "HashSet<>", "ICollection<>", "IDictionary<,>", "IList<>", "ISet<>",
+ "LinkedList<>", "List<>", "Queue<>", "SortedDictionary<,>", "SortedList<,>", "SortedSet<>",
+ "Stack<>", "SynchronizedCollection<>", "SynchronizedKeyedCollection<>",
+ "SynchronizedReadOnlyCollection<>"
+ ]
}
/** A collection type. */
@@ -105,36 +76,18 @@ class EmptyCollectionCreation extends ObjectCreation {
}
private string readonlyMethodName() {
- result = "BinarySearch" or
- result = "Clone" or
- result = "Contains" or
- result = "ContainsKey" or
- result = "ContainsValue" or
- result = "CopyTo" or
- result = "Equals" or
- result = "FixedArray" or
- result = "FixedSize" or
- result = "Get" or
- result = "GetEnumerator" or
- result = "GetHashCode" or
- result = "GetRange" or
- result = "IndexOf" or
- result = "IsProperSubsetOf" or
- result = "IsProperSupersetOf" or
- result = "IsSubsetOf" or
- result = "IsSupersetOf" or
- result = "LastIndexOf" or
- result = "MemberwiseClone" or
- result = "Peek" or
- result = "ToArray" or
- result = "ToString" or
- result = "TryGetValue"
+ result =
+ [
+ "BinarySearch", "Clone", "Contains", "ContainsKey", "ContainsValue", "CopyTo", "Equals",
+ "FixedArray", "FixedSize", "Get", "GetEnumerator", "GetHashCode", "GetRange", "IndexOf",
+ "IsProperSubsetOf", "IsProperSupersetOf", "IsSubsetOf", "IsSupersetOf", "LastIndexOf",
+ "MemberwiseClone", "Peek", "ToArray", "ToString", "TryGetValue"
+ ]
}
private string noAddMethodName() {
result = readonlyMethodName() or
- result = "Dequeue" or
- result = "Pop"
+ result = ["Dequeue", "Pop"]
}
/** Holds if `a` is an access that does not modify a collection. */
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/commons/GeneratedCode.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/commons/GeneratedCode.qll
index 42e04ddcbb9..38d559d8ffd 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/commons/GeneratedCode.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/commons/GeneratedCode.qll
@@ -57,7 +57,7 @@ abstract class GeneratedCodeComment extends CommentLine { }
*/
class GenericGeneratedCodeComment extends GeneratedCodeComment {
GenericGeneratedCodeComment() {
- exists(string line, string entity, string was, string automatically | line = getText() |
+ exists(string line, string entity, string was, string automatically | line = this.getText() |
entity = "file|class|interface|art[ei]fact|module|script" and
was = "was|is|has been" and
automatically = "automatically |mechanically |auto[- ]?" and
@@ -70,7 +70,7 @@ class GenericGeneratedCodeComment extends GeneratedCodeComment {
/** A comment warning against modifications. */
class DontModifyMarkerComment extends GeneratedCodeComment {
DontModifyMarkerComment() {
- exists(string line | line = getText() |
+ exists(string line | line = this.getText() |
line.regexpMatch("(?i).*\\bGenerated by\\b.*\\bDo not edit\\b.*") or
line.regexpMatch("(?i).*\\bAny modifications to this file will be lost\\b.*")
)
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/BasicBlocks.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/BasicBlocks.qll
index b4448a71380..08e5925ad50 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/BasicBlocks.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/BasicBlocks.qll
@@ -11,7 +11,7 @@ private import ControlFlow::SuccessorTypes
*/
class BasicBlock extends TBasicBlockStart {
/** Gets an immediate successor of this basic block, if any. */
- BasicBlock getASuccessor() { result.getFirstNode() = getLastNode().getASuccessor() }
+ BasicBlock getASuccessor() { result.getFirstNode() = this.getLastNode().getASuccessor() }
/** Gets an immediate successor of this basic block of a given type, if any. */
BasicBlock getASuccessorByType(ControlFlow::SuccessorType t) {
@@ -42,7 +42,7 @@ class BasicBlock extends TBasicBlockStart {
* The basic block on line 2 is an immediate `true` successor of the
* basic block on line 1.
*/
- BasicBlock getATrueSuccessor() { result.getFirstNode() = getLastNode().getATrueSuccessor() }
+ BasicBlock getATrueSuccessor() { result.getFirstNode() = this.getLastNode().getATrueSuccessor() }
/**
* Gets an immediate `false` successor, if any.
@@ -60,25 +60,27 @@ class BasicBlock extends TBasicBlockStart {
* The basic block on line 2 is an immediate `false` successor of the
* basic block on line 1.
*/
- BasicBlock getAFalseSuccessor() { result.getFirstNode() = getLastNode().getAFalseSuccessor() }
+ BasicBlock getAFalseSuccessor() {
+ result.getFirstNode() = this.getLastNode().getAFalseSuccessor()
+ }
/** Gets the control flow node at a specific (zero-indexed) position in this basic block. */
- ControlFlow::Node getNode(int pos) { bbIndex(getFirstNode(), result, pos) }
+ ControlFlow::Node getNode(int pos) { bbIndex(this.getFirstNode(), result, pos) }
/** Gets a control flow node in this basic block. */
- ControlFlow::Node getANode() { result = getNode(_) }
+ ControlFlow::Node getANode() { result = this.getNode(_) }
/** Gets the first control flow node in this basic block. */
ControlFlow::Node getFirstNode() { this = TBasicBlockStart(result) }
/** Gets the last control flow node in this basic block. */
- ControlFlow::Node getLastNode() { result = getNode(length() - 1) }
+ ControlFlow::Node getLastNode() { result = this.getNode(this.length() - 1) }
/** Gets the callable that this basic block belongs to. */
final Callable getCallable() { result = this.getFirstNode().getEnclosingCallable() }
/** Gets the length of this basic block. */
- int length() { result = strictcount(getANode()) }
+ int length() { result = strictcount(this.getANode()) }
/**
* Holds if this basic block immediately dominates basic block `bb`.
@@ -151,7 +153,7 @@ class BasicBlock extends TBasicBlockStart {
*/
predicate dominates(BasicBlock bb) {
bb = this or
- strictlyDominates(bb)
+ this.strictlyDominates(bb)
}
/**
@@ -177,14 +179,14 @@ class BasicBlock extends TBasicBlockStart {
* does not dominate the basic block on line 6.
*/
predicate inDominanceFrontier(BasicBlock df) {
- dominatesPredecessor(df) and
- not strictlyDominates(df)
+ this.dominatesPredecessor(df) and
+ not this.strictlyDominates(df)
}
/**
* Holds if this basic block dominates a predecessor of `df`.
*/
- private predicate dominatesPredecessor(BasicBlock df) { dominates(df.getAPredecessor()) }
+ private predicate dominatesPredecessor(BasicBlock df) { this.dominates(df.getAPredecessor()) }
/**
* Gets the basic block that immediately dominates this basic block, if any.
@@ -263,7 +265,7 @@ class BasicBlock extends TBasicBlockStart {
* post-dominates itself.
*/
predicate postDominates(BasicBlock bb) {
- strictlyPostDominates(bb) or
+ this.strictlyPostDominates(bb) or
this = bb
}
@@ -276,10 +278,10 @@ class BasicBlock extends TBasicBlockStart {
predicate inLoop() { this.getASuccessor+() = this }
/** Gets a textual representation of this basic block. */
- string toString() { result = getFirstNode().toString() }
+ string toString() { result = this.getFirstNode().toString() }
/** Gets the location of this basic block. */
- Location getLocation() { result = getFirstNode().getLocation() }
+ Location getLocation() { result = this.getFirstNode().getLocation() }
}
/**
@@ -420,7 +422,7 @@ private module JoinBlockPredecessors {
/** A basic block with more than one predecessor. */
class JoinBlock extends BasicBlock {
- JoinBlock() { getFirstNode().isJoin() }
+ JoinBlock() { this.getFirstNode().isJoin() }
/**
* Gets the `i`th predecessor of this join block, with respect to some
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/ControlFlowElement.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/ControlFlowElement.qll
index 7601b83f6b8..9e7fd92d2a4 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/ControlFlowElement.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/ControlFlowElement.qll
@@ -72,13 +72,14 @@ class ControlFlowElement extends ExprOrStmtParent, @control_flow_element {
ControlFlowElement getAReachableElement() {
// Reachable in same basic block
exists(BasicBlock bb, int i, int j |
- bb.getNode(i) = getAControlFlowNode() and
+ bb.getNode(i) = this.getAControlFlowNode() and
bb.getNode(j) = result.getAControlFlowNode() and
i < j
)
or
// Reachable in different basic blocks
- getAControlFlowNode().getBasicBlock().getASuccessor+().getANode() = result.getAControlFlowNode()
+ this.getAControlFlowNode().getBasicBlock().getASuccessor+().getANode() =
+ result.getAControlFlowNode()
}
pragma[noinline]
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/ControlFlowGraph.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/ControlFlowGraph.qll
index 96b73d8978d..c94184b4f66 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/ControlFlowGraph.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/ControlFlowGraph.qll
@@ -33,10 +33,10 @@ module ControlFlow {
ControlFlowElement getElement() { none() }
/** Gets the location of this control flow node. */
- Location getLocation() { result = getElement().getLocation() }
+ Location getLocation() { result = this.getElement().getLocation() }
/** Holds if this control flow node has conditional successors. */
- predicate isCondition() { exists(getASuccessorByType(any(ConditionalSuccessor e))) }
+ predicate isCondition() { exists(this.getASuccessorByType(any(ConditionalSuccessor e))) }
/** Gets the basic block that this control flow node belongs to. */
BasicBlock getBasicBlock() { result.getANode() = this }
@@ -67,7 +67,7 @@ module ControlFlow {
// potentially very large predicate, so must be inlined
pragma[inline]
predicate dominates(Node that) {
- strictlyDominates(that)
+ this.strictlyDominates(that)
or
this = that
}
@@ -138,7 +138,7 @@ module ControlFlow {
// potentially very large predicate, so must be inlined
pragma[inline]
predicate postDominates(Node that) {
- strictlyPostDominates(that)
+ this.strictlyPostDominates(that)
or
this = that
}
@@ -186,13 +186,13 @@ module ControlFlow {
Node getASuccessorByType(SuccessorType t) { result = getASuccessor(this, t) }
/** Gets an immediate successor, if any. */
- Node getASuccessor() { result = getASuccessorByType(_) }
+ Node getASuccessor() { result = this.getASuccessorByType(_) }
/** Gets an immediate predecessor node of a given flow type, if any. */
Node getAPredecessorByType(SuccessorType t) { result.getASuccessorByType(t) = this }
/** Gets an immediate predecessor, if any. */
- Node getAPredecessor() { result = getAPredecessorByType(_) }
+ Node getAPredecessor() { result = this.getAPredecessorByType(_) }
/**
* Gets an immediate `true` successor, if any.
@@ -211,7 +211,7 @@ module ControlFlow {
* on line 1.
*/
Node getATrueSuccessor() {
- result = getASuccessorByType(any(BooleanSuccessor t | t.getValue() = true))
+ result = this.getASuccessorByType(any(BooleanSuccessor t | t.getValue() = true))
}
/**
@@ -231,7 +231,7 @@ module ControlFlow {
* on line 1.
*/
Node getAFalseSuccessor() {
- result = getASuccessorByType(any(BooleanSuccessor t | t.getValue() = false))
+ result = this.getASuccessorByType(any(BooleanSuccessor t | t.getValue() = false))
}
/** Holds if this node has more than one predecessor. */
@@ -285,7 +285,7 @@ module ControlFlow {
override Callable getEnclosingCallable() { result = this.getCallable() }
- override Location getLocation() { result = getCallable().getLocation() }
+ override Location getLocation() { result = this.getCallable().getLocation() }
override string toString() {
exists(string s |
@@ -293,7 +293,7 @@ module ControlFlow {
or
normal = false and s = "abnormal"
|
- result = "exit " + getCallable() + " (" + s + ")"
+ result = "exit " + this.getCallable() + " (" + s + ")"
)
}
}
@@ -307,9 +307,9 @@ module ControlFlow {
override Callable getEnclosingCallable() { result = this.getCallable() }
- override Location getLocation() { result = getCallable().getLocation() }
+ override Location getLocation() { result = this.getCallable().getLocation() }
- override string toString() { result = "exit " + getCallable().toString() }
+ override string toString() { result = "exit " + this.getCallable().toString() }
}
/**
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/Guards.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/Guards.qll
index 5a402717401..5e3f00c3c5e 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/Guards.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/Guards.qll
@@ -1740,7 +1740,7 @@ module Internal {
e = this.getAChildExpr()
or
exists(Expr mid |
- descendant(mid) and
+ this.descendant(mid) and
not interestingDescendantCandidate(mid) and
e = mid.getAChildExpr()
)
@@ -1748,7 +1748,7 @@ module Internal {
/** Holds if `e` is an interesting descendant of this descendant. */
predicate interestingDescendant(Expr e) {
- descendant(e) and
+ this.descendant(e) and
interestingDescendantCandidate(e)
}
}
@@ -1797,7 +1797,7 @@ module Internal {
override predicate candidate(ControlFlowElement x, ControlFlowElement y) {
exists(BasicBlock bb, Declaration d |
- candidateAux(x, d, bb) and
+ this.candidateAux(x, d, bb) and
y =
any(AccessOrCallExpr e |
e.getAControlFlowNode().getBasicBlock() = bb and
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll
index 8a02fb95dee..473fa8f83c4 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll
@@ -495,7 +495,7 @@ module Expressions {
// Flow from last element of left operand to first element of right operand
last(this.getLeftOperand(), pred, c) and
c.(NullnessCompletion).isNull() and
- first(getRightOperand(), succ)
+ first(this.getRightOperand(), succ)
or
// Post-order: flow from last element of left operand to element itself
last(this.getLeftOperand(), pred, c) and
@@ -504,7 +504,7 @@ module Expressions {
not c.(NullnessCompletion).isNull()
or
// Post-order: flow from last element of right operand to element itself
- last(getRightOperand(), pred, c) and
+ last(this.getRightOperand(), pred, c) and
c instanceof NormalCompletion and
succ = this
}
@@ -575,7 +575,7 @@ module Expressions {
PostOrderTree.super.last(last, c)
or
// Qualifier exits with a `null` completion
- lastQualifier(last, c) and
+ this.lastQualifier(last, c) and
c.(NullnessCompletion).isNull()
}
@@ -1483,7 +1483,7 @@ module Statements {
)
or
// Flow into `finally` block
- pred = getAFinallyPredecessor(c, true) and
+ pred = this.getAFinallyPredecessor(c, true) and
first(this.getFinally(), succ)
}
}
@@ -1540,7 +1540,7 @@ module Statements {
c =
any(NestedCompletion nc |
nc.getNestLevel() = 0 and
- this.throwMayBeUncaught(nc.getOuterCompletion().(ThrowCompletion)) and
+ this.throwMayBeUncaught(nc.getOuterCompletion()) and
(
// Incompatible exception type: clause itself
last = this and
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/ControlFlowGraphImplShared.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/ControlFlowGraphImplShared.qll
index 050a9384729..ee901b9192e 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/ControlFlowGraphImplShared.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/ControlFlowGraphImplShared.qll
@@ -924,7 +924,8 @@ module Consistency {
succSplits(pred, predSplits, succ, succSplits, c) and
split.hasEntry(pred, succ, c) and
not split.getKind() = predSplits.getASplit().getKind() and
- not split = succSplits.getASplit()
+ not split = succSplits.getASplit() and
+ split.getKind().isEnabled(succ)
}
query predicate breakInvariant5(
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/PreBasicBlocks.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/PreBasicBlocks.qll
index 31155dea0ae..de44808b18e 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/PreBasicBlocks.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/PreBasicBlocks.qll
@@ -69,9 +69,9 @@ class PreBasicBlock extends ControlFlowElement {
ControlFlowElement getFirstElement() { result = this }
- ControlFlowElement getLastElement() { result = this.getElement(length() - 1) }
+ ControlFlowElement getLastElement() { result = this.getElement(this.length() - 1) }
- int length() { result = strictcount(getAnElement()) }
+ int length() { result = strictcount(this.getAnElement()) }
predicate immediatelyDominates(PreBasicBlock bb) { bbIDominates(this, bb) }
@@ -117,7 +117,7 @@ class ConditionBlock extends PreBasicBlock {
pragma[nomagic]
predicate controls(PreBasicBlock controlled, SuccessorTypes::ConditionalSuccessor s) {
- exists(PreBasicBlock succ, ConditionalCompletion c | immediatelyControls(succ, c) |
+ exists(PreBasicBlock succ, ConditionalCompletion c | this.immediatelyControls(succ, c) |
succ.dominates(controlled) and
s = c.getAMatchingSuccessorType()
)
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/Splitting.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/Splitting.qll
index 4d1d39de988..83ea302e691 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/Splitting.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/Splitting.qll
@@ -628,7 +628,7 @@ module FinallySplitting {
*/
private predicate exit(ControlFlowElement pred, Completion c, boolean inherited) {
exists(TryStmt try, FinallySplitType type |
- exit0(pred, try, this.getNestLevel(), c) and
+ this.exit0(pred, try, this.getNestLevel(), c) and
type = this.getType()
|
if last(try.getFinally(), pred, c)
@@ -690,18 +690,18 @@ module FinallySplitting {
override predicate hasExit(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
succ(pred, succ, c) and
(
- exit(pred, c, _)
+ this.exit(pred, c, _)
or
- exit(pred, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _)
+ this.exit(pred, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _)
)
}
override predicate hasExitScope(CfgScope scope, ControlFlowElement last, Completion c) {
scopeLast(scope, last, c) and
(
- exit(last, c, _)
+ this.exit(last, c, _)
or
- exit(last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _)
+ this.exit(last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _)
)
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/SuccessorType.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/SuccessorType.qll
index 648c2cd847c..76da2fb62ef 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/SuccessorType.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/SuccessorType.qll
@@ -77,7 +77,7 @@ module SuccessorTypes {
class BooleanSuccessor extends ConditionalSuccessor, TBooleanSuccessor {
override boolean getValue() { this = TBooleanSuccessor(result) }
- override string toString() { result = getValue().toString() }
+ override string toString() { result = this.getValue().toString() }
}
/**
@@ -310,7 +310,7 @@ module SuccessorTypes {
/** Gets the type of exception. */
ExceptionClass getExceptionClass() { this = TExceptionSuccessor(result) }
- override string toString() { result = "exception(" + getExceptionClass().getName() + ")" }
+ override string toString() { result = "exception(" + this.getExceptionClass().getName() + ")" }
}
/**
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/pressa/SsaImplCommon.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/pressa/SsaImplCommon.qll
index 884f4406d01..eae5d23f544 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/pressa/SsaImplCommon.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/pressa/SsaImplCommon.qll
@@ -141,24 +141,23 @@ private module Liveness {
private import Liveness
-/** Holds if `bb1` strictly dominates `bb2`. */
-private predicate strictlyDominates(BasicBlock bb1, BasicBlock bb2) {
- bb1 = getImmediateBasicBlockDominator+(bb2)
-}
-
-/** Holds if `bb1` dominates a predecessor of `bb2`. */
-private predicate dominatesPredecessor(BasicBlock bb1, BasicBlock bb2) {
- exists(BasicBlock pred | pred = getABasicBlockPredecessor(bb2) |
- bb1 = pred
- or
- strictlyDominates(bb1, pred)
- )
-}
-
-/** Holds if `df` is in the dominance frontier of `bb`. */
+/**
+ * Holds if `df` is in the dominance frontier of `bb`.
+ *
+ * This is equivalent to:
+ *
+ * ```ql
+ * bb = getImmediateBasicBlockDominator*(getABasicBlockPredecessor(df)) and
+ * not bb = getImmediateBasicBlockDominator+(df)
+ * ```
+ */
private predicate inDominanceFrontier(BasicBlock bb, BasicBlock df) {
- dominatesPredecessor(bb, df) and
- not strictlyDominates(bb, df)
+ bb = getABasicBlockPredecessor(df) and not bb = getImmediateBasicBlockDominator(df)
+ or
+ exists(BasicBlock prev | inDominanceFrontier(prev, df) |
+ bb = getImmediateBasicBlockDominator(prev) and
+ not bb = getImmediateBasicBlockDominator(df)
+ )
}
/**
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/ExternalFlow.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/ExternalFlow.qll
index b689602d7bf..8b0bc25f261 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/ExternalFlow.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/ExternalFlow.qll
@@ -427,7 +427,7 @@ private Element interpretElement0(
result = t
or
subtypes = true and
- result = t.(UnboundValueOrRefType).getASubTypeUnbound+()
+ result = t.getASubTypeUnbound+()
) and
result = t and
name = "" and
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/FlowSummary.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/FlowSummary.qll
index a20c3876050..3e5c8e51ba5 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/FlowSummary.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/FlowSummary.qll
@@ -78,4 +78,21 @@ module SummaryComponentStack {
class SummarizedCallable = Impl::Public::SummarizedCallable;
+private class SummarizedCallableDefaultClearsContent extends Impl::Public::SummarizedCallable {
+ SummarizedCallableDefaultClearsContent() {
+ this instanceof Impl::Public::SummarizedCallable or none()
+ }
+
+ // By default, we assume that all stores into arguments are definite
+ override predicate clearsContent(int i, DataFlow::Content content) {
+ exists(SummaryComponentStack output |
+ this.propagatesFlow(_, output, _) and
+ output.drop(_) =
+ SummaryComponentStack::push(SummaryComponent::content(content),
+ SummaryComponentStack::argument(i)) and
+ not content instanceof DataFlow::ElementContent
+ )
+ }
+}
+
class RequiredSummaryComponentStack = Impl::Public::RequiredSummaryComponentStack;
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/LibraryTypeDataFlow.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/LibraryTypeDataFlow.qll
index f36783f56c6..f405484a55d 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/LibraryTypeDataFlow.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/LibraryTypeDataFlow.qll
@@ -505,20 +505,20 @@ class SystemBooleanFlow extends LibraryTypeDataFlow, SystemBooleanStruct {
CallableFlowSource source, CallableFlowSink sink, SourceDeclarationCallable c,
boolean preservesValue
) {
- methodFlow(source, sink, c) and
+ this.methodFlow(source, sink, c) and
preservesValue = false
}
private predicate methodFlow(
CallableFlowSource source, CallableFlowSink sink, SourceDeclarationMethod m
) {
- m = getParseMethod() and
+ m = this.getParseMethod() and
(
source = TCallableFlowSourceArg(0) and
sink = TCallableFlowSinkReturn()
)
or
- m = getTryParseMethod() and
+ m = this.getTryParseMethod() and
(
source = TCallableFlowSourceArg(0) and
(
@@ -537,12 +537,12 @@ class SystemUriFlow extends LibraryTypeDataFlow, SystemUriClass {
boolean preservesValue
) {
(
- constructorFlow(source, sink, c)
+ this.constructorFlow(source, sink, c)
or
- methodFlow(source, sink, c)
+ this.methodFlow(source, sink, c)
or
exists(Property p |
- propertyFlow(p) and
+ this.propertyFlow(p) and
source = TCallableFlowSourceQualifier() and
sink = TCallableFlowSinkReturn() and
c = p.getGetter()
@@ -552,7 +552,7 @@ class SystemUriFlow extends LibraryTypeDataFlow, SystemUriClass {
}
private predicate constructorFlow(CallableFlowSource source, CallableFlowSink sink, Constructor c) {
- c = getAMember() and
+ c = this.getAMember() and
c.getParameter(0).getType() instanceof StringType and
source = TCallableFlowSourceArg(0) and
sink = TCallableFlowSinkReturn()
@@ -567,11 +567,11 @@ class SystemUriFlow extends LibraryTypeDataFlow, SystemUriClass {
}
private predicate propertyFlow(Property p) {
- p = getPathAndQueryProperty()
+ p = this.getPathAndQueryProperty()
or
- p = getQueryProperty()
+ p = this.getQueryProperty()
or
- p = getOriginalStringProperty()
+ p = this.getOriginalStringProperty()
}
}
@@ -582,15 +582,15 @@ class SystemIOStringReaderFlow extends LibraryTypeDataFlow, SystemIOStringReader
boolean preservesValue
) {
(
- constructorFlow(source, sink, c)
+ this.constructorFlow(source, sink, c)
or
- methodFlow(source, sink, c)
+ this.methodFlow(source, sink, c)
) and
preservesValue = false
}
private predicate constructorFlow(CallableFlowSource source, CallableFlowSink sink, Constructor c) {
- c = getAMember() and
+ c = this.getAMember() and
c.getParameter(0).getType() instanceof StringType and
source = TCallableFlowSourceArg(0) and
sink = TCallableFlowSinkReturn()
@@ -599,7 +599,7 @@ class SystemIOStringReaderFlow extends LibraryTypeDataFlow, SystemIOStringReader
private predicate methodFlow(
CallableFlowSource source, CallableFlowSink sink, SourceDeclarationMethod m
) {
- m.getDeclaringType() = getABaseType*() and
+ m.getDeclaringType() = this.getABaseType*() and
m.getName().matches("Read%") and
source = TCallableFlowSourceQualifier() and
sink = TCallableFlowSinkReturn()
@@ -612,17 +612,17 @@ class SystemStringFlow extends LibraryTypeDataFlow, SystemStringClass {
CallableFlowSource source, AccessPath sourceAp, CallableFlowSink sink, AccessPath sinkAp,
SourceDeclarationCallable c, boolean preservesValue
) {
- constructorFlow(source, sourceAp, sink, sinkAp, c) and
+ this.constructorFlow(source, sourceAp, sink, sinkAp, c) and
preservesValue = false
or
- methodFlow(source, sourceAp, sink, sinkAp, c, preservesValue)
+ this.methodFlow(source, sourceAp, sink, sinkAp, c, preservesValue)
}
private predicate constructorFlow(
CallableFlowSource source, AccessPath sourceAp, CallableFlowSink sink, AccessPath sinkAp,
Constructor c
) {
- c = getAMember() and
+ c = this.getAMember() and
c.getParameter(0).getType().(ArrayType).getElementType() instanceof CharType and
source = TCallableFlowSourceArg(0) and
sourceAp = AccessPath::element() and
@@ -641,14 +641,14 @@ class SystemStringFlow extends LibraryTypeDataFlow, SystemStringClass {
sinkAp = AccessPath::empty() and
preservesValue = true
or
- m = getSplitMethod() and
+ m = this.getSplitMethod() and
source = TCallableFlowSourceQualifier() and
sourceAp = AccessPath::empty() and
sink = TCallableFlowSinkReturn() and
sinkAp = AccessPath::element() and
preservesValue = false
or
- m = getReplaceMethod() and
+ m = this.getReplaceMethod() and
sourceAp = AccessPath::empty() and
sinkAp = AccessPath::empty() and
(
@@ -661,21 +661,21 @@ class SystemStringFlow extends LibraryTypeDataFlow, SystemStringClass {
preservesValue = false
)
or
- m = getSubstringMethod() and
+ m = this.getSubstringMethod() and
source = TCallableFlowSourceQualifier() and
sourceAp = AccessPath::empty() and
sink = TCallableFlowSinkReturn() and
sinkAp = AccessPath::empty() and
preservesValue = false
or
- m = getCloneMethod() and
+ m = this.getCloneMethod() and
source = TCallableFlowSourceQualifier() and
sourceAp = AccessPath::empty() and
sink = TCallableFlowSinkReturn() and
sinkAp = AccessPath::empty() and
preservesValue = true
or
- m = getInsertMethod() and
+ m = this.getInsertMethod() and
sourceAp = AccessPath::empty() and
sinkAp = AccessPath::empty() and
(
@@ -688,21 +688,21 @@ class SystemStringFlow extends LibraryTypeDataFlow, SystemStringClass {
preservesValue = false
)
or
- m = getNormalizeMethod() and
+ m = this.getNormalizeMethod() and
source = TCallableFlowSourceQualifier() and
sourceAp = AccessPath::empty() and
sink = TCallableFlowSinkReturn() and
sinkAp = AccessPath::empty() and
preservesValue = false
or
- m = getRemoveMethod() and
+ m = this.getRemoveMethod() and
source = TCallableFlowSourceQualifier() and
sourceAp = AccessPath::empty() and
sink = TCallableFlowSinkReturn() and
sinkAp = AccessPath::empty() and
preservesValue = false
or
- m = getAMethod() and
+ m = this.getAMethod() and
m.getName().regexpMatch("((ToLower|ToUpper)(Invariant)?)|(Trim(Start|End)?)|(Pad(Left|Right))") and
source = TCallableFlowSourceQualifier() and
sourceAp = AccessPath::empty() and
@@ -710,7 +710,7 @@ class SystemStringFlow extends LibraryTypeDataFlow, SystemStringClass {
sinkAp = AccessPath::empty() and
preservesValue = false
or
- m = getConcatMethod() and
+ m = this.getConcatMethod() and
exists(int i |
source = getFlowSourceArg(m, i, sourceAp) and
sink = TCallableFlowSinkReturn() and
@@ -718,20 +718,20 @@ class SystemStringFlow extends LibraryTypeDataFlow, SystemStringClass {
preservesValue = false
)
or
- m = getCopyMethod() and
+ m = this.getCopyMethod() and
source = TCallableFlowSourceArg(0) and
sourceAp = AccessPath::empty() and
sink = TCallableFlowSinkReturn() and
sinkAp = AccessPath::empty() and
preservesValue = true
or
- m = getJoinMethod() and
+ m = this.getJoinMethod() and
source = getFlowSourceArg(m, [0, 1], sourceAp) and
sink = TCallableFlowSinkReturn() and
sinkAp = AccessPath::empty() and
preservesValue = false
or
- m = getFormatMethod() and
+ m = this.getFormatMethod() and
exists(int i |
(m.getParameter(0).getType() instanceof SystemIFormatProviderInterface implies i != 0) and
source = getFlowSourceArg(m, i, sourceAp) and
@@ -749,10 +749,10 @@ class SystemTextStringBuilderFlow extends LibraryTypeDataFlow, SystemTextStringB
SourceDeclarationCallable c, boolean preservesValue
) {
(
- constructorFlow(source, sourceAp, sink, sinkAp, c) and
+ this.constructorFlow(source, sourceAp, sink, sinkAp, c) and
preservesValue = true
or
- methodFlow(source, sourceAp, sink, sinkAp, c, preservesValue)
+ this.methodFlow(source, sourceAp, sink, sinkAp, c, preservesValue)
)
}
@@ -760,7 +760,7 @@ class SystemTextStringBuilderFlow extends LibraryTypeDataFlow, SystemTextStringB
CallableFlowSource source, AccessPath sourceAp, CallableFlowSink sink, AccessPath sinkAp,
Constructor c
) {
- c = getAMember() and
+ c = this.getAMember() and
c.getParameter(0).getType() instanceof StringType and
source = TCallableFlowSourceArg(0) and
sourceAp = AccessPath::empty() and
@@ -894,7 +894,7 @@ class IEnumerableFlow extends LibraryTypeDataFlow, RefType {
) {
preservesValue = true and
(
- methodFlowLINQExtensions(source, sourceAp, sink, sinkAp, c)
+ this.methodFlowLINQExtensions(source, sourceAp, sink, sinkAp, c)
or
c = this.getFind() and
sourceAp = AccessPath::element() and
@@ -1674,14 +1674,14 @@ class SystemConvertFlow extends LibraryTypeDataFlow, SystemConvertClass {
CallableFlowSource source, CallableFlowSink sink, SourceDeclarationCallable c,
boolean preservesValue
) {
- methodFlow(source, sink, c) and
+ this.methodFlow(source, sink, c) and
preservesValue = false
}
private predicate methodFlow(
CallableFlowSource source, CallableFlowSink sink, SourceDeclarationMethod m
) {
- m = getAMethod() and
+ m = this.getAMethod() and
source = TCallableFlowSourceArg(0) and
sink = TCallableFlowSinkReturn()
}
@@ -1694,7 +1694,7 @@ class SystemWebHttpCookieFlow extends LibraryTypeDataFlow, SystemWebHttpCookie {
boolean preservesValue
) {
exists(Property p |
- propertyFlow(p) and
+ this.propertyFlow(p) and
source = TCallableFlowSourceQualifier() and
sink = TCallableFlowSinkReturn() and
c = p.getGetter()
@@ -1703,8 +1703,8 @@ class SystemWebHttpCookieFlow extends LibraryTypeDataFlow, SystemWebHttpCookie {
}
private predicate propertyFlow(Property p) {
- p = getValueProperty() or
- p = getValuesProperty()
+ p = this.getValueProperty() or
+ p = this.getValuesProperty()
}
}
@@ -1715,7 +1715,7 @@ class SystemNetCookieFlow extends LibraryTypeDataFlow, SystemNetCookieClass {
boolean preservesValue
) {
exists(Property p |
- propertyFlow(p) and
+ this.propertyFlow(p) and
source = TCallableFlowSourceQualifier() and
sink = TCallableFlowSinkReturn() and
c = p.getGetter()
@@ -1733,7 +1733,7 @@ class SystemNetIPHostEntryFlow extends LibraryTypeDataFlow, SystemNetIPHostEntry
boolean preservesValue
) {
exists(Property p |
- propertyFlow(p) and
+ this.propertyFlow(p) and
source = TCallableFlowSourceQualifier() and
sink = TCallableFlowSinkReturn() and
c = p.getGetter()
@@ -1742,8 +1742,8 @@ class SystemNetIPHostEntryFlow extends LibraryTypeDataFlow, SystemNetIPHostEntry
}
private predicate propertyFlow(Property p) {
- p = getHostNameProperty() or
- p = getAliasesProperty()
+ p = this.getHostNameProperty() or
+ p = this.getAliasesProperty()
}
}
@@ -1755,7 +1755,7 @@ class SystemWebUIWebControlsTextBoxFlow extends LibraryTypeDataFlow,
boolean preservesValue
) {
exists(Property p |
- propertyFlow(p) and
+ this.propertyFlow(p) and
source = TCallableFlowSourceQualifier() and
sink = TCallableFlowSinkReturn() and
c = p.getGetter()
@@ -1763,7 +1763,7 @@ class SystemWebUIWebControlsTextBoxFlow extends LibraryTypeDataFlow,
preservesValue = false
}
- private predicate propertyFlow(Property p) { p = getTextProperty() }
+ private predicate propertyFlow(Property p) { p = this.getTextProperty() }
}
/** Data flow for `System.Collections.Generic.KeyValuePair`. */
@@ -1864,11 +1864,11 @@ class SystemThreadingTasksTaskFlow extends LibraryTypeDataFlow, SystemThreadingT
SourceDeclarationCallable c, boolean preservesValue
) {
(
- constructorFlow(source, sink, c) and
+ this.constructorFlow(source, sink, c) and
sourceAp = AccessPath::empty() and
sinkAp = AccessPath::empty()
or
- methodFlow(source, sourceAp, sink, sinkAp, c)
+ this.methodFlow(source, sourceAp, sink, sinkAp, c)
) and
preservesValue = true
}
@@ -1954,9 +1954,9 @@ class SystemThreadingTasksTaskTFlow extends LibraryTypeDataFlow, SystemThreading
SourceDeclarationCallable c, boolean preservesValue
) {
(
- constructorFlow(source, sourceAp, sink, sinkAp, c)
+ this.constructorFlow(source, sourceAp, sink, sinkAp, c)
or
- methodFlow(source, sourceAp, sink, sinkAp, c)
+ this.methodFlow(source, sourceAp, sink, sinkAp, c)
) and
preservesValue = true
or
@@ -2101,14 +2101,14 @@ private class SystemRuntimeCompilerServicesConfiguredTaskAwaitableTFlow extends
class SystemThreadingTasksFactoryFlow extends LibraryTypeDataFlow {
SystemThreadingTasksFactoryFlow() {
this instanceof SystemThreadingTasksClass and
- getName().regexpMatch("TaskFactory(<>)?")
+ this.getName().regexpMatch("TaskFactory(<>)?")
}
override predicate callableFlow(
CallableFlowSource source, AccessPath sourceAp, CallableFlowSink sink, AccessPath sinkAp,
SourceDeclarationCallable c, boolean preservesValue
) {
- methodFlow(source, sourceAp, sink, sinkAp, c) and
+ this.methodFlow(source, sourceAp, sink, sinkAp, c) and
preservesValue = true
}
@@ -2236,12 +2236,12 @@ library class SystemTextEncodingFlow extends LibraryTypeDataFlow, SystemTextEnco
preservesValue = false and
c = this.getAMethod() and
exists(Method m | m.getAnOverrider*().getUnboundDeclaration() = c |
- m = getGetBytesMethod() and
+ m = this.getGetBytesMethod() and
source = getFlowSourceArg(m, 0, sourceAp) and
sink = TCallableFlowSinkReturn() and
sinkAp = AccessPath::empty()
or
- m = [getGetStringMethod(), getGetCharsMethod()] and
+ m = [this.getGetStringMethod(), this.getGetCharsMethod()] and
source = TCallableFlowSourceArg(0) and
sourceAp = AccessPath::element() and
sink = TCallableFlowSinkReturn() and
@@ -2257,9 +2257,9 @@ library class SystemIOMemoryStreamFlow extends LibraryTypeDataFlow, SystemIOMemo
boolean preservesValue
) {
(
- constructorFlow(source, sink, c)
+ this.constructorFlow(source, sink, c)
or
- c = getToArrayMethod().getAnOverrider*() and
+ c = this.getToArrayMethod().getAnOverrider*() and
source = TCallableFlowSourceQualifier() and
sink = TCallableFlowSinkReturn()
) and
@@ -2267,7 +2267,7 @@ library class SystemIOMemoryStreamFlow extends LibraryTypeDataFlow, SystemIOMemo
}
private predicate constructorFlow(CallableFlowSource source, CallableFlowSink sink, Constructor c) {
- c = getAMember() and
+ c = this.getAMember() and
c.getParameter(0).getType().(ArrayType).getElementType() instanceof ByteType and
source = TCallableFlowSourceArg(0) and
sink = TCallableFlowSinkReturn()
@@ -2281,17 +2281,17 @@ class SystemIOStreamFlow extends LibraryTypeDataFlow, SystemIOStreamClass {
boolean preservesValue
) {
(
- c = getAReadMethod().getAnOverrider*() and
+ c = this.getAReadMethod().getAnOverrider*() and
c.getParameter(0).getType().(ArrayType).getElementType() instanceof ByteType and
sink = TCallableFlowSinkArg(0) and
source = TCallableFlowSourceQualifier()
or
- c = getAWriteMethod().getAnOverrider*() and
+ c = this.getAWriteMethod().getAnOverrider*() and
c.getParameter(0).getType().(ArrayType).getElementType() instanceof ByteType and
source = TCallableFlowSourceArg(0) and
sink = TCallableFlowSinkQualifier()
or
- c = any(Method m | m = getAMethod() and m.getName().matches("CopyTo%")).getAnOverrider*() and
+ c = any(Method m | m = this.getAMethod() and m.getName().matches("CopyTo%")).getAnOverrider*() and
c.getParameter(0).getType() instanceof SystemIOStreamClass and
source = TCallableFlowSourceQualifier() and
sink = TCallableFlowSinkArg(0)
@@ -2307,12 +2307,12 @@ class SystemIOCompressionDeflateStreamFlow extends LibraryTypeDataFlow,
CallableFlowSource source, CallableFlowSink sink, SourceDeclarationCallable c,
boolean preservesValue
) {
- constructorFlow(source, sink, c) and
+ this.constructorFlow(source, sink, c) and
preservesValue = false
}
private predicate constructorFlow(CallableFlowSource source, CallableFlowSink sink, Constructor c) {
- c = getAMember() and
+ c = this.getAMember() and
source = TCallableFlowSourceArg(0) and
sink = TCallableFlowSinkReturn()
}
@@ -2324,7 +2324,7 @@ class SystemXmlXmlReaderFlow extends LibraryTypeDataFlow, SystemXmlXmlReaderClas
CallableFlowSource source, CallableFlowSink sink, SourceDeclarationCallable c,
boolean preservesValue
) {
- c = getCreateMethod() and
+ c = this.getCreateMethod() and
source = TCallableFlowSourceArg(0) and
sink = TCallableFlowSinkReturn() and
preservesValue = false
@@ -2337,7 +2337,7 @@ class SystemXmlXmlDocumentFlow extends LibraryTypeDataFlow, SystemXmlXmlDocument
CallableFlowSource source, CallableFlowSink sink, SourceDeclarationCallable c,
boolean preservesValue
) {
- c = getLoadMethod() and
+ c = this.getLoadMethod() and
source = TCallableFlowSourceArg(0) and
sink = TCallableFlowSinkQualifier() and
preservesValue = false
@@ -2352,13 +2352,13 @@ class SystemXmlXmlNodeFlow extends LibraryTypeDataFlow, SystemXmlXmlNodeClass {
) {
(
exists(Property p |
- p = getAProperty() and
+ p = this.getAProperty() and
c = p.getGetter() and
source = TCallableFlowSourceQualifier() and
sink = TCallableFlowSinkReturn()
)
or
- c = getASelectNodeMethod() and
+ c = this.getASelectNodeMethod() and
source = TCallableFlowSourceQualifier() and
sink = TCallableFlowSinkReturn()
) and
@@ -2372,7 +2372,7 @@ class SystemXmlXmlNamedNodeMapFlow extends LibraryTypeDataFlow, SystemXmlXmlName
CallableFlowSource source, CallableFlowSink sink, SourceDeclarationCallable c,
boolean preservesValue
) {
- c = getGetNamedItemMethod() and
+ c = this.getGetNamedItemMethod() and
source = TCallableFlowSourceQualifier() and
sink = TCallableFlowSinkReturn() and
preservesValue = true
@@ -2385,14 +2385,14 @@ class SystemIOPathFlow extends LibraryTypeDataFlow, SystemIOPathClass {
CallableFlowSource source, AccessPath sourceAp, CallableFlowSink sink, AccessPath sinkAp,
SourceDeclarationCallable c, boolean preservesValue
) {
- c = getAMethod("Combine") and
+ c = this.getAMethod("Combine") and
source = getFlowSourceArg(c, _, sourceAp) and
sink = TCallableFlowSinkReturn() and
sinkAp = AccessPath::empty() and
preservesValue = false
or
exists(Parameter p |
- c = getAMethod() and
+ c = this.getAMethod() and
c.getName().matches("Get%") and
p = c.getAParameter() and
p.hasName("path") and
@@ -2411,10 +2411,10 @@ class SystemWebHttpUtilityFlow extends LibraryTypeDataFlow, SystemWebHttpUtility
boolean preservesValue
) {
(
- c = getAnHtmlAttributeEncodeMethod() or
- c = getAnHtmlEncodeMethod() or
- c = getAJavaScriptStringEncodeMethod() or
- c = getAnUrlEncodeMethod()
+ c = this.getAnHtmlAttributeEncodeMethod() or
+ c = this.getAnHtmlEncodeMethod() or
+ c = this.getAJavaScriptStringEncodeMethod() or
+ c = this.getAnUrlEncodeMethod()
) and
source = TCallableFlowSourceArg(0) and
sink = TCallableFlowSinkReturn() and
@@ -2429,8 +2429,8 @@ class SystemWebHttpServerUtilityFlow extends LibraryTypeDataFlow, SystemWebHttpS
boolean preservesValue
) {
(
- c = getAnHtmlEncodeMethod() or
- c = getAnUrlEncodeMethod()
+ c = this.getAnHtmlEncodeMethod() or
+ c = this.getAnUrlEncodeMethod()
) and
source = TCallableFlowSourceArg(0) and
sink = TCallableFlowSinkReturn() and
@@ -2445,8 +2445,8 @@ class SystemNetWebUtilityFlow extends LibraryTypeDataFlow, SystemNetWebUtility {
boolean preservesValue
) {
(
- c = getAnHtmlEncodeMethod() or
- c = getAnUrlEncodeMethod()
+ c = this.getAnHtmlEncodeMethod() or
+ c = this.getAnUrlEncodeMethod()
) and
source = TCallableFlowSourceArg(0) and
sink = TCallableFlowSinkReturn() and
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/SSA.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/SSA.qll
index 44307d68e1f..4f70b53275d 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/SSA.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/SSA.qll
@@ -76,9 +76,9 @@ module Ssa {
override Callable getEnclosingCallable() { this = SsaImpl::TLocalVar(result, _) }
- override string toString() { result = getAssignable().getName() }
+ override string toString() { result = this.getAssignable().getName() }
- override Location getLocation() { result = getAssignable().getLocation() }
+ override Location getLocation() { result = this.getAssignable().getLocation() }
}
/** A fully qualified field or property. */
@@ -105,7 +105,7 @@ module Ssa {
)
}
- override Location getLocation() { result = getFirstAccess().getLocation() }
+ override Location getLocation() { result = this.getFirstAccess().getLocation() }
}
/** A plain field or property. */
@@ -115,8 +115,8 @@ module Ssa {
override string toString() {
exists(Assignable f, string prefix |
- f = getAssignable() and
- result = prefix + "." + getAssignable()
+ f = this.getAssignable() and
+ result = prefix + "." + this.getAssignable()
|
if f.(Modifiable).isStatic()
then prefix = f.getDeclaringType().getQualifiedName()
@@ -134,7 +134,7 @@ module Ssa {
override SourceVariable getQualifier() { this = SsaImpl::TQualifiedFieldOrProp(_, result, _) }
- override string toString() { result = getQualifier() + "." + getAssignable() }
+ override string toString() { result = this.getQualifier() + "." + this.getAssignable() }
}
}
@@ -611,20 +611,20 @@ module Ssa {
* and which targets the same assignable as this SSA definition.
*/
final AssignableDefinition getAPossibleDefinition() {
- exists(Callable setter | SsaImpl::updatesNamedFieldOrProp(_, _, getCall(), _, setter) |
+ exists(Callable setter | SsaImpl::updatesNamedFieldOrProp(_, _, this.getCall(), _, setter) |
result.getEnclosingCallable() = setter and
result.getTarget() = this.getSourceVariable().getAssignable()
)
or
- SsaImpl::updatesCapturedVariable(_, _, getCall(), _, result, _) and
+ SsaImpl::updatesCapturedVariable(_, _, this.getCall(), _, result, _) and
result.getTarget() = this.getSourceVariable().getAssignable()
}
override string toString() {
- result = getToStringPrefix(this) + "SSA call def(" + getSourceVariable() + ")"
+ result = getToStringPrefix(this) + "SSA call def(" + this.getSourceVariable() + ")"
}
- override Location getLocation() { result = getCall().getLocation() }
+ override Location getLocation() { result = this.getCall().getLocation() }
}
/**
@@ -649,10 +649,10 @@ module Ssa {
final Definition getQualifierDefinition() { result = q }
override string toString() {
- result = getToStringPrefix(this) + "SSA qualifier def(" + getSourceVariable() + ")"
+ result = getToStringPrefix(this) + "SSA qualifier def(" + this.getSourceVariable() + ")"
}
- override Location getLocation() { result = getQualifierDefinition().getLocation() }
+ override Location getLocation() { result = this.getQualifierDefinition().getLocation() }
}
/**
@@ -699,7 +699,7 @@ module Ssa {
}
override string toString() {
- result = getToStringPrefix(this) + "SSA phi(" + getSourceVariable() + ")"
+ result = getToStringPrefix(this) + "SSA phi(" + this.getSourceVariable() + ")"
}
/*
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll
index f43a550af57..c28ceabb438 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll
@@ -2,6 +2,42 @@ private import DataFlowImplSpecific::Private
private import DataFlowImplSpecific::Public
import Cached
+module DataFlowImplCommonPublic {
+ private newtype TFlowFeature =
+ TFeatureHasSourceCallContext() or
+ TFeatureHasSinkCallContext() or
+ TFeatureEqualSourceSinkCallContext()
+
+ /** A flow configuration feature for use in `Configuration::getAFeature()`. */
+ class FlowFeature extends TFlowFeature {
+ string toString() { none() }
+ }
+
+ /**
+ * A flow configuration feature that implies that sources have some existing
+ * call context.
+ */
+ class FeatureHasSourceCallContext extends FlowFeature, TFeatureHasSourceCallContext {
+ override string toString() { result = "FeatureHasSourceCallContext" }
+ }
+
+ /**
+ * A flow configuration feature that implies that sinks have some existing
+ * call context.
+ */
+ class FeatureHasSinkCallContext extends FlowFeature, TFeatureHasSinkCallContext {
+ override string toString() { result = "FeatureHasSinkCallContext" }
+ }
+
+ /**
+ * A flow configuration feature that implies that source-sink pairs have some
+ * shared existing call context.
+ */
+ class FeatureEqualSourceSinkCallContext extends FlowFeature, TFeatureEqualSourceSinkCallContext {
+ override string toString() { result = "FeatureEqualSourceSinkCallContext" }
+ }
+}
+
/**
* The cost limits for the `AccessPathFront` to `AccessPathApprox` expansion.
*
@@ -251,7 +287,7 @@ private module Cached {
predicate forceCachingInSameStage() { any() }
cached
- predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = n.getEnclosingCallable() }
+ predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = nodeGetEnclosingCallable(n) }
cached
predicate callEnclosingCallable(DataFlowCall call, DataFlowCallable c) {
@@ -316,9 +352,7 @@ private module Cached {
}
cached
- predicate parameterNode(Node n, DataFlowCallable c, int i) {
- n.(ParameterNode).isParameterOf(c, i)
- }
+ predicate parameterNode(Node p, DataFlowCallable c, int pos) { isParameterNode(p, c, pos) }
cached
predicate argumentNode(Node n, DataFlowCall call, int pos) {
@@ -801,6 +835,9 @@ private module Cached {
exists(Node n | getNodeEnclosingCallable(n) = callable | isUnreachableInCallCached(n, call))
}
+ cached
+ predicate allowParameterReturnInSelfCached(ParamNode p) { allowParameterReturnInSelf(p) }
+
cached
newtype TCallContext =
TAnyCallContext() or
@@ -937,7 +974,7 @@ class CallContextSpecificCall extends CallContextCall, TSpecificCall {
}
override predicate relevantFor(DataFlowCallable callable) {
- recordDataFlowCallSite(getCall(), callable)
+ recordDataFlowCallSite(this.getCall(), callable)
}
override predicate matchesCall(DataFlowCall call) { call = this.getCall() }
@@ -1257,7 +1294,7 @@ abstract class AccessPathFront extends TAccessPathFront {
TypedContent getHead() { this = TFrontHead(result) }
- predicate isClearedAt(Node n) { clearsContentCached(n, getHead().getContent()) }
+ predicate isClearedAt(Node n) { clearsContentCached(n, this.getHead().getContent()) }
}
class AccessPathFrontNil extends AccessPathFront, TFrontNil {
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplConsistency.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplConsistency.qll
index a55e65a81f6..acf31338f9a 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplConsistency.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplConsistency.qll
@@ -31,7 +31,7 @@ module Consistency {
query predicate uniqueEnclosingCallable(Node n, string msg) {
exists(int c |
n instanceof RelevantNode and
- c = count(n.getEnclosingCallable()) and
+ c = count(nodeGetEnclosingCallable(n)) and
c != 1 and
msg = "Node should have one enclosing callable but has " + c + "."
)
@@ -85,13 +85,13 @@ module Consistency {
}
query predicate parameterCallable(ParameterNode p, string msg) {
- exists(DataFlowCallable c | p.isParameterOf(c, _) and c != p.getEnclosingCallable()) and
+ exists(DataFlowCallable c | isParameterNode(p, c, _) and c != nodeGetEnclosingCallable(p)) and
msg = "Callable mismatch for parameter."
}
query predicate localFlowIsLocal(Node n1, Node n2, string msg) {
simpleLocalFlowStep(n1, n2) and
- n1.getEnclosingCallable() != n2.getEnclosingCallable() and
+ nodeGetEnclosingCallable(n1) != nodeGetEnclosingCallable(n2) and
msg = "Local flow step does not preserve enclosing callable."
}
@@ -106,7 +106,7 @@ module Consistency {
query predicate unreachableNodeCCtx(Node n, DataFlowCall call, string msg) {
isUnreachableInCall(n, call) and
exists(DataFlowCallable c |
- c = n.getEnclosingCallable() and
+ c = nodeGetEnclosingCallable(n) and
not viableCallable(call) = c
) and
msg = "Call context for isUnreachableInCall is inconsistent with call graph."
@@ -120,7 +120,7 @@ module Consistency {
n.(ArgumentNode).argumentOf(call, _) and
msg = "ArgumentNode and call does not share enclosing callable."
) and
- n.getEnclosingCallable() != call.getEnclosingCallable()
+ nodeGetEnclosingCallable(n) != call.getEnclosingCallable()
}
// This predicate helps the compiler forget that in some languages
@@ -151,7 +151,7 @@ module Consistency {
}
query predicate postIsInSameCallable(PostUpdateNode n, string msg) {
- n.getEnclosingCallable() != n.getPreUpdateNode().getEnclosingCallable() and
+ nodeGetEnclosingCallable(n) != nodeGetEnclosingCallable(n.getPreUpdateNode()) and
msg = "PostUpdateNode does not share callable with its pre-update node."
}
@@ -175,6 +175,7 @@ module Consistency {
query predicate postWithInFlow(Node n, string msg) {
isPostUpdateNode(n) and
+ not clearsContent(n, _) and
simpleLocalFlowStep(_, n) and
msg = "PostUpdateNode should not be the target of local flow."
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll
index 6776025d172..729d351d391 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll
@@ -18,6 +18,12 @@ private import semmle.code.csharp.frameworks.NHibernate
private import semmle.code.csharp.frameworks.system.Collections
private import semmle.code.csharp.frameworks.system.threading.Tasks
+/** Gets the callable in which this node occurs. */
+DataFlowCallable nodeGetEnclosingCallable(Node n) { result = n.getEnclosingCallable() }
+
+/** Holds if `p` is a `ParameterNode` of `c` with position `pos`. */
+predicate isParameterNode(ParameterNode p, DataFlowCallable c, int pos) { p.isParameterOf(c, pos) }
+
abstract class NodeImpl extends Node {
/** Do not call: use `getEnclosingCallable()` instead. */
abstract DataFlowCallable getEnclosingCallableImpl();
@@ -310,6 +316,18 @@ module LocalFlow {
result = n.(ExplicitParameterNode).getSsaDefinition()
}
+ /**
+ * Holds if there is a local use-use flow step from `nodeFrom` to `nodeTo`
+ * involving SSA definition `def`.
+ */
+ predicate localSsaFlowStepUseUse(Ssa::Definition def, Node nodeFrom, Node nodeTo) {
+ exists(ControlFlow::Node cfnFrom, ControlFlow::Node cfnTo |
+ SsaImpl::adjacentReadPairSameVar(def, cfnFrom, cfnTo) and
+ nodeTo = TExprNode(cfnTo) and
+ nodeFrom = TExprNode(cfnFrom)
+ )
+ }
+
/**
* Holds if there is a local flow step from `nodeFrom` to `nodeTo` involving
* SSA definition `def.
@@ -322,14 +340,7 @@ module LocalFlow {
)
or
// Flow from read to next read
- exists(ControlFlow::Node cfnFrom, ControlFlow::Node cfnTo |
- SsaImpl::adjacentReadPairSameVar(def, cfnFrom, cfnTo) and
- nodeTo = TExprNode(cfnTo)
- |
- nodeFrom = TExprNode(cfnFrom)
- or
- cfnFrom = nodeFrom.(PostUpdateNode).getPreUpdateNode().getControlFlowNode()
- )
+ localSsaFlowStepUseUse(def, nodeFrom.(PostUpdateNode).getPreUpdateNode(), nodeTo)
or
// Flow into phi node
exists(Ssa::PhiNode phi |
@@ -399,6 +410,12 @@ module LocalFlow {
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
or
+ exists(Ssa::Definition def |
+ LocalFlow::localSsaFlowStepUseUse(def, nodeFrom, nodeTo) and
+ not FlowSummaryImpl::Private::Steps::summaryClearsContentArg(nodeFrom, _) and
+ not LocalFlow::usesInstanceField(def)
+ )
+ or
LocalFlow::localFlowCapturedVarStep(nodeFrom, nodeTo)
or
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, true)
@@ -716,6 +733,8 @@ private module Cached {
predicate localFlowStepImpl(Node nodeFrom, Node nodeTo) {
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
or
+ LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo)
+ or
exists(Ssa::Definition def |
LocalFlow::localSsaFlowStep(def, nodeFrom, nodeTo) and
LocalFlow::usesInstanceField(def)
@@ -772,12 +791,14 @@ predicate nodeIsHidden(Node n) {
def instanceof Ssa::ImplicitCallDefinition
)
or
- exists(Parameter p |
- p = n.(ParameterNode).getParameter() and
+ exists(Parameter p | p = n.(ParameterNode).getParameter() |
not p.fromSource()
+ or
+ p.getCallable() instanceof SummarizedCallable
)
or
- n = TInstanceParameterNode(any(Callable c | not c.fromSource()))
+ n =
+ TInstanceParameterNode(any(Callable c | not c.fromSource() or c instanceof SummarizedCallable))
or
n instanceof YieldReturnNode
or
@@ -1691,9 +1712,6 @@ predicate clearsContent(Node n, Content c) {
or
fieldOrPropertyStore(_, c, _, n.(ObjectInitializerNode).getInitializer(), false)
or
- FlowSummaryImpl::Private::Steps::summaryStoresIntoArg(c, n) and
- not c instanceof ElementContent
- or
FlowSummaryImpl::Private::Steps::summaryClearsContent(n, c)
or
exists(WithExpr we, ObjectInitializer oi, FieldOrProperty f |
@@ -2003,3 +2021,14 @@ predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preserves
preservesValue = false
)
}
+
+/**
+ * Holds if flow is allowed to pass from parameter `p` and back to itself as a
+ * side-effect, resulting in a summary from `p` to itself.
+ *
+ * One example would be to allow flow like `p.foo = p.bar;`, which is disallowed
+ * by default as a heuristic.
+ */
+predicate allowParameterReturnInSelf(ParameterNode p) {
+ FlowSummaryImpl::Private::summaryAllowParameterReturnInSelf(p)
+}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/FlowSummaryImpl.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/FlowSummaryImpl.qll
index 83076558ec4..5955285bd6f 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/FlowSummaryImpl.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/FlowSummaryImpl.qll
@@ -261,7 +261,10 @@ module Private {
private newtype TSummaryNodeState =
TSummaryNodeInputState(SummaryComponentStack s) { inputState(_, s) } or
- TSummaryNodeOutputState(SummaryComponentStack s) { outputState(_, s) }
+ TSummaryNodeOutputState(SummaryComponentStack s) { outputState(_, s) } or
+ TSummaryNodeClearsContentState(int i, boolean post) {
+ any(SummarizedCallable sc).clearsContent(i, _) and post in [false, true]
+ }
/**
* A state used to break up (complex) flow summaries into atomic flow steps.
@@ -308,6 +311,12 @@ module Private {
this = TSummaryNodeOutputState(s) and
result = "to write: " + s
)
+ or
+ exists(int i, boolean post, string postStr |
+ this = TSummaryNodeClearsContentState(i, post) and
+ (if post = true then postStr = " (post)" else postStr = "") and
+ result = "clear: " + i + postStr
+ )
}
}
@@ -329,6 +338,11 @@ module Private {
not parameterReadState(c, state, _)
or
state.isOutputState(c, _)
+ or
+ exists(int i |
+ c.clearsContent(i, _) and
+ state = TSummaryNodeClearsContentState(i, _)
+ )
}
pragma[noinline]
@@ -364,6 +378,8 @@ module Private {
parameterReadState(c, _, i)
or
isParameterPostUpdate(_, c, i)
+ or
+ c.clearsContent(i, _)
}
private predicate callbackOutput(
@@ -436,6 +452,12 @@ module Private {
)
)
)
+ or
+ exists(SummarizedCallable c, int i, ParamNode p |
+ n = summaryNode(c, TSummaryNodeClearsContentState(i, false)) and
+ p.isParameterOf(c, i) and
+ result = getNodeType(p)
+ )
}
/** Holds if summary node `out` contains output of kind `rk` from call `c`. */
@@ -461,6 +483,9 @@ module Private {
exists(SummarizedCallable c, int i |
isParameterPostUpdate(post, c, i) and
pre.(ParamNode).isParameterOf(c, i)
+ or
+ pre = summaryNode(c, TSummaryNodeClearsContentState(i, false)) and
+ post = summaryNode(c, TSummaryNodeClearsContentState(i, true))
)
or
exists(SummarizedCallable callable, SummaryComponentStack s |
@@ -478,6 +503,17 @@ module Private {
)
}
+ /**
+ * Holds if flow is allowed to pass from parameter `p`, to a return
+ * node, and back out to `p`.
+ */
+ predicate summaryAllowParameterReturnInSelf(ParamNode p) {
+ exists(SummarizedCallable c, int i |
+ c.clearsContent(i, _) and
+ p.isParameterOf(c, i)
+ )
+ }
+
/** Provides a compilation of flow summaries to atomic data-flow steps. */
module Steps {
/**
@@ -504,11 +540,21 @@ module Private {
// for `StringBuilder.append(x)` with a specified value flow from qualifier to
// return value and taint flow from argument 0 to the qualifier, then this
// allows us to infer taint flow from argument 0 to the return value.
- succ instanceof ParamNode and summaryPostUpdateNode(pred, succ) and preservesValue = true
+ succ instanceof ParamNode and
+ summaryPostUpdateNode(pred, succ) and
+ preservesValue = true
or
// Similarly we would like to chain together summaries where values get passed
// into callbacks along the way.
- pred instanceof ArgNode and summaryPostUpdateNode(succ, pred) and preservesValue = true
+ pred instanceof ArgNode and
+ summaryPostUpdateNode(succ, pred) and
+ preservesValue = true
+ or
+ exists(SummarizedCallable c, int i |
+ pred.(ParamNode).isParameterOf(c, i) and
+ succ = summaryNode(c, TSummaryNodeClearsContentState(i, _)) and
+ preservesValue = true
+ )
}
/**
@@ -536,10 +582,39 @@ module Private {
}
/**
- * Holds if values stored inside content `c` are cleared when passed as
- * input of type `input` in `call`.
+ * Holds if values stored inside content `c` are cleared at `n`. `n` is a
+ * synthesized summary node, so in order for values to be cleared at calls
+ * to the relevant method, it is important that flow does not pass over
+ * the argument, either via use-use flow or def-use flow.
+ *
+ * Example:
+ *
+ * ```
+ * a.b = taint;
+ * a.clearB(); // assume we have a flow summary for `clearB` that clears `b` on the qualifier
+ * sink(a.b);
+ * ```
+ *
+ * In the above, flow should not pass from `a` on the first line (or the second
+ * line) to `a` on the third line. Instead, there will be synthesized flow from
+ * `a` on line 2 to the post-update node for `a` on that line (via an intermediate
+ * node where field `b` is cleared).
*/
- predicate summaryClearsContent(ArgNode arg, Content c) {
+ predicate summaryClearsContent(Node n, Content c) {
+ exists(SummarizedCallable sc, int i |
+ n = summaryNode(sc, TSummaryNodeClearsContentState(i, true)) and
+ sc.clearsContent(i, c)
+ )
+ }
+
+ /**
+ * Holds if values stored inside content `c` are cleared inside a
+ * callable to which `arg` is an argument.
+ *
+ * In such cases, it is important to prevent use-use flow out of
+ * `arg` (see comment for `summaryClearsContent`).
+ */
+ predicate summaryClearsContentArg(ArgNode arg, Content c) {
exists(DataFlowCall call, int i |
viableCallable(call).(SummarizedCallable).clearsContent(i, c) and
arg.argumentOf(call, i)
@@ -599,25 +674,6 @@ module Private {
ret.getKind() = rk
)
}
-
- /**
- * Holds if data is written into content `c` of argument `arg` using a flow summary.
- *
- * Depending on the type of `c`, this predicate may be relevant to include in the
- * definition of `clearsContent()`.
- */
- predicate summaryStoresIntoArg(Content c, Node arg) {
- exists(ParamUpdateReturnKind rk, ReturnNodeExt ret, PostUpdateNode out |
- exists(DataFlowCall call, SummarizedCallable callable |
- getNodeEnclosingCallable(ret) = callable and
- viableCallable(call) = callable and
- summaryStoreStep(_, c, ret) and
- ret.getKind() = pragma[only_bind_into](rk) and
- out = rk.getAnOutNode(call) and
- arg = out.getPreUpdateNode()
- )
- )
- }
}
/**
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/SsaImplCommon.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/SsaImplCommon.qll
index 884f4406d01..eae5d23f544 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/SsaImplCommon.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/SsaImplCommon.qll
@@ -141,24 +141,23 @@ private module Liveness {
private import Liveness
-/** Holds if `bb1` strictly dominates `bb2`. */
-private predicate strictlyDominates(BasicBlock bb1, BasicBlock bb2) {
- bb1 = getImmediateBasicBlockDominator+(bb2)
-}
-
-/** Holds if `bb1` dominates a predecessor of `bb2`. */
-private predicate dominatesPredecessor(BasicBlock bb1, BasicBlock bb2) {
- exists(BasicBlock pred | pred = getABasicBlockPredecessor(bb2) |
- bb1 = pred
- or
- strictlyDominates(bb1, pred)
- )
-}
-
-/** Holds if `df` is in the dominance frontier of `bb`. */
+/**
+ * Holds if `df` is in the dominance frontier of `bb`.
+ *
+ * This is equivalent to:
+ *
+ * ```ql
+ * bb = getImmediateBasicBlockDominator*(getABasicBlockPredecessor(df)) and
+ * not bb = getImmediateBasicBlockDominator+(df)
+ * ```
+ */
private predicate inDominanceFrontier(BasicBlock bb, BasicBlock df) {
- dominatesPredecessor(bb, df) and
- not strictlyDominates(bb, df)
+ bb = getABasicBlockPredecessor(df) and not bb = getImmediateBasicBlockDominator(df)
+ or
+ exists(BasicBlock prev | inDominanceFrontier(prev, df) |
+ bb = getImmediateBasicBlockDominator(prev) and
+ not bb = getImmediateBasicBlockDominator(df)
+ )
}
/**
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/basessa/SsaImplCommon.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/basessa/SsaImplCommon.qll
index 884f4406d01..eae5d23f544 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/basessa/SsaImplCommon.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/basessa/SsaImplCommon.qll
@@ -141,24 +141,23 @@ private module Liveness {
private import Liveness
-/** Holds if `bb1` strictly dominates `bb2`. */
-private predicate strictlyDominates(BasicBlock bb1, BasicBlock bb2) {
- bb1 = getImmediateBasicBlockDominator+(bb2)
-}
-
-/** Holds if `bb1` dominates a predecessor of `bb2`. */
-private predicate dominatesPredecessor(BasicBlock bb1, BasicBlock bb2) {
- exists(BasicBlock pred | pred = getABasicBlockPredecessor(bb2) |
- bb1 = pred
- or
- strictlyDominates(bb1, pred)
- )
-}
-
-/** Holds if `df` is in the dominance frontier of `bb`. */
+/**
+ * Holds if `df` is in the dominance frontier of `bb`.
+ *
+ * This is equivalent to:
+ *
+ * ```ql
+ * bb = getImmediateBasicBlockDominator*(getABasicBlockPredecessor(df)) and
+ * not bb = getImmediateBasicBlockDominator+(df)
+ * ```
+ */
private predicate inDominanceFrontier(BasicBlock bb, BasicBlock df) {
- dominatesPredecessor(bb, df) and
- not strictlyDominates(bb, df)
+ bb = getABasicBlockPredecessor(df) and not bb = getImmediateBasicBlockDominator(df)
+ or
+ exists(BasicBlock prev | inDominanceFrontier(prev, df) |
+ bb = getImmediateBasicBlockDominator(prev) and
+ not bb = getImmediateBasicBlockDominator(df)
+ )
}
/**
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/rangeanalysis/RangeUtils.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/rangeanalysis/RangeUtils.qll
index f007ba939e8..067a9b94f45 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/rangeanalysis/RangeUtils.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/rangeanalysis/RangeUtils.qll
@@ -419,9 +419,9 @@ module ExprNode {
* "else" expression of this conditional expression.
*/
ExprNode getBranchExpr(boolean branch) {
- branch = true and result = getTrueExpr()
+ branch = true and result = this.getTrueExpr()
or
- branch = false and result = getFalseExpr()
+ branch = false and result = this.getFalseExpr()
}
}
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/rangeanalysis/SsaReadPositionCommon.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/rangeanalysis/SsaReadPositionCommon.qll
index 558ecd1b88b..e450c11b5ab 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/rangeanalysis/SsaReadPositionCommon.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/rangeanalysis/SsaReadPositionCommon.qll
@@ -28,7 +28,7 @@ class SsaReadPositionBlock extends SsaReadPosition, TSsaReadPositionBlock {
/** Gets the basic block corresponding to this position. */
BasicBlock getBlock() { this = TSsaReadPositionBlock(result) }
- override predicate hasReadOfVar(SsaVariable v) { getBlock() = getAReadBasicBlock(v) }
+ override predicate hasReadOfVar(SsaVariable v) { this.getBlock() = getAReadBasicBlock(v) }
override string toString() { result = "block" }
}
@@ -49,8 +49,8 @@ class SsaReadPositionPhiInputEdge extends SsaReadPosition, TSsaReadPositionPhiIn
/** Holds if `inp` is an input to `phi` along this edge. */
predicate phiInput(SsaPhiNode phi, SsaVariable inp) {
- phi.hasInputFromBlock(inp, getOrigBlock()) and
- getPhiBlock() = phi.getBasicBlock()
+ phi.hasInputFromBlock(inp, this.getOrigBlock()) and
+ this.getPhiBlock() = phi.getBasicBlock()
}
override string toString() { result = "edge" }
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking1/TaintTrackingImpl.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking1/TaintTrackingImpl.qll
index f4f73b8247c..acb029c23d9 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking1/TaintTrackingImpl.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking1/TaintTrackingImpl.qll
@@ -75,24 +75,26 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isSanitizer(DataFlow::Node node) { none() }
final override predicate isBarrier(DataFlow::Node node) {
- isSanitizer(node) or
+ this.isSanitizer(node) or
defaultTaintSanitizer(node)
}
/** Holds if taint propagation into `node` is prohibited. */
predicate isSanitizerIn(DataFlow::Node node) { none() }
- final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) }
+ final override predicate isBarrierIn(DataFlow::Node node) { this.isSanitizerIn(node) }
/** Holds if taint propagation out of `node` is prohibited. */
predicate isSanitizerOut(DataFlow::Node node) { none() }
- final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) }
+ final override predicate isBarrierOut(DataFlow::Node node) { this.isSanitizerOut(node) }
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
- final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { isSanitizerGuard(guard) }
+ final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
+ this.isSanitizerGuard(guard)
+ }
/**
* Holds if the additional taint propagation step from `node1` to `node2`
@@ -101,7 +103,7 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
- isAdditionalTaintStep(node1, node2) or
+ this.isAdditionalTaintStep(node1, node2) or
defaultAdditionalTaintStep(node1, node2)
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking2/TaintTrackingImpl.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking2/TaintTrackingImpl.qll
index f4f73b8247c..acb029c23d9 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking2/TaintTrackingImpl.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking2/TaintTrackingImpl.qll
@@ -75,24 +75,26 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isSanitizer(DataFlow::Node node) { none() }
final override predicate isBarrier(DataFlow::Node node) {
- isSanitizer(node) or
+ this.isSanitizer(node) or
defaultTaintSanitizer(node)
}
/** Holds if taint propagation into `node` is prohibited. */
predicate isSanitizerIn(DataFlow::Node node) { none() }
- final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) }
+ final override predicate isBarrierIn(DataFlow::Node node) { this.isSanitizerIn(node) }
/** Holds if taint propagation out of `node` is prohibited. */
predicate isSanitizerOut(DataFlow::Node node) { none() }
- final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) }
+ final override predicate isBarrierOut(DataFlow::Node node) { this.isSanitizerOut(node) }
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
- final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { isSanitizerGuard(guard) }
+ final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
+ this.isSanitizerGuard(guard)
+ }
/**
* Holds if the additional taint propagation step from `node1` to `node2`
@@ -101,7 +103,7 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
- isAdditionalTaintStep(node1, node2) or
+ this.isAdditionalTaintStep(node1, node2) or
defaultAdditionalTaintStep(node1, node2)
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking3/TaintTrackingImpl.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking3/TaintTrackingImpl.qll
index f4f73b8247c..acb029c23d9 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking3/TaintTrackingImpl.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking3/TaintTrackingImpl.qll
@@ -75,24 +75,26 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isSanitizer(DataFlow::Node node) { none() }
final override predicate isBarrier(DataFlow::Node node) {
- isSanitizer(node) or
+ this.isSanitizer(node) or
defaultTaintSanitizer(node)
}
/** Holds if taint propagation into `node` is prohibited. */
predicate isSanitizerIn(DataFlow::Node node) { none() }
- final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) }
+ final override predicate isBarrierIn(DataFlow::Node node) { this.isSanitizerIn(node) }
/** Holds if taint propagation out of `node` is prohibited. */
predicate isSanitizerOut(DataFlow::Node node) { none() }
- final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) }
+ final override predicate isBarrierOut(DataFlow::Node node) { this.isSanitizerOut(node) }
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
- final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { isSanitizerGuard(guard) }
+ final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
+ this.isSanitizerGuard(guard)
+ }
/**
* Holds if the additional taint propagation step from `node1` to `node2`
@@ -101,7 +103,7 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
- isAdditionalTaintStep(node1, node2) or
+ this.isAdditionalTaintStep(node1, node2) or
defaultAdditionalTaintStep(node1, node2)
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking4/TaintTrackingImpl.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking4/TaintTrackingImpl.qll
index f4f73b8247c..acb029c23d9 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking4/TaintTrackingImpl.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking4/TaintTrackingImpl.qll
@@ -75,24 +75,26 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isSanitizer(DataFlow::Node node) { none() }
final override predicate isBarrier(DataFlow::Node node) {
- isSanitizer(node) or
+ this.isSanitizer(node) or
defaultTaintSanitizer(node)
}
/** Holds if taint propagation into `node` is prohibited. */
predicate isSanitizerIn(DataFlow::Node node) { none() }
- final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) }
+ final override predicate isBarrierIn(DataFlow::Node node) { this.isSanitizerIn(node) }
/** Holds if taint propagation out of `node` is prohibited. */
predicate isSanitizerOut(DataFlow::Node node) { none() }
- final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) }
+ final override predicate isBarrierOut(DataFlow::Node node) { this.isSanitizerOut(node) }
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
- final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { isSanitizerGuard(guard) }
+ final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
+ this.isSanitizerGuard(guard)
+ }
/**
* Holds if the additional taint propagation step from `node1` to `node2`
@@ -101,7 +103,7 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
- isAdditionalTaintStep(node1, node2) or
+ this.isAdditionalTaintStep(node1, node2) or
defaultAdditionalTaintStep(node1, node2)
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking5/TaintTrackingImpl.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking5/TaintTrackingImpl.qll
index f4f73b8247c..acb029c23d9 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking5/TaintTrackingImpl.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking5/TaintTrackingImpl.qll
@@ -75,24 +75,26 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isSanitizer(DataFlow::Node node) { none() }
final override predicate isBarrier(DataFlow::Node node) {
- isSanitizer(node) or
+ this.isSanitizer(node) or
defaultTaintSanitizer(node)
}
/** Holds if taint propagation into `node` is prohibited. */
predicate isSanitizerIn(DataFlow::Node node) { none() }
- final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) }
+ final override predicate isBarrierIn(DataFlow::Node node) { this.isSanitizerIn(node) }
/** Holds if taint propagation out of `node` is prohibited. */
predicate isSanitizerOut(DataFlow::Node node) { none() }
- final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) }
+ final override predicate isBarrierOut(DataFlow::Node node) { this.isSanitizerOut(node) }
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
- final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { isSanitizerGuard(guard) }
+ final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
+ this.isSanitizerGuard(guard)
+ }
/**
* Holds if the additional taint propagation step from `node1` to `node2`
@@ -101,7 +103,7 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
- isAdditionalTaintStep(node1, node2) or
+ this.isAdditionalTaintStep(node1, node2) or
defaultAdditionalTaintStep(node1, node2)
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dispatch/Dispatch.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dispatch/Dispatch.qll
index d0b4ef45ce8..509bdfb5e04 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dispatch/Dispatch.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dispatch/Dispatch.qll
@@ -11,10 +11,10 @@ private import RuntimeCallable
/** A call. */
class DispatchCall extends Internal::TDispatchCall {
/** Gets a textual representation of this call. */
- string toString() { result = getCall().toString() }
+ string toString() { result = this.getCall().toString() }
/** Gets the location of this call. */
- Location getLocation() { result = getCall().getLocation() }
+ Location getLocation() { result = this.getCall().getLocation() }
/** Gets the underlying expression of this call. */
Expr getCall() { result = Internal::getCall(this) }
@@ -209,7 +209,7 @@ private module Internal {
abstract Expr getArgument(int i);
/** Gets the number of arguments of this call. */
- int getNumberOfArguments() { result = count(int i | exists(getArgument(i))) }
+ int getNumberOfArguments() { result = count(int i | exists(this.getArgument(i))) }
/** Gets the qualifier of this call, if any. */
abstract Expr getQualifier();
@@ -506,12 +506,12 @@ private module Internal {
}
override RuntimeCallable getADynamicTarget() {
- result = getAViableInherited()
+ result = this.getAViableInherited()
or
- result = getAViableOverrider()
+ result = this.getAViableOverrider()
or
// Simple case: target method cannot be overridden
- result = getAStaticTarget() and
+ result = this.getAStaticTarget() and
not result instanceof OverridableCallable
}
@@ -779,9 +779,9 @@ private module Internal {
)
}
- override Expr getQualifier() { result = getCall().getQualifier() }
+ override Expr getQualifier() { result = this.getCall().getQualifier() }
- override Method getAStaticTarget() { result = getCall().getTarget() }
+ override Method getAStaticTarget() { result = this.getCall().getTarget() }
}
/**
@@ -793,24 +793,24 @@ private module Internal {
private class DispatchAccessorCall extends DispatchMethodOrAccessorCall, TDispatchAccessorCall {
override AccessorCall getCall() { this = TDispatchAccessorCall(result) }
- override Expr getArgument(int i) { result = getCall().getArgument(i) }
+ override Expr getArgument(int i) { result = this.getCall().getArgument(i) }
- override Expr getQualifier() { result = getCall().(MemberAccess).getQualifier() }
+ override Expr getQualifier() { result = this.getCall().(MemberAccess).getQualifier() }
- override Accessor getAStaticTarget() { result = getCall().getTarget() }
+ override Accessor getAStaticTarget() { result = this.getCall().getTarget() }
override RuntimeAccessor getADynamicTarget() {
result = DispatchMethodOrAccessorCall.super.getADynamicTarget() and
// Calls to accessors may have `dynamic` expression arguments,
// so we need to check that the types match
- forall(Type argumentType, int i | hasDynamicArg(i, argumentType) |
+ forall(Type argumentType, int i | this.hasDynamicArg(i, argumentType) |
argumentType.isImplicitlyConvertibleTo(result.getParameter(i).getType())
)
}
private predicate hasDynamicArg(int i, Type argumentType) {
exists(Expr argument |
- argument = getArgument(i) and
+ argument = this.getArgument(i) and
argument.stripImplicitCasts().getType() instanceof DynamicType and
argumentType = getAPossibleType(argument, _)
)
@@ -896,7 +896,7 @@ private module Internal {
// names and number of parameters. This set is further reduced in
// `getADynamicTarget()` by taking type information into account.
override Callable getAStaticTarget() {
- result = getACallableWithMatchingName() and
+ result = this.getACallableWithMatchingName() and
exists(int minArgs |
minArgs =
count(Parameter p |
@@ -904,16 +904,19 @@ private module Internal {
not p.hasDefaultValue() and
not p.isParams()
) and
- getNumberOfArguments() >= minArgs and
- (result.(Method).hasParams() or getNumberOfArguments() <= result.getNumberOfParameters())
+ this.getNumberOfArguments() >= minArgs and
+ (
+ result.(Method).hasParams() or
+ this.getNumberOfArguments() <= result.getNumberOfParameters()
+ )
)
}
private RuntimeCallable getACallableWithMatchingName() {
- result.(Operator).getFunctionName() = getName()
+ result.(Operator).getFunctionName() = this.getName()
or
not result instanceof Operator and
- result.getUndecoratedName() = getName()
+ result.getUndecoratedName() = this.getName()
}
// A callable is viable if the following conditions are all satisfied:
@@ -987,7 +990,7 @@ private module Internal {
* type of one of the arguments.
*/
RuntimeCallable getADynamicTargetCandidate() {
- result = getAStaticTarget() and
+ result = this.getAStaticTarget() and
(
result = getADynamicTargetCandidateInstanceMethod(this.getAQualifierType())
or
@@ -999,13 +1002,13 @@ private module Internal {
result instanceof RuntimeInstanceAccessor and
this.hasUnknownQualifierType()
or
- result = getADynamicTargetCandidateOperator()
+ result = this.getADynamicTargetCandidateOperator()
)
}
pragma[noinline]
private RuntimeOperator getADynamicTargetCandidateOperator() {
- result = getAStaticTarget() and
+ result = this.getAStaticTarget() and
result.getDeclaringType() = result.getAParameter().getType()
}
}
@@ -1138,8 +1141,8 @@ private module Internal {
result = DispatchReflectionOrDynamicCall.super.getADynamicTargetCandidate()
or
// Static callables can be called using reflection as well
- result = getAStaticTarget() and
- result.getDeclaringType() = getStaticType() and
+ result = this.getAStaticTarget() and
+ result.getDeclaringType() = this.getStaticType() and
result.(Modifiable).isStatic()
}
@@ -1147,7 +1150,7 @@ private module Internal {
override Expr getArgument(int i) {
exists(int args, ArrayCreation ac |
this = TDispatchReflectionCall(_, _, _, _, args) and
- ac = getAMethodCallArgSource(getCall().getArgument(args)) and
+ ac = getAMethodCallArgSource(this.getCall().getArgument(args)) and
result = ac.getInitializer().getElement(i)
)
}
@@ -1158,20 +1161,20 @@ private module Internal {
TDispatchDynamicMethodCall {
override DynamicMethodCall getCall() { this = TDispatchDynamicMethodCall(result) }
- override string getName() { result = getCall().getLateBoundTargetName() }
+ override string getName() { result = this.getCall().getLateBoundTargetName() }
- override Expr getQualifier() { result = getCall().getQualifier() }
+ override Expr getQualifier() { result = this.getCall().getQualifier() }
override RuntimeMethod getADynamicTargetCandidate() {
- if exists(getCall().getTarget())
+ if exists(this.getCall().getTarget())
then
// static method call
- result = getCall().getTarget()
+ result = this.getCall().getTarget()
else result = DispatchReflectionOrDynamicCall.super.getADynamicTargetCandidate()
}
// Does not take named arguments into account
- override Expr getArgument(int i) { result = getCall().getArgument(i) }
+ override Expr getArgument(int i) { result = this.getCall().getArgument(i) }
}
/** An operator call using dynamic types. */
@@ -1181,14 +1184,14 @@ private module Internal {
override string getName() {
exists(Operator o |
- o.getName() = getCall().getLateBoundTargetName() and
+ o.getName() = this.getCall().getLateBoundTargetName() and
result = o.getFunctionName()
)
}
override Expr getQualifier() { none() }
- override Expr getArgument(int i) { result = getCall().getArgument(i) }
+ override Expr getArgument(int i) { result = this.getCall().getArgument(i) }
}
/** A (potential) call to a property accessor using dynamic types. */
@@ -1255,7 +1258,7 @@ private module Internal {
any(DynamicMemberAccess dma | this = TDispatchDynamicEventAccess(_, dma, _)).getQualifier()
}
- override Expr getArgument(int i) { i = 0 and result = getCall().getRValue() }
+ override Expr getArgument(int i) { i = 0 and result = this.getCall().getRValue() }
}
/** A call to a constructor using dynamic types. */
@@ -1267,9 +1270,9 @@ private module Internal {
override Expr getQualifier() { none() }
- override Expr getArgument(int i) { result = getCall().getArgument(i) }
+ override Expr getArgument(int i) { result = this.getCall().getArgument(i) }
- override RuntimeCallable getADynamicTargetCandidate() { result = getCall().getTarget() }
+ override RuntimeCallable getADynamicTargetCandidate() { result = this.getCall().getTarget() }
}
/** A call where the target can be resolved statically. */
@@ -1285,8 +1288,8 @@ private module Internal {
)
}
- override Callable getAStaticTarget() { result = getCall().getTarget() }
+ override Callable getAStaticTarget() { result = this.getCall().getTarget() }
- override RuntimeCallable getADynamicTarget() { result = getCall().getTarget() }
+ override RuntimeCallable getADynamicTarget() { result = this.getCall().getTarget() }
}
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dispatch/OverridableCallable.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dispatch/OverridableCallable.qll
index 4913ea2bc3c..dc963881cbf 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dispatch/OverridableCallable.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/dispatch/OverridableCallable.qll
@@ -58,13 +58,13 @@ class OverridableCallable extends Callable {
* `I.M.getAnImplementorSubType(D) = C.M`.
*/
private Callable getAnImplementorSubType(ValueOrRefType t) {
- result = getAnImplementor(t)
+ result = this.getAnImplementor(t)
or
exists(ValueOrRefType mid |
- result = getAnImplementorSubType(mid) and
+ result = this.getAnImplementorSubType(mid) and
t.getBaseClass() = mid and
// There must be no other implementation of this callable in `t`
- forall(Callable other | other = getAnImplementor(t) | other = result)
+ forall(Callable other | other = this.getAnImplementor(t) | other = result)
)
}
@@ -107,8 +107,8 @@ class OverridableCallable extends Callable {
* implements this interface callable, if any.
*/
private Callable getAnOverridingImplementor() {
- result = getAnUltimateImplementor() and
- not result = getAnImplementor(_)
+ result = this.getAnUltimateImplementor() and
+ not result = this.getAnImplementor(_)
}
/**
@@ -150,10 +150,10 @@ class OverridableCallable extends Callable {
}
private Callable getInherited1(ValueOrRefType t) {
- result = getInherited0(t)
+ result = this.getInherited0(t)
or
// An interface implementation
- result = getAnImplementorSubType(t)
+ result = this.getAnImplementorSubType(t)
}
pragma[noinline]
@@ -171,7 +171,7 @@ class OverridableCallable extends Callable {
private predicate isDeclaringSubType(ValueOrRefType t) {
t = this.getDeclaringType()
or
- exists(ValueOrRefType mid | isDeclaringSubType(mid) | t = mid.getASubType())
+ exists(ValueOrRefType mid | this.isDeclaringSubType(mid) | t = mid.getASubType())
}
pragma[noinline]
@@ -232,7 +232,7 @@ class OverridableAccessor extends Accessor, OverridableCallable {
override Accessor getAnImplementor(ValueOrRefType t) {
exists(Virtualizable implementor, int kind |
- getAnImplementorAux(t, implementor, kind) and
+ this.getAnImplementorAux(t, implementor, kind) and
result.getDeclaration() = implementor and
getAccessorKind(result) = kind
)
@@ -241,7 +241,7 @@ class OverridableAccessor extends Accessor, OverridableCallable {
// predicate folding to get proper join order
private predicate getAnImplementorAux(ValueOrRefType t, Virtualizable implementor, int kind) {
exists(Virtualizable implementee |
- implementee = getDeclaration() and
+ implementee = this.getDeclaration() and
kind = getAccessorKind(this) and
implementor = implementee.getAnImplementor(t)
)
@@ -249,7 +249,7 @@ class OverridableAccessor extends Accessor, OverridableCallable {
override Accessor getAnUltimateImplementor() {
exists(Virtualizable implementor, int kind |
- getAnUltimateImplementorAux(implementor, kind) and
+ this.getAnUltimateImplementorAux(implementor, kind) and
result.getDeclaration() = implementor and
getAccessorKind(result) = kind
)
@@ -258,7 +258,7 @@ class OverridableAccessor extends Accessor, OverridableCallable {
// predicate folding to get proper join order
private predicate getAnUltimateImplementorAux(Virtualizable implementor, int kind) {
exists(Virtualizable implementee |
- implementee = getDeclaration() and
+ implementee = this.getDeclaration() and
kind = getAccessorKind(this) and
implementor = implementee.getAnUltimateImplementor()
)
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Access.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Access.qll
index ab3ea182056..9d7cf3a5867 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Access.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Access.qll
@@ -115,7 +115,7 @@ class MemberAccess extends Access, QualifiableExpr, @member_access_expr {
not exists(MemberInitializer mi | mi.getLValue() = this)
}
- override Member getQualifiedDeclaration() { result = getTarget() }
+ override Member getQualifiedDeclaration() { result = this.getTarget() }
override Member getTarget() { none() }
}
@@ -147,8 +147,8 @@ class AssignableAccess extends Access, @assignable_access_expr {
* or a `ref` argument in a method call.
*/
predicate isOutOrRefArgument() {
- isOutArgument() or
- isRefArgument()
+ this.isOutArgument() or
+ this.isRefArgument()
}
/**
@@ -507,7 +507,7 @@ class ElementAccess extends AssignableAccess, QualifiableExpr, @element_access_e
* Gets an index expression of this element access, for example
* `1` in `x[0, 1]`.
*/
- Expr getAnIndex() { result = getIndex(_) }
+ Expr getAnIndex() { result = this.getIndex(_) }
/**
* Gets the `i`th index expression of this element access, for example the
@@ -515,7 +515,7 @@ class ElementAccess extends AssignableAccess, QualifiableExpr, @element_access_e
*/
Expr getIndex(int i) { result = this.getChild(i) and i >= 0 }
- override Assignable getQualifiedDeclaration() { result = getTarget() }
+ override Assignable getQualifiedDeclaration() { result = this.getTarget() }
}
/**
@@ -615,7 +615,7 @@ class IndexerWrite extends IndexerAccess, ElementWrite { }
* ```
*/
class VirtualIndexerAccess extends IndexerAccess {
- VirtualIndexerAccess() { targetIsOverridableOrImplementable() }
+ VirtualIndexerAccess() { this.targetIsOverridableOrImplementable() }
}
/**
@@ -647,7 +647,7 @@ library class EventAccessExpr extends Expr, @event_access_expr {
* ```
*/
class EventAccess extends AssignableMemberAccess, EventAccessExpr {
- override Event getTarget() { result = getEvent() }
+ override Event getTarget() { result = this.getEvent() }
override string getAPrimaryQlClass() { result = "EventAccess" }
}
@@ -707,7 +707,7 @@ class EventWrite extends EventAccess, AssignableWrite { }
* ```
*/
class VirtualEventAccess extends EventAccess {
- VirtualEventAccess() { targetIsOverridableOrImplementable() }
+ VirtualEventAccess() { this.targetIsOverridableOrImplementable() }
}
/**
@@ -787,7 +787,7 @@ class LocalFunctionAccess extends CallableAccess {
* ```
*/
class VirtualMethodAccess extends MethodAccess {
- VirtualMethodAccess() { targetIsOverridableOrImplementable() }
+ VirtualMethodAccess() { this.targetIsOverridableOrImplementable() }
}
/**
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/ArithmeticOperation.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/ArithmeticOperation.qll
index ac98c0eafcf..f20bfba1589 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/ArithmeticOperation.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/ArithmeticOperation.qll
@@ -138,10 +138,10 @@ class DivExpr extends BinaryArithmeticOperation, @div_expr {
override string getOperator() { result = "/" }
/** Gets the numerator of this division operation. */
- Expr getNumerator() { result = getLeftOperand() }
+ Expr getNumerator() { result = this.getLeftOperand() }
/** Gets the denominator of this division operation. */
- Expr getDenominator() { result = getRightOperand() }
+ Expr getDenominator() { result = this.getRightOperand() }
override string getAPrimaryQlClass() { result = "DivExpr" }
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Assignment.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Assignment.qll
index 88ef770160a..562a4dd9cd5 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Assignment.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Assignment.qll
@@ -27,7 +27,7 @@ class Assignment extends Operation, @assign_expr {
Expr getRValue() { result = this.getChild(0) }
/** Gets the variable being assigned to, if any. */
- Variable getTargetVariable() { result.getAnAccess() = getLValue() }
+ Variable getTargetVariable() { result.getAnAccess() = this.getLValue() }
override string getOperator() { none() }
}
@@ -38,7 +38,7 @@ class Assignment extends Operation, @assign_expr {
class LocalVariableDeclAndInitExpr extends LocalVariableDeclExpr, Assignment {
override string getOperator() { result = "=" }
- override LocalVariable getTargetVariable() { result = getVariable() }
+ override LocalVariable getTargetVariable() { result = this.getVariable() }
override LocalVariableAccess getLValue() { result = Assignment.super.getLValue() }
@@ -86,7 +86,7 @@ class AssignOperation extends Assignment, @assign_op_expr {
* If an expanded version exists, then it is used in the control
* flow graph.
*/
- predicate hasExpandedAssignment() { exists(getExpandedAssignment()) }
+ predicate hasExpandedAssignment() { exists(this.getExpandedAssignment()) }
override string toString() { result = "... " + this.getOperator() + " ..." }
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Call.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Call.qll
index 6dc88e941ef..a4c4ab1b670 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Call.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Call.qll
@@ -47,7 +47,7 @@ class Call extends DotNet::Call, Expr, @call {
override Expr getRawArgument(int i) { result = this.getArgument(i) }
- override Expr getAnArgument() { result = getArgument(_) }
+ override Expr getAnArgument() { result = this.getArgument(_) }
/** Gets the number of arguments of this call. */
int getNumberOfArguments() { result = count(this.getAnArgument()) }
@@ -80,7 +80,7 @@ class Call extends DotNet::Call, Expr, @call {
*/
cached
override Expr getArgumentForParameter(DotNet::Parameter p) {
- getTarget().getAParameter() = p and
+ this.getTarget().getAParameter() = p and
(
// Appears in the positional part of the call
result = this.getImplicitArgument(p.getPosition()) and
@@ -94,7 +94,7 @@ class Call extends DotNet::Call, Expr, @call {
)
or
// Appears in the named part of the call
- result = getExplicitArgument(p.getName()) and
+ result = this.getExplicitArgument(p.getName()) and
(p.(Parameter).isParams() implies isValidExplicitParamsType(p, result.getType()))
)
}
@@ -112,13 +112,13 @@ class Call extends DotNet::Call, Expr, @call {
pragma[noinline]
private Expr getImplicitArgument(int pos) {
- result = getArgument(pos) and
+ result = this.getArgument(pos) and
not exists(result.getExplicitArgumentName())
}
pragma[nomagic]
private Expr getExplicitArgument(string name) {
- result = getAnArgument() and
+ result = this.getAnArgument() and
result.getExplicitArgumentName() = name
}
@@ -131,7 +131,7 @@ class Call extends DotNet::Call, Expr, @call {
*/
Expr getArgumentForName(string name) {
exists(Parameter p |
- result = getArgumentForParameter(p) and
+ result = this.getArgumentForParameter(p) and
p.hasName(name)
)
}
@@ -219,7 +219,7 @@ class Call extends DotNet::Call, Expr, @call {
*/
Expr getRuntimeArgumentForParameter(Parameter p) {
exists(Callable c |
- c = getARuntimeTarget() and
+ c = this.getARuntimeTarget() and
p = c.getAParameter() and
result = this.getRuntimeArgument(p.getPosition())
)
@@ -231,7 +231,7 @@ class Call extends DotNet::Call, Expr, @call {
*/
Expr getRuntimeArgumentForName(string name) {
exists(Parameter p |
- result = getRuntimeArgumentForParameter(p) and
+ result = this.getRuntimeArgumentForParameter(p) and
p.hasName(name)
)
}
@@ -240,19 +240,19 @@ class Call extends DotNet::Call, Expr, @call {
* Gets an argument that corresponds to a parameter of a potential
* run-time target of this call.
*/
- Expr getARuntimeArgument() { result = getRuntimeArgument(_) }
+ Expr getARuntimeArgument() { result = this.getRuntimeArgument(_) }
/**
* Gets the number of arguments that correspond to a parameter of a potential
* run-time target of this call.
*/
- int getNumberOfRuntimeArguments() { result = count(getARuntimeArgument()) }
+ int getNumberOfRuntimeArguments() { result = count(this.getARuntimeArgument()) }
/**
* Holds if this call has no arguments that correspond to a parameter of a
* potential (run-time) target of this call.
*/
- predicate hasNoRuntimeArguments() { not exists(getARuntimeArgument()) }
+ predicate hasNoRuntimeArguments() { not exists(this.getARuntimeArgument()) }
override string toString() { result = "call" }
}
@@ -295,19 +295,19 @@ private predicate isValidExplicitParamsType(Parameter p, Type t) {
class MethodCall extends Call, QualifiableExpr, LateBindableExpr, @method_invocation_expr {
override Method getTarget() { expr_call(this, result) }
- override Method getQualifiedDeclaration() { result = getTarget() }
+ override Method getQualifiedDeclaration() { result = this.getTarget() }
override string toString() { result = "call to method " + concat(this.getTarget().getName()) }
override string getAPrimaryQlClass() { result = "MethodCall" }
override Expr getRawArgument(int i) {
- if exists(getQualifier())
+ if exists(this.getQualifier())
then
- i = 0 and result = getQualifier()
+ i = 0 and result = this.getQualifier()
or
- result = getArgument(i - 1)
- else result = getArgument(i)
+ result = this.getArgument(i - 1)
+ else result = this.getArgument(i)
}
}
@@ -336,7 +336,7 @@ class ExtensionMethodCall extends MethodCall {
override Expr getArgument(int i) {
exists(int j | result = this.getChildExpr(j) |
- if isOrdinaryStaticCall() then (j = i and j >= 0) else (j = i - 1 and j >= -1)
+ if this.isOrdinaryStaticCall() then (j = i and j >= 0) else (j = i - 1 and j >= -1)
)
}
@@ -379,8 +379,8 @@ class ExtensionMethodCall extends MethodCall {
*/
class VirtualMethodCall extends MethodCall {
VirtualMethodCall() {
- not getQualifier() instanceof BaseAccess and
- getTarget().isOverridableOrImplementable()
+ not this.getQualifier() instanceof BaseAccess and
+ this.getTarget().isOverridableOrImplementable()
}
}
@@ -573,7 +573,7 @@ class DelegateLikeCall extends Call, DelegateLikeCall_ {
)
}
- override Expr getRuntimeArgument(int i) { result = getArgument(i) }
+ override Expr getRuntimeArgument(int i) { result = this.getArgument(i) }
}
/**
@@ -618,11 +618,11 @@ class DelegateCall extends DelegateLikeCall, @delegate_invocation_expr {
}
deprecated private AddEventSource getAnAddEventSourceSameEnclosingCallable() {
- result = getAnAddEventSource(this.getEnclosingCallable())
+ result = this.getAnAddEventSource(this.getEnclosingCallable())
}
deprecated private AddEventSource getAnAddEventSourceDifferentEnclosingCallable() {
- exists(Callable c | result = getAnAddEventSource(c) | c != this.getEnclosingCallable())
+ exists(Callable c | result = this.getAnAddEventSource(c) | c != this.getEnclosingCallable())
}
/**
@@ -683,7 +683,7 @@ class AccessorCall extends Call, QualifiableExpr, @call_access_expr {
*/
class PropertyCall extends AccessorCall, PropertyAccessExpr {
override Accessor getTarget() {
- exists(PropertyAccess pa, Property p | pa = this and p = getProperty() |
+ exists(PropertyAccess pa, Property p | pa = this and p = this.getProperty() |
pa instanceof AssignableRead and result = p.getGetter()
or
pa instanceof AssignableWrite and result = p.getSetter()
@@ -718,7 +718,7 @@ class PropertyCall extends AccessorCall, PropertyAccessExpr {
*/
class IndexerCall extends AccessorCall, IndexerAccessExpr {
override Accessor getTarget() {
- exists(IndexerAccess ia, Indexer i | ia = this and i = getIndexer() |
+ exists(IndexerAccess ia, Indexer i | ia = this and i = this.getIndexer() |
ia instanceof AssignableRead and result = i.getGetter()
or
ia instanceof AssignableWrite and result = i.getSetter()
@@ -761,7 +761,7 @@ class IndexerCall extends AccessorCall, IndexerAccessExpr {
class EventCall extends AccessorCall, EventAccessExpr {
override EventAccessor getTarget() {
exists(Event e, AddOrRemoveEventExpr aoree |
- e = getEvent() and
+ e = this.getEvent() and
aoree.getLValue() = this
|
aoree instanceof AddEventExpr and result = e.getAddEventAccessor()
@@ -799,7 +799,7 @@ class EventCall extends AccessorCall, EventAccessExpr {
class LocalFunctionCall extends Call, @local_function_invocation_expr {
override LocalFunction getTarget() { expr_call(this, result) }
- override string toString() { result = "call to local function " + getTarget().getName() }
+ override string toString() { result = "call to local function " + this.getTarget().getName() }
override string getAPrimaryQlClass() { result = "LocalFunctionCall" }
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Creation.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Creation.qll
index c9ae3919004..84bcf7b87bc 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Creation.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Creation.qll
@@ -45,7 +45,7 @@ class ObjectInitializer extends ObjectOrCollectionInitializer, @object_init_expr
* }
* ```
*/
- MemberInitializer getAMemberInitializer() { result = getMemberInitializer(_) }
+ MemberInitializer getAMemberInitializer() { result = this.getMemberInitializer(_) }
/**
* Gets the `i`th member initializer of this object initializer. For example,
@@ -122,7 +122,7 @@ class CollectionInitializer extends ObjectOrCollectionInitializer, @collection_i
* };
* ```
*/
- ElementInitializer getAnElementInitializer() { result = getElementInitializer(_) }
+ ElementInitializer getAnElementInitializer() { result = this.getElementInitializer(_) }
/**
* Gets the `i`th element initializer of this collection initializer, for
@@ -180,7 +180,7 @@ class ElementInitializer extends MethodCall {
*/
class ObjectCreation extends Call, LateBindableExpr, @object_creation_expr {
/** Gets the type of the newly created object. */
- ValueOrRefType getObjectType() { result = getType() }
+ ValueOrRefType getObjectType() { result = this.getType() }
override Constructor getTarget() { expr_call(this, result) }
@@ -320,7 +320,7 @@ class ArrayInitializer extends Expr, @array_init_expr {
* };
* ```
*/
- Expr getAnElement() { result = getElement(_) }
+ Expr getAnElement() { result = this.getElement(_) }
/**
* Gets the `i`th element of this array initializer, for example the second
@@ -365,7 +365,7 @@ class ArrayCreation extends Expr, @array_creation_expr {
* new int[5, 10]
* ```
*/
- Expr getALengthArgument() { result = getLengthArgument(_) }
+ Expr getALengthArgument() { result = this.getLengthArgument(_) }
/**
* Gets the `i`th dimension's length argument of this array creation, for
@@ -427,7 +427,7 @@ class AnonymousFunctionExpr extends Expr, Callable, Modifiable, @anonymous_funct
override string toString() { result = Expr.super.toString() }
- override string toStringWithTypes() { result = toString() }
+ override string toStringWithTypes() { result = this.toString() }
}
/**
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Dynamic.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Dynamic.qll
index ea6012ca3e1..eda31432f38 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Dynamic.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Dynamic.qll
@@ -15,7 +15,7 @@ private import semmle.code.csharp.dispatch.Dispatch
* (`DynamicAccessorCall`), or a dynamic element access (`DynamicElementAccess`).
*/
class DynamicExpr extends LateBindableExpr {
- DynamicExpr() { isLateBound() }
+ DynamicExpr() { this.isLateBound() }
}
/**
@@ -67,7 +67,7 @@ class DynamicObjectCreation extends DynamicExpr, ObjectCreation {
* may not be known at compile-time (as in the example above).
*/
class DynamicMethodCall extends DynamicExpr, MethodCall {
- override string toString() { result = "dynamic call to method " + getLateBoundTargetName() }
+ override string toString() { result = "dynamic call to method " + this.getLateBoundTargetName() }
override string getAPrimaryQlClass() { result = "DynamicMethodCall" }
}
@@ -97,7 +97,9 @@ class DynamicMethodCall extends DynamicExpr, MethodCall {
* target operator may not be known at compile-time (as in the example above).
*/
class DynamicOperatorCall extends DynamicExpr, OperatorCall {
- override string toString() { result = "dynamic call to operator " + getLateBoundTargetName() }
+ override string toString() {
+ result = "dynamic call to operator " + this.getLateBoundTargetName()
+ }
override string getAPrimaryQlClass() { result = "DynamicOperatorCall" }
}
@@ -189,7 +191,9 @@ class DynamicAccess extends DynamicExpr {
*/
class DynamicMemberAccess extends DynamicAccess, MemberAccess, AssignableAccess,
@dynamic_member_access_expr {
- override string toString() { result = "dynamic access to member " + getLateBoundTargetName() }
+ override string toString() {
+ result = "dynamic access to member " + this.getLateBoundTargetName()
+ }
override string getAPrimaryQlClass() { result = "DynamicMemberAccess" }
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Expr.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Expr.qll
index 0988bb84340..47477afe2b9 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Expr.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Expr.qll
@@ -103,7 +103,7 @@ class Expr extends DotNet::Expr, ControlFlowElement, @expr {
class LateBindableExpr extends Expr, @late_bindable_expr {
/** Holds if this expression is late bound. */
predicate isLateBound() {
- exists(getLateBoundTargetName()) or
+ exists(this.getLateBoundTargetName()) or
isDynamicMemberAccess(this) or
isDynamicElementAccess(this)
}
@@ -221,9 +221,9 @@ class BinaryOperation extends Operation, @bin_op {
/** Gets the other operand of this binary operation, given operand `o`. */
Expr getOtherOperand(Expr o) {
- o = getLeftOperand() and result = getRightOperand()
+ o = this.getLeftOperand() and result = this.getRightOperand()
or
- o = getRightOperand() and result = getLeftOperand()
+ o = this.getRightOperand() and result = this.getLeftOperand()
}
override string getOperator() { none() }
@@ -368,7 +368,7 @@ class RelationalPatternExpr extends PatternExpr, @relational_pattern_expr {
/** Gets the expression of this relational pattern. */
Expr getExpr() { result = this.getChild(0) }
- override string toString() { result = getOperator() + " ..." }
+ override string toString() { result = this.getOperator() + " ..." }
}
/** A less-than pattern, for example `< 10` in `x is < 10`. */
@@ -520,7 +520,7 @@ class NotPatternExpr extends UnaryPatternExpr, @not_pattern_expr {
/** A binary pattern. For example, `1 or 2`. */
class BinaryPatternExpr extends PatternExpr, @binary_pattern_expr {
/** Gets a pattern. */
- PatternExpr getAnOperand() { result = getLeftOperand() or result = getRightOperand() }
+ PatternExpr getAnOperand() { result = this.getLeftOperand() or result = this.getRightOperand() }
/** Gets the left pattern. */
PatternExpr getLeftOperand() { result = this.getChild(0) }
@@ -743,7 +743,7 @@ class DefaultValueExpr extends Expr, @default_expr {
TypeAccess getTypeAccess() { result = this.getChild(0) }
override string toString() {
- if exists(getTypeAccess()) then result = "default(...)" else result = "default"
+ if exists(this.getTypeAccess()) then result = "default(...)" else result = "default"
}
override string getAPrimaryQlClass() { result = "DefaultValueExpr" }
@@ -757,7 +757,7 @@ class SizeofExpr extends UnaryOperation, @sizeof_expr {
* Gets the type access in this `sizeof` expression, for example `int` in
* `sizeof(int)`.
*/
- TypeAccess getTypeAccess() { result = getOperand() }
+ TypeAccess getTypeAccess() { result = this.getOperand() }
override string getOperator() { result = "sizeof(..)" }
@@ -830,7 +830,7 @@ class AddressOfExpr extends UnaryOperation, @address_of_expr {
*/
class AwaitExpr extends Expr, @await_expr {
/** Gets the expression being awaited. */
- Expr getExpr() { result = getChild(0) }
+ Expr getExpr() { result = this.getChild(0) }
override string toString() { result = "await ..." }
@@ -881,7 +881,7 @@ class InterpolatedStringExpr extends Expr, @interpolated_string_expr {
* element (`getText(0)` gets the text).
*/
Expr getInsert(int i) {
- result = getChild(i) and
+ result = this.getChild(i) and
not result instanceof StringLiteral
}
@@ -891,13 +891,13 @@ class InterpolatedStringExpr extends Expr, @interpolated_string_expr {
* `$"Hello, {name}!"`. Note that there is no text element at index `i = 1`,
* but instead an insert (`getInsert(1)` gets the insert).
*/
- StringLiteral getText(int i) { result = getChild(i) }
+ StringLiteral getText(int i) { result = this.getChild(i) }
/** Gets an insert in this interpolated string. */
- Expr getAnInsert() { result = getInsert(_) }
+ Expr getAnInsert() { result = this.getInsert(_) }
/** Gets a text element in this interpolated string. */
- StringLiteral getAText() { result = getText(_) }
+ StringLiteral getAText() { result = this.getText(_) }
}
/**
@@ -914,7 +914,7 @@ class ThrowElement extends ControlFlowElement, DotNet::Throw, @throw_element {
/** Gets the type of exception being thrown. */
Class getThrownExceptionType() {
- result = getExpr().getType()
+ result = this.getExpr().getType()
or
// Corner case: `throw null`
this.getExpr().getType() instanceof NullType and
@@ -958,7 +958,7 @@ class QualifiableExpr extends Expr, @qualifiable_expr {
Expr getQualifier() { result = this.getChildExpr(-1) }
/** Holds if this expression is qualified. */
- final predicate hasQualifier() { exists(getQualifier()) }
+ final predicate hasQualifier() { exists(this.getQualifier()) }
/** Holds if this expression has an implicit `this` qualifier. */
predicate hasImplicitThisQualifier() { this.getQualifier().(ThisAccess).isImplicit() }
@@ -1029,10 +1029,10 @@ class TupleExpr extends Expr, @tuple_expr {
override string toString() { result = "(..., ...)" }
/** Gets the `i`th argument of this tuple. */
- Expr getArgument(int i) { result = getChild(i) }
+ Expr getArgument(int i) { result = this.getChild(i) }
/** Gets an argument of this tuple. */
- Expr getAnArgument() { result = getArgument(_) }
+ Expr getAnArgument() { result = this.getArgument(_) }
/** Holds if this tuple is a read access. */
deprecated predicate isReadAccess() { not this = getAnAssignOrForeachChild() }
@@ -1057,11 +1057,11 @@ class TupleExpr extends Expr, @tuple_expr {
*/
class RefExpr extends Expr, @ref_expr {
/** Gets the expression being referenced. */
- Expr getExpr() { result = getChild(0) }
+ Expr getExpr() { result = this.getChild(0) }
override string toString() { result = "ref ..." }
- override Type getType() { result = getExpr().getType() }
+ override Type getType() { result = this.getExpr().getType() }
override string getAPrimaryQlClass() { result = "RefExpr" }
}
@@ -1154,7 +1154,7 @@ class DefineSymbolExpr extends Expr, @define_symbol_expr {
/** Gets the name of the symbol. */
string getName() { directive_define_symbols(this, result) }
- override string toString() { result = getName() }
+ override string toString() { result = this.getName() }
override string getAPrimaryQlClass() { result = "DefineSymbolExpr" }
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Literal.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Literal.qll
index 0ca9f6d0db0..842e27fb96b 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Literal.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/exprs/Literal.qll
@@ -23,9 +23,9 @@ class Literal extends DotNet::Literal, Expr, @literal_expr {
class BoolLiteral extends Literal, @bool_literal_expr {
/** Gets the value of this Boolean literal. */
boolean getBoolValue() {
- getValue() = "true" and result = true
+ this.getValue() = "true" and result = true
or
- getValue() = "false" and result = false
+ this.getValue() = "false" and result = false
}
override string getAPrimaryQlClass() { result = "BoolLiteral" }
@@ -105,7 +105,7 @@ class DecimalLiteral extends RealLiteral, @decimal_literal_expr {
* A `string` literal, for example `"Hello, World!"`.
*/
class StringLiteral extends DotNet::StringLiteral, Literal, @string_literal_expr {
- override string toString() { result = "\"" + getValue().replaceAll("\"", "\\\"") + "\"" }
+ override string toString() { result = "\"" + this.getValue().replaceAll("\"", "\\\"") + "\"" }
override string getAPrimaryQlClass() { result = "StringLiteral" }
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/EntityFramework.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/EntityFramework.qll
index 36882d3b12e..723832906c6 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/EntityFramework.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/EntityFramework.qll
@@ -239,7 +239,7 @@ module EntityFramework {
private class SystemDataEntityDbSetSqlQuerySinkModelCsv extends SinkModelCsv {
override predicate row(string row) {
row =
- ["System.Data.Entity;DbSet;false;SqlQuery;(System.String,System.Object[]);;Argument[0];sql"]
+ "System.Data.Entity;DbSet;false;SqlQuery;(System.String,System.Object[]);;Argument[0];sql"
}
}
@@ -317,7 +317,7 @@ module EntityFramework {
dist = 0
)
or
- step(_, _, c1, t1, dist - 1) and
+ this.step(_, _, c1, t1, dist - 1) and
dist < DataFlowPrivate::accessPathLimit() - 1 and
not isNotMapped(t2) and
(
@@ -374,11 +374,11 @@ module EntityFramework {
}
private predicate stepRev(Content c1, Type t1, Content c2, Type t2, int dist) {
- step(c1, t1, c2, t2, dist) and
- c2.(PropertyContent).getProperty() = getAColumnProperty(dist)
+ this.step(c1, t1, c2, t2, dist) and
+ c2.(PropertyContent).getProperty() = this.getAColumnProperty(dist)
or
- stepRev(c2, t2, _, _, dist + 1) and
- step(c1, t1, c2, t2, dist)
+ this.stepRev(c2, t2, _, _, dist + 1) and
+ this.step(c1, t1, c2, t2, dist)
}
/** Gets a `SaveChanges[Async]` method. */
@@ -453,8 +453,8 @@ module EntityFramework {
) {
exists(Property mapped |
preservesValue = true and
- input(input, mapped) and
- output(output, mapped)
+ this.input(input, mapped) and
+ this.output(output, mapped)
)
}
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/Format.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/Format.qll
index 3c659d86d46..2ffbfe6e7e1 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/Format.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/Format.qll
@@ -155,13 +155,13 @@ class InvalidFormatString extends StringLiteral {
int oldstartcolumn, int padding
|
this.getLocation().hasLocationInfo(filepath, startline, oldstartcolumn, endline, endcolumn) and
- startcolumn = padding + oldstartcolumn + getInvalidOffset() and
+ startcolumn = padding + oldstartcolumn + this.getInvalidOffset() and
toUrl(filepath, startline, startcolumn, endline, endcolumn, result)
|
// Single-line string literal beginning " or @"
// Figure out the correct indent.
startline = endline and
- padding = endcolumn - oldstartcolumn - getValue().length()
+ padding = endcolumn - oldstartcolumn - this.getValue().length()
or
// Multi-line literal beginning @"
startline != endline and
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/JsonNET.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/JsonNET.qll
index abd820bdfe4..c0c1a765469 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/JsonNET.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/JsonNET.qll
@@ -62,25 +62,25 @@ module JsonNET {
boolean preservesValue
) {
// ToString methods
- c = getAToStringMethod() and
+ c = this.getAToStringMethod() and
preservesValue = false and
source = any(CallableFlowSourceArg arg | arg.getArgumentIndex() = 0) and
sink instanceof CallableFlowSinkReturn
or
// Deserialize methods
- c = getADeserializeMethod() and
+ c = this.getADeserializeMethod() and
preservesValue = false and
source = any(CallableFlowSourceArg arg | arg.getArgumentIndex() = 0) and
sink instanceof CallableFlowSinkReturn
or
// Serialize methods
- c = getASerializeMethod() and
+ c = this.getASerializeMethod() and
preservesValue = false and
source = any(CallableFlowSourceArg arg | arg.getArgumentIndex() = 0) and
sink instanceof CallableFlowSinkReturn
or
// Populate methods
- c = getAPopulateMethod() and
+ c = this.getAPopulateMethod() and
preservesValue = false and
source = any(CallableFlowSourceArg arg | arg.getArgumentIndex() = 0) and
sink = any(CallableFlowSinkArg arg | arg.getArgumentIndex() = 1)
@@ -120,21 +120,13 @@ module JsonNET {
SerializedMember() {
// This member has a Json attribute
exists(Class attribute | attribute = this.getAnAttribute().getType() |
- attribute.hasName("JsonPropertyAttribute")
- or
- attribute.hasName("JsonDictionaryAttribute")
- or
- attribute.hasName("JsonRequiredAttribute")
- or
- attribute.hasName("JsonArrayAttribute")
- or
- attribute.hasName("JsonConverterAttribute")
- or
- attribute.hasName("JsonExtensionDataAttribute")
- or
- attribute.hasName("SerializableAttribute") // System.SerializableAttribute
- or
- attribute.hasName("DataMemberAttribute") // System.DataMemberAttribute
+ attribute
+ .hasName([
+ "JsonPropertyAttribute", "JsonDictionaryAttribute", "JsonRequiredAttribute",
+ "JsonArrayAttribute", "JsonConverterAttribute", "JsonExtensionDataAttribute",
+ "SerializableAttribute", // System.SerializableAttribute
+ "DataMemberAttribute" // System.DataMemberAttribute
+ ])
)
or
// This field is a member of an explicitly serialized type
@@ -175,7 +167,7 @@ module JsonNET {
/** Any attribute class that marks a member to not be serialized. */
private class NotSerializedAttributeClass extends JsonClass {
NotSerializedAttributeClass() {
- this.hasName("JsonIgnoreAttribute") or this.hasName("NonSerializedAttribute")
+ this.hasName(["JsonIgnoreAttribute", "NonSerializedAttribute"])
}
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/Moq.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/Moq.qll
index 92811122696..e0705ac7d98 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/Moq.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/Moq.qll
@@ -21,7 +21,7 @@ class ReturnsMethod extends Method {
*/
Expr getAReturnedExpr() {
exists(MethodCall mc, Expr arg |
- mc = getACall() and
+ mc = this.getACall() and
arg = mc.getArgument(0)
|
if arg instanceof LambdaExpr then arg.(LambdaExpr).canReturn(result) else result = arg
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/NHibernate.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/NHibernate.qll
index ddf7fd410bf..6ce6efb815f 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/NHibernate.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/NHibernate.qll
@@ -28,15 +28,7 @@ module NHibernate {
/** Gets a type parameter that specifies a mapped class. */
TypeParameter getAMappedObjectTp() {
- exists(string methodName |
- methodName = "Load<>"
- or
- methodName = "Merge<>"
- or
- methodName = "Get<>"
- or
- methodName = "Query<>"
- |
+ exists(string methodName | methodName = ["Load<>", "Merge<>", "Get<>", "Query<>"] |
result = this.getAMethod(methodName).(UnboundGenericMethod).getTypeParameter(0)
)
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/System.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/System.qll
index 20ab350ffd4..e33004f109d 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/System.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/System.qll
@@ -157,7 +157,7 @@ class SystemIComparableTInterface extends SystemUnboundGenericInterface {
result.getDeclaringType() = this and
result.hasName("CompareTo") and
result.getNumberOfParameters() = 1 and
- result.getParameter(0).getType() = getTypeParameter(0) and
+ result.getParameter(0).getType() = this.getTypeParameter(0) and
result.getReturnType() instanceof IntType
}
}
@@ -171,7 +171,7 @@ class SystemIEquatableTInterface extends SystemUnboundGenericInterface {
result.getDeclaringType() = this and
result.hasName("Equals") and
result.getNumberOfParameters() = 1 and
- result.getParameter(0).getType() = getTypeParameter(0) and
+ result.getParameter(0).getType() = this.getTypeParameter(0) and
result.getReturnType() instanceof BoolType
}
}
@@ -239,7 +239,7 @@ class SystemLazyClass extends SystemUnboundGenericClass {
Property getValueProperty() {
result.getDeclaringType() = this and
result.hasName("Value") and
- result.getType() = getTypeParameter(0)
+ result.getType() = this.getTypeParameter(0)
}
}
@@ -254,7 +254,7 @@ class SystemNullableStruct extends SystemUnboundGenericStruct {
Property getValueProperty() {
result.getDeclaringType() = this and
result.hasName("Value") and
- result.getType() = getTypeParameter(0)
+ result.getType() = this.getTypeParameter(0)
}
/** Gets the `HasValue` property. */
@@ -268,7 +268,7 @@ class SystemNullableStruct extends SystemUnboundGenericStruct {
Method getAGetValueOrDefaultMethod() {
result.getDeclaringType() = this and
result.hasName("GetValueOrDefault") and
- result.getReturnType() = getTypeParameter(0)
+ result.getReturnType() = this.getTypeParameter(0)
}
}
@@ -588,7 +588,7 @@ class IEquatableEqualsMethod extends Method {
m = any(SystemIEquatableTInterface i).getAConstructedGeneric().getAMethod() and
m.getUnboundDeclaration() = any(SystemIEquatableTInterface i).getEqualsMethod()
|
- this = m or getAnUltimateImplementee() = m
+ this = m or this.getAnUltimateImplementee() = m
)
}
}
@@ -677,7 +677,7 @@ class DisposeMethod extends Method {
/** A method with the signature `void Dispose(bool)`. */
library class DisposeBoolMethod extends Method {
DisposeBoolMethod() {
- hasName("Dispose") and
+ this.hasName("Dispose") and
this.getReturnType() instanceof VoidType and
this.getNumberOfParameters() = 1 and
this.getParameter(0).getType() instanceof BoolType
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/WCF.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/WCF.qll
index 655648d88c9..befb5f3ae1f 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/WCF.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/WCF.qll
@@ -49,7 +49,7 @@ class OperationMethod extends Method {
i.getAnAttribute() instanceof ServiceContractAttribute and
m.getDeclaringType() = i and
m.getAnAttribute() instanceof OperationContractAttribute and
- getImplementee() = m
+ this.getImplementee() = m
)
}
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/microsoft/AspNetCore.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/microsoft/AspNetCore.qll
index 5fe6665bd47..a918b603818 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/microsoft/AspNetCore.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/microsoft/AspNetCore.qll
@@ -6,129 +6,133 @@ import semmle.code.csharp.frameworks.Microsoft
/** The `Microsoft.AspNetCore` namespace. */
class MicrosoftAspNetCoreNamespace extends Namespace {
MicrosoftAspNetCoreNamespace() {
- getParentNamespace() instanceof MicrosoftNamespace and
- hasName("AspNetCore")
+ this.getParentNamespace() instanceof MicrosoftNamespace and
+ this.hasName("AspNetCore")
}
}
/** The `Microsoft.AspNetCore.Mvc` namespace. */
class MicrosoftAspNetCoreMvcNamespace extends Namespace {
MicrosoftAspNetCoreMvcNamespace() {
- getParentNamespace() instanceof MicrosoftAspNetCoreNamespace and
- hasName("Mvc")
+ this.getParentNamespace() instanceof MicrosoftAspNetCoreNamespace and
+ this.hasName("Mvc")
}
}
/** The 'Microsoft.AspNetCore.Mvc.ViewFeatures' namespace. */
class MicrosoftAspNetCoreMvcViewFeatures extends Namespace {
MicrosoftAspNetCoreMvcViewFeatures() {
- getParentNamespace() instanceof MicrosoftAspNetCoreMvcNamespace and
- hasName("ViewFeatures")
+ this.getParentNamespace() instanceof MicrosoftAspNetCoreMvcNamespace and
+ this.hasName("ViewFeatures")
}
}
/** The 'Microsoft.AspNetCore.Mvc.Rendering' namespace. */
class MicrosoftAspNetCoreMvcRendering extends Namespace {
MicrosoftAspNetCoreMvcRendering() {
- getParentNamespace() instanceof MicrosoftAspNetCoreMvcNamespace and
- hasName("Rendering")
+ this.getParentNamespace() instanceof MicrosoftAspNetCoreMvcNamespace and
+ this.hasName("Rendering")
}
}
/** An attribute whose type is in the `Microsoft.AspNetCore.Mvc` namespace. */
class MicrosoftAspNetCoreMvcAttribute extends Attribute {
MicrosoftAspNetCoreMvcAttribute() {
- getType().getNamespace() instanceof MicrosoftAspNetCoreMvcNamespace
+ this.getType().getNamespace() instanceof MicrosoftAspNetCoreMvcNamespace
}
}
/** A `Microsoft.AspNetCore.Mvc.HttpPost` attribute. */
class MicrosoftAspNetCoreMvcHttpPostAttribute extends MicrosoftAspNetCoreMvcAttribute {
- MicrosoftAspNetCoreMvcHttpPostAttribute() { getType().hasName("HttpPostAttribute") }
+ MicrosoftAspNetCoreMvcHttpPostAttribute() { this.getType().hasName("HttpPostAttribute") }
}
/** A `Microsoft.AspNetCore.Mvc.HttpPut` attribute. */
class MicrosoftAspNetCoreMvcHttpPutAttribute extends MicrosoftAspNetCoreMvcAttribute {
- MicrosoftAspNetCoreMvcHttpPutAttribute() { getType().hasName("HttpPutAttribute") }
+ MicrosoftAspNetCoreMvcHttpPutAttribute() { this.getType().hasName("HttpPutAttribute") }
}
/** A `Microsoft.AspNetCore.Mvc.HttpDelete` attribute. */
class MicrosoftAspNetCoreMvcHttpDeleteAttribute extends MicrosoftAspNetCoreMvcAttribute {
- MicrosoftAspNetCoreMvcHttpDeleteAttribute() { getType().hasName("HttpDeleteAttribute") }
+ MicrosoftAspNetCoreMvcHttpDeleteAttribute() { this.getType().hasName("HttpDeleteAttribute") }
}
/** A `Microsoft.AspNetCore.Mvc.NonAction` attribute. */
class MicrosoftAspNetCoreMvcNonActionAttribute extends MicrosoftAspNetCoreMvcAttribute {
- MicrosoftAspNetCoreMvcNonActionAttribute() { getType().hasName("NonActionAttribute") }
+ MicrosoftAspNetCoreMvcNonActionAttribute() { this.getType().hasName("NonActionAttribute") }
}
/** The `Microsoft.AspNetCore.Antiforgery` namespace. */
class MicrosoftAspNetCoreAntiforgeryNamespace extends Namespace {
MicrosoftAspNetCoreAntiforgeryNamespace() {
- getParentNamespace() instanceof MicrosoftAspNetCoreNamespace and
- hasName("Antiforgery")
+ this.getParentNamespace() instanceof MicrosoftAspNetCoreNamespace and
+ this.hasName("Antiforgery")
}
}
/** The `Microsoft.AspNetCore.Mvc.Filters` namespace. */
class MicrosoftAspNetCoreMvcFilters extends Namespace {
MicrosoftAspNetCoreMvcFilters() {
- getParentNamespace() instanceof MicrosoftAspNetCoreMvcNamespace and
- hasName("Filters")
+ this.getParentNamespace() instanceof MicrosoftAspNetCoreMvcNamespace and
+ this.hasName("Filters")
}
}
/** The `Microsoft.AspNetCore.Mvc.Filters.IFilterMetadataInterface` interface. */
class MicrosoftAspNetCoreMvcIFilterMetadataInterface extends Interface {
MicrosoftAspNetCoreMvcIFilterMetadataInterface() {
- getNamespace() instanceof MicrosoftAspNetCoreMvcFilters and
- hasName("IFilterMetadata")
+ this.getNamespace() instanceof MicrosoftAspNetCoreMvcFilters and
+ this.hasName("IFilterMetadata")
}
}
/** The `Microsoft.AspNetCore.IAuthorizationFilter` interface. */
class MicrosoftAspNetCoreIAuthorizationFilterInterface extends Interface {
MicrosoftAspNetCoreIAuthorizationFilterInterface() {
- getNamespace() instanceof MicrosoftAspNetCoreMvcFilters and
- hasName("IAsyncAuthorizationFilter")
+ this.getNamespace() instanceof MicrosoftAspNetCoreMvcFilters and
+ this.hasName("IAsyncAuthorizationFilter")
}
/** Gets the `OnAuthorizationAsync` method. */
- Method getOnAuthorizationMethod() { result = getAMethod("OnAuthorizationAsync") }
+ Method getOnAuthorizationMethod() { result = this.getAMethod("OnAuthorizationAsync") }
}
/** The `Microsoft.AspNetCore.IAntiforgery` interface. */
class MicrosoftAspNetCoreIAntiForgeryInterface extends Interface {
MicrosoftAspNetCoreIAntiForgeryInterface() {
- getNamespace() instanceof MicrosoftAspNetCoreAntiforgeryNamespace and
- hasName("IAntiforgery")
+ this.getNamespace() instanceof MicrosoftAspNetCoreAntiforgeryNamespace and
+ this.hasName("IAntiforgery")
}
/** Gets the `ValidateRequestAsync` method. */
- Method getValidateMethod() { result = getAMethod("ValidateRequestAsync") }
+ Method getValidateMethod() { result = this.getAMethod("ValidateRequestAsync") }
}
/** The `Microsoft.AspNetCore.DefaultAntiForgery` class, or another user-supplied class that implements `IAntiForgery`. */
class AntiForgeryClass extends Class {
- AntiForgeryClass() { getABaseInterface*() instanceof MicrosoftAspNetCoreIAntiForgeryInterface }
+ AntiForgeryClass() {
+ this.getABaseInterface*() instanceof MicrosoftAspNetCoreIAntiForgeryInterface
+ }
/** Gets the `ValidateRequestAsync` method. */
- Method getValidateMethod() { result = getAMethod("ValidateRequestAsync") }
+ Method getValidateMethod() { result = this.getAMethod("ValidateRequestAsync") }
}
/** An authorization filter class defined by AspNetCore or the user. */
class AuthorizationFilterClass extends Class {
AuthorizationFilterClass() {
- getABaseInterface*() instanceof MicrosoftAspNetCoreIAuthorizationFilterInterface
+ this.getABaseInterface*() instanceof MicrosoftAspNetCoreIAuthorizationFilterInterface
}
/** Gets the `OnAuthorization` method provided by this filter. */
- Method getOnAuthorizationMethod() { result = getAMethod("OnAuthorizationAsync") }
+ Method getOnAuthorizationMethod() { result = this.getAMethod("OnAuthorizationAsync") }
}
/** An attribute whose type has a name like `[Auto...]Validate[...]Anti[Ff]orgery[...Token]Attribute`. */
class ValidateAntiForgeryAttribute extends Attribute {
- ValidateAntiForgeryAttribute() { getType().getName().matches("%Validate%Anti_orgery%Attribute") }
+ ValidateAntiForgeryAttribute() {
+ this.getType().getName().matches("%Validate%Anti_orgery%Attribute")
+ }
}
/**
@@ -137,43 +141,43 @@ class ValidateAntiForgeryAttribute extends Attribute {
*/
class ValidateAntiforgeryTokenAuthorizationFilter extends Class {
ValidateAntiforgeryTokenAuthorizationFilter() {
- getABaseInterface*() instanceof MicrosoftAspNetCoreMvcIFilterMetadataInterface and
- getName().matches("%Validate%Anti_orgery%")
+ this.getABaseInterface*() instanceof MicrosoftAspNetCoreMvcIFilterMetadataInterface and
+ this.getName().matches("%Validate%Anti_orgery%")
}
}
/** The `Microsoft.AspNetCore.Mvc.Filters.FilterCollection` class. */
class MicrosoftAspNetCoreMvcFilterCollection extends Class {
MicrosoftAspNetCoreMvcFilterCollection() {
- getNamespace() instanceof MicrosoftAspNetCoreMvcFilters and
- hasName("FilterCollection")
+ this.getNamespace() instanceof MicrosoftAspNetCoreMvcFilters and
+ this.hasName("FilterCollection")
}
/** Gets an `Add` method. */
Method getAddMethod() {
- result = getAMethod("Add") or
- result = getABaseType().getAMethod("Add")
+ result = this.getAMethod("Add") or
+ result = this.getABaseType().getAMethod("Add")
}
}
/** The `Microsoft.AspNetCore.Mvc.MvcOptions` class. */
class MicrosoftAspNetCoreMvcOptions extends Class {
MicrosoftAspNetCoreMvcOptions() {
- getNamespace() instanceof MicrosoftAspNetCoreMvcNamespace and
- hasName("MvcOptions")
+ this.getNamespace() instanceof MicrosoftAspNetCoreMvcNamespace and
+ this.hasName("MvcOptions")
}
/** Gets the `Filters` property. */
- Property getFilterCollectionProperty() { result = getProperty("Filters") }
+ Property getFilterCollectionProperty() { result = this.getProperty("Filters") }
}
/** The base class for controllers in MVC, i.e. `Microsoft.AspNetCore.Mvc.Controller` or `Microsoft.AspNetCore.Mvc.ControllerBase` class. */
class MicrosoftAspNetCoreMvcControllerBaseClass extends Class {
MicrosoftAspNetCoreMvcControllerBaseClass() {
- getNamespace() instanceof MicrosoftAspNetCoreMvcNamespace and
+ this.getNamespace() instanceof MicrosoftAspNetCoreMvcNamespace and
(
- hasName("Controller") or
- hasName("ControllerBase")
+ this.hasName("Controller") or
+ this.hasName("ControllerBase")
)
}
}
@@ -181,12 +185,12 @@ class MicrosoftAspNetCoreMvcControllerBaseClass extends Class {
/** A subtype of `Microsoft.AspNetCore.Mvc.Controller` or `Microsoft.AspNetCore.Mvc.ControllerBase`. */
class MicrosoftAspNetCoreMvcController extends Class {
MicrosoftAspNetCoreMvcController() {
- getABaseType*() instanceof MicrosoftAspNetCoreMvcControllerBaseClass
+ this.getABaseType*() instanceof MicrosoftAspNetCoreMvcControllerBaseClass
}
/** Gets an action method for this controller. */
Method getAnActionMethod() {
- result = getAMethod() and
+ result = this.getAMethod() and
result.isPublic() and
not result.isStatic() and
not result.getAnAttribute() instanceof MicrosoftAspNetCoreMvcNonActionAttribute
@@ -208,12 +212,12 @@ class MicrosoftAspNetCoreMvcController extends Class {
/** The `Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper` interface. */
class MicrosoftAspNetCoreMvcRenderingIHtmlHelperInterface extends Interface {
MicrosoftAspNetCoreMvcRenderingIHtmlHelperInterface() {
- getNamespace() instanceof MicrosoftAspNetCoreMvcRendering and
- hasName("IHtmlHelper")
+ this.getNamespace() instanceof MicrosoftAspNetCoreMvcRendering and
+ this.hasName("IHtmlHelper")
}
/** Gets the `Raw` method. */
- Method getRawMethod() { result = getAMethod("Raw") }
+ Method getRawMethod() { result = this.getAMethod("Raw") }
}
/** A class deriving from `Microsoft.AspNetCore.Mvc.Razor.RazorPageBase`, implements Razor page in ASPNET Core. */
@@ -223,7 +227,7 @@ class MicrosoftAspNetCoreMvcRazorPageBase extends Class {
}
/** Gets the `WriteLiteral` method. */
- Method getWriteLiteralMethod() { result = getAMethod("WriteLiteral") }
+ Method getWriteLiteralMethod() { result = this.getAMethod("WriteLiteral") }
}
/** A class deriving from `Microsoft.AspNetCore.Http.HttpRequest`, implements `HttpRequest` in ASP.NET Core. */
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/system/collections/Generic.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/system/collections/Generic.qll
index a3616e57522..2b632d2b07c 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/system/collections/Generic.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/system/collections/Generic.qll
@@ -41,8 +41,8 @@ class SystemCollectionsGenericIComparerTInterface extends SystemCollectionsGener
result.getDeclaringType() = this and
result.hasName("Compare") and
result.getNumberOfParameters() = 2 and
- result.getParameter(0).getType() = getTypeParameter(0) and
- result.getParameter(1).getType() = getTypeParameter(0) and
+ result.getParameter(0).getType() = this.getTypeParameter(0) and
+ result.getParameter(1).getType() = this.getTypeParameter(0) and
result.getReturnType() instanceof IntType
}
}
@@ -56,8 +56,8 @@ class SystemCollectionsGenericIEqualityComparerTInterface extends SystemCollecti
result.getDeclaringType() = this and
result.hasName("Equals") and
result.getNumberOfParameters() = 2 and
- result.getParameter(0).getType() = getTypeParameter(0) and
- result.getParameter(1).getType() = getTypeParameter(0) and
+ result.getParameter(0).getType() = this.getTypeParameter(0) and
+ result.getParameter(1).getType() = this.getTypeParameter(0) and
result.getReturnType() instanceof BoolType
}
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/system/data/SqlClient.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/system/data/SqlClient.qll
index 858100fe7f7..c3b6f1fdd6d 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/system/data/SqlClient.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/system/data/SqlClient.qll
@@ -13,7 +13,7 @@ class SystemDataSqlClientNamespace extends Namespace {
/** A class in the `System.Data.SqlClient` namespace. */
class SystemDataSqlClientClass extends Class {
- SystemDataSqlClientClass() { getNamespace() instanceof SystemDataSqlClientNamespace }
+ SystemDataSqlClientClass() { this.getNamespace() instanceof SystemDataSqlClientNamespace }
}
/** The `System.Data.SqlClient.SqlDataAdapter` class. */
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/system/text/RegularExpressions.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/system/text/RegularExpressions.qll
index 1820192da11..531fa6ef721 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/system/text/RegularExpressions.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/system/text/RegularExpressions.qll
@@ -67,7 +67,7 @@ class RegexOperation extends Call {
*/
Expr getInput() {
if this instanceof MethodCall
- then result = getArgumentForName("input")
+ then result = this.getArgumentForName("input")
else
exists(MethodCall call |
call.getTarget() = any(SystemTextRegularExpressionsRegexClass rs).getAMethod() and
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/system/web/Mvc.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/system/web/Mvc.qll
index 78aaa6dc065..b2051a8464f 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/system/web/Mvc.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/system/web/Mvc.qll
@@ -6,8 +6,8 @@ private import semmle.code.csharp.frameworks.system.Web
/** The `System.Web.Mvc` namespace. */
class SystemWebMvcNamespace extends Namespace {
SystemWebMvcNamespace() {
- getParentNamespace() instanceof SystemWebNamespace and
- hasName("Mvc")
+ this.getParentNamespace() instanceof SystemWebNamespace and
+ this.hasName("Mvc")
}
}
@@ -31,7 +31,7 @@ class SystemWebMvcHtmlHelperClass extends SystemWebMvcClass {
/** An attribute whose type is in the `System.Web.Mvc` namespace. */
class SystemWebMvcAttribute extends Attribute {
- SystemWebMvcAttribute() { getType().getNamespace() instanceof SystemWebMvcNamespace }
+ SystemWebMvcAttribute() { this.getType().getNamespace() instanceof SystemWebMvcNamespace }
}
/** An attribute whose type is `System.Web.Mvc.HttpPost`. */
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/system/web/WebPages.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/system/web/WebPages.qll
index 0d43f76719b..915acbfe41f 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/system/web/WebPages.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/frameworks/system/web/WebPages.qll
@@ -6,16 +6,16 @@ private import semmle.code.csharp.frameworks.system.Web
/** The `System.Web.WebPages` namespace. */
class SystemWebWebPagesNamespace extends Namespace {
SystemWebWebPagesNamespace() {
- getParentNamespace() instanceof SystemWebNamespace and
- hasName("WebPages")
+ this.getParentNamespace() instanceof SystemWebNamespace and
+ this.hasName("WebPages")
}
}
/** The `System.Web.WebPages.WebPageExecutingBase` class. */
class SystemWebWebPagesWebPageExecutingBaseClass extends Class {
SystemWebWebPagesWebPageExecutingBaseClass() {
- getNamespace() instanceof SystemWebWebPagesNamespace and
- hasName("WebPageExecutingBase")
+ this.getNamespace() instanceof SystemWebWebPagesNamespace and
+ this.hasName("WebPageExecutingBase")
}
}
@@ -24,8 +24,8 @@ class WebPageClass extends Class {
WebPageClass() { this.getBaseClass*() instanceof SystemWebWebPagesWebPageExecutingBaseClass }
/** Gets the `WriteLiteral` method. */
- Method getWriteLiteralMethod() { result = getAMethod("WriteLiteral") }
+ Method getWriteLiteralMethod() { result = this.getAMethod("WriteLiteral") }
/** Gets the `WriteLiteralTo` method. */
- Method getWriteLiteralToMethod() { result = getAMethod("WriteLiteralTo") }
+ Method getWriteLiteralToMethod() { result = this.getAMethod("WriteLiteralTo") }
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/PrivateData.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/PrivateData.qll
index 10bb06679b7..3ca81e614ad 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/PrivateData.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/PrivateData.qll
@@ -14,26 +14,22 @@ import semmle.code.csharp.frameworks.system.windows.Forms
/** A string for `match` that identifies strings that look like they represent private data. */
private string privateNames() {
- // Inspired by the list on https://cwe.mitre.org/data/definitions/359.html
- // Government identifiers, such as Social Security Numbers
- result = "%social%security%number%" or
- // Contact information, such as home addresses and telephone numbers
- result = "%postcode%" or
- result = "%zipcode%" or
- result = "%telephone%" or
- // Geographic location - where the user is (or was)
- result = "%latitude%" or
- result = "%longitude%" or
- // Financial data - such as credit card numbers, salary, bank accounts, and debts
- result = "%creditcard%" or
- result = "%salary%" or
- result = "%bankaccount%" or
- // Communications - e-mail addresses, private e-mail messages, SMS text messages, chat logs, etc.
- result = "%email%" or
- result = "%mobile%" or
- result = "%employer%" or
- // Health - medical conditions, insurance status, prescription records
- result = "%medical%"
+ result =
+ [
+ // Inspired by the list on https://cwe.mitre.org/data/definitions/359.html
+ // Government identifiers, such as Social Security Numbers
+ "%social%security%number%",
+ // Contact information, such as home addresses and telephone numbers
+ "%postcode%", "%zipcode%", "%telephone%",
+ // Geographic location - where the user is (or was)
+ "%latitude%", "%longitude%",
+ // Financial data - such as credit card numbers, salary, bank accounts, and debts
+ "%creditcard%", "%salary%", "%bankaccount%",
+ // Communications - e-mail addresses, private e-mail messages, SMS text messages, chat logs, etc.
+ "%email%", "%mobile%", "%employer%",
+ // Health - medical conditions, insurance status, prescription records
+ "%medical%"
+ ]
}
/** An expression that might contain private data. */
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/SensitiveActions.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/SensitiveActions.qll
index cc7701ad318..483000895aa 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/SensitiveActions.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/SensitiveActions.qll
@@ -72,7 +72,7 @@ class SensitiveProperty extends Property {
/** A parameter to a library method that may hold a sensitive value. */
class SensitiveLibraryParameter extends Parameter {
SensitiveLibraryParameter() {
- fromLibrary() and
+ this.fromLibrary() and
exists(string s | this.getName().toLowerCase() = s | s.matches(suspicious()))
}
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/cryptography/HardcodedSymmetricEncryptionKey.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/cryptography/HardcodedSymmetricEncryptionKey.qll
index 915dae12a9f..3cf3fa107bf 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/cryptography/HardcodedSymmetricEncryptionKey.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/cryptography/HardcodedSymmetricEncryptionKey.qll
@@ -21,7 +21,7 @@ module HardcodedSymmetricEncryptionKey {
abstract class Sanitizer extends DataFlow::ExprNode { }
private class ByteArrayType extends ArrayType {
- ByteArrayType() { getElementType() instanceof ByteType }
+ ByteArrayType() { this.getElementType() instanceof ByteType }
}
private class ByteArrayLiteralSource extends Source {
@@ -49,7 +49,7 @@ module HardcodedSymmetricEncryptionKey {
private class SymmetricEncryptionCreateEncryptorSink extends Sink {
SymmetricEncryptionCreateEncryptorSink() {
exists(SymmetricAlgorithm ag, MethodCall mc | mc = ag.getASymmetricEncryptor() |
- asExpr() = mc.getArgumentForName("rgbKey")
+ this.asExpr() = mc.getArgumentForName("rgbKey")
)
}
@@ -59,7 +59,7 @@ module HardcodedSymmetricEncryptionKey {
private class SymmetricEncryptionCreateDecryptorSink extends Sink {
SymmetricEncryptionCreateDecryptorSink() {
exists(SymmetricAlgorithm ag, MethodCall mc | mc = ag.getASymmetricDecryptor() |
- asExpr() = mc.getArgumentForName("rgbKey")
+ this.asExpr() = mc.getArgumentForName("rgbKey")
)
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/dataflow/ExternalAPIsQuery.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/dataflow/ExternalAPIsQuery.qll
index bccd71d7096..fd643b5b7f0 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/dataflow/ExternalAPIsQuery.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/dataflow/ExternalAPIsQuery.qll
@@ -69,7 +69,7 @@ class ExternalAPIDataNode extends DataFlow::Node {
int getIndex() { result = i }
/** Gets the description of the callable being called. */
- string getCallableDescription() { result = getCallable().getQualifiedName() }
+ string getCallableDescription() { result = this.getCallable().getQualifiedName() }
}
/** A configuration for tracking flow from `RemoteFlowSource`s to `ExternalAPIDataNode`s. */
@@ -108,7 +108,7 @@ class ExternalAPIUsedWithUntrustedData extends TExternalAPI {
/** Gets the number of untrusted sources used with this external API. */
int getNumberOfUntrustedSources() {
- result = count(getUntrustedDataNode().getAnUntrustedSource())
+ result = count(this.getUntrustedDataNode().getAnUntrustedSource())
}
/** Gets a textual representation of this element. */
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/dataflow/XSSSinks.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/dataflow/XSSSinks.qll
index 4be005be4de..d0999605b61 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/dataflow/XSSSinks.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/dataflow/XSSSinks.qll
@@ -166,7 +166,7 @@ class AspInlineMember extends AspInlineCode {
Member getMember() { result = member }
/** Gets the type of this member. */
- Type getType() { result = getMemberType(getMember()) }
+ Type getType() { result = getMemberType(this.getMember()) }
}
/** Gets a value that is written to the member accessed by the given `AspInlineMember`. */
@@ -251,7 +251,7 @@ private class HttpResponseBaseSink extends Sink {
*/
private class StringContentSinkModelCsv extends SinkModelCsv {
override predicate row(string row) {
- row = ["System.Net.Http;StringContent;false;StringContent;;;Argument[0];xss"]
+ row = "System.Net.Http;StringContent;false;StringContent;;;Argument[0];xss"
}
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/dataflow/flowsinks/ExternalLocationSink.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/dataflow/flowsinks/ExternalLocationSink.qll
index 673473eb49b..30736bdabf6 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/dataflow/flowsinks/ExternalLocationSink.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/dataflow/flowsinks/ExternalLocationSink.qll
@@ -38,12 +38,7 @@ class TraceMessageSink extends ExternalLocationSink {
trace.hasQualifiedName("System.Diagnostics", "TraceSource")
|
this.getExpr() = trace.getAMethod().getACall().getArgumentForName(parameterName) and
- (
- parameterName = "format" or
- parameterName = "args" or
- parameterName = "message" or
- parameterName = "category"
- )
+ parameterName = ["format", "args", "message", "category"]
)
}
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/dataflow/flowsources/Remote.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/dataflow/flowsources/Remote.qll
index 25647e50f2e..2448f80e8ba 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/dataflow/flowsources/Remote.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/dataflow/flowsources/Remote.qll
@@ -43,15 +43,8 @@ class AspNetQueryStringMember extends Member {
* request.
*/
private string getHttpRequestFlowPropertyNames() {
- result = "QueryString" or
- result = "Headers" or
- result = "RawUrl" or
- result = "Url" or
- result = "Cookies" or
- result = "Form" or
- result = "Params" or
- result = "Path" or
- result = "PathInfo"
+ result =
+ ["QueryString", "Headers", "RawUrl", "Url", "Cookies", "Form", "Params", "Path", "PathInfo"]
}
/** A data flow source of remote user input (ASP.NET query string). */
@@ -86,7 +79,7 @@ class AspNetUnvalidatedQueryStringRemoteFlowSource extends AspNetRemoteFlowSourc
/** A data flow source of remote user input (ASP.NET user input). */
class AspNetUserInputRemoteFlowSource extends AspNetRemoteFlowSource, DataFlow::ExprNode {
- AspNetUserInputRemoteFlowSource() { getType() instanceof SystemWebUIWebControlsTextBoxClass }
+ AspNetUserInputRemoteFlowSource() { this.getType() instanceof SystemWebUIWebControlsTextBoxClass }
override string getSourceType() { result = "ASP.NET user input" }
}
@@ -113,7 +106,7 @@ class AspNetServiceRemoteFlowSource extends RemoteFlowSource, DataFlow::Paramete
/** A data flow source of remote user input (ASP.NET request message). */
class SystemNetHttpRequestMessageRemoteFlowSource extends RemoteFlowSource, DataFlow::ExprNode {
SystemNetHttpRequestMessageRemoteFlowSource() {
- getType() instanceof SystemWebHttpRequestMessageClass
+ this.getType() instanceof SystemWebHttpRequestMessageClass
}
override string getSourceType() { result = "ASP.NET request message" }
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/xml/InsecureXMLQuery.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/xml/InsecureXMLQuery.qll
index 2483452113a..e885fdb2778 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/xml/InsecureXMLQuery.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/csharp/security/xml/InsecureXMLQuery.qll
@@ -157,8 +157,8 @@ module XmlReader {
override predicate isUnsafe(string reason) {
exists(string dtdReason, string resolverReason |
- dtdEnabled(dtdReason, _) and
- insecureResolver(resolverReason, _) and
+ this.dtdEnabled(dtdReason, _) and
+ this.insecureResolver(resolverReason, _) and
reason = dtdReason + ", " + resolverReason
)
}
@@ -209,9 +209,7 @@ module XmlReader {
/** Provides predicates related to `System.Xml.XmlTextReader`. */
module XmlTextReader {
private class InsecureXmlTextReader extends InsecureXmlProcessing, ObjectCreation {
- InsecureXmlTextReader() {
- this.getObjectType().(ValueOrRefType).hasQualifiedName("System.Xml.XmlTextReader")
- }
+ InsecureXmlTextReader() { this.getObjectType().hasQualifiedName("System.Xml.XmlTextReader") }
override predicate isUnsafe(string reason) {
not exists(Expr xmlResolverVal |
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Callable.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Callable.qll
index 2beccfe422c..0a63e5c95cd 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Callable.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Callable.qll
@@ -75,7 +75,7 @@ class Callable extends Parameterizable, @dotnet_callable {
}
private string getReturnTypeLabel() {
- result = getReturnType().getLabel()
+ result = this.getReturnType().getLabel()
or
not exists(this.getReturnType()) and result = "System.Void"
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Declaration.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Declaration.qll
index 7f78077d766..60e434f9ae6 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Declaration.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Declaration.qll
@@ -16,7 +16,7 @@ class Declaration extends NamedElement, @dotnet_declaration {
string getUndecoratedName() { none() }
/** Holds if this element has undecorated name 'name'. */
- final predicate hasUndecoratedName(string name) { name = getUndecoratedName() }
+ final predicate hasUndecoratedName(string name) { name = this.getUndecoratedName() }
/** Gets the type containing this declaration, if any. */
Type getDeclaringType() { none() }
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Element.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Element.qll
index 3b1955887f9..d8c27f88e10 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Element.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Element.qll
@@ -35,7 +35,7 @@ class Element extends @dotnet_element {
* Gets the "language" of this program element, as defined by the extension of the filename.
* For example, C# has language "cs", and Visual Basic has language "vb".
*/
- final string getLanguage() { result = getLocation().getFile().getExtension() }
+ final string getLanguage() { result = this.getLocation().getFile().getExtension() }
/** Gets the full textual representation of this element, including type information. */
string toStringWithTypes() { result = this.toString() }
@@ -43,7 +43,7 @@ class Element extends @dotnet_element {
/**
* Gets a comma-separated list of the names of the primary CodeQL classes to which this element belongs.
*/
- final string getPrimaryQlClasses() { result = concat(getAPrimaryQlClass(), ",") }
+ final string getPrimaryQlClasses() { result = concat(this.getAPrimaryQlClass(), ",") }
/**
* Gets the name of a primary CodeQL class to which this element belongs.
@@ -66,7 +66,7 @@ class NamedElement extends Element, @dotnet_named_element {
string getName() { none() }
/** Holds if this element has name 'name'. */
- final predicate hasName(string name) { name = getName() }
+ final predicate hasName(string name) { name = this.getName() }
/**
* Gets the fully qualified name of this element, for example the
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Expr.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Expr.qll
index e5bea3a52d7..15d658f54c2 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Expr.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Expr.qll
@@ -45,7 +45,7 @@ class Call extends Expr, @dotnet_call {
Expr getArgument(int i) { none() }
/** Gets an argument to this call. */
- Expr getAnArgument() { result = getArgument(_) }
+ Expr getAnArgument() { result = this.getArgument(_) }
/** Gets the expression that is supplied for parameter `p`. */
Expr getArgumentForParameter(Parameter p) { none() }
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Generics.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Generics.qll
index 9b236cdbfb9..f84718d4b82 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Generics.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Generics.qll
@@ -14,7 +14,7 @@ abstract class UnboundGeneric extends Generic {
abstract TypeParameter getTypeParameter(int i);
/** Gets a type parameter. */
- TypeParameter getATypeParameter() { result = getTypeParameter(_) }
+ TypeParameter getATypeParameter() { result = this.getTypeParameter(_) }
/**
* Gets one of the constructed versions of this declaration,
@@ -32,7 +32,7 @@ abstract class ConstructedGeneric extends Generic {
abstract Type getTypeArgument(int i);
/** Gets a type argument. */
- Type getATypeArgument() { result = getTypeArgument(_) }
+ Type getATypeArgument() { result = this.getTypeArgument(_) }
/**
* Gets the unbound generic declaration from which this declaration was
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Namespace.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Namespace.qll
index 55b42e737db..324448728de 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Namespace.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Namespace.qll
@@ -33,7 +33,7 @@ class Namespace extends Declaration, @namespace {
override string toString() { result = this.getQualifiedName() }
/** Holds if this is the global namespace. */
- final predicate isGlobalNamespace() { getName() = "" }
+ final predicate isGlobalNamespace() { this.getName() = "" }
/** Gets the simple name of this namespace, for example `IO` in `System.IO`. */
final override string getName() { namespaces(this, result) }
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Type.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Type.qll
index 5dc9c409c18..81fefa96550 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Type.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Type.qll
@@ -24,11 +24,11 @@ class ValueOrRefType extends Type, @dotnet_valueorreftype {
Namespace getDeclaringNamespace() { none() }
private string getPrefixWithTypes() {
- result = getDeclaringType().getLabel() + "."
+ result = this.getDeclaringType().getLabel() + "."
or
- if getDeclaringNamespace().isGlobalNamespace()
+ if this.getDeclaringNamespace().isGlobalNamespace()
then result = ""
- else result = getDeclaringNamespace().getQualifiedName() + "."
+ else result = this.getDeclaringNamespace().getQualifiedName() + "."
}
pragma[noinline]
@@ -64,9 +64,9 @@ class TypeParameter extends Type, @dotnet_type_parameter {
/** Gets the index of this type parameter. For example the index of `U` in `Func` is 1. */
int getIndex() { none() }
- final override string getLabel() { result = "!" + getIndex() }
+ final override string getLabel() { result = "!" + this.getIndex() }
- override string getUndecoratedName() { result = "!" + getIndex() }
+ override string getUndecoratedName() { result = "!" + this.getIndex() }
}
/** A pointer type. */
@@ -76,9 +76,9 @@ class PointerType extends Type, @dotnet_pointer_type {
override string getName() { result = this.getReferentType().getName() + "*" }
- final override string getLabel() { result = getReferentType().getLabel() + "*" }
+ final override string getLabel() { result = this.getReferentType().getLabel() + "*" }
- override string toStringWithTypes() { result = getReferentType().toStringWithTypes() + "*" }
+ override string toStringWithTypes() { result = this.getReferentType().toStringWithTypes() + "*" }
}
/** An array type. */
@@ -86,7 +86,7 @@ class ArrayType extends ValueOrRefType, @dotnet_array_type {
/** Gets the type of the array element. */
Type getElementType() { none() }
- final override string getLabel() { result = getElementType().getLabel() + "[]" }
+ final override string getLabel() { result = this.getElementType().getLabel() + "[]" }
- override string toStringWithTypes() { result = getElementType().toStringWithTypes() + "[]" }
+ override string toStringWithTypes() { result = this.getElementType().toStringWithTypes() + "[]" }
}
diff --git a/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Variable.qll b/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Variable.qll
index 9f9a12e7d98..ee9ccebbbe6 100644
--- a/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Variable.qll
+++ b/repo-tests/codeql/csharp/ql/lib/semmle/code/dotnet/Variable.qll
@@ -15,10 +15,10 @@ class Field extends Variable, Member, @dotnet_field { }
/** A parameter to a .Net callable, property or function pointer type. */
class Parameter extends Variable, @dotnet_parameter {
/** Gets the raw position of this parameter, including the `this` parameter at index 0. */
- final int getRawPosition() { this = getDeclaringElement().getRawParameter(result) }
+ final int getRawPosition() { this = this.getDeclaringElement().getRawParameter(result) }
/** Gets the position of this parameter, excluding the `this` parameter. */
- int getPosition() { this = getDeclaringElement().getParameter(result) }
+ int getPosition() { this = this.getDeclaringElement().getParameter(result) }
/** Gets the callable defining this parameter. */
Callable getCallable() { result = this.getDeclaringElement() }
diff --git a/repo-tests/codeql/csharp/ql/src/Bad Practices/Implementation Hiding/ExposeRepresentation.ql b/repo-tests/codeql/csharp/ql/src/Bad Practices/Implementation Hiding/ExposeRepresentation.ql
index 2ade29f6fa4..e9f9b6cb8c6 100644
--- a/repo-tests/codeql/csharp/ql/src/Bad Practices/Implementation Hiding/ExposeRepresentation.ql
+++ b/repo-tests/codeql/csharp/ql/src/Bad Practices/Implementation Hiding/ExposeRepresentation.ql
@@ -29,17 +29,22 @@ predicate returnsCollection(Callable c, Field f) {
not c.(Modifiable).isStatic()
}
-predicate mayWriteToCollection(Expr modified) {
- modified instanceof CollectionModificationAccess
+predicate nodeMayWriteToCollection(Node modified) {
+ modified.asExpr() instanceof CollectionModificationAccess
or
- exists(Expr mid | mayWriteToCollection(mid) | localExprFlow(modified, mid))
+ exists(Node mid | nodeMayWriteToCollection(mid) | localFlowStep(modified, mid))
or
- exists(MethodCall mid, Callable c | mayWriteToCollection(mid) |
- mid.getTarget() = c and
- c.canReturn(modified)
+ exists(Node mid, MethodCall mc, Callable c | nodeMayWriteToCollection(mid) |
+ mc = mid.asExpr() and
+ mc.getTarget() = c and
+ c.canReturn(modified.asExpr())
)
}
+predicate mayWriteToCollection(Expr modified) {
+ nodeMayWriteToCollection(any(ExprNode n | n.getExpr() = modified))
+}
+
predicate modificationAfter(Expr before, Expr after) {
mayWriteToCollection(after) and
localFlowStep+(exprNode(before), exprNode(after))
diff --git a/repo-tests/codeql/csharp/ql/src/Bad Practices/Magic Constants/MagicConstants.qll b/repo-tests/codeql/csharp/ql/src/Bad Practices/Magic Constants/MagicConstants.qll
index d604dfaeefe..b65cdcd1961 100644
--- a/repo-tests/codeql/csharp/ql/src/Bad Practices/Magic Constants/MagicConstants.qll
+++ b/repo-tests/codeql/csharp/ql/src/Bad Practices/Magic Constants/MagicConstants.qll
@@ -7,179 +7,30 @@ import semmle.code.csharp.frameworks.System
*/
private predicate trivialPositiveIntValue(string s) {
- s = "0" or
- s = "1" or
- s = "2" or
- s = "3" or
- s = "4" or
- s = "5" or
- s = "6" or
- s = "7" or
- s = "8" or
- s = "9" or
- s = "10" or
- s = "11" or
- s = "12" or
- s = "13" or
- s = "14" or
- s = "15" or
- s = "16" or
- s = "17" or
- s = "18" or
- s = "19" or
- s = "20" or
- s = "16" or
- s = "32" or
- s = "64" or
- s = "128" or
- s = "256" or
- s = "512" or
- s = "1024" or
- s = "2048" or
- s = "4096" or
- s = "16384" or
- s = "32768" or
- s = "65536" or
- s = "1048576" or
- s = "2147483648" or
- s = "4294967296" or
- s = "15" or
- s = "31" or
- s = "63" or
- s = "127" or
- s = "255" or
- s = "511" or
- s = "1023" or
- s = "2047" or
- s = "4095" or
- s = "16383" or
- s = "32767" or
- s = "65535" or
- s = "1048577" or
- s = "2147483647" or
- s = "4294967295" or
- s = "0x00000001" or
- s = "0x00000002" or
- s = "0x00000004" or
- s = "0x00000008" or
- s = "0x00000010" or
- s = "0x00000020" or
- s = "0x00000040" or
- s = "0x00000080" or
- s = "0x00000100" or
- s = "0x00000200" or
- s = "0x00000400" or
- s = "0x00000800" or
- s = "0x00001000" or
- s = "0x00002000" or
- s = "0x00004000" or
- s = "0x00008000" or
- s = "0x00010000" or
- s = "0x00020000" or
- s = "0x00040000" or
- s = "0x00080000" or
- s = "0x00100000" or
- s = "0x00200000" or
- s = "0x00400000" or
- s = "0x00800000" or
- s = "0x01000000" or
- s = "0x02000000" or
- s = "0x04000000" or
- s = "0x08000000" or
- s = "0x10000000" or
- s = "0x20000000" or
- s = "0x40000000" or
- s = "0x80000000" or
- s = "0x00000001" or
- s = "0x00000003" or
- s = "0x00000007" or
- s = "0x0000000f" or
- s = "0x0000001f" or
- s = "0x0000003f" or
- s = "0x0000007f" or
- s = "0x000000ff" or
- s = "0x000001ff" or
- s = "0x000003ff" or
- s = "0x000007ff" or
- s = "0x00000fff" or
- s = "0x00001fff" or
- s = "0x00003fff" or
- s = "0x00007fff" or
- s = "0x0000ffff" or
- s = "0x0001ffff" or
- s = "0x0003ffff" or
- s = "0x0007ffff" or
- s = "0x000fffff" or
- s = "0x001fffff" or
- s = "0x003fffff" or
- s = "0x007fffff" or
- s = "0x00ffffff" or
- s = "0x01ffffff" or
- s = "0x03ffffff" or
- s = "0x07ffffff" or
- s = "0x0fffffff" or
- s = "0x1fffffff" or
- s = "0x3fffffff" or
- s = "0x7fffffff" or
- s = "0xffffffff" or
- s = "0x0001" or
- s = "0x0002" or
- s = "0x0004" or
- s = "0x0008" or
- s = "0x0010" or
- s = "0x0020" or
- s = "0x0040" or
- s = "0x0080" or
- s = "0x0100" or
- s = "0x0200" or
- s = "0x0400" or
- s = "0x0800" or
- s = "0x1000" or
- s = "0x2000" or
- s = "0x4000" or
- s = "0x8000" or
- s = "0x0001" or
- s = "0x0003" or
- s = "0x0007" or
- s = "0x000f" or
- s = "0x001f" or
- s = "0x003f" or
- s = "0x007f" or
- s = "0x00ff" or
- s = "0x01ff" or
- s = "0x03ff" or
- s = "0x07ff" or
- s = "0x0fff" or
- s = "0x1fff" or
- s = "0x3fff" or
- s = "0x7fff" or
- s = "0xffff" or
- s = "0x01" or
- s = "0x02" or
- s = "0x04" or
- s = "0x08" or
- s = "0x10" or
- s = "0x20" or
- s = "0x40" or
- s = "0x80" or
- s = "0x01" or
- s = "0x03" or
- s = "0x07" or
- s = "0x0f" or
- s = "0x1f" or
- s = "0x3f" or
- s = "0x7f" or
- s = "0xff" or
- s = "0x00" or
- s = "10" or
- s = "100" or
- s = "1000" or
- s = "10000" or
- s = "100000" or
- s = "1000000" or
- s = "10000000" or
- s = "100000000" or
- s = "1000000000"
+ s =
+ [
+ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16",
+ "17", "18", "19", "20", "16", "32", "64", "128", "256", "512", "1024", "2048", "4096",
+ "16384", "32768", "65536", "1048576", "2147483648", "4294967296", "15", "31", "63", "127",
+ "255", "511", "1023", "2047", "4095", "16383", "32767", "65535", "1048577", "2147483647",
+ "4294967295", "0x00000001", "0x00000002", "0x00000004", "0x00000008", "0x00000010",
+ "0x00000020", "0x00000040", "0x00000080", "0x00000100", "0x00000200", "0x00000400",
+ "0x00000800", "0x00001000", "0x00002000", "0x00004000", "0x00008000", "0x00010000",
+ "0x00020000", "0x00040000", "0x00080000", "0x00100000", "0x00200000", "0x00400000",
+ "0x00800000", "0x01000000", "0x02000000", "0x04000000", "0x08000000", "0x10000000",
+ "0x20000000", "0x40000000", "0x80000000", "0x00000001", "0x00000003", "0x00000007",
+ "0x0000000f", "0x0000001f", "0x0000003f", "0x0000007f", "0x000000ff", "0x000001ff",
+ "0x000003ff", "0x000007ff", "0x00000fff", "0x00001fff", "0x00003fff", "0x00007fff",
+ "0x0000ffff", "0x0001ffff", "0x0003ffff", "0x0007ffff", "0x000fffff", "0x001fffff",
+ "0x003fffff", "0x007fffff", "0x00ffffff", "0x01ffffff", "0x03ffffff", "0x07ffffff",
+ "0x0fffffff", "0x1fffffff", "0x3fffffff", "0x7fffffff", "0xffffffff", "0x0001", "0x0002",
+ "0x0004", "0x0008", "0x0010", "0x0020", "0x0040", "0x0080", "0x0100", "0x0200", "0x0400",
+ "0x0800", "0x1000", "0x2000", "0x4000", "0x8000", "0x0001", "0x0003", "0x0007", "0x000f",
+ "0x001f", "0x003f", "0x007f", "0x00ff", "0x01ff", "0x03ff", "0x07ff", "0x0fff", "0x1fff",
+ "0x3fff", "0x7fff", "0xffff", "0x01", "0x02", "0x04", "0x08", "0x10", "0x20", "0x40", "0x80",
+ "0x01", "0x03", "0x07", "0x0f", "0x1f", "0x3f", "0x7f", "0xff", "0x00", "10", "100", "1000",
+ "10000", "100000", "1000000", "10000000", "100000000", "1000000000"
+ ]
}
private predicate trivialIntValue(string s) {
@@ -193,15 +44,7 @@ private predicate intTrivial(Literal lit) {
}
private predicate powerOfTen(float f) {
- f = 10 or
- f = 100 or
- f = 1000 or
- f = 10000 or
- f = 100000 or
- f = 1000000 or
- f = 10000000 or
- f = 100000000 or
- f = 1000000000
+ f = [10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000]
}
private predicate floatTrivial(Literal lit) {
diff --git a/repo-tests/codeql/csharp/ql/src/Bad Practices/Naming Conventions/DefaultControlNames.ql b/repo-tests/codeql/csharp/ql/src/Bad Practices/Naming Conventions/DefaultControlNames.ql
index be381328582..2bf51653d99 100644
--- a/repo-tests/codeql/csharp/ql/src/Bad Practices/Naming Conventions/DefaultControlNames.ql
+++ b/repo-tests/codeql/csharp/ql/src/Bad Practices/Naming Conventions/DefaultControlNames.ql
@@ -13,16 +13,11 @@
import csharp
predicate controlName(string prefix) {
- prefix = "[Ll]abel" or
- prefix = "[Bb]utton" or
- prefix = "[Pp]anel" or
- prefix = "[Rr]adio[Bb]utton" or
- prefix = "[Pp]rop" or
- prefix = "[Ss]atus[Ss]trip" or
- prefix = "[Tt]able[Ll]ayout[Dd]esigner" or
- prefix = "[Tt]ext[Bb]ox" or
- prefix = "[Tt]ool[Ss]trip" or
- prefix = "[Pp]icture[Bb]ox"
+ prefix =
+ [
+ "[Ll]abel", "[Bb]utton", "[Pp]anel", "[Rr]adio[Bb]utton", "[Pp]rop", "[Ss]atus[Ss]trip",
+ "[Tt]able[Ll]ayout[Dd]esigner", "[Tt]ext[Bb]ox", "[Tt]ool[Ss]trip", "[Pp]icture[Bb]ox"
+ ]
}
predicate usedInHumanWrittenCode(Field f) {
diff --git a/repo-tests/codeql/csharp/ql/src/Bad Practices/Naming Conventions/VariableNameTooShort.ql b/repo-tests/codeql/csharp/ql/src/Bad Practices/Naming Conventions/VariableNameTooShort.ql
index f161edda6c4..cb778465df8 100644
--- a/repo-tests/codeql/csharp/ql/src/Bad Practices/Naming Conventions/VariableNameTooShort.ql
+++ b/repo-tests/codeql/csharp/ql/src/Bad Practices/Naming Conventions/VariableNameTooShort.ql
@@ -34,16 +34,7 @@ select variable, "Variable name '" + name + "' is too short."
// Adjustable: acceptable short names
//
predicate allowedName(string name) {
- name = "url" or
- name = "cmd" or
- name = "UK" or
- name = "uri" or
- name = "top" or
- name = "row" or
- name = "pin" or
- name = "log" or
- name = "key" or
- name = "_"
+ name = ["url", "cmd", "UK", "uri", "top", "row", "pin", "log", "key", "_"]
}
//
diff --git a/repo-tests/codeql/csharp/ql/src/Dead Code/DeadStoreOfLocal.ql b/repo-tests/codeql/csharp/ql/src/Dead Code/DeadStoreOfLocal.ql
index 19766a550ec..4612091743f 100644
--- a/repo-tests/codeql/csharp/ql/src/Dead Code/DeadStoreOfLocal.ql
+++ b/repo-tests/codeql/csharp/ql/src/Dead Code/DeadStoreOfLocal.ql
@@ -37,21 +37,11 @@ Expr getADelegateExpr(Callable c) {
*/
predicate nonEscapingCall(Call c) {
exists(string name | c.getTarget().hasName(name) |
- name = "ForEach" or
- name = "Count" or
- name = "Any" or
- name = "All" or
- name = "Average" or
- name = "Aggregate" or
- name = "First" or
- name = "Last" or
- name = "FirstOrDefault" or
- name = "LastOrDefault" or
- name = "LongCount" or
- name = "Max" or
- name = "Single" or
- name = "SingleOrDefault" or
- name = "Sum"
+ name =
+ [
+ "ForEach", "Count", "Any", "All", "Average", "Aggregate", "First", "Last", "FirstOrDefault",
+ "LastOrDefault", "LongCount", "Max", "Single", "SingleOrDefault", "Sum"
+ ]
)
}
@@ -116,12 +106,7 @@ class RelevantDefinition extends AssignableDefinition {
private predicate isDefaultLikeInitializer() {
this.isInitializer() and
exists(Expr e | e = this.getSource().stripCasts() |
- exists(string val | val = e.getValue() |
- val = "0" or
- val = "-1" or
- val = "" or
- val = "false"
- )
+ e.getValue() = ["0", "-1", "", "false"]
or
e instanceof NullLiteral
or
diff --git a/repo-tests/codeql/csharp/ql/src/Diagnostics/DiagnosticExtractionErrors.ql b/repo-tests/codeql/csharp/ql/src/Diagnostics/DiagnosticExtractionErrors.ql
index 23943d8491f..e9e2a42bfa8 100644
--- a/repo-tests/codeql/csharp/ql/src/Diagnostics/DiagnosticExtractionErrors.ql
+++ b/repo-tests/codeql/csharp/ql/src/Diagnostics/DiagnosticExtractionErrors.ql
@@ -21,8 +21,8 @@ abstract private class DiagnosticError extends TDiagnosticError {
abstract Location getLocation();
string getLocationMessage() {
- if getLocation().getFile().fromSource()
- then result = " in " + getLocation().getFile()
+ if this.getLocation().getFile().fromSource()
+ then result = " in " + this.getLocation().getFile()
else result = ""
}
}
diff --git a/repo-tests/codeql/csharp/ql/src/Security Features/CWE-020/ExternalAPIsUsedWithUntrustedData.ql b/repo-tests/codeql/csharp/ql/src/Security Features/CWE-020/ExternalAPIsUsedWithUntrustedData.ql
index d34c8037e8b..8b6a657ecd4 100644
--- a/repo-tests/codeql/csharp/ql/src/Security Features/CWE-020/ExternalAPIsUsedWithUntrustedData.ql
+++ b/repo-tests/codeql/csharp/ql/src/Security Features/CWE-020/ExternalAPIsUsedWithUntrustedData.ql
@@ -3,7 +3,7 @@
* @description This reports the external APIs that are used with untrusted data, along with how
* frequently the API is called, and how many unique sources of untrusted data flow
* to it.
- * @id csharp/count-untrusted-data-external-api
+ * @id cs/count-untrusted-data-external-api
* @kind table
* @tags security external/cwe/cwe-20
*/
diff --git a/repo-tests/codeql/csharp/ql/src/Security Features/CWE-020/UntrustedDataToExternalAPI.ql b/repo-tests/codeql/csharp/ql/src/Security Features/CWE-020/UntrustedDataToExternalAPI.ql
index 88709f2e0cc..3db73db09d4 100644
--- a/repo-tests/codeql/csharp/ql/src/Security Features/CWE-020/UntrustedDataToExternalAPI.ql
+++ b/repo-tests/codeql/csharp/ql/src/Security Features/CWE-020/UntrustedDataToExternalAPI.ql
@@ -1,7 +1,7 @@
/**
* @name Untrusted data passed to external API
* @description Data provided remotely is used in this external API without sanitization, which could be a security risk.
- * @id csharp/untrusted-data-to-external-api
+ * @id cs/untrusted-data-to-external-api
* @kind path-problem
* @precision low
* @problem.severity error
diff --git a/repo-tests/codeql/csharp/ql/src/Stubs/Stubs.qll b/repo-tests/codeql/csharp/ql/src/Stubs/Stubs.qll
index 6f92fb12f55..ea38367c876 100644
--- a/repo-tests/codeql/csharp/ql/src/Stubs/Stubs.qll
+++ b/repo-tests/codeql/csharp/ql/src/Stubs/Stubs.qll
@@ -128,6 +128,7 @@ abstract private class GeneratedType extends Type, GeneratedElement {
/** Gets the entire C# stub code for this type. */
pragma[nomagic]
final string getStub(Assembly assembly) {
+ this.isInAssembly(assembly) and
if this.isDuplicate(assembly)
then
result =
@@ -161,7 +162,7 @@ abstract private class GeneratedType extends Type, GeneratedElement {
if this instanceof Enum
then result = ""
else
- if exists(getAnInterestingBaseType())
+ if exists(this.getAnInterestingBaseType())
then
result =
" : " +
@@ -220,15 +221,15 @@ abstract private class GeneratedType extends Type, GeneratedElement {
}
final Type getAGeneratedType() {
- result = getAnInterestingBaseType()
+ result = this.getAnInterestingBaseType()
or
- result = getAGeneratedMember().(Callable).getReturnType()
+ result = this.getAGeneratedMember().(Callable).getReturnType()
or
- result = getAGeneratedMember().(Callable).getAParameter().getType()
+ result = this.getAGeneratedMember().(Callable).getAParameter().getType()
or
- result = getAGeneratedMember().(Property).getType()
+ result = this.getAGeneratedMember().(Property).getType()
or
- result = getAGeneratedMember().(Field).getType()
+ result = this.getAGeneratedMember().(Field).getType()
}
}
@@ -331,7 +332,8 @@ private class GeneratedNamespace extends Namespace, GeneratedElement {
final string getStubs(Assembly assembly) {
result =
- getPreamble() + getTypeStubs(assembly) + getSubNamespaceStubs(assembly) + getPostAmble()
+ this.getPreamble() + this.getTypeStubs(assembly) + this.getSubNamespaceStubs(assembly) +
+ this.getPostAmble()
}
/** Gets the `n`th generated child namespace, indexed from 0. */
@@ -358,7 +360,7 @@ private class GeneratedNamespace extends Namespace, GeneratedElement {
this.isInAssembly(assembly) and
result =
concat(GeneratedNamespace child, int i |
- child = getChildNamespace(i) and child.isInAssembly(assembly)
+ child = this.getChildNamespace(i) and child.isInAssembly(assembly)
|
child.getStubs(assembly) order by i
)
@@ -612,83 +614,18 @@ private string stubImplementation(Virtualizable c) {
}
private predicate isKeyword(string s) {
- s = "abstract" or
- s = "as" or
- s = "base" or
- s = "bool" or
- s = "break" or
- s = "byte" or
- s = "case" or
- s = "catch" or
- s = "char" or
- s = "checked" or
- s = "class" or
- s = "const" or
- s = "continue" or
- s = "decimal" or
- s = "default" or
- s = "delegate" or
- s = "do" or
- s = "double" or
- s = "else" or
- s = "enum" or
- s = "event" or
- s = "explicit" or
- s = "extern" or
- s = "false" or
- s = "finally" or
- s = "fixed" or
- s = "float" or
- s = "for" or
- s = "foreach" or
- s = "goto" or
- s = "if" or
- s = "implicit" or
- s = "in" or
- s = "int" or
- s = "interface" or
- s = "internal" or
- s = "is" or
- s = "lock" or
- s = "long" or
- s = "namespace" or
- s = "new" or
- s = "null" or
- s = "object" or
- s = "operator" or
- s = "out" or
- s = "override" or
- s = "params" or
- s = "private" or
- s = "protected" or
- s = "public" or
- s = "readonly" or
- s = "ref" or
- s = "return" or
- s = "sbyte" or
- s = "sealed" or
- s = "short" or
- s = "sizeof" or
- s = "stackalloc" or
- s = "static" or
- s = "string" or
- s = "struct" or
- s = "switch" or
- s = "this" or
- s = "throw" or
- s = "true" or
- s = "try" or
- s = "typeof" or
- s = "uint" or
- s = "ulong" or
- s = "unchecked" or
- s = "unsafe" or
- s = "ushort" or
- s = "using" or
- s = "virtual" or
- s = "void" or
- s = "volatile" or
- s = "while"
+ s =
+ [
+ "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked",
+ "class", "const", "continue", "decimal", "default", "delegate", "do", "double", "else",
+ "enum", "event", "explicit", "extern", "false", "finally", "fixed", "float", "for", "foreach",
+ "goto", "if", "implicit", "in", "int", "interface", "internal", "is", "lock", "long",
+ "namespace", "new", "null", "object", "operator", "out", "override", "params", "private",
+ "protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof",
+ "stackalloc", "static", "string", "struct", "switch", "this", "throw", "true", "try",
+ "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "virtual", "void",
+ "volatile", "while"
+ ]
}
bindingset[s]
@@ -760,7 +697,7 @@ private string stubMethod(Method m, Assembly assembly) {
if not m.getDeclaringType() instanceof Enum
then
result =
- " " + stubModifiers(m) + stubClassName(m.(Method).getReturnType()) + " " +
+ " " + stubModifiers(m) + stubClassName(m.getReturnType()) + " " +
stubExplicitImplementation(m) + escapeIfKeyword(m.getUndecoratedName()) +
stubGenericMethodParams(m) + "(" + stubParameters(m) + ")" +
stubTypeParametersConstraints(m) + stubImplementation(m) + ";\n"
diff --git a/repo-tests/codeql/csharp/ql/src/experimental/CWE-918/RequestForgery.ql b/repo-tests/codeql/csharp/ql/src/experimental/CWE-918/RequestForgery.ql
new file mode 100644
index 00000000000..8b2025c4ce2
--- /dev/null
+++ b/repo-tests/codeql/csharp/ql/src/experimental/CWE-918/RequestForgery.ql
@@ -0,0 +1,19 @@
+/**
+ * @name Uncontrolled data used in network request
+ * @description Sending network requests with user-controlled data allows for request forgery attacks.
+ * @kind path-problem
+ * @problem.severity error
+ * @precision high
+ * @id cs/request-forgery
+ * @tags security
+ * external/cwe/cwe-918
+ */
+
+import csharp
+import RequestForgery::RequestForgery
+import semmle.code.csharp.dataflow.DataFlow::DataFlow::PathGraph
+
+from RequestForgeryConfiguration c, DataFlow::PathNode source, DataFlow::PathNode sink
+where c.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "$@ flows to here and is used in a server side web request.",
+ source.getNode(), "User-provided value"
diff --git a/repo-tests/codeql/csharp/ql/src/experimental/CWE-918/RequestForgery.qll b/repo-tests/codeql/csharp/ql/src/experimental/CWE-918/RequestForgery.qll
new file mode 100644
index 00000000000..0f73e0f3d8b
--- /dev/null
+++ b/repo-tests/codeql/csharp/ql/src/experimental/CWE-918/RequestForgery.qll
@@ -0,0 +1,234 @@
+import csharp
+
+module RequestForgery {
+ import semmle.code.csharp.controlflow.Guards
+ import semmle.code.csharp.frameworks.System
+ import semmle.code.csharp.frameworks.system.Web
+ import semmle.code.csharp.frameworks.Format
+ import semmle.code.csharp.security.dataflow.flowsources.Remote
+
+ /**
+ * A data flow source for server side request forgery vulnerabilities.
+ */
+ abstract private class Source extends DataFlow::Node { }
+
+ /**
+ * A data flow sink for server side request forgery vulnerabilities.
+ */
+ abstract private class Sink extends DataFlow::ExprNode { }
+
+ /**
+ * A data flow BarrierGuard which blocks the flow of taint for
+ * server side request forgery vulnerabilities.
+ */
+ abstract private class BarrierGuard extends DataFlow::BarrierGuard { }
+
+ /**
+ * A data flow configuration for detecting server side request forgery vulnerabilities.
+ */
+ class RequestForgeryConfiguration extends DataFlow::Configuration {
+ RequestForgeryConfiguration() { this = "Server Side Request forgery" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof Source }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
+
+ override predicate isAdditionalFlowStep(DataFlow::Node prev, DataFlow::Node succ) {
+ interpolatedStringFlowStep(prev, succ)
+ or
+ stringReplaceStep(prev, succ)
+ or
+ uriCreationStep(prev, succ)
+ or
+ formatConvertStep(prev, succ)
+ or
+ toStringStep(prev, succ)
+ or
+ stringConcatStep(prev, succ)
+ or
+ stringFormatStep(prev, succ)
+ or
+ pathCombineStep(prev, succ)
+ }
+
+ override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
+ guard instanceof BarrierGuard
+ }
+ }
+
+ /**
+ * A remote data flow source taken as a source
+ * for Server Side Request Forgery(SSRF) Vulnerabilities.
+ */
+ private class RemoteFlowSourceAsSource extends Source {
+ RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource }
+ }
+
+ /**
+ * An url argument to a `HttpRequestMessage` constructor call
+ * taken as a sink for Server Side Request Forgery(SSRF) Vulnerabilities.
+ */
+ private class SystemWebHttpRequestMessageSink extends Sink {
+ SystemWebHttpRequestMessageSink() {
+ exists(Class c | c.hasQualifiedName("System.Net.Http.HttpRequestMessage") |
+ c.getAConstructor().getACall().getArgument(1) = this.asExpr()
+ )
+ }
+ }
+
+ /**
+ * An argument to a `WebRequest.Create` call taken as a
+ * sink for Server Side Request Forgery(SSRF) Vulnerabilities. *
+ */
+ private class SystemNetWebRequestCreateSink extends Sink {
+ SystemNetWebRequestCreateSink() {
+ exists(Method m |
+ m.getDeclaringType().hasQualifiedName("System.Net.WebRequest") and m.hasName("Create")
+ |
+ m.getACall().getArgument(0) = this.asExpr()
+ )
+ }
+ }
+
+ /**
+ * An argument to a new HTTP Request call of a `System.Net.Http.HttpClient` object
+ * taken as a sink for Server Side Request Forgery(SSRF) Vulnerabilities.
+ */
+ private class SystemNetHttpClientSink extends Sink {
+ SystemNetHttpClientSink() {
+ exists(Method m |
+ m.getDeclaringType().hasQualifiedName("System.Net.Http.HttpClient") and
+ m.hasName([
+ "DeleteAsync", "GetAsync", "GetByteArrayAsync", "GetStreamAsync", "GetStringAsync",
+ "PatchAsync", "PostAsync", "PutAsync"
+ ])
+ |
+ m.getACall().getArgument(0) = this.asExpr()
+ )
+ }
+ }
+
+ /**
+ * An url argument to a method call of a `System.Net.WebClient` object
+ * taken as a sink for Server Side Request Forgery(SSRF) Vulnerabilities.
+ */
+ private class SystemNetClientBaseAddressSink extends Sink {
+ SystemNetClientBaseAddressSink() {
+ exists(Property p |
+ p.hasName("BaseAddress") and
+ p.getDeclaringType()
+ .hasQualifiedName(["System.Net.WebClient", "System.Net.Http.HttpClient"])
+ |
+ p.getAnAssignedValue() = this.asExpr()
+ )
+ }
+ }
+
+ /**
+ * A method call which checks the base of the tainted uri is assumed
+ * to be a guard for Server Side Request Forgery(SSRF) Vulnerabilities.
+ * This guard considers all checks as valid.
+ */
+ private class BaseUriGuard extends BarrierGuard, MethodCall {
+ BaseUriGuard() { this.getTarget().hasQualifiedName("System.Uri", "IsBaseOf") }
+
+ override predicate checks(Expr e, AbstractValue v) {
+ // we consider any checks against the tainted value to sainitize the taint.
+ // This implies any check such as shown below block the taint flow.
+ // Uri url = new Uri("whitelist.com")
+ // if (url.isBaseOf(`taint1))
+ (e = this.getArgument(0) or e = this.getQualifier()) and
+ v.(AbstractValues::BooleanValue).getValue() = true
+ }
+ }
+
+ /**
+ * A method call which checks if the Uri starts with a white-listed string is assumed
+ * to be a guard for Server Side Request Forgery(SSRF) Vulnerabilities.
+ * This guard considers all checks as valid.
+ */
+ private class StringStartsWithBarrierGuard extends BarrierGuard, MethodCall {
+ StringStartsWithBarrierGuard() {
+ this.getTarget().hasQualifiedName("System.String", "StartsWith")
+ }
+
+ override predicate checks(Expr e, AbstractValue v) {
+ // Any check such as the ones shown below
+ // "https://myurl.com/".startsWith(`taint`)
+ // `taint`.startsWith("https://myurl.com/")
+ // are assumed to sainitize the taint
+ (e = this.getQualifier() or this.getArgument(0) = e) and
+ v.(AbstractValues::BooleanValue).getValue() = true
+ }
+ }
+
+ private predicate stringFormatStep(DataFlow::Node prev, DataFlow::Node succ) {
+ exists(FormatCall c | c.getArgument(0) = prev.asExpr() and c = succ.asExpr())
+ }
+
+ private predicate pathCombineStep(DataFlow::Node prev, DataFlow::Node succ) {
+ exists(MethodCall combineCall |
+ combineCall.getTarget().hasQualifiedName("System.IO.Path", "Combine") and
+ combineCall.getArgument(0) = prev.asExpr() and
+ combineCall = succ.asExpr()
+ )
+ }
+
+ private predicate uriCreationStep(DataFlow::Node prev, DataFlow::Node succ) {
+ exists(ObjectCreation oc |
+ oc.getTarget().getDeclaringType().hasQualifiedName("System.Uri") and
+ oc.getArgument(0) = prev.asExpr() and
+ oc = succ.asExpr()
+ )
+ }
+
+ private predicate interpolatedStringFlowStep(DataFlow::Node prev, DataFlow::Node succ) {
+ exists(InterpolatedStringExpr i |
+ // allow `$"http://{`taint`}/blabla/");"` or
+ // allow `$"https://{`taint`}/blabla/");"`
+ i.getText(0).getValue().matches(["http://", "http://"]) and
+ i.getInsert(1) = prev.asExpr() and
+ succ.asExpr() = i
+ or
+ // allow `$"{`taint`}/blabla/");"`
+ i.getInsert(0) = prev.asExpr() and
+ succ.asExpr() = i
+ )
+ }
+
+ private predicate stringReplaceStep(DataFlow::Node prev, DataFlow::Node succ) {
+ exists(MethodCall mc, SystemStringClass s |
+ mc = s.getReplaceMethod().getACall() and
+ mc.getQualifier() = prev.asExpr() and
+ succ.asExpr() = mc
+ )
+ }
+
+ private predicate stringConcatStep(DataFlow::Node prev, DataFlow::Node succ) {
+ exists(AddExpr a |
+ a.getLeftOperand() = prev.asExpr()
+ or
+ a.getRightOperand() = prev.asExpr() and
+ a.getLeftOperand().(StringLiteral).getValue() = ["http://", "https://"]
+ |
+ a = succ.asExpr()
+ )
+ }
+
+ private predicate formatConvertStep(DataFlow::Node prev, DataFlow::Node succ) {
+ exists(Method m |
+ m.hasQualifiedName("System.Convert",
+ ["FromBase64String", "FromHexString", "FromBase64CharArray"]) and
+ m.getParameter(0) = prev.asParameter() and
+ succ.asExpr() = m.getACall()
+ )
+ }
+
+ private predicate toStringStep(DataFlow::Node prev, DataFlow::Node succ) {
+ exists(MethodCall ma |
+ ma.getTarget().hasName("ToString") and
+ ma.getQualifier() = prev.asExpr() and
+ succ.asExpr() = ma
+ )
+ }
+}
diff --git a/repo-tests/codeql/csharp/ql/src/experimental/ir/IRConsistency.ql b/repo-tests/codeql/csharp/ql/src/experimental/ir/IRConsistency.ql
index 375d38ec5de..6344b237dfd 100644
--- a/repo-tests/codeql/csharp/ql/src/experimental/ir/IRConsistency.ql
+++ b/repo-tests/codeql/csharp/ql/src/experimental/ir/IRConsistency.ql
@@ -2,7 +2,7 @@
* @name IR Consistency Check
* @description Performs consistency checks on the Intermediate Representation. This query should have no results.
* @kind table
- * @id csharp/ir-consistency-check
+ * @id cs/ir-consistency-check
*/
import implementation.raw.IRConsistency
diff --git a/repo-tests/codeql/csharp/ql/src/experimental/ir/PrintIR.ql b/repo-tests/codeql/csharp/ql/src/experimental/ir/PrintIR.ql
index 1f93a39ad6f..3bc50831fd2 100644
--- a/repo-tests/codeql/csharp/ql/src/experimental/ir/PrintIR.ql
+++ b/repo-tests/codeql/csharp/ql/src/experimental/ir/PrintIR.ql
@@ -1,7 +1,7 @@
/**
* @name Print IR
* @description Outputs a representation of the IR graph
- * @id csharp/print-ir
+ * @id cs/print-ir
* @kind graph
*/
diff --git a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/IRBlock.qll b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/IRBlock.qll
index 4b86f9a7cec..bb8630a5e0c 100644
--- a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/IRBlock.qll
+++ b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/IRBlock.qll
@@ -24,7 +24,7 @@ class IRBlockBase extends TIRBlock {
final string toString() { result = getFirstInstruction(this).toString() }
/** Gets the source location of the first non-`Phi` instruction in this block. */
- final Language::Location getLocation() { result = getFirstInstruction().getLocation() }
+ final Language::Location getLocation() { result = this.getFirstInstruction().getLocation() }
/**
* INTERNAL: Do not use.
@@ -39,7 +39,7 @@ class IRBlockBase extends TIRBlock {
) and
this =
rank[result + 1](IRBlock funcBlock, int sortOverride, int sortKey1, int sortKey2 |
- funcBlock.getEnclosingFunction() = getEnclosingFunction() and
+ funcBlock.getEnclosingFunction() = this.getEnclosingFunction() and
funcBlock.getFirstInstruction().hasSortKeys(sortKey1, sortKey2) and
// Ensure that the block containing `EnterFunction` always comes first.
if funcBlock.getFirstInstruction() instanceof EnterFunctionInstruction
@@ -59,15 +59,15 @@ class IRBlockBase extends TIRBlock {
* Get the `Phi` instructions that appear at the start of this block.
*/
final PhiInstruction getAPhiInstruction() {
- Construction::getPhiInstructionBlockStart(result) = getFirstInstruction()
+ Construction::getPhiInstructionBlockStart(result) = this.getFirstInstruction()
}
/**
* Gets an instruction in this block. This includes `Phi` instructions.
*/
final Instruction getAnInstruction() {
- result = getInstruction(_) or
- result = getAPhiInstruction()
+ result = this.getInstruction(_) or
+ result = this.getAPhiInstruction()
}
/**
@@ -78,7 +78,9 @@ class IRBlockBase extends TIRBlock {
/**
* Gets the last instruction in this block.
*/
- final Instruction getLastInstruction() { result = getInstruction(getInstructionCount() - 1) }
+ final Instruction getLastInstruction() {
+ result = this.getInstruction(this.getInstructionCount() - 1)
+ }
/**
* Gets the number of non-`Phi` instructions in this block.
@@ -149,7 +151,7 @@ class IRBlock extends IRBlockBase {
* Block `A` dominates block `B` if any control flow path from the entry block of the function to
* block `B` must pass through block `A`. A block always dominates itself.
*/
- final predicate dominates(IRBlock block) { strictlyDominates(block) or this = block }
+ final predicate dominates(IRBlock block) { this.strictlyDominates(block) or this = block }
/**
* Gets a block on the dominance frontier of this block.
@@ -159,8 +161,8 @@ class IRBlock extends IRBlockBase {
*/
pragma[noinline]
final IRBlock dominanceFrontier() {
- dominates(result.getAPredecessor()) and
- not strictlyDominates(result)
+ this.dominates(result.getAPredecessor()) and
+ not this.strictlyDominates(result)
}
/**
@@ -189,7 +191,7 @@ class IRBlock extends IRBlockBase {
* Block `A` post-dominates block `B` if any control flow path from `B` to the exit block of the
* function must pass through block `A`. A block always post-dominates itself.
*/
- final predicate postDominates(IRBlock block) { strictlyPostDominates(block) or this = block }
+ final predicate postDominates(IRBlock block) { this.strictlyPostDominates(block) or this = block }
/**
* Gets a block on the post-dominance frontier of this block.
@@ -199,16 +201,16 @@ class IRBlock extends IRBlockBase {
*/
pragma[noinline]
final IRBlock postPominanceFrontier() {
- postDominates(result.getASuccessor()) and
- not strictlyPostDominates(result)
+ this.postDominates(result.getASuccessor()) and
+ not this.strictlyPostDominates(result)
}
/**
* Holds if this block is reachable from the entry block of its function.
*/
final predicate isReachableFromFunctionEntry() {
- this = getEnclosingIRFunction().getEntryBlock() or
- getAPredecessor().isReachableFromFunctionEntry()
+ this = this.getEnclosingIRFunction().getEntryBlock() or
+ this.getAPredecessor().isReachableFromFunctionEntry()
}
}
diff --git a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/IRConsistency.ql b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/IRConsistency.ql
index a35d67a23ed..342e84a2b5e 100644
--- a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/IRConsistency.ql
+++ b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/IRConsistency.ql
@@ -2,7 +2,7 @@
* @name Raw IR Consistency Check
* @description Performs consistency checks on the Intermediate Representation. This query should have no results.
* @kind table
- * @id csharp/raw-ir-consistency-check
+ * @id cs/raw-ir-consistency-check
*/
import IRConsistency
diff --git a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/Instruction.qll b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/Instruction.qll
index 6f471d8a7e8..1c2cc493338 100644
--- a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/Instruction.qll
+++ b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/Instruction.qll
@@ -41,7 +41,7 @@ class Instruction extends Construction::TStageInstruction {
}
/** Gets a textual representation of this element. */
- final string toString() { result = getOpcode().toString() + ": " + getAST().toString() }
+ final string toString() { result = this.getOpcode().toString() + ": " + this.getAST().toString() }
/**
* Gets a string showing the result, opcode, and operands of the instruction, equivalent to what
@@ -50,7 +50,8 @@ class Instruction extends Construction::TStageInstruction {
* `mu0_28(int) = Store r0_26, r0_27`
*/
final string getDumpString() {
- result = getResultString() + " = " + getOperationString() + " " + getOperandsString()
+ result =
+ this.getResultString() + " = " + this.getOperationString() + " " + this.getOperandsString()
}
private predicate shouldGenerateDumpStrings() {
@@ -66,10 +67,13 @@ class Instruction extends Construction::TStageInstruction {
* VariableAddress[x]
*/
final string getOperationString() {
- shouldGenerateDumpStrings() and
- if exists(getImmediateString())
- then result = getOperationPrefix() + getOpcode().toString() + "[" + getImmediateString() + "]"
- else result = getOperationPrefix() + getOpcode().toString()
+ this.shouldGenerateDumpStrings() and
+ if exists(this.getImmediateString())
+ then
+ result =
+ this.getOperationPrefix() + this.getOpcode().toString() + "[" + this.getImmediateString() +
+ "]"
+ else result = this.getOperationPrefix() + this.getOpcode().toString()
}
/**
@@ -78,17 +82,17 @@ class Instruction extends Construction::TStageInstruction {
string getImmediateString() { none() }
private string getOperationPrefix() {
- shouldGenerateDumpStrings() and
+ this.shouldGenerateDumpStrings() and
if this instanceof SideEffectInstruction then result = "^" else result = ""
}
private string getResultPrefix() {
- shouldGenerateDumpStrings() and
- if getResultIRType() instanceof IRVoidType
+ this.shouldGenerateDumpStrings() and
+ if this.getResultIRType() instanceof IRVoidType
then result = "v"
else
- if hasMemoryResult()
- then if isResultModeled() then result = "m" else result = "mu"
+ if this.hasMemoryResult()
+ then if this.isResultModeled() then result = "m" else result = "mu"
else result = "r"
}
@@ -97,7 +101,7 @@ class Instruction extends Construction::TStageInstruction {
* used by debugging and printing code only.
*/
int getDisplayIndexInBlock() {
- shouldGenerateDumpStrings() and
+ this.shouldGenerateDumpStrings() and
exists(IRBlock block |
this = block.getInstruction(result)
or
@@ -111,12 +115,12 @@ class Instruction extends Construction::TStageInstruction {
}
private int getLineRank() {
- shouldGenerateDumpStrings() and
+ this.shouldGenerateDumpStrings() and
this =
rank[result](Instruction instr |
instr =
- getAnInstructionAtLine(getEnclosingIRFunction(), getLocation().getFile(),
- getLocation().getStartLine())
+ getAnInstructionAtLine(this.getEnclosingIRFunction(), this.getLocation().getFile(),
+ this.getLocation().getStartLine())
|
instr order by instr.getBlock().getDisplayIndex(), instr.getDisplayIndexInBlock()
)
@@ -130,8 +134,9 @@ class Instruction extends Construction::TStageInstruction {
* Example: `r1_1`
*/
string getResultId() {
- shouldGenerateDumpStrings() and
- result = getResultPrefix() + getAST().getLocation().getStartLine() + "_" + getLineRank()
+ this.shouldGenerateDumpStrings() and
+ result =
+ this.getResultPrefix() + this.getAST().getLocation().getStartLine() + "_" + this.getLineRank()
}
/**
@@ -142,8 +147,8 @@ class Instruction extends Construction::TStageInstruction {
* Example: `r1_1(int*)`
*/
final string getResultString() {
- shouldGenerateDumpStrings() and
- result = getResultId() + "(" + getResultLanguageType().getDumpString() + ")"
+ this.shouldGenerateDumpStrings() and
+ result = this.getResultId() + "(" + this.getResultLanguageType().getDumpString() + ")"
}
/**
@@ -153,10 +158,10 @@ class Instruction extends Construction::TStageInstruction {
* Example: `func:r3_4, this:r3_5`
*/
string getOperandsString() {
- shouldGenerateDumpStrings() and
+ this.shouldGenerateDumpStrings() and
result =
concat(Operand operand |
- operand = getAnOperand()
+ operand = this.getAnOperand()
|
operand.getDumpString(), ", " order by operand.getDumpSortOrder()
)
@@ -190,7 +195,7 @@ class Instruction extends Construction::TStageInstruction {
* Gets the function that contains this instruction.
*/
final Language::Function getEnclosingFunction() {
- result = getEnclosingIRFunction().getFunction()
+ result = this.getEnclosingIRFunction().getFunction()
}
/**
@@ -208,7 +213,7 @@ class Instruction extends Construction::TStageInstruction {
/**
* Gets the location of the source code for this instruction.
*/
- final Language::Location getLocation() { result = getAST().getLocation() }
+ final Language::Location getLocation() { result = this.getAST().getLocation() }
/**
* Gets the `Expr` whose result is computed by this instruction, if any. The `Expr` may be a
@@ -243,7 +248,7 @@ class Instruction extends Construction::TStageInstruction {
* a result, its result type will be `IRVoidType`.
*/
cached
- final IRType getResultIRType() { result = getResultLanguageType().getIRType() }
+ final IRType getResultIRType() { result = this.getResultLanguageType().getIRType() }
/**
* Gets the type of the result produced by this instruction. If the
@@ -254,7 +259,7 @@ class Instruction extends Construction::TStageInstruction {
*/
final Language::Type getResultType() {
exists(Language::LanguageType resultType |
- resultType = getResultLanguageType() and
+ resultType = this.getResultLanguageType() and
(
resultType.hasUnspecifiedType(result, _)
or
@@ -283,7 +288,7 @@ class Instruction extends Construction::TStageInstruction {
* result of the `Load` instruction is a prvalue of type `int`, representing
* the integer value loaded from variable `x`.
*/
- final predicate isGLValue() { getResultLanguageType().hasType(_, true) }
+ final predicate isGLValue() { this.getResultLanguageType().hasType(_, true) }
/**
* Gets the size of the result produced by this instruction, in bytes. If the
@@ -292,7 +297,7 @@ class Instruction extends Construction::TStageInstruction {
* If `this.isGLValue()` holds for this instruction, the value of
* `getResultSize()` will always be the size of a pointer.
*/
- final int getResultSize() { result = getResultLanguageType().getByteSize() }
+ final int getResultSize() { result = this.getResultLanguageType().getByteSize() }
/**
* Gets the opcode that specifies the operation performed by this instruction.
@@ -314,14 +319,16 @@ class Instruction extends Construction::TStageInstruction {
/**
* Holds if this instruction produces a memory result.
*/
- final predicate hasMemoryResult() { exists(getResultMemoryAccess()) }
+ final predicate hasMemoryResult() { exists(this.getResultMemoryAccess()) }
/**
* Gets the kind of memory access performed by this instruction's result.
* Holds only for instructions with a memory result.
*/
pragma[inline]
- final MemoryAccessKind getResultMemoryAccess() { result = getOpcode().getWriteMemoryAccess() }
+ final MemoryAccessKind getResultMemoryAccess() {
+ result = this.getOpcode().getWriteMemoryAccess()
+ }
/**
* Holds if the memory access performed by this instruction's result will not always write to
@@ -332,7 +339,7 @@ class Instruction extends Construction::TStageInstruction {
* (for example, the global side effects of a function call).
*/
pragma[inline]
- final predicate hasResultMayMemoryAccess() { getOpcode().hasMayWriteMemoryAccess() }
+ final predicate hasResultMayMemoryAccess() { this.getOpcode().hasMayWriteMemoryAccess() }
/**
* Gets the operand that holds the memory address to which this instruction stores its
@@ -340,7 +347,7 @@ class Instruction extends Construction::TStageInstruction {
* is `r1`.
*/
final AddressOperand getResultAddressOperand() {
- getResultMemoryAccess().usesAddressOperand() and
+ this.getResultMemoryAccess().usesAddressOperand() and
result.getUse() = this
}
@@ -349,7 +356,7 @@ class Instruction extends Construction::TStageInstruction {
* result, if any. For example, in `m3 = Store r1, r2`, the result of `getResultAddressOperand()`
* is the instruction that defines `r1`.
*/
- final Instruction getResultAddress() { result = getResultAddressOperand().getDef() }
+ final Instruction getResultAddress() { result = this.getResultAddressOperand().getDef() }
/**
* Holds if the result of this instruction is precisely modeled in SSA. Always
@@ -368,7 +375,7 @@ class Instruction extends Construction::TStageInstruction {
*/
final predicate isResultModeled() {
// Register results are always in SSA form.
- not hasMemoryResult() or
+ not this.hasMemoryResult() or
Construction::hasModeledMemoryResult(this)
}
@@ -412,7 +419,7 @@ class Instruction extends Construction::TStageInstruction {
/**
* Gets all direct successors of this instruction.
*/
- final Instruction getASuccessor() { result = getSuccessor(_) }
+ final Instruction getASuccessor() { result = this.getSuccessor(_) }
/**
* Gets a predecessor of this instruction such that the predecessor reaches
@@ -423,7 +430,7 @@ class Instruction extends Construction::TStageInstruction {
/**
* Gets all direct predecessors of this instruction.
*/
- final Instruction getAPredecessor() { result = getPredecessor(_) }
+ final Instruction getAPredecessor() { result = this.getPredecessor(_) }
}
/**
@@ -543,7 +550,7 @@ class IndexedInstruction extends Instruction {
* at this instruction. This instruction has no predecessors.
*/
class EnterFunctionInstruction extends Instruction {
- EnterFunctionInstruction() { getOpcode() instanceof Opcode::EnterFunction }
+ EnterFunctionInstruction() { this.getOpcode() instanceof Opcode::EnterFunction }
}
/**
@@ -554,7 +561,7 @@ class EnterFunctionInstruction extends Instruction {
* struct, or union, see `FieldAddressInstruction`.
*/
class VariableAddressInstruction extends VariableInstruction {
- VariableAddressInstruction() { getOpcode() instanceof Opcode::VariableAddress }
+ VariableAddressInstruction() { this.getOpcode() instanceof Opcode::VariableAddress }
}
/**
@@ -566,7 +573,7 @@ class VariableAddressInstruction extends VariableInstruction {
* The result has an `IRFunctionAddress` type.
*/
class FunctionAddressInstruction extends FunctionInstruction {
- FunctionAddressInstruction() { getOpcode() instanceof Opcode::FunctionAddress }
+ FunctionAddressInstruction() { this.getOpcode() instanceof Opcode::FunctionAddress }
}
/**
@@ -577,7 +584,7 @@ class FunctionAddressInstruction extends FunctionInstruction {
* initializes that parameter.
*/
class InitializeParameterInstruction extends VariableInstruction {
- InitializeParameterInstruction() { getOpcode() instanceof Opcode::InitializeParameter }
+ InitializeParameterInstruction() { this.getOpcode() instanceof Opcode::InitializeParameter }
/**
* Gets the parameter initialized by this instruction.
@@ -603,7 +610,7 @@ class InitializeParameterInstruction extends VariableInstruction {
* initialized elsewhere, would not otherwise have a definition in this function.
*/
class InitializeNonLocalInstruction extends Instruction {
- InitializeNonLocalInstruction() { getOpcode() instanceof Opcode::InitializeNonLocal }
+ InitializeNonLocalInstruction() { this.getOpcode() instanceof Opcode::InitializeNonLocal }
}
/**
@@ -611,7 +618,7 @@ class InitializeNonLocalInstruction extends Instruction {
* with the value of that memory on entry to the function.
*/
class InitializeIndirectionInstruction extends VariableInstruction {
- InitializeIndirectionInstruction() { getOpcode() instanceof Opcode::InitializeIndirection }
+ InitializeIndirectionInstruction() { this.getOpcode() instanceof Opcode::InitializeIndirection }
/**
* Gets the parameter initialized by this instruction.
@@ -635,24 +642,24 @@ class InitializeIndirectionInstruction extends VariableInstruction {
* An instruction that initializes the `this` pointer parameter of the enclosing function.
*/
class InitializeThisInstruction extends Instruction {
- InitializeThisInstruction() { getOpcode() instanceof Opcode::InitializeThis }
+ InitializeThisInstruction() { this.getOpcode() instanceof Opcode::InitializeThis }
}
/**
* An instruction that computes the address of a non-static field of an object.
*/
class FieldAddressInstruction extends FieldInstruction {
- FieldAddressInstruction() { getOpcode() instanceof Opcode::FieldAddress }
+ FieldAddressInstruction() { this.getOpcode() instanceof Opcode::FieldAddress }
/**
* Gets the operand that provides the address of the object containing the field.
*/
- final UnaryOperand getObjectAddressOperand() { result = getAnOperand() }
+ final UnaryOperand getObjectAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the object containing the field.
*/
- final Instruction getObjectAddress() { result = getObjectAddressOperand().getDef() }
+ final Instruction getObjectAddress() { result = this.getObjectAddressOperand().getDef() }
}
/**
@@ -661,17 +668,19 @@ class FieldAddressInstruction extends FieldInstruction {
* This instruction is used for element access to C# arrays.
*/
class ElementsAddressInstruction extends UnaryInstruction {
- ElementsAddressInstruction() { getOpcode() instanceof Opcode::ElementsAddress }
+ ElementsAddressInstruction() { this.getOpcode() instanceof Opcode::ElementsAddress }
/**
* Gets the operand that provides the address of the array object.
*/
- final UnaryOperand getArrayObjectAddressOperand() { result = getAnOperand() }
+ final UnaryOperand getArrayObjectAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the array object.
*/
- final Instruction getArrayObjectAddress() { result = getArrayObjectAddressOperand().getDef() }
+ final Instruction getArrayObjectAddress() {
+ result = this.getArrayObjectAddressOperand().getDef()
+ }
}
/**
@@ -685,7 +694,7 @@ class ElementsAddressInstruction extends UnaryInstruction {
* taken may want to ignore any function that contains an `ErrorInstruction`.
*/
class ErrorInstruction extends Instruction {
- ErrorInstruction() { getOpcode() instanceof Opcode::Error }
+ ErrorInstruction() { this.getOpcode() instanceof Opcode::Error }
}
/**
@@ -695,7 +704,7 @@ class ErrorInstruction extends Instruction {
* an initializer, or whose initializer only partially initializes the variable.
*/
class UninitializedInstruction extends VariableInstruction {
- UninitializedInstruction() { getOpcode() instanceof Opcode::Uninitialized }
+ UninitializedInstruction() { this.getOpcode() instanceof Opcode::Uninitialized }
/**
* Gets the variable that is uninitialized.
@@ -710,7 +719,7 @@ class UninitializedInstruction extends VariableInstruction {
* least one instruction, even when the AST has no semantic effect.
*/
class NoOpInstruction extends Instruction {
- NoOpInstruction() { getOpcode() instanceof Opcode::NoOp }
+ NoOpInstruction() { this.getOpcode() instanceof Opcode::NoOp }
}
/**
@@ -732,32 +741,42 @@ class NoOpInstruction extends Instruction {
* `void`-returning function.
*/
class ReturnInstruction extends Instruction {
- ReturnInstruction() { getOpcode() instanceof ReturnOpcode }
+ ReturnInstruction() { this.getOpcode() instanceof ReturnOpcode }
}
/**
* An instruction that returns control to the caller of the function, without returning a value.
*/
class ReturnVoidInstruction extends ReturnInstruction {
- ReturnVoidInstruction() { getOpcode() instanceof Opcode::ReturnVoid }
+ ReturnVoidInstruction() { this.getOpcode() instanceof Opcode::ReturnVoid }
}
/**
* An instruction that returns control to the caller of the function, including a return value.
*/
class ReturnValueInstruction extends ReturnInstruction {
- ReturnValueInstruction() { getOpcode() instanceof Opcode::ReturnValue }
+ ReturnValueInstruction() { this.getOpcode() instanceof Opcode::ReturnValue }
/**
* Gets the operand that provides the value being returned by the function.
*/
- final LoadOperand getReturnValueOperand() { result = getAnOperand() }
+ final LoadOperand getReturnValueOperand() { result = this.getAnOperand() }
+
+ /**
+ * Gets the operand that provides the address of the value being returned by the function.
+ */
+ final AddressOperand getReturnAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the value being returned by the function, if an
* exact definition is available.
*/
- final Instruction getReturnValue() { result = getReturnValueOperand().getDef() }
+ final Instruction getReturnValue() { result = this.getReturnValueOperand().getDef() }
+
+ /**
+ * Gets the instruction whose result provides the address of the value being returned by the function.
+ */
+ final Instruction getReturnAddress() { result = this.getReturnAddressOperand().getDef() }
}
/**
@@ -770,28 +789,28 @@ class ReturnValueInstruction extends ReturnInstruction {
* that the caller initialized the memory pointed to by the parameter before the call.
*/
class ReturnIndirectionInstruction extends VariableInstruction {
- ReturnIndirectionInstruction() { getOpcode() instanceof Opcode::ReturnIndirection }
+ ReturnIndirectionInstruction() { this.getOpcode() instanceof Opcode::ReturnIndirection }
/**
* Gets the operand that provides the value of the pointed-to memory.
*/
- final SideEffectOperand getSideEffectOperand() { result = getAnOperand() }
+ final SideEffectOperand getSideEffectOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the value of the pointed-to memory, if an exact
* definition is available.
*/
- final Instruction getSideEffect() { result = getSideEffectOperand().getDef() }
+ final Instruction getSideEffect() { result = this.getSideEffectOperand().getDef() }
/**
* Gets the operand that provides the address of the pointed-to memory.
*/
- final AddressOperand getSourceAddressOperand() { result = getAnOperand() }
+ final AddressOperand getSourceAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the pointed-to memory.
*/
- final Instruction getSourceAddress() { result = getSourceAddressOperand().getDef() }
+ final Instruction getSourceAddress() { result = this.getSourceAddressOperand().getDef() }
/**
* Gets the parameter for which this instruction reads the final pointed-to value within the
@@ -826,7 +845,7 @@ class ReturnIndirectionInstruction extends VariableInstruction {
* - `StoreInstruction` - Copies a register operand to a memory result.
*/
class CopyInstruction extends Instruction {
- CopyInstruction() { getOpcode() instanceof CopyOpcode }
+ CopyInstruction() { this.getOpcode() instanceof CopyOpcode }
/**
* Gets the operand that provides the input value of the copy.
@@ -837,16 +856,16 @@ class CopyInstruction extends Instruction {
* Gets the instruction whose result provides the input value of the copy, if an exact definition
* is available.
*/
- final Instruction getSourceValue() { result = getSourceValueOperand().getDef() }
+ final Instruction getSourceValue() { result = this.getSourceValueOperand().getDef() }
}
/**
* An instruction that returns a register result containing a copy of its register operand.
*/
class CopyValueInstruction extends CopyInstruction, UnaryInstruction {
- CopyValueInstruction() { getOpcode() instanceof Opcode::CopyValue }
+ CopyValueInstruction() { this.getOpcode() instanceof Opcode::CopyValue }
- final override UnaryOperand getSourceValueOperand() { result = getAnOperand() }
+ final override UnaryOperand getSourceValueOperand() { result = this.getAnOperand() }
}
/**
@@ -863,47 +882,49 @@ private string getAddressOperandDescription(AddressOperand operand) {
* An instruction that returns a register result containing a copy of its memory operand.
*/
class LoadInstruction extends CopyInstruction {
- LoadInstruction() { getOpcode() instanceof Opcode::Load }
+ LoadInstruction() { this.getOpcode() instanceof Opcode::Load }
final override string getImmediateString() {
- result = getAddressOperandDescription(getSourceAddressOperand())
+ result = getAddressOperandDescription(this.getSourceAddressOperand())
}
/**
* Gets the operand that provides the address of the value being loaded.
*/
- final AddressOperand getSourceAddressOperand() { result = getAnOperand() }
+ final AddressOperand getSourceAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the value being loaded.
*/
- final Instruction getSourceAddress() { result = getSourceAddressOperand().getDef() }
+ final Instruction getSourceAddress() { result = this.getSourceAddressOperand().getDef() }
- final override LoadOperand getSourceValueOperand() { result = getAnOperand() }
+ final override LoadOperand getSourceValueOperand() { result = this.getAnOperand() }
}
/**
* An instruction that returns a memory result containing a copy of its register operand.
*/
class StoreInstruction extends CopyInstruction {
- StoreInstruction() { getOpcode() instanceof Opcode::Store }
+ StoreInstruction() { this.getOpcode() instanceof Opcode::Store }
final override string getImmediateString() {
- result = getAddressOperandDescription(getDestinationAddressOperand())
+ result = getAddressOperandDescription(this.getDestinationAddressOperand())
}
/**
* Gets the operand that provides the address of the location to which the value will be stored.
*/
- final AddressOperand getDestinationAddressOperand() { result = getAnOperand() }
+ final AddressOperand getDestinationAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the location to which the value will
* be stored, if an exact definition is available.
*/
- final Instruction getDestinationAddress() { result = getDestinationAddressOperand().getDef() }
+ final Instruction getDestinationAddress() {
+ result = this.getDestinationAddressOperand().getDef()
+ }
- final override StoreValueOperand getSourceValueOperand() { result = getAnOperand() }
+ final override StoreValueOperand getSourceValueOperand() { result = this.getAnOperand() }
}
/**
@@ -911,27 +932,27 @@ class StoreInstruction extends CopyInstruction {
* operand.
*/
class ConditionalBranchInstruction extends Instruction {
- ConditionalBranchInstruction() { getOpcode() instanceof Opcode::ConditionalBranch }
+ ConditionalBranchInstruction() { this.getOpcode() instanceof Opcode::ConditionalBranch }
/**
* Gets the operand that provides the Boolean condition controlling the branch.
*/
- final ConditionOperand getConditionOperand() { result = getAnOperand() }
+ final ConditionOperand getConditionOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the Boolean condition controlling the branch.
*/
- final Instruction getCondition() { result = getConditionOperand().getDef() }
+ final Instruction getCondition() { result = this.getConditionOperand().getDef() }
/**
* Gets the instruction to which control will flow if the condition is true.
*/
- final Instruction getTrueSuccessor() { result = getSuccessor(EdgeKind::trueEdge()) }
+ final Instruction getTrueSuccessor() { result = this.getSuccessor(EdgeKind::trueEdge()) }
/**
* Gets the instruction to which control will flow if the condition is false.
*/
- final Instruction getFalseSuccessor() { result = getSuccessor(EdgeKind::falseEdge()) }
+ final Instruction getFalseSuccessor() { result = this.getSuccessor(EdgeKind::falseEdge()) }
}
/**
@@ -943,14 +964,14 @@ class ConditionalBranchInstruction extends Instruction {
* successors.
*/
class ExitFunctionInstruction extends Instruction {
- ExitFunctionInstruction() { getOpcode() instanceof Opcode::ExitFunction }
+ ExitFunctionInstruction() { this.getOpcode() instanceof Opcode::ExitFunction }
}
/**
* An instruction whose result is a constant value.
*/
class ConstantInstruction extends ConstantValueInstruction {
- ConstantInstruction() { getOpcode() instanceof Opcode::Constant }
+ ConstantInstruction() { this.getOpcode() instanceof Opcode::Constant }
}
/**
@@ -959,7 +980,7 @@ class ConstantInstruction extends ConstantValueInstruction {
class IntegerConstantInstruction extends ConstantInstruction {
IntegerConstantInstruction() {
exists(IRType resultType |
- resultType = getResultIRType() and
+ resultType = this.getResultIRType() and
(resultType instanceof IRIntegerType or resultType instanceof IRBooleanType)
)
}
@@ -969,7 +990,7 @@ class IntegerConstantInstruction extends ConstantInstruction {
* An instruction whose result is a constant value of floating-point type.
*/
class FloatConstantInstruction extends ConstantInstruction {
- FloatConstantInstruction() { getResultIRType() instanceof IRFloatingPointType }
+ FloatConstantInstruction() { this.getResultIRType() instanceof IRFloatingPointType }
}
/**
@@ -978,7 +999,9 @@ class FloatConstantInstruction extends ConstantInstruction {
class StringConstantInstruction extends VariableInstruction {
override IRStringLiteral var;
- final override string getImmediateString() { result = Language::getStringLiteralText(getValue()) }
+ final override string getImmediateString() {
+ result = Language::getStringLiteralText(this.getValue())
+ }
/**
* Gets the string literal whose address is returned by this instruction.
@@ -990,37 +1013,37 @@ class StringConstantInstruction extends VariableInstruction {
* An instruction whose result is computed from two operands.
*/
class BinaryInstruction extends Instruction {
- BinaryInstruction() { getOpcode() instanceof BinaryOpcode }
+ BinaryInstruction() { this.getOpcode() instanceof BinaryOpcode }
/**
* Gets the left operand of this binary instruction.
*/
- final LeftOperand getLeftOperand() { result = getAnOperand() }
+ final LeftOperand getLeftOperand() { result = this.getAnOperand() }
/**
* Gets the right operand of this binary instruction.
*/
- final RightOperand getRightOperand() { result = getAnOperand() }
+ final RightOperand getRightOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the value of the left operand of this binary
* instruction.
*/
- final Instruction getLeft() { result = getLeftOperand().getDef() }
+ final Instruction getLeft() { result = this.getLeftOperand().getDef() }
/**
* Gets the instruction whose result provides the value of the right operand of this binary
* instruction.
*/
- final Instruction getRight() { result = getRightOperand().getDef() }
+ final Instruction getRight() { result = this.getRightOperand().getDef() }
/**
* Holds if this instruction's operands are `op1` and `op2`, in either order.
*/
final predicate hasOperands(Operand op1, Operand op2) {
- op1 = getLeftOperand() and op2 = getRightOperand()
+ op1 = this.getLeftOperand() and op2 = this.getRightOperand()
or
- op1 = getRightOperand() and op2 = getLeftOperand()
+ op1 = this.getRightOperand() and op2 = this.getLeftOperand()
}
}
@@ -1028,7 +1051,7 @@ class BinaryInstruction extends Instruction {
* An instruction that computes the result of an arithmetic operation.
*/
class ArithmeticInstruction extends Instruction {
- ArithmeticInstruction() { getOpcode() instanceof ArithmeticOpcode }
+ ArithmeticInstruction() { this.getOpcode() instanceof ArithmeticOpcode }
}
/**
@@ -1050,7 +1073,7 @@ class UnaryArithmeticInstruction extends ArithmeticInstruction, UnaryInstruction
* performed according to IEEE-754.
*/
class AddInstruction extends BinaryArithmeticInstruction {
- AddInstruction() { getOpcode() instanceof Opcode::Add }
+ AddInstruction() { this.getOpcode() instanceof Opcode::Add }
}
/**
@@ -1061,7 +1084,7 @@ class AddInstruction extends BinaryArithmeticInstruction {
* according to IEEE-754.
*/
class SubInstruction extends BinaryArithmeticInstruction {
- SubInstruction() { getOpcode() instanceof Opcode::Sub }
+ SubInstruction() { this.getOpcode() instanceof Opcode::Sub }
}
/**
@@ -1072,7 +1095,7 @@ class SubInstruction extends BinaryArithmeticInstruction {
* performed according to IEEE-754.
*/
class MulInstruction extends BinaryArithmeticInstruction {
- MulInstruction() { getOpcode() instanceof Opcode::Mul }
+ MulInstruction() { this.getOpcode() instanceof Opcode::Mul }
}
/**
@@ -1083,7 +1106,7 @@ class MulInstruction extends BinaryArithmeticInstruction {
* to IEEE-754.
*/
class DivInstruction extends BinaryArithmeticInstruction {
- DivInstruction() { getOpcode() instanceof Opcode::Div }
+ DivInstruction() { this.getOpcode() instanceof Opcode::Div }
}
/**
@@ -1093,7 +1116,7 @@ class DivInstruction extends BinaryArithmeticInstruction {
* division by zero or integer overflow is undefined.
*/
class RemInstruction extends BinaryArithmeticInstruction {
- RemInstruction() { getOpcode() instanceof Opcode::Rem }
+ RemInstruction() { this.getOpcode() instanceof Opcode::Rem }
}
/**
@@ -1104,14 +1127,14 @@ class RemInstruction extends BinaryArithmeticInstruction {
* is performed according to IEEE-754.
*/
class NegateInstruction extends UnaryArithmeticInstruction {
- NegateInstruction() { getOpcode() instanceof Opcode::Negate }
+ NegateInstruction() { this.getOpcode() instanceof Opcode::Negate }
}
/**
* An instruction that computes the result of a bitwise operation.
*/
class BitwiseInstruction extends Instruction {
- BitwiseInstruction() { getOpcode() instanceof BitwiseOpcode }
+ BitwiseInstruction() { this.getOpcode() instanceof BitwiseOpcode }
}
/**
@@ -1130,7 +1153,7 @@ class UnaryBitwiseInstruction extends BitwiseInstruction, UnaryInstruction { }
* Both operands must have the same integer type, which will also be the result type.
*/
class BitAndInstruction extends BinaryBitwiseInstruction {
- BitAndInstruction() { getOpcode() instanceof Opcode::BitAnd }
+ BitAndInstruction() { this.getOpcode() instanceof Opcode::BitAnd }
}
/**
@@ -1139,7 +1162,7 @@ class BitAndInstruction extends BinaryBitwiseInstruction {
* Both operands must have the same integer type, which will also be the result type.
*/
class BitOrInstruction extends BinaryBitwiseInstruction {
- BitOrInstruction() { getOpcode() instanceof Opcode::BitOr }
+ BitOrInstruction() { this.getOpcode() instanceof Opcode::BitOr }
}
/**
@@ -1148,7 +1171,7 @@ class BitOrInstruction extends BinaryBitwiseInstruction {
* Both operands must have the same integer type, which will also be the result type.
*/
class BitXorInstruction extends BinaryBitwiseInstruction {
- BitXorInstruction() { getOpcode() instanceof Opcode::BitXor }
+ BitXorInstruction() { this.getOpcode() instanceof Opcode::BitXor }
}
/**
@@ -1159,7 +1182,7 @@ class BitXorInstruction extends BinaryBitwiseInstruction {
* rightmost bits are zero-filled.
*/
class ShiftLeftInstruction extends BinaryBitwiseInstruction {
- ShiftLeftInstruction() { getOpcode() instanceof Opcode::ShiftLeft }
+ ShiftLeftInstruction() { this.getOpcode() instanceof Opcode::ShiftLeft }
}
/**
@@ -1172,7 +1195,7 @@ class ShiftLeftInstruction extends BinaryBitwiseInstruction {
* of the left operand.
*/
class ShiftRightInstruction extends BinaryBitwiseInstruction {
- ShiftRightInstruction() { getOpcode() instanceof Opcode::ShiftRight }
+ ShiftRightInstruction() { this.getOpcode() instanceof Opcode::ShiftRight }
}
/**
@@ -1183,7 +1206,7 @@ class PointerArithmeticInstruction extends BinaryInstruction {
int elementSize;
PointerArithmeticInstruction() {
- getOpcode() instanceof PointerArithmeticOpcode and
+ this.getOpcode() instanceof PointerArithmeticOpcode and
elementSize = Raw::getInstructionElementSize(this)
}
@@ -1206,7 +1229,7 @@ class PointerArithmeticInstruction extends BinaryInstruction {
* An instruction that adds or subtracts an integer offset from a pointer.
*/
class PointerOffsetInstruction extends PointerArithmeticInstruction {
- PointerOffsetInstruction() { getOpcode() instanceof PointerOffsetOpcode }
+ PointerOffsetInstruction() { this.getOpcode() instanceof PointerOffsetOpcode }
}
/**
@@ -1217,7 +1240,7 @@ class PointerOffsetInstruction extends PointerArithmeticInstruction {
* overflow is undefined.
*/
class PointerAddInstruction extends PointerOffsetInstruction {
- PointerAddInstruction() { getOpcode() instanceof Opcode::PointerAdd }
+ PointerAddInstruction() { this.getOpcode() instanceof Opcode::PointerAdd }
}
/**
@@ -1228,7 +1251,7 @@ class PointerAddInstruction extends PointerOffsetInstruction {
* pointer underflow is undefined.
*/
class PointerSubInstruction extends PointerOffsetInstruction {
- PointerSubInstruction() { getOpcode() instanceof Opcode::PointerSub }
+ PointerSubInstruction() { this.getOpcode() instanceof Opcode::PointerSub }
}
/**
@@ -1241,31 +1264,31 @@ class PointerSubInstruction extends PointerOffsetInstruction {
* undefined.
*/
class PointerDiffInstruction extends PointerArithmeticInstruction {
- PointerDiffInstruction() { getOpcode() instanceof Opcode::PointerDiff }
+ PointerDiffInstruction() { this.getOpcode() instanceof Opcode::PointerDiff }
}
/**
* An instruction whose result is computed from a single operand.
*/
class UnaryInstruction extends Instruction {
- UnaryInstruction() { getOpcode() instanceof UnaryOpcode }
+ UnaryInstruction() { this.getOpcode() instanceof UnaryOpcode }
/**
* Gets the sole operand of this instruction.
*/
- final UnaryOperand getUnaryOperand() { result = getAnOperand() }
+ final UnaryOperand getUnaryOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the sole operand of this instruction.
*/
- final Instruction getUnary() { result = getUnaryOperand().getDef() }
+ final Instruction getUnary() { result = this.getUnaryOperand().getDef() }
}
/**
* An instruction that converts the value of its operand to a value of a different type.
*/
class ConvertInstruction extends UnaryInstruction {
- ConvertInstruction() { getOpcode() instanceof Opcode::Convert }
+ ConvertInstruction() { this.getOpcode() instanceof Opcode::Convert }
}
/**
@@ -1279,7 +1302,7 @@ class ConvertInstruction extends UnaryInstruction {
* `as` expression.
*/
class CheckedConvertOrNullInstruction extends UnaryInstruction {
- CheckedConvertOrNullInstruction() { getOpcode() instanceof Opcode::CheckedConvertOrNull }
+ CheckedConvertOrNullInstruction() { this.getOpcode() instanceof Opcode::CheckedConvertOrNull }
}
/**
@@ -1293,7 +1316,7 @@ class CheckedConvertOrNullInstruction extends UnaryInstruction {
* expression.
*/
class CheckedConvertOrThrowInstruction extends UnaryInstruction {
- CheckedConvertOrThrowInstruction() { getOpcode() instanceof Opcode::CheckedConvertOrThrow }
+ CheckedConvertOrThrowInstruction() { this.getOpcode() instanceof Opcode::CheckedConvertOrThrow }
}
/**
@@ -1306,7 +1329,7 @@ class CheckedConvertOrThrowInstruction extends UnaryInstruction {
* the most-derived object.
*/
class CompleteObjectAddressInstruction extends UnaryInstruction {
- CompleteObjectAddressInstruction() { getOpcode() instanceof Opcode::CompleteObjectAddress }
+ CompleteObjectAddressInstruction() { this.getOpcode() instanceof Opcode::CompleteObjectAddress }
}
/**
@@ -1351,7 +1374,7 @@ class InheritanceConversionInstruction extends UnaryInstruction {
* An instruction that converts from the address of a derived class to the address of a base class.
*/
class ConvertToBaseInstruction extends InheritanceConversionInstruction {
- ConvertToBaseInstruction() { getOpcode() instanceof ConvertToBaseOpcode }
+ ConvertToBaseInstruction() { this.getOpcode() instanceof ConvertToBaseOpcode }
}
/**
@@ -1361,7 +1384,9 @@ class ConvertToBaseInstruction extends InheritanceConversionInstruction {
* If the operand holds a null address, the result is a null address.
*/
class ConvertToNonVirtualBaseInstruction extends ConvertToBaseInstruction {
- ConvertToNonVirtualBaseInstruction() { getOpcode() instanceof Opcode::ConvertToNonVirtualBase }
+ ConvertToNonVirtualBaseInstruction() {
+ this.getOpcode() instanceof Opcode::ConvertToNonVirtualBase
+ }
}
/**
@@ -1371,7 +1396,7 @@ class ConvertToNonVirtualBaseInstruction extends ConvertToBaseInstruction {
* If the operand holds a null address, the result is a null address.
*/
class ConvertToVirtualBaseInstruction extends ConvertToBaseInstruction {
- ConvertToVirtualBaseInstruction() { getOpcode() instanceof Opcode::ConvertToVirtualBase }
+ ConvertToVirtualBaseInstruction() { this.getOpcode() instanceof Opcode::ConvertToVirtualBase }
}
/**
@@ -1381,7 +1406,7 @@ class ConvertToVirtualBaseInstruction extends ConvertToBaseInstruction {
* If the operand holds a null address, the result is a null address.
*/
class ConvertToDerivedInstruction extends InheritanceConversionInstruction {
- ConvertToDerivedInstruction() { getOpcode() instanceof Opcode::ConvertToDerived }
+ ConvertToDerivedInstruction() { this.getOpcode() instanceof Opcode::ConvertToDerived }
}
/**
@@ -1390,7 +1415,7 @@ class ConvertToDerivedInstruction extends InheritanceConversionInstruction {
* The operand must have an integer type, which will also be the result type.
*/
class BitComplementInstruction extends UnaryBitwiseInstruction {
- BitComplementInstruction() { getOpcode() instanceof Opcode::BitComplement }
+ BitComplementInstruction() { this.getOpcode() instanceof Opcode::BitComplement }
}
/**
@@ -1399,14 +1424,14 @@ class BitComplementInstruction extends UnaryBitwiseInstruction {
* The operand must have a Boolean type, which will also be the result type.
*/
class LogicalNotInstruction extends UnaryInstruction {
- LogicalNotInstruction() { getOpcode() instanceof Opcode::LogicalNot }
+ LogicalNotInstruction() { this.getOpcode() instanceof Opcode::LogicalNot }
}
/**
* An instruction that compares two numeric operands.
*/
class CompareInstruction extends BinaryInstruction {
- CompareInstruction() { getOpcode() instanceof CompareOpcode }
+ CompareInstruction() { this.getOpcode() instanceof CompareOpcode }
}
/**
@@ -1417,7 +1442,7 @@ class CompareInstruction extends BinaryInstruction {
* unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareEQInstruction extends CompareInstruction {
- CompareEQInstruction() { getOpcode() instanceof Opcode::CompareEQ }
+ CompareEQInstruction() { this.getOpcode() instanceof Opcode::CompareEQ }
}
/**
@@ -1428,14 +1453,14 @@ class CompareEQInstruction extends CompareInstruction {
* `left == right`. Floating-point comparison is performed according to IEEE-754.
*/
class CompareNEInstruction extends CompareInstruction {
- CompareNEInstruction() { getOpcode() instanceof Opcode::CompareNE }
+ CompareNEInstruction() { this.getOpcode() instanceof Opcode::CompareNE }
}
/**
* An instruction that does a relative comparison of two values, such as `<` or `>=`.
*/
class RelationalInstruction extends CompareInstruction {
- RelationalInstruction() { getOpcode() instanceof RelationalOpcode }
+ RelationalInstruction() { this.getOpcode() instanceof RelationalOpcode }
/**
* Gets the operand on the "greater" (or "greater-or-equal") side
@@ -1467,11 +1492,11 @@ class RelationalInstruction extends CompareInstruction {
* are unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareLTInstruction extends RelationalInstruction {
- CompareLTInstruction() { getOpcode() instanceof Opcode::CompareLT }
+ CompareLTInstruction() { this.getOpcode() instanceof Opcode::CompareLT }
- override Instruction getLesser() { result = getLeft() }
+ override Instruction getLesser() { result = this.getLeft() }
- override Instruction getGreater() { result = getRight() }
+ override Instruction getGreater() { result = this.getRight() }
override predicate isStrict() { any() }
}
@@ -1484,11 +1509,11 @@ class CompareLTInstruction extends RelationalInstruction {
* are unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareGTInstruction extends RelationalInstruction {
- CompareGTInstruction() { getOpcode() instanceof Opcode::CompareGT }
+ CompareGTInstruction() { this.getOpcode() instanceof Opcode::CompareGT }
- override Instruction getLesser() { result = getRight() }
+ override Instruction getLesser() { result = this.getRight() }
- override Instruction getGreater() { result = getLeft() }
+ override Instruction getGreater() { result = this.getLeft() }
override predicate isStrict() { any() }
}
@@ -1502,11 +1527,11 @@ class CompareGTInstruction extends RelationalInstruction {
* are unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareLEInstruction extends RelationalInstruction {
- CompareLEInstruction() { getOpcode() instanceof Opcode::CompareLE }
+ CompareLEInstruction() { this.getOpcode() instanceof Opcode::CompareLE }
- override Instruction getLesser() { result = getLeft() }
+ override Instruction getLesser() { result = this.getLeft() }
- override Instruction getGreater() { result = getRight() }
+ override Instruction getGreater() { result = this.getRight() }
override predicate isStrict() { none() }
}
@@ -1520,11 +1545,11 @@ class CompareLEInstruction extends RelationalInstruction {
* are unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareGEInstruction extends RelationalInstruction {
- CompareGEInstruction() { getOpcode() instanceof Opcode::CompareGE }
+ CompareGEInstruction() { this.getOpcode() instanceof Opcode::CompareGE }
- override Instruction getLesser() { result = getRight() }
+ override Instruction getLesser() { result = this.getRight() }
- override Instruction getGreater() { result = getLeft() }
+ override Instruction getGreater() { result = this.getLeft() }
override predicate isStrict() { none() }
}
@@ -1543,78 +1568,78 @@ class CompareGEInstruction extends RelationalInstruction {
* of any case edge.
*/
class SwitchInstruction extends Instruction {
- SwitchInstruction() { getOpcode() instanceof Opcode::Switch }
+ SwitchInstruction() { this.getOpcode() instanceof Opcode::Switch }
/** Gets the operand that provides the integer value controlling the switch. */
- final ConditionOperand getExpressionOperand() { result = getAnOperand() }
+ final ConditionOperand getExpressionOperand() { result = this.getAnOperand() }
/** Gets the instruction whose result provides the integer value controlling the switch. */
- final Instruction getExpression() { result = getExpressionOperand().getDef() }
+ final Instruction getExpression() { result = this.getExpressionOperand().getDef() }
/** Gets the successor instructions along the case edges of the switch. */
- final Instruction getACaseSuccessor() { exists(CaseEdge edge | result = getSuccessor(edge)) }
+ final Instruction getACaseSuccessor() { exists(CaseEdge edge | result = this.getSuccessor(edge)) }
/** Gets the successor instruction along the default edge of the switch, if any. */
- final Instruction getDefaultSuccessor() { result = getSuccessor(EdgeKind::defaultEdge()) }
+ final Instruction getDefaultSuccessor() { result = this.getSuccessor(EdgeKind::defaultEdge()) }
}
/**
* An instruction that calls a function.
*/
class CallInstruction extends Instruction {
- CallInstruction() { getOpcode() instanceof Opcode::Call }
+ CallInstruction() { this.getOpcode() instanceof Opcode::Call }
final override string getImmediateString() {
- result = getStaticCallTarget().toString()
+ result = this.getStaticCallTarget().toString()
or
- not exists(getStaticCallTarget()) and result = "?"
+ not exists(this.getStaticCallTarget()) and result = "?"
}
/**
* Gets the operand the specifies the target function of the call.
*/
- final CallTargetOperand getCallTargetOperand() { result = getAnOperand() }
+ final CallTargetOperand getCallTargetOperand() { result = this.getAnOperand() }
/**
* Gets the `Instruction` that computes the target function of the call. This is usually a
* `FunctionAddress` instruction, but can also be an arbitrary instruction that produces a
* function pointer.
*/
- final Instruction getCallTarget() { result = getCallTargetOperand().getDef() }
+ final Instruction getCallTarget() { result = this.getCallTargetOperand().getDef() }
/**
* Gets all of the argument operands of the call, including the `this` pointer, if any.
*/
- final ArgumentOperand getAnArgumentOperand() { result = getAnOperand() }
+ final ArgumentOperand getAnArgumentOperand() { result = this.getAnOperand() }
/**
* Gets the `Function` that the call targets, if this is statically known.
*/
final Language::Function getStaticCallTarget() {
- result = getCallTarget().(FunctionAddressInstruction).getFunctionSymbol()
+ result = this.getCallTarget().(FunctionAddressInstruction).getFunctionSymbol()
}
/**
* Gets all of the arguments of the call, including the `this` pointer, if any.
*/
- final Instruction getAnArgument() { result = getAnArgumentOperand().getDef() }
+ final Instruction getAnArgument() { result = this.getAnArgumentOperand().getDef() }
/**
* Gets the `this` pointer argument operand of the call, if any.
*/
- final ThisArgumentOperand getThisArgumentOperand() { result = getAnOperand() }
+ final ThisArgumentOperand getThisArgumentOperand() { result = this.getAnOperand() }
/**
* Gets the `this` pointer argument of the call, if any.
*/
- final Instruction getThisArgument() { result = getThisArgumentOperand().getDef() }
+ final Instruction getThisArgument() { result = this.getThisArgumentOperand().getDef() }
/**
* Gets the argument operand at the specified index.
*/
pragma[noinline]
final PositionalArgumentOperand getPositionalArgumentOperand(int index) {
- result = getAnOperand() and
+ result = this.getAnOperand() and
result.getIndex() = index
}
@@ -1623,7 +1648,7 @@ class CallInstruction extends Instruction {
*/
pragma[noinline]
final Instruction getPositionalArgument(int index) {
- result = getPositionalArgumentOperand(index).getDef()
+ result = this.getPositionalArgumentOperand(index).getDef()
}
/**
@@ -1631,16 +1656,16 @@ class CallInstruction extends Instruction {
*/
pragma[noinline]
final ArgumentOperand getArgumentOperand(int index) {
- index >= 0 and result = getPositionalArgumentOperand(index)
+ index >= 0 and result = this.getPositionalArgumentOperand(index)
or
- index = -1 and result = getThisArgumentOperand()
+ index = -1 and result = this.getThisArgumentOperand()
}
/**
* Gets the argument at the specified index, or `this` if `index` is `-1`.
*/
pragma[noinline]
- final Instruction getArgument(int index) { result = getArgumentOperand(index).getDef() }
+ final Instruction getArgument(int index) { result = this.getArgumentOperand(index).getDef() }
/**
* Gets the number of arguments of the call, including the `this` pointer, if any.
@@ -1665,7 +1690,7 @@ class CallInstruction extends Instruction {
* An instruction representing a side effect of a function call.
*/
class SideEffectInstruction extends Instruction {
- SideEffectInstruction() { getOpcode() instanceof SideEffectOpcode }
+ SideEffectInstruction() { this.getOpcode() instanceof SideEffectOpcode }
/**
* Gets the instruction whose execution causes this side effect.
@@ -1680,7 +1705,7 @@ class SideEffectInstruction extends Instruction {
* accessed by that call.
*/
class CallSideEffectInstruction extends SideEffectInstruction {
- CallSideEffectInstruction() { getOpcode() instanceof Opcode::CallSideEffect }
+ CallSideEffectInstruction() { this.getOpcode() instanceof Opcode::CallSideEffect }
}
/**
@@ -1691,7 +1716,7 @@ class CallSideEffectInstruction extends SideEffectInstruction {
* call target cannot write to escaped memory.
*/
class CallReadSideEffectInstruction extends SideEffectInstruction {
- CallReadSideEffectInstruction() { getOpcode() instanceof Opcode::CallReadSideEffect }
+ CallReadSideEffectInstruction() { this.getOpcode() instanceof Opcode::CallReadSideEffect }
}
/**
@@ -1699,33 +1724,33 @@ class CallReadSideEffectInstruction extends SideEffectInstruction {
* specific parameter.
*/
class ReadSideEffectInstruction extends SideEffectInstruction, IndexedInstruction {
- ReadSideEffectInstruction() { getOpcode() instanceof ReadSideEffectOpcode }
+ ReadSideEffectInstruction() { this.getOpcode() instanceof ReadSideEffectOpcode }
/** Gets the operand for the value that will be read from this instruction, if known. */
- final SideEffectOperand getSideEffectOperand() { result = getAnOperand() }
+ final SideEffectOperand getSideEffectOperand() { result = this.getAnOperand() }
/** Gets the value that will be read from this instruction, if known. */
- final Instruction getSideEffect() { result = getSideEffectOperand().getDef() }
+ final Instruction getSideEffect() { result = this.getSideEffectOperand().getDef() }
/** Gets the operand for the address from which this instruction may read. */
- final AddressOperand getArgumentOperand() { result = getAnOperand() }
+ final AddressOperand getArgumentOperand() { result = this.getAnOperand() }
/** Gets the address from which this instruction may read. */
- final Instruction getArgumentDef() { result = getArgumentOperand().getDef() }
+ final Instruction getArgumentDef() { result = this.getArgumentOperand().getDef() }
}
/**
* An instruction representing the read of an indirect parameter within a function call.
*/
class IndirectReadSideEffectInstruction extends ReadSideEffectInstruction {
- IndirectReadSideEffectInstruction() { getOpcode() instanceof Opcode::IndirectReadSideEffect }
+ IndirectReadSideEffectInstruction() { this.getOpcode() instanceof Opcode::IndirectReadSideEffect }
}
/**
* An instruction representing the read of an indirect buffer parameter within a function call.
*/
class BufferReadSideEffectInstruction extends ReadSideEffectInstruction {
- BufferReadSideEffectInstruction() { getOpcode() instanceof Opcode::BufferReadSideEffect }
+ BufferReadSideEffectInstruction() { this.getOpcode() instanceof Opcode::BufferReadSideEffect }
}
/**
@@ -1733,18 +1758,18 @@ class BufferReadSideEffectInstruction extends ReadSideEffectInstruction {
*/
class SizedBufferReadSideEffectInstruction extends ReadSideEffectInstruction {
SizedBufferReadSideEffectInstruction() {
- getOpcode() instanceof Opcode::SizedBufferReadSideEffect
+ this.getOpcode() instanceof Opcode::SizedBufferReadSideEffect
}
/**
* Gets the operand that holds the number of bytes read from the buffer.
*/
- final BufferSizeOperand getBufferSizeOperand() { result = getAnOperand() }
+ final BufferSizeOperand getBufferSizeOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the number of bytes read from the buffer.
*/
- final Instruction getBufferSize() { result = getBufferSizeOperand().getDef() }
+ final Instruction getBufferSize() { result = this.getBufferSizeOperand().getDef() }
}
/**
@@ -1752,17 +1777,17 @@ class SizedBufferReadSideEffectInstruction extends ReadSideEffectInstruction {
* specific parameter.
*/
class WriteSideEffectInstruction extends SideEffectInstruction, IndexedInstruction {
- WriteSideEffectInstruction() { getOpcode() instanceof WriteSideEffectOpcode }
+ WriteSideEffectInstruction() { this.getOpcode() instanceof WriteSideEffectOpcode }
/**
* Get the operand that holds the address of the memory to be written.
*/
- final AddressOperand getDestinationAddressOperand() { result = getAnOperand() }
+ final AddressOperand getDestinationAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the memory to be written.
*/
- Instruction getDestinationAddress() { result = getDestinationAddressOperand().getDef() }
+ Instruction getDestinationAddress() { result = this.getDestinationAddressOperand().getDef() }
}
/**
@@ -1770,7 +1795,7 @@ class WriteSideEffectInstruction extends SideEffectInstruction, IndexedInstructi
*/
class IndirectMustWriteSideEffectInstruction extends WriteSideEffectInstruction {
IndirectMustWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::IndirectMustWriteSideEffect
+ this.getOpcode() instanceof Opcode::IndirectMustWriteSideEffect
}
}
@@ -1780,7 +1805,7 @@ class IndirectMustWriteSideEffectInstruction extends WriteSideEffectInstruction
*/
class BufferMustWriteSideEffectInstruction extends WriteSideEffectInstruction {
BufferMustWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::BufferMustWriteSideEffect
+ this.getOpcode() instanceof Opcode::BufferMustWriteSideEffect
}
}
@@ -1790,18 +1815,18 @@ class BufferMustWriteSideEffectInstruction extends WriteSideEffectInstruction {
*/
class SizedBufferMustWriteSideEffectInstruction extends WriteSideEffectInstruction {
SizedBufferMustWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::SizedBufferMustWriteSideEffect
+ this.getOpcode() instanceof Opcode::SizedBufferMustWriteSideEffect
}
/**
* Gets the operand that holds the number of bytes written to the buffer.
*/
- final BufferSizeOperand getBufferSizeOperand() { result = getAnOperand() }
+ final BufferSizeOperand getBufferSizeOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the number of bytes written to the buffer.
*/
- final Instruction getBufferSize() { result = getBufferSizeOperand().getDef() }
+ final Instruction getBufferSize() { result = this.getBufferSizeOperand().getDef() }
}
/**
@@ -1812,7 +1837,7 @@ class SizedBufferMustWriteSideEffectInstruction extends WriteSideEffectInstructi
*/
class IndirectMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
IndirectMayWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::IndirectMayWriteSideEffect
+ this.getOpcode() instanceof Opcode::IndirectMayWriteSideEffect
}
}
@@ -1822,7 +1847,9 @@ class IndirectMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
* Unlike `BufferWriteSideEffectInstruction`, the buffer might not be completely overwritten.
*/
class BufferMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
- BufferMayWriteSideEffectInstruction() { getOpcode() instanceof Opcode::BufferMayWriteSideEffect }
+ BufferMayWriteSideEffectInstruction() {
+ this.getOpcode() instanceof Opcode::BufferMayWriteSideEffect
+ }
}
/**
@@ -1832,18 +1859,18 @@ class BufferMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
*/
class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
SizedBufferMayWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::SizedBufferMayWriteSideEffect
+ this.getOpcode() instanceof Opcode::SizedBufferMayWriteSideEffect
}
/**
* Gets the operand that holds the number of bytes written to the buffer.
*/
- final BufferSizeOperand getBufferSizeOperand() { result = getAnOperand() }
+ final BufferSizeOperand getBufferSizeOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the number of bytes written to the buffer.
*/
- final Instruction getBufferSize() { result = getBufferSizeOperand().getDef() }
+ final Instruction getBufferSize() { result = this.getBufferSizeOperand().getDef() }
}
/**
@@ -1852,80 +1879,80 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio
*/
class InitializeDynamicAllocationInstruction extends SideEffectInstruction {
InitializeDynamicAllocationInstruction() {
- getOpcode() instanceof Opcode::InitializeDynamicAllocation
+ this.getOpcode() instanceof Opcode::InitializeDynamicAllocation
}
/**
* Gets the operand that represents the address of the allocation this instruction is initializing.
*/
- final AddressOperand getAllocationAddressOperand() { result = getAnOperand() }
+ final AddressOperand getAllocationAddressOperand() { result = this.getAnOperand() }
/**
* Gets the address for the allocation this instruction is initializing.
*/
- final Instruction getAllocationAddress() { result = getAllocationAddressOperand().getDef() }
+ final Instruction getAllocationAddress() { result = this.getAllocationAddressOperand().getDef() }
}
/**
* An instruction representing a GNU or MSVC inline assembly statement.
*/
class InlineAsmInstruction extends Instruction {
- InlineAsmInstruction() { getOpcode() instanceof Opcode::InlineAsm }
+ InlineAsmInstruction() { this.getOpcode() instanceof Opcode::InlineAsm }
}
/**
* An instruction that throws an exception.
*/
class ThrowInstruction extends Instruction {
- ThrowInstruction() { getOpcode() instanceof ThrowOpcode }
+ ThrowInstruction() { this.getOpcode() instanceof ThrowOpcode }
}
/**
* An instruction that throws a new exception.
*/
class ThrowValueInstruction extends ThrowInstruction {
- ThrowValueInstruction() { getOpcode() instanceof Opcode::ThrowValue }
+ ThrowValueInstruction() { this.getOpcode() instanceof Opcode::ThrowValue }
/**
* Gets the address operand of the exception thrown by this instruction.
*/
- final AddressOperand getExceptionAddressOperand() { result = getAnOperand() }
+ final AddressOperand getExceptionAddressOperand() { result = this.getAnOperand() }
/**
* Gets the address of the exception thrown by this instruction.
*/
- final Instruction getExceptionAddress() { result = getExceptionAddressOperand().getDef() }
+ final Instruction getExceptionAddress() { result = this.getExceptionAddressOperand().getDef() }
/**
* Gets the operand for the exception thrown by this instruction.
*/
- final LoadOperand getExceptionOperand() { result = getAnOperand() }
+ final LoadOperand getExceptionOperand() { result = this.getAnOperand() }
/**
* Gets the exception thrown by this instruction.
*/
- final Instruction getException() { result = getExceptionOperand().getDef() }
+ final Instruction getException() { result = this.getExceptionOperand().getDef() }
}
/**
* An instruction that re-throws the current exception.
*/
class ReThrowInstruction extends ThrowInstruction {
- ReThrowInstruction() { getOpcode() instanceof Opcode::ReThrow }
+ ReThrowInstruction() { this.getOpcode() instanceof Opcode::ReThrow }
}
/**
* An instruction that exits the current function by propagating an exception.
*/
class UnwindInstruction extends Instruction {
- UnwindInstruction() { getOpcode() instanceof Opcode::Unwind }
+ UnwindInstruction() { this.getOpcode() instanceof Opcode::Unwind }
}
/**
* An instruction that starts a `catch` handler.
*/
class CatchInstruction extends Instruction {
- CatchInstruction() { getOpcode() instanceof CatchOpcode }
+ CatchInstruction() { this.getOpcode() instanceof CatchOpcode }
}
/**
@@ -1935,7 +1962,7 @@ class CatchByTypeInstruction extends CatchInstruction {
Language::LanguageType exceptionType;
CatchByTypeInstruction() {
- getOpcode() instanceof Opcode::CatchByType and
+ this.getOpcode() instanceof Opcode::CatchByType and
exceptionType = Raw::getInstructionExceptionType(this)
}
@@ -1951,21 +1978,21 @@ class CatchByTypeInstruction extends CatchInstruction {
* An instruction that catches any exception.
*/
class CatchAnyInstruction extends CatchInstruction {
- CatchAnyInstruction() { getOpcode() instanceof Opcode::CatchAny }
+ CatchAnyInstruction() { this.getOpcode() instanceof Opcode::CatchAny }
}
/**
* An instruction that initializes all escaped memory.
*/
class AliasedDefinitionInstruction extends Instruction {
- AliasedDefinitionInstruction() { getOpcode() instanceof Opcode::AliasedDefinition }
+ AliasedDefinitionInstruction() { this.getOpcode() instanceof Opcode::AliasedDefinition }
}
/**
* An instruction that consumes all escaped memory on exit from the function.
*/
class AliasedUseInstruction extends Instruction {
- AliasedUseInstruction() { getOpcode() instanceof Opcode::AliasedUse }
+ AliasedUseInstruction() { this.getOpcode() instanceof Opcode::AliasedUse }
}
/**
@@ -1979,7 +2006,7 @@ class AliasedUseInstruction extends Instruction {
* runtime.
*/
class PhiInstruction extends Instruction {
- PhiInstruction() { getOpcode() instanceof Opcode::Phi }
+ PhiInstruction() { this.getOpcode() instanceof Opcode::Phi }
/**
* Gets all of the instruction's `PhiInputOperand`s, representing the values that flow from each predecessor block.
@@ -2047,29 +2074,29 @@ class PhiInstruction extends Instruction {
* https://link.springer.com/content/pdf/10.1007%2F3-540-61053-7_66.pdf.
*/
class ChiInstruction extends Instruction {
- ChiInstruction() { getOpcode() instanceof Opcode::Chi }
+ ChiInstruction() { this.getOpcode() instanceof Opcode::Chi }
/**
* Gets the operand that represents the previous state of all memory that might be aliased by the
* memory write.
*/
- final ChiTotalOperand getTotalOperand() { result = getAnOperand() }
+ final ChiTotalOperand getTotalOperand() { result = this.getAnOperand() }
/**
* Gets the operand that represents the previous state of all memory that might be aliased by the
* memory write.
*/
- final Instruction getTotal() { result = getTotalOperand().getDef() }
+ final Instruction getTotal() { result = this.getTotalOperand().getDef() }
/**
* Gets the operand that represents the new value written by the memory write.
*/
- final ChiPartialOperand getPartialOperand() { result = getAnOperand() }
+ final ChiPartialOperand getPartialOperand() { result = this.getAnOperand() }
/**
* Gets the operand that represents the new value written by the memory write.
*/
- final Instruction getPartial() { result = getPartialOperand().getDef() }
+ final Instruction getPartial() { result = this.getPartialOperand().getDef() }
/**
* Gets the bit range `[startBit, endBit)` updated by the partial operand of this `ChiInstruction`, relative to the start address of the total operand.
@@ -2093,7 +2120,7 @@ class ChiInstruction extends Instruction {
* or `Switch` instruction where that particular edge is infeasible.
*/
class UnreachedInstruction extends Instruction {
- UnreachedInstruction() { getOpcode() instanceof Opcode::Unreached }
+ UnreachedInstruction() { this.getOpcode() instanceof Opcode::Unreached }
}
/**
@@ -2106,7 +2133,7 @@ class BuiltInOperationInstruction extends Instruction {
Language::BuiltInOperation operation;
BuiltInOperationInstruction() {
- getOpcode() instanceof BuiltInOperationOpcode and
+ this.getOpcode() instanceof BuiltInOperationOpcode and
operation = Raw::getInstructionBuiltInOperation(this)
}
@@ -2122,9 +2149,9 @@ class BuiltInOperationInstruction extends Instruction {
* actual operation is specified by the `getBuiltInOperation()` predicate.
*/
class BuiltInInstruction extends BuiltInOperationInstruction {
- BuiltInInstruction() { getOpcode() instanceof Opcode::BuiltIn }
+ BuiltInInstruction() { this.getOpcode() instanceof Opcode::BuiltIn }
- final override string getImmediateString() { result = getBuiltInOperation().toString() }
+ final override string getImmediateString() { result = this.getBuiltInOperation().toString() }
}
/**
@@ -2135,7 +2162,7 @@ class BuiltInInstruction extends BuiltInOperationInstruction {
* to the `...` parameter.
*/
class VarArgsStartInstruction extends UnaryInstruction {
- VarArgsStartInstruction() { getOpcode() instanceof Opcode::VarArgsStart }
+ VarArgsStartInstruction() { this.getOpcode() instanceof Opcode::VarArgsStart }
}
/**
@@ -2145,7 +2172,7 @@ class VarArgsStartInstruction extends UnaryInstruction {
* a result.
*/
class VarArgsEndInstruction extends UnaryInstruction {
- VarArgsEndInstruction() { getOpcode() instanceof Opcode::VarArgsEnd }
+ VarArgsEndInstruction() { this.getOpcode() instanceof Opcode::VarArgsEnd }
}
/**
@@ -2155,7 +2182,7 @@ class VarArgsEndInstruction extends UnaryInstruction {
* argument.
*/
class VarArgInstruction extends UnaryInstruction {
- VarArgInstruction() { getOpcode() instanceof Opcode::VarArg }
+ VarArgInstruction() { this.getOpcode() instanceof Opcode::VarArg }
}
/**
@@ -2166,7 +2193,7 @@ class VarArgInstruction extends UnaryInstruction {
* argument of the `...` parameter.
*/
class NextVarArgInstruction extends UnaryInstruction {
- NextVarArgInstruction() { getOpcode() instanceof Opcode::NextVarArg }
+ NextVarArgInstruction() { this.getOpcode() instanceof Opcode::NextVarArg }
}
/**
@@ -2180,5 +2207,5 @@ class NextVarArgInstruction extends UnaryInstruction {
* The result is the address of the newly allocated object.
*/
class NewObjInstruction extends Instruction {
- NewObjInstruction() { getOpcode() instanceof Opcode::NewObj }
+ NewObjInstruction() { this.getOpcode() instanceof Opcode::NewObj }
}
diff --git a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/Operand.qll b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/Operand.qll
index d7cf89ca9aa..85d217bd361 100644
--- a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/Operand.qll
+++ b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/Operand.qll
@@ -46,12 +46,12 @@ class Operand extends TStageOperand {
/**
* Gets the location of the source code for this operand.
*/
- final Language::Location getLocation() { result = getUse().getLocation() }
+ final Language::Location getLocation() { result = this.getUse().getLocation() }
/**
* Gets the function that contains this operand.
*/
- final IRFunction getEnclosingIRFunction() { result = getUse().getEnclosingIRFunction() }
+ final IRFunction getEnclosingIRFunction() { result = this.getUse().getEnclosingIRFunction() }
/**
* Gets the `Instruction` that consumes this operand.
@@ -74,7 +74,7 @@ class Operand extends TStageOperand {
*/
final Instruction getDef() {
result = this.getAnyDef() and
- getDefinitionOverlap() instanceof MustExactlyOverlap
+ this.getDefinitionOverlap() instanceof MustExactlyOverlap
}
/**
@@ -82,7 +82,7 @@ class Operand extends TStageOperand {
*
* Gets the `Instruction` that consumes this operand.
*/
- deprecated final Instruction getUseInstruction() { result = getUse() }
+ deprecated final Instruction getUseInstruction() { result = this.getUse() }
/**
* DEPRECATED: use `getAnyDef` or `getDef`. The exact replacement for this
@@ -91,7 +91,7 @@ class Operand extends TStageOperand {
*
* Gets the `Instruction` whose result is the value of the operand.
*/
- deprecated final Instruction getDefinitionInstruction() { result = getAnyDef() }
+ deprecated final Instruction getDefinitionInstruction() { result = this.getAnyDef() }
/**
* Gets the overlap relationship between the operand's definition and its use.
@@ -101,7 +101,9 @@ class Operand extends TStageOperand {
/**
* Holds if the result of the definition instruction does not exactly overlap this use.
*/
- final predicate isDefinitionInexact() { not getDefinitionOverlap() instanceof MustExactlyOverlap }
+ final predicate isDefinitionInexact() {
+ not this.getDefinitionOverlap() instanceof MustExactlyOverlap
+ }
/**
* Gets a prefix to use when dumping the operand in an operand list.
@@ -121,7 +123,7 @@ class Operand extends TStageOperand {
* For example: `this:r3_5`
*/
final string getDumpString() {
- result = getDumpLabel() + getInexactSpecifier() + getDefinitionId()
+ result = this.getDumpLabel() + this.getInexactSpecifier() + this.getDefinitionId()
}
/**
@@ -129,9 +131,9 @@ class Operand extends TStageOperand {
* definition is not modeled in SSA.
*/
private string getDefinitionId() {
- result = getAnyDef().getResultId()
+ result = this.getAnyDef().getResultId()
or
- not exists(getAnyDef()) and result = "m?"
+ not exists(this.getAnyDef()) and result = "m?"
}
/**
@@ -140,7 +142,7 @@ class Operand extends TStageOperand {
* the empty string.
*/
private string getInexactSpecifier() {
- if isDefinitionInexact() then result = "~" else result = ""
+ if this.isDefinitionInexact() then result = "~" else result = ""
}
/**
@@ -155,7 +157,7 @@ class Operand extends TStageOperand {
* the definition type, such as in the case of a partial read or a read from a pointer that
* has been cast to a different type.
*/
- Language::LanguageType getLanguageType() { result = getAnyDef().getResultLanguageType() }
+ Language::LanguageType getLanguageType() { result = this.getAnyDef().getResultLanguageType() }
/**
* Gets the language-neutral type of the value consumed by this operand. This is usually the same
@@ -164,7 +166,7 @@ class Operand extends TStageOperand {
* from the definition type, such as in the case of a partial read or a read from a pointer that
* has been cast to a different type.
*/
- final IRType getIRType() { result = getLanguageType().getIRType() }
+ final IRType getIRType() { result = this.getLanguageType().getIRType() }
/**
* Gets the type of the value consumed by this operand. This is usually the same as the
@@ -173,7 +175,7 @@ class Operand extends TStageOperand {
* the definition type, such as in the case of a partial read or a read from a pointer that
* has been cast to a different type.
*/
- final Language::Type getType() { getLanguageType().hasType(result, _) }
+ final Language::Type getType() { this.getLanguageType().hasType(result, _) }
/**
* Holds if the value consumed by this operand is a glvalue. If this
@@ -182,13 +184,13 @@ class Operand extends TStageOperand {
* not hold, the value of the operand represents a value whose type is
* given by `getType()`.
*/
- final predicate isGLValue() { getLanguageType().hasType(_, true) }
+ final predicate isGLValue() { this.getLanguageType().hasType(_, true) }
/**
* Gets the size of the value consumed by this operand, in bytes. If the operand does not have
* a known constant size, this predicate does not hold.
*/
- final int getSize() { result = getLanguageType().getByteSize() }
+ final int getSize() { result = this.getLanguageType().getByteSize() }
}
/**
@@ -205,7 +207,7 @@ class MemoryOperand extends Operand {
/**
* Gets the kind of memory access performed by the operand.
*/
- MemoryAccessKind getMemoryAccess() { result = getUse().getOpcode().getReadMemoryAccess() }
+ MemoryAccessKind getMemoryAccess() { result = this.getUse().getOpcode().getReadMemoryAccess() }
/**
* Holds if the memory access performed by this operand will not always read from every bit in the
@@ -215,7 +217,7 @@ class MemoryOperand extends Operand {
* conservative estimate of the memory that might actually be accessed at runtime (for example,
* the global side effects of a function call).
*/
- predicate hasMayReadMemoryAccess() { getUse().getOpcode().hasMayReadMemoryAccess() }
+ predicate hasMayReadMemoryAccess() { this.getUse().getOpcode().hasMayReadMemoryAccess() }
/**
* Returns the operand that holds the memory address from which the current operand loads its
@@ -223,8 +225,8 @@ class MemoryOperand extends Operand {
* is `r1`.
*/
final AddressOperand getAddressOperand() {
- getMemoryAccess().usesAddressOperand() and
- result.getUse() = getUse()
+ this.getMemoryAccess().usesAddressOperand() and
+ result.getUse() = this.getUse()
}
}
@@ -294,7 +296,7 @@ class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand, TNonPhiMemoryOpe
result = unique(Instruction defInstr | hasDefinition(defInstr, _))
}
- final override Overlap getDefinitionOverlap() { hasDefinition(_, result) }
+ final override Overlap getDefinitionOverlap() { this.hasDefinition(_, result) }
pragma[noinline]
private predicate hasDefinition(Instruction defInstr, Overlap overlap) {
@@ -449,13 +451,17 @@ class PhiInputOperand extends MemoryOperand, TPhiOperand {
final override Overlap getDefinitionOverlap() { result = overlap }
- final override int getDumpSortOrder() { result = 11 + getPredecessorBlock().getDisplayIndex() }
-
- final override string getDumpLabel() {
- result = "from " + getPredecessorBlock().getDisplayIndex().toString() + ":"
+ final override int getDumpSortOrder() {
+ result = 11 + this.getPredecessorBlock().getDisplayIndex()
}
- final override string getDumpId() { result = getPredecessorBlock().getDisplayIndex().toString() }
+ final override string getDumpLabel() {
+ result = "from " + this.getPredecessorBlock().getDisplayIndex().toString() + ":"
+ }
+
+ final override string getDumpId() {
+ result = this.getPredecessorBlock().getDisplayIndex().toString()
+ }
/**
* Gets the predecessor block from which this value comes.
diff --git a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/PrintIR.ql b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/PrintIR.ql
index 21ef4de1eae..ac77496f283 100644
--- a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/PrintIR.ql
+++ b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/PrintIR.ql
@@ -1,7 +1,7 @@
/**
* @name Print Raw IR
* @description Outputs a representation of the Raw IR graph
- * @id csharp/print-raw-ir
+ * @id cs/print-raw-ir
* @kind graph
*/
diff --git a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedCondition.qll b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedCondition.qll
index a172800b377..99833c70d0b 100644
--- a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedCondition.qll
+++ b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedCondition.qll
@@ -139,13 +139,13 @@ class TranslatedLogicalOrExpr extends TranslatedBinaryLogicalOperation {
override LogicalOrExpr expr;
override Instruction getChildTrueSuccessor(ConditionBase child) {
- child = getAnOperand() and
+ child = this.getAnOperand() and
result = this.getConditionContext().getChildTrueSuccessor(this)
}
override Instruction getChildFalseSuccessor(ConditionBase child) {
child = this.getLeftOperand() and
- result = getRightOperand().getFirstInstruction()
+ result = this.getRightOperand().getFirstInstruction()
or
child = this.getRightOperand() and
result = this.getConditionContext().getChildFalseSuccessor(this)
diff --git a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedDeclaration.qll b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedDeclaration.qll
index 86cbdbb4360..9b4fbbba723 100644
--- a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedDeclaration.qll
+++ b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedDeclaration.qll
@@ -48,7 +48,7 @@ class TranslatedLocalVariableDeclaration extends TranslatedLocalDeclaration,
override LocalVariable getDeclVar() { result = var }
- override Type getVarType() { result = getVariableType(getDeclVar()) }
+ override Type getVarType() { result = getVariableType(this.getDeclVar()) }
override Type getTargetType() { result = getVariableType(var) }
@@ -58,7 +58,7 @@ class TranslatedLocalVariableDeclaration extends TranslatedLocalDeclaration,
or
this.hasUninitializedInstruction() and tag = InitializerStoreTag()
) and
- result = getIRUserVariable(getFunction(), getDeclVar())
+ result = getIRUserVariable(this.getFunction(), this.getDeclVar())
}
override TranslatedInitialization getInitialization() {
diff --git a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedElement.qll b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedElement.qll
index 04e05dc9814..ea1ad7931cb 100644
--- a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedElement.qll
+++ b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedElement.qll
@@ -456,7 +456,7 @@ abstract class TranslatedElement extends TTranslatedElement {
* there is no enclosing `try`.
*/
Instruction getExceptionSuccessorInstruction() {
- result = getParent().getExceptionSuccessorInstruction()
+ result = this.getParent().getExceptionSuccessorInstruction()
}
/**
@@ -558,7 +558,7 @@ abstract class TranslatedElement extends TTranslatedElement {
* Gets the temporary variable generated by this element with tag `tag`.
*/
final IRTempVariable getTempVariable(TempVariableTag tag) {
- result.getAST() = getAST() and
+ result.getAST() = this.getAST() and
result.getTag() = tag and
this.hasTempVariable(tag, _)
}
diff --git a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedExpr.qll b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedExpr.qll
index 72c408a3f2a..4f4c1a5f073 100644
--- a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedExpr.qll
+++ b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedExpr.qll
@@ -98,7 +98,7 @@ abstract class TranslatedCoreExpr extends TranslatedExpr {
}
final CSharpType getResultCSharpType() {
- if isResultLValue() = true
+ if this.isResultLValue() = true
then result = getTypeForGLValue(expr.getType())
else result = getTypeForPRValue(expr.getType())
}
@@ -138,18 +138,18 @@ class TranslatedConditionValue extends TranslatedCoreExpr, ConditionContext,
tag = ConditionValueFalseConstantTag()
) and
opcode instanceof Opcode::Constant and
- resultType = getResultCSharpType()
+ resultType = this.getResultCSharpType()
or
(
tag = ConditionValueTrueStoreTag() or
tag = ConditionValueFalseStoreTag()
) and
opcode instanceof Opcode::Store and
- resultType = getResultCSharpType()
+ resultType = this.getResultCSharpType()
or
tag = ConditionValueResultLoadTag() and
opcode instanceof Opcode::Load and
- resultType = getResultCSharpType()
+ resultType = this.getResultCSharpType()
}
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
@@ -258,7 +258,7 @@ class TranslatedLoad extends TranslatedExpr, TTranslatedLoad {
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CSharpType resultType) {
tag = LoadTag() and
opcode instanceof Opcode::Load and
- if producesExprResult()
+ if this.producesExprResult()
then resultType = getTypeForPRValue(expr.getType())
else resultType = getTypeForGLValue(expr.getType())
}
@@ -542,7 +542,7 @@ class TranslatedArrayAccess extends TranslatedNonConstantExpr {
}
final override TranslatedElement getChild(int id) {
- id = -1 and result = getBaseOperand()
+ id = -1 and result = this.getBaseOperand()
or
result = this.getOffsetOperand(id)
}
@@ -559,7 +559,7 @@ class TranslatedArrayAccess extends TranslatedNonConstantExpr {
or
// The successor of the last `PointerAdd` instruction is
// the successor of the `TranslatedArrayAccess`.
- tag = PointerAddTag(getRank() - 1) and
+ tag = PointerAddTag(this.getRank() - 1) and
result = this.getParent().getChildSuccessor(this)
or
// The successor of an `ElementsAddress` instruction is
@@ -582,27 +582,29 @@ class TranslatedArrayAccess extends TranslatedNonConstantExpr {
result = this.getInstruction(PointerAddTag(child.getAST().getIndex()))
}
- override Instruction getResult() { result = this.getInstruction(PointerAddTag(getRank() - 1)) }
+ override Instruction getResult() {
+ result = this.getInstruction(PointerAddTag(this.getRank() - 1))
+ }
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CSharpType resultType) {
exists(int index |
- inBounds(index) and
+ this.inBounds(index) and
tag = PointerAddTag(index) and
opcode instanceof Opcode::PointerAdd and
- resultType = getTypeForPRValue(getArrayOfDim(getRank() - index, expr.getType()))
+ resultType = getTypeForPRValue(getArrayOfDim(this.getRank() - index, expr.getType()))
)
or
exists(int index |
- inBounds(index) and
+ this.inBounds(index) and
tag = ElementsAddressTag(index) and
opcode instanceof Opcode::ElementsAddress and
- resultType = getTypeForPRValue(getArrayOfDim(getRank() - index, expr.getType()))
+ resultType = getTypeForPRValue(getArrayOfDim(this.getRank() - index, expr.getType()))
)
}
override Instruction getInstructionOperand(InstructionTag tag, OperandTag operandTag) {
exists(int index |
- inBounds(index) and
+ this.inBounds(index) and
tag = PointerAddTag(index) and
(
operandTag instanceof LeftOperandTag and
@@ -632,7 +634,7 @@ class TranslatedArrayAccess extends TranslatedNonConstantExpr {
override int getInstructionElementSize(InstructionTag tag) {
exists(int index |
- inBounds(index) and
+ this.inBounds(index) and
tag = PointerAddTag(index) and
result = Language::getTypeSize(expr.getQualifier().getType().(ArrayType).getElementType())
)
@@ -989,9 +991,9 @@ abstract class TranslatedSingleInstructionExpr extends TranslatedNonConstantExpr
abstract Opcode getOpcode();
final override predicate hasInstruction(Opcode opcode, InstructionTag tag, CSharpType resultType) {
- opcode = getOpcode() and
+ opcode = this.getOpcode() and
tag = OnlyInstructionTag() and
- resultType = getResultCSharpType()
+ resultType = this.getResultCSharpType()
}
final override Instruction getResult() { result = this.getInstruction(OnlyInstructionTag()) }
@@ -1081,7 +1083,7 @@ class TranslatedCast extends TranslatedNonConstantExpr {
)
}
- private TranslatedExpr getOperand() { result = getTranslatedExpr(expr.(Cast).getExpr()) }
+ private TranslatedExpr getOperand() { result = getTranslatedExpr(expr.getExpr()) }
private Opcode getOpcode() {
expr instanceof CastExpr and result instanceof Opcode::CheckedConvertOrThrow
@@ -1181,15 +1183,15 @@ class TranslatedBinaryOperation extends TranslatedSingleInstructionExpr {
}
override Opcode getOpcode() {
- result = binaryArithmeticOpcode(expr.(BinaryArithmeticOperation)) or
- result = binaryBitwiseOpcode(expr.(BinaryBitwiseOperation)) or
- result = comparisonOpcode(expr.(ComparisonOperation))
+ result = binaryArithmeticOpcode(expr) or
+ result = binaryBitwiseOpcode(expr) or
+ result = comparisonOpcode(expr)
}
override int getInstructionElementSize(InstructionTag tag) {
tag = OnlyInstructionTag() and
exists(Opcode opcode |
- opcode = getOpcode() and
+ opcode = this.getOpcode() and
(
opcode instanceof Opcode::PointerAdd or
opcode instanceof Opcode::PointerSub or
@@ -1200,7 +1202,9 @@ class TranslatedBinaryOperation extends TranslatedSingleInstructionExpr {
}
private TranslatedExpr getPointerOperand() {
- if swapOperandsOnOp() then result = this.getRightOperand() else result = this.getLeftOperand()
+ if this.swapOperandsOnOp()
+ then result = this.getRightOperand()
+ else result = this.getLeftOperand()
}
private predicate swapOperandsOnOp() {
@@ -1425,7 +1429,7 @@ class TranslatedAssignOperation extends TranslatedAssignment {
resultType = getTypeForPRValue(this.getLeftOperand().getResultType())
or
tag = AssignOperationOpTag() and
- opcode = getOpcode() and
+ opcode = this.getOpcode() and
resultType = getTypeForPRValue(this.getConvertedLeftOperandType())
or
tag = AssignmentStoreTag() and
@@ -1452,7 +1456,7 @@ class TranslatedAssignOperation extends TranslatedAssignment {
opcode instanceof Opcode::PointerSub
)
) and
- result = Language::getTypeSize(getResultType().(PointerType).getReferentType())
+ result = Language::getTypeSize(this.getResultType().(PointerType).getReferentType())
}
override Instruction getInstructionOperand(InstructionTag tag, OperandTag operandTag) {
@@ -1799,7 +1803,7 @@ class TranslatedIsExpr extends TranslatedNonConstantExpr {
result = this.getInstruction(GeneratedConstantTag())
)
or
- hasVar() and
+ this.hasVar() and
tag = GeneratedBranchTag() and
operandTag instanceof ConditionOperandTag and
result = this.getInstruction(GeneratedNEQTag())
@@ -1848,7 +1852,7 @@ class TranslatedLambdaExpr extends TranslatedNonConstantExpr, InitializationCont
}
override Instruction getChildSuccessor(TranslatedElement child) {
- child = getInitialization() and
+ child = this.getInitialization() and
result = this.getInstruction(LoadTag())
}
@@ -1922,7 +1926,7 @@ class TranslatedDelegateCall extends TranslatedNonConstantExpr {
override Instruction getChildSuccessor(TranslatedElement child) {
child = this.getInvokeCall() and
- result = getParent().getChildSuccessor(this)
+ result = this.getParent().getChildSuccessor(this)
}
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CSharpType resultType) {
@@ -1973,7 +1977,7 @@ abstract class TranslatedCreation extends TranslatedCoreExpr, TTranslatedCreatio
else result = this.getInstruction(NewObjTag())
}
- override Instruction getReceiver() { result = getInstruction(NewObjTag()) }
+ override Instruction getReceiver() { result = this.getInstruction(NewObjTag()) }
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
kind instanceof GotoEdge and
@@ -1998,11 +2002,11 @@ abstract class TranslatedCreation extends TranslatedCoreExpr, TTranslatedCreatio
child = this.getConstructorCall() and
if exists(this.getInitializerExpr())
then result = this.getInitializerExpr().getFirstInstruction()
- else result = getLoadOrChildSuccessor()
+ else result = this.getLoadOrChildSuccessor()
)
or
child = this.getInitializerExpr() and
- result = getLoadOrChildSuccessor()
+ result = this.getLoadOrChildSuccessor()
}
private Instruction getLoadOrChildSuccessor() {
diff --git a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedFunction.qll b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedFunction.qll
index 65488a1b95d..94b48b0985d 100644
--- a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedFunction.qll
+++ b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedFunction.qll
@@ -68,20 +68,20 @@ class TranslatedFunction extends TranslatedElement, TTranslatedFunction {
or
(
tag = AliasedDefinitionTag() and
- if exists(getThisType())
+ if exists(this.getThisType())
then result = this.getInstruction(InitializeThisTag())
else
- if exists(getParameter(0))
+ if exists(this.getParameter(0))
then result = this.getParameter(0).getFirstInstruction()
else result = this.getBodyOrReturn()
)
or
(
tag = InitializeThisTag() and
- if exists(getParameter(0))
+ if exists(this.getParameter(0))
then result = this.getParameter(0).getFirstInstruction()
else
- if exists(getConstructorInitializer())
+ if exists(this.getConstructorInitializer())
then result = this.getConstructorInitializer().getFirstInstruction()
else result = this.getBodyOrReturn()
)
@@ -106,7 +106,7 @@ class TranslatedFunction extends TranslatedElement, TTranslatedFunction {
if exists(callable.getParameter(paramIndex + 1))
then result = this.getParameter(paramIndex + 1).getFirstInstruction()
else
- if exists(getConstructorInitializer())
+ if exists(this.getConstructorInitializer())
then result = this.getConstructorInitializer().getFirstInstruction()
else result = this.getBodyOrReturn()
)
@@ -136,12 +136,12 @@ class TranslatedFunction extends TranslatedElement, TTranslatedFunction {
or
tag = InitializeThisTag() and
opcode instanceof Opcode::InitializeThis and
- resultType = getTypeForGLValue(getThisType())
+ resultType = getTypeForGLValue(this.getThisType())
or
tag = ReturnValueAddressTag() and
opcode instanceof Opcode::VariableAddress and
- not getReturnType() instanceof VoidType and
- resultType = getTypeForGLValue(getReturnType())
+ not this.getReturnType() instanceof VoidType and
+ resultType = getTypeForGLValue(this.getReturnType())
or
(
tag = ReturnTag() and
@@ -201,7 +201,7 @@ class TranslatedFunction extends TranslatedElement, TTranslatedFunction {
final override predicate hasTempVariable(TempVariableTag tag, CSharpType type) {
tag = ReturnValueTempVar() and
type = getTypeForPRValue(this.getReturnType()) and
- not getReturnType() instanceof VoidType
+ not this.getReturnType() instanceof VoidType
}
/**
@@ -320,7 +320,7 @@ class TranslatedParameter extends TranslatedElement, TTranslatedParameter {
tag = InitializerStoreTag() or
tag = InitializerVariableAddressTag()
) and
- result = getIRUserVariable(getFunction(), param)
+ result = getIRUserVariable(this.getFunction(), param)
}
final override Instruction getInstructionOperand(InstructionTag tag, OperandTag operandTag) {
diff --git a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedInitialization.qll b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedInitialization.qll
index cbe0e7c1d2a..77e41c15e72 100644
--- a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedInitialization.qll
+++ b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedInitialization.qll
@@ -139,7 +139,7 @@ class TranslatedDirectInitialization extends TranslatedInitialization {
opcode instanceof Opcode::Store and
resultType = getTypeForPRValue(this.getContext().getTargetType())
or
- needsConversion() and
+ this.needsConversion() and
tag = AssignmentConvertRightTag() and
// For now only use `Opcode::Convert` to
// crudely represent conversions. Could
@@ -153,9 +153,9 @@ class TranslatedDirectInitialization extends TranslatedInitialization {
result = this.getParent().getChildSuccessor(this) and
kind instanceof GotoEdge
or
- needsConversion() and
+ this.needsConversion() and
tag = AssignmentConvertRightTag() and
- result = getInstruction(InitializerStoreTag()) and
+ result = this.getInstruction(InitializerStoreTag()) and
kind instanceof GotoEdge
}
@@ -203,7 +203,7 @@ abstract class TranslatedElementInitialization extends TranslatedElement {
ArrayInitializer initList;
final override string toString() {
- result = initList.toString() + "[" + getElementIndex().toString() + "]"
+ result = initList.toString() + "[" + this.getElementIndex().toString() + "]"
}
final override Language::AST getAST() { result = initList }
@@ -211,54 +211,54 @@ abstract class TranslatedElementInitialization extends TranslatedElement {
final override Callable getFunction() { result = initList.getEnclosingCallable() }
final override Instruction getFirstInstruction() {
- result = this.getInstruction(getElementIndexTag())
+ result = this.getInstruction(this.getElementIndexTag())
}
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CSharpType resultType) {
- tag = getElementIndexTag() and
+ tag = this.getElementIndexTag() and
opcode instanceof Opcode::Constant and
resultType = getIntType()
or
- tag = getElementAddressTag() and
+ tag = this.getElementAddressTag() and
opcode instanceof Opcode::PointerAdd and
- resultType = getTypeForGLValue(getElementType())
+ resultType = getTypeForGLValue(this.getElementType())
}
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
- tag = getElementIndexTag() and
- result = this.getInstruction(getElementAddressTag()) and
+ tag = this.getElementIndexTag() and
+ result = this.getInstruction(this.getElementAddressTag()) and
kind instanceof GotoEdge
}
override Instruction getInstructionOperand(InstructionTag tag, OperandTag operandTag) {
- tag = getElementAddressTag() and
+ tag = this.getElementAddressTag() and
(
operandTag instanceof LeftOperandTag and
result = this.getParent().(InitializationContext).getTargetAddress()
or
operandTag instanceof RightOperandTag and
- result = this.getInstruction(getElementIndexTag())
+ result = this.getInstruction(this.getElementIndexTag())
)
}
override int getInstructionElementSize(InstructionTag tag) {
- tag = getElementAddressTag() and
- result = Language::getTypeSize(getElementType())
+ tag = this.getElementAddressTag() and
+ result = Language::getTypeSize(this.getElementType())
}
override string getInstructionConstantValue(InstructionTag tag) {
- tag = getElementIndexTag() and
- result = getElementIndex().toString()
+ tag = this.getElementIndexTag() and
+ result = this.getElementIndex().toString()
}
abstract int getElementIndex();
final InstructionTag getElementAddressTag() {
- result = InitializerElementAddressTag(getElementIndex())
+ result = InitializerElementAddressTag(this.getElementIndex())
}
final InstructionTag getElementIndexTag() {
- result = InitializerElementIndexTag(getElementIndex())
+ result = InitializerElementIndexTag(this.getElementIndex())
}
final ArrayInitializer getInitList() { result = initList }
@@ -278,14 +278,16 @@ class TranslatedExplicitElementInitialization extends TranslatedElementInitializ
this = TTranslatedExplicitElementInitialization(initList, elementIndex)
}
- override Instruction getTargetAddress() { result = this.getInstruction(getElementAddressTag()) }
+ override Instruction getTargetAddress() {
+ result = this.getInstruction(this.getElementAddressTag())
+ }
- override Type getTargetType() { result = getElementType() }
+ override Type getTargetType() { result = this.getElementType() }
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
result = TranslatedElementInitialization.super.getInstructionSuccessor(tag, kind)
or
- tag = getElementAddressTag() and
+ tag = this.getElementAddressTag() and
result = this.getInitialization().getFirstInstruction() and
kind instanceof GotoEdge
}
@@ -340,7 +342,7 @@ class TranslatedConstructorInitializer extends TranslatedConstructorCallFromCons
override string toString() { result = "constructor init: " + call.toString() }
override Instruction getFirstInstruction() {
- if needsConversion()
+ if this.needsConversion()
then result = this.getInstruction(OnlyInstructionTag())
else result = this.getConstructorCall().getFirstInstruction()
}
@@ -361,13 +363,13 @@ class TranslatedConstructorInitializer extends TranslatedConstructorCallFromCons
override Instruction getReceiver() {
if this.needsConversion()
then result = this.getInstruction(OnlyInstructionTag())
- else result = getTranslatedFunction(getFunction()).getInitializeThisInstruction()
+ else result = getTranslatedFunction(this.getFunction()).getInitializeThisInstruction()
}
override Instruction getInstructionOperand(InstructionTag tag, OperandTag operandTag) {
tag = OnlyInstructionTag() and
operandTag instanceof UnaryOperandTag and
- result = getTranslatedFunction(getFunction()).getInitializeThisInstruction()
+ result = getTranslatedFunction(this.getFunction()).getInitializeThisInstruction()
}
predicate needsConversion() {
diff --git a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedStmt.qll b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedStmt.qll
index 81de9a6b7c9..ac69a9c0f28 100644
--- a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedStmt.qll
+++ b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/raw/internal/TranslatedStmt.qll
@@ -79,7 +79,7 @@ class TranslatedDeclStmt extends TranslatedStmt {
override Instruction getChildSuccessor(TranslatedElement child) {
exists(int index |
child = this.getLocalDeclaration(index) and
- if index = (getChildCount() - 1)
+ if index = (this.getChildCount() - 1)
then result = this.getParent().getChildSuccessor(this)
else result = this.getLocalDeclaration(index + 1).getFirstInstruction()
)
@@ -89,7 +89,7 @@ class TranslatedDeclStmt extends TranslatedStmt {
class TranslatedExprStmt extends TranslatedStmt {
override ExprStmt stmt;
- TranslatedExpr getExpr() { result = getTranslatedExpr(stmt.(ExprStmt).getExpr()) }
+ TranslatedExpr getExpr() { result = getTranslatedExpr(stmt.getExpr()) }
override TranslatedElement getChild(int id) { id = 0 and result = this.getExpr() }
@@ -123,7 +123,7 @@ class TranslatedExprStmtAccessorSet extends TranslatedExprStmt {
}
override TranslatedExpr getExpr() {
- result = getTranslatedExpr(stmt.(ExprStmt).getExpr().(AssignExpr).getLValue())
+ result = getTranslatedExpr(stmt.getExpr().(AssignExpr).getLValue())
}
override TranslatedElement getChild(int id) { id = 0 and result = this.getExpr() }
@@ -276,14 +276,14 @@ class TranslatedBlock extends TranslatedStmt {
override TranslatedElement getChild(int id) { result = this.getStmt(id) }
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CSharpType resultType) {
- isEmpty() and
+ this.isEmpty() and
opcode instanceof Opcode::NoOp and
tag = OnlyInstructionTag() and
resultType = getVoidType()
}
override Instruction getFirstInstruction() {
- if isEmpty()
+ if this.isEmpty()
then result = this.getInstruction(OnlyInstructionTag())
else result = this.getStmt(0).getFirstInstruction()
}
@@ -303,7 +303,7 @@ class TranslatedBlock extends TranslatedStmt {
override Instruction getChildSuccessor(TranslatedElement child) {
exists(int index |
child = this.getStmt(index) and
- if index = (getStmtCount() - 1)
+ if index = (this.getStmtCount() - 1)
then result = this.getParent().getChildSuccessor(this)
else result = this.getStmt(index + 1).getFirstInstruction()
)
@@ -347,7 +347,7 @@ class TranslatedCatchByTypeClause extends TranslatedClause {
}
override TranslatedElement getChild(int id) {
- id = 0 and result = getParameter()
+ id = 0 and result = this.getParameter()
or
result = super.getChild(id)
}
@@ -355,14 +355,14 @@ class TranslatedCatchByTypeClause extends TranslatedClause {
override Instruction getChildSuccessor(TranslatedElement child) {
result = super.getChildSuccessor(child)
or
- child = getParameter() and result = this.getBlock().getFirstInstruction()
+ child = this.getParameter() and result = this.getBlock().getFirstInstruction()
}
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = CatchTag() and
(
kind instanceof GotoEdge and
- result = getParameter().getFirstInstruction()
+ result = this.getParameter().getFirstInstruction()
or
kind instanceof ExceptionEdge and
result = this.getParent().(TranslatedTryStmt).getNextHandler(this)
@@ -559,8 +559,8 @@ abstract class TranslatedLoop extends TranslatedStmt, ConditionContext {
final TranslatedStmt getBody() { result = getTranslatedStmt(stmt.getBody()) }
final Instruction getFirstConditionInstruction() {
- if hasCondition()
- then result = getCondition().getFirstInstruction()
+ if this.hasCondition()
+ then result = this.getCondition().getFirstInstruction()
else result = this.getBody().getFirstInstruction()
}
@@ -611,13 +611,13 @@ class TranslatedForStmt extends TranslatedLoop {
override ForStmt stmt;
override TranslatedElement getChild(int id) {
- initializerIndex(id) and result = this.getDeclAndInit(id)
+ this.initializerIndex(id) and result = this.getDeclAndInit(id)
or
- result = this.getUpdate(updateIndex(id))
+ result = this.getUpdate(this.updateIndex(id))
or
- id = initializersNo() + updatesNo() and result = this.getCondition()
+ id = this.initializersNo() + this.updatesNo() and result = this.getCondition()
or
- id = initializersNo() + updatesNo() + 1 and result = this.getBody()
+ id = this.initializersNo() + this.updatesNo() + 1 and result = this.getBody()
}
private TranslatedElement getDeclAndInit(int index) {
@@ -636,11 +636,11 @@ class TranslatedForStmt extends TranslatedLoop {
private int updatesNo() { result = count(stmt.getAnUpdate()) }
- private predicate initializerIndex(int index) { index in [0 .. initializersNo() - 1] }
+ private predicate initializerIndex(int index) { index in [0 .. this.initializersNo() - 1] }
private int updateIndex(int index) {
- result in [0 .. updatesNo() - 1] and
- index = initializersNo() + result
+ result in [0 .. this.updatesNo() - 1] and
+ index = this.initializersNo() + result
}
override Instruction getFirstInstruction() {
@@ -652,11 +652,11 @@ class TranslatedForStmt extends TranslatedLoop {
override Instruction getChildSuccessor(TranslatedElement child) {
exists(int index |
child = this.getDeclAndInit(index) and
- index < initializersNo() - 1 and
+ index < this.initializersNo() - 1 and
result = this.getDeclAndInit(index + 1).getFirstInstruction()
)
or
- child = this.getDeclAndInit(initializersNo() - 1) and
+ child = this.getDeclAndInit(this.initializersNo() - 1) and
result = this.getFirstConditionInstruction()
or
(
@@ -671,7 +671,7 @@ class TranslatedForStmt extends TranslatedLoop {
result = this.getUpdate(index + 1).getFirstInstruction()
)
or
- child = this.getUpdate(updatesNo() - 1) and
+ child = this.getUpdate(this.updatesNo() - 1) and
result = this.getFirstConditionInstruction()
}
}
@@ -693,7 +693,7 @@ abstract class TranslatedSpecificJump extends TranslatedStmt {
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = OnlyInstructionTag() and
kind instanceof GotoEdge and
- result = getTargetInstruction()
+ result = this.getTargetInstruction()
}
override Instruction getChildSuccessor(TranslatedElement child) { none() }
@@ -832,7 +832,7 @@ class TranslatedSwitchStmt extends TranslatedStmt {
not exists(stmt.getDefaultCase()) and
tag = SwitchBranchTag() and
kind instanceof DefaultEdge and
- result = getParent().getChildSuccessor(this)
+ result = this.getParent().getChildSuccessor(this)
}
private EdgeKind getCaseEdge(CaseStmt caseStmt) {
@@ -862,19 +862,21 @@ class TranslatedEnumeratorForeach extends TranslatedLoop {
override ForeachStmt stmt;
override TranslatedElement getChild(int id) {
- id = 0 and result = getTempEnumDecl()
+ id = 0 and result = this.getTempEnumDecl()
or
- id = 1 and result = getTry()
+ id = 1 and result = this.getTry()
}
- override Instruction getFirstInstruction() { result = getTempEnumDecl().getFirstInstruction() }
+ override Instruction getFirstInstruction() {
+ result = this.getTempEnumDecl().getFirstInstruction()
+ }
override Instruction getChildSuccessor(TranslatedElement child) {
- child = getTempEnumDecl() and
- result = getTry().getFirstInstruction()
+ child = this.getTempEnumDecl() and
+ result = this.getTry().getFirstInstruction()
or
- child = getTry() and
- result = getParent().getChildSuccessor(this)
+ child = this.getTry() and
+ result = this.getParent().getChildSuccessor(this)
}
private TranslatedElement getTry() { result = ForeachElements::getTry(stmt) }
@@ -909,9 +911,9 @@ class TranslatedFixedStmt extends TranslatedStmt {
override FixedStmt stmt;
override TranslatedElement getChild(int id) {
- result = getDecl(id)
+ result = this.getDecl(id)
or
- id = noDecls() and result = this.getBody()
+ id = this.noDecls() and result = this.getBody()
}
override Instruction getFirstInstruction() { result = this.getDecl(0).getFirstInstruction() }
@@ -947,24 +949,26 @@ class TranslatedLockStmt extends TranslatedStmt {
override LockStmt stmt;
override TranslatedElement getChild(int id) {
- id = 0 and result = getLockedVarDecl()
+ id = 0 and result = this.getLockedVarDecl()
or
- id = 1 and result = getLockWasTakenDecl()
+ id = 1 and result = this.getLockWasTakenDecl()
or
- id = 2 and result = getTry()
+ id = 2 and result = this.getTry()
}
- override Instruction getFirstInstruction() { result = getLockedVarDecl().getFirstInstruction() }
+ override Instruction getFirstInstruction() {
+ result = this.getLockedVarDecl().getFirstInstruction()
+ }
override Instruction getChildSuccessor(TranslatedElement child) {
- child = getLockedVarDecl() and
- result = getLockWasTakenDecl().getFirstInstruction()
+ child = this.getLockedVarDecl() and
+ result = this.getLockWasTakenDecl().getFirstInstruction()
or
- child = getLockWasTakenDecl() and
- result = getTry().getFirstInstruction()
+ child = this.getLockWasTakenDecl() and
+ result = this.getTry().getFirstInstruction()
or
- child = getTry() and
- result = getParent().getChildSuccessor(this)
+ child = this.getTry() and
+ result = this.getParent().getChildSuccessor(this)
}
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CSharpType resultType) {
@@ -1017,13 +1021,13 @@ class TranslatedUsingBlockStmt extends TranslatedStmt {
override UsingBlockStmt stmt;
override TranslatedElement getChild(int id) {
- result = getDecl(id)
+ result = this.getDecl(id)
or
- id = getNumberOfDecls() and result = this.getBody()
+ id = this.getNumberOfDecls() and result = this.getBody()
}
override Instruction getFirstInstruction() {
- if getNumberOfDecls() > 0
+ if this.getNumberOfDecls() > 0
then result = this.getDecl(0).getFirstInstruction()
else result = this.getBody().getFirstInstruction()
}
@@ -1060,7 +1064,7 @@ class TranslatedUsingBlockStmt extends TranslatedStmt {
class TranslatedUsingDeclStmt extends TranslatedStmt {
override UsingDeclStmt stmt;
- override TranslatedElement getChild(int id) { result = getDecl(id) }
+ override TranslatedElement getChild(int id) { result = this.getDecl(id) }
override Instruction getFirstInstruction() { result = this.getDecl(0).getFirstInstruction() }
diff --git a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/IRBlock.qll b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/IRBlock.qll
index 4b86f9a7cec..bb8630a5e0c 100644
--- a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/IRBlock.qll
+++ b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/IRBlock.qll
@@ -24,7 +24,7 @@ class IRBlockBase extends TIRBlock {
final string toString() { result = getFirstInstruction(this).toString() }
/** Gets the source location of the first non-`Phi` instruction in this block. */
- final Language::Location getLocation() { result = getFirstInstruction().getLocation() }
+ final Language::Location getLocation() { result = this.getFirstInstruction().getLocation() }
/**
* INTERNAL: Do not use.
@@ -39,7 +39,7 @@ class IRBlockBase extends TIRBlock {
) and
this =
rank[result + 1](IRBlock funcBlock, int sortOverride, int sortKey1, int sortKey2 |
- funcBlock.getEnclosingFunction() = getEnclosingFunction() and
+ funcBlock.getEnclosingFunction() = this.getEnclosingFunction() and
funcBlock.getFirstInstruction().hasSortKeys(sortKey1, sortKey2) and
// Ensure that the block containing `EnterFunction` always comes first.
if funcBlock.getFirstInstruction() instanceof EnterFunctionInstruction
@@ -59,15 +59,15 @@ class IRBlockBase extends TIRBlock {
* Get the `Phi` instructions that appear at the start of this block.
*/
final PhiInstruction getAPhiInstruction() {
- Construction::getPhiInstructionBlockStart(result) = getFirstInstruction()
+ Construction::getPhiInstructionBlockStart(result) = this.getFirstInstruction()
}
/**
* Gets an instruction in this block. This includes `Phi` instructions.
*/
final Instruction getAnInstruction() {
- result = getInstruction(_) or
- result = getAPhiInstruction()
+ result = this.getInstruction(_) or
+ result = this.getAPhiInstruction()
}
/**
@@ -78,7 +78,9 @@ class IRBlockBase extends TIRBlock {
/**
* Gets the last instruction in this block.
*/
- final Instruction getLastInstruction() { result = getInstruction(getInstructionCount() - 1) }
+ final Instruction getLastInstruction() {
+ result = this.getInstruction(this.getInstructionCount() - 1)
+ }
/**
* Gets the number of non-`Phi` instructions in this block.
@@ -149,7 +151,7 @@ class IRBlock extends IRBlockBase {
* Block `A` dominates block `B` if any control flow path from the entry block of the function to
* block `B` must pass through block `A`. A block always dominates itself.
*/
- final predicate dominates(IRBlock block) { strictlyDominates(block) or this = block }
+ final predicate dominates(IRBlock block) { this.strictlyDominates(block) or this = block }
/**
* Gets a block on the dominance frontier of this block.
@@ -159,8 +161,8 @@ class IRBlock extends IRBlockBase {
*/
pragma[noinline]
final IRBlock dominanceFrontier() {
- dominates(result.getAPredecessor()) and
- not strictlyDominates(result)
+ this.dominates(result.getAPredecessor()) and
+ not this.strictlyDominates(result)
}
/**
@@ -189,7 +191,7 @@ class IRBlock extends IRBlockBase {
* Block `A` post-dominates block `B` if any control flow path from `B` to the exit block of the
* function must pass through block `A`. A block always post-dominates itself.
*/
- final predicate postDominates(IRBlock block) { strictlyPostDominates(block) or this = block }
+ final predicate postDominates(IRBlock block) { this.strictlyPostDominates(block) or this = block }
/**
* Gets a block on the post-dominance frontier of this block.
@@ -199,16 +201,16 @@ class IRBlock extends IRBlockBase {
*/
pragma[noinline]
final IRBlock postPominanceFrontier() {
- postDominates(result.getASuccessor()) and
- not strictlyPostDominates(result)
+ this.postDominates(result.getASuccessor()) and
+ not this.strictlyPostDominates(result)
}
/**
* Holds if this block is reachable from the entry block of its function.
*/
final predicate isReachableFromFunctionEntry() {
- this = getEnclosingIRFunction().getEntryBlock() or
- getAPredecessor().isReachableFromFunctionEntry()
+ this = this.getEnclosingIRFunction().getEntryBlock() or
+ this.getAPredecessor().isReachableFromFunctionEntry()
}
}
diff --git a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Instruction.qll b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Instruction.qll
index 6f471d8a7e8..1c2cc493338 100644
--- a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Instruction.qll
+++ b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Instruction.qll
@@ -41,7 +41,7 @@ class Instruction extends Construction::TStageInstruction {
}
/** Gets a textual representation of this element. */
- final string toString() { result = getOpcode().toString() + ": " + getAST().toString() }
+ final string toString() { result = this.getOpcode().toString() + ": " + this.getAST().toString() }
/**
* Gets a string showing the result, opcode, and operands of the instruction, equivalent to what
@@ -50,7 +50,8 @@ class Instruction extends Construction::TStageInstruction {
* `mu0_28(int) = Store r0_26, r0_27`
*/
final string getDumpString() {
- result = getResultString() + " = " + getOperationString() + " " + getOperandsString()
+ result =
+ this.getResultString() + " = " + this.getOperationString() + " " + this.getOperandsString()
}
private predicate shouldGenerateDumpStrings() {
@@ -66,10 +67,13 @@ class Instruction extends Construction::TStageInstruction {
* VariableAddress[x]
*/
final string getOperationString() {
- shouldGenerateDumpStrings() and
- if exists(getImmediateString())
- then result = getOperationPrefix() + getOpcode().toString() + "[" + getImmediateString() + "]"
- else result = getOperationPrefix() + getOpcode().toString()
+ this.shouldGenerateDumpStrings() and
+ if exists(this.getImmediateString())
+ then
+ result =
+ this.getOperationPrefix() + this.getOpcode().toString() + "[" + this.getImmediateString() +
+ "]"
+ else result = this.getOperationPrefix() + this.getOpcode().toString()
}
/**
@@ -78,17 +82,17 @@ class Instruction extends Construction::TStageInstruction {
string getImmediateString() { none() }
private string getOperationPrefix() {
- shouldGenerateDumpStrings() and
+ this.shouldGenerateDumpStrings() and
if this instanceof SideEffectInstruction then result = "^" else result = ""
}
private string getResultPrefix() {
- shouldGenerateDumpStrings() and
- if getResultIRType() instanceof IRVoidType
+ this.shouldGenerateDumpStrings() and
+ if this.getResultIRType() instanceof IRVoidType
then result = "v"
else
- if hasMemoryResult()
- then if isResultModeled() then result = "m" else result = "mu"
+ if this.hasMemoryResult()
+ then if this.isResultModeled() then result = "m" else result = "mu"
else result = "r"
}
@@ -97,7 +101,7 @@ class Instruction extends Construction::TStageInstruction {
* used by debugging and printing code only.
*/
int getDisplayIndexInBlock() {
- shouldGenerateDumpStrings() and
+ this.shouldGenerateDumpStrings() and
exists(IRBlock block |
this = block.getInstruction(result)
or
@@ -111,12 +115,12 @@ class Instruction extends Construction::TStageInstruction {
}
private int getLineRank() {
- shouldGenerateDumpStrings() and
+ this.shouldGenerateDumpStrings() and
this =
rank[result](Instruction instr |
instr =
- getAnInstructionAtLine(getEnclosingIRFunction(), getLocation().getFile(),
- getLocation().getStartLine())
+ getAnInstructionAtLine(this.getEnclosingIRFunction(), this.getLocation().getFile(),
+ this.getLocation().getStartLine())
|
instr order by instr.getBlock().getDisplayIndex(), instr.getDisplayIndexInBlock()
)
@@ -130,8 +134,9 @@ class Instruction extends Construction::TStageInstruction {
* Example: `r1_1`
*/
string getResultId() {
- shouldGenerateDumpStrings() and
- result = getResultPrefix() + getAST().getLocation().getStartLine() + "_" + getLineRank()
+ this.shouldGenerateDumpStrings() and
+ result =
+ this.getResultPrefix() + this.getAST().getLocation().getStartLine() + "_" + this.getLineRank()
}
/**
@@ -142,8 +147,8 @@ class Instruction extends Construction::TStageInstruction {
* Example: `r1_1(int*)`
*/
final string getResultString() {
- shouldGenerateDumpStrings() and
- result = getResultId() + "(" + getResultLanguageType().getDumpString() + ")"
+ this.shouldGenerateDumpStrings() and
+ result = this.getResultId() + "(" + this.getResultLanguageType().getDumpString() + ")"
}
/**
@@ -153,10 +158,10 @@ class Instruction extends Construction::TStageInstruction {
* Example: `func:r3_4, this:r3_5`
*/
string getOperandsString() {
- shouldGenerateDumpStrings() and
+ this.shouldGenerateDumpStrings() and
result =
concat(Operand operand |
- operand = getAnOperand()
+ operand = this.getAnOperand()
|
operand.getDumpString(), ", " order by operand.getDumpSortOrder()
)
@@ -190,7 +195,7 @@ class Instruction extends Construction::TStageInstruction {
* Gets the function that contains this instruction.
*/
final Language::Function getEnclosingFunction() {
- result = getEnclosingIRFunction().getFunction()
+ result = this.getEnclosingIRFunction().getFunction()
}
/**
@@ -208,7 +213,7 @@ class Instruction extends Construction::TStageInstruction {
/**
* Gets the location of the source code for this instruction.
*/
- final Language::Location getLocation() { result = getAST().getLocation() }
+ final Language::Location getLocation() { result = this.getAST().getLocation() }
/**
* Gets the `Expr` whose result is computed by this instruction, if any. The `Expr` may be a
@@ -243,7 +248,7 @@ class Instruction extends Construction::TStageInstruction {
* a result, its result type will be `IRVoidType`.
*/
cached
- final IRType getResultIRType() { result = getResultLanguageType().getIRType() }
+ final IRType getResultIRType() { result = this.getResultLanguageType().getIRType() }
/**
* Gets the type of the result produced by this instruction. If the
@@ -254,7 +259,7 @@ class Instruction extends Construction::TStageInstruction {
*/
final Language::Type getResultType() {
exists(Language::LanguageType resultType |
- resultType = getResultLanguageType() and
+ resultType = this.getResultLanguageType() and
(
resultType.hasUnspecifiedType(result, _)
or
@@ -283,7 +288,7 @@ class Instruction extends Construction::TStageInstruction {
* result of the `Load` instruction is a prvalue of type `int`, representing
* the integer value loaded from variable `x`.
*/
- final predicate isGLValue() { getResultLanguageType().hasType(_, true) }
+ final predicate isGLValue() { this.getResultLanguageType().hasType(_, true) }
/**
* Gets the size of the result produced by this instruction, in bytes. If the
@@ -292,7 +297,7 @@ class Instruction extends Construction::TStageInstruction {
* If `this.isGLValue()` holds for this instruction, the value of
* `getResultSize()` will always be the size of a pointer.
*/
- final int getResultSize() { result = getResultLanguageType().getByteSize() }
+ final int getResultSize() { result = this.getResultLanguageType().getByteSize() }
/**
* Gets the opcode that specifies the operation performed by this instruction.
@@ -314,14 +319,16 @@ class Instruction extends Construction::TStageInstruction {
/**
* Holds if this instruction produces a memory result.
*/
- final predicate hasMemoryResult() { exists(getResultMemoryAccess()) }
+ final predicate hasMemoryResult() { exists(this.getResultMemoryAccess()) }
/**
* Gets the kind of memory access performed by this instruction's result.
* Holds only for instructions with a memory result.
*/
pragma[inline]
- final MemoryAccessKind getResultMemoryAccess() { result = getOpcode().getWriteMemoryAccess() }
+ final MemoryAccessKind getResultMemoryAccess() {
+ result = this.getOpcode().getWriteMemoryAccess()
+ }
/**
* Holds if the memory access performed by this instruction's result will not always write to
@@ -332,7 +339,7 @@ class Instruction extends Construction::TStageInstruction {
* (for example, the global side effects of a function call).
*/
pragma[inline]
- final predicate hasResultMayMemoryAccess() { getOpcode().hasMayWriteMemoryAccess() }
+ final predicate hasResultMayMemoryAccess() { this.getOpcode().hasMayWriteMemoryAccess() }
/**
* Gets the operand that holds the memory address to which this instruction stores its
@@ -340,7 +347,7 @@ class Instruction extends Construction::TStageInstruction {
* is `r1`.
*/
final AddressOperand getResultAddressOperand() {
- getResultMemoryAccess().usesAddressOperand() and
+ this.getResultMemoryAccess().usesAddressOperand() and
result.getUse() = this
}
@@ -349,7 +356,7 @@ class Instruction extends Construction::TStageInstruction {
* result, if any. For example, in `m3 = Store r1, r2`, the result of `getResultAddressOperand()`
* is the instruction that defines `r1`.
*/
- final Instruction getResultAddress() { result = getResultAddressOperand().getDef() }
+ final Instruction getResultAddress() { result = this.getResultAddressOperand().getDef() }
/**
* Holds if the result of this instruction is precisely modeled in SSA. Always
@@ -368,7 +375,7 @@ class Instruction extends Construction::TStageInstruction {
*/
final predicate isResultModeled() {
// Register results are always in SSA form.
- not hasMemoryResult() or
+ not this.hasMemoryResult() or
Construction::hasModeledMemoryResult(this)
}
@@ -412,7 +419,7 @@ class Instruction extends Construction::TStageInstruction {
/**
* Gets all direct successors of this instruction.
*/
- final Instruction getASuccessor() { result = getSuccessor(_) }
+ final Instruction getASuccessor() { result = this.getSuccessor(_) }
/**
* Gets a predecessor of this instruction such that the predecessor reaches
@@ -423,7 +430,7 @@ class Instruction extends Construction::TStageInstruction {
/**
* Gets all direct predecessors of this instruction.
*/
- final Instruction getAPredecessor() { result = getPredecessor(_) }
+ final Instruction getAPredecessor() { result = this.getPredecessor(_) }
}
/**
@@ -543,7 +550,7 @@ class IndexedInstruction extends Instruction {
* at this instruction. This instruction has no predecessors.
*/
class EnterFunctionInstruction extends Instruction {
- EnterFunctionInstruction() { getOpcode() instanceof Opcode::EnterFunction }
+ EnterFunctionInstruction() { this.getOpcode() instanceof Opcode::EnterFunction }
}
/**
@@ -554,7 +561,7 @@ class EnterFunctionInstruction extends Instruction {
* struct, or union, see `FieldAddressInstruction`.
*/
class VariableAddressInstruction extends VariableInstruction {
- VariableAddressInstruction() { getOpcode() instanceof Opcode::VariableAddress }
+ VariableAddressInstruction() { this.getOpcode() instanceof Opcode::VariableAddress }
}
/**
@@ -566,7 +573,7 @@ class VariableAddressInstruction extends VariableInstruction {
* The result has an `IRFunctionAddress` type.
*/
class FunctionAddressInstruction extends FunctionInstruction {
- FunctionAddressInstruction() { getOpcode() instanceof Opcode::FunctionAddress }
+ FunctionAddressInstruction() { this.getOpcode() instanceof Opcode::FunctionAddress }
}
/**
@@ -577,7 +584,7 @@ class FunctionAddressInstruction extends FunctionInstruction {
* initializes that parameter.
*/
class InitializeParameterInstruction extends VariableInstruction {
- InitializeParameterInstruction() { getOpcode() instanceof Opcode::InitializeParameter }
+ InitializeParameterInstruction() { this.getOpcode() instanceof Opcode::InitializeParameter }
/**
* Gets the parameter initialized by this instruction.
@@ -603,7 +610,7 @@ class InitializeParameterInstruction extends VariableInstruction {
* initialized elsewhere, would not otherwise have a definition in this function.
*/
class InitializeNonLocalInstruction extends Instruction {
- InitializeNonLocalInstruction() { getOpcode() instanceof Opcode::InitializeNonLocal }
+ InitializeNonLocalInstruction() { this.getOpcode() instanceof Opcode::InitializeNonLocal }
}
/**
@@ -611,7 +618,7 @@ class InitializeNonLocalInstruction extends Instruction {
* with the value of that memory on entry to the function.
*/
class InitializeIndirectionInstruction extends VariableInstruction {
- InitializeIndirectionInstruction() { getOpcode() instanceof Opcode::InitializeIndirection }
+ InitializeIndirectionInstruction() { this.getOpcode() instanceof Opcode::InitializeIndirection }
/**
* Gets the parameter initialized by this instruction.
@@ -635,24 +642,24 @@ class InitializeIndirectionInstruction extends VariableInstruction {
* An instruction that initializes the `this` pointer parameter of the enclosing function.
*/
class InitializeThisInstruction extends Instruction {
- InitializeThisInstruction() { getOpcode() instanceof Opcode::InitializeThis }
+ InitializeThisInstruction() { this.getOpcode() instanceof Opcode::InitializeThis }
}
/**
* An instruction that computes the address of a non-static field of an object.
*/
class FieldAddressInstruction extends FieldInstruction {
- FieldAddressInstruction() { getOpcode() instanceof Opcode::FieldAddress }
+ FieldAddressInstruction() { this.getOpcode() instanceof Opcode::FieldAddress }
/**
* Gets the operand that provides the address of the object containing the field.
*/
- final UnaryOperand getObjectAddressOperand() { result = getAnOperand() }
+ final UnaryOperand getObjectAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the object containing the field.
*/
- final Instruction getObjectAddress() { result = getObjectAddressOperand().getDef() }
+ final Instruction getObjectAddress() { result = this.getObjectAddressOperand().getDef() }
}
/**
@@ -661,17 +668,19 @@ class FieldAddressInstruction extends FieldInstruction {
* This instruction is used for element access to C# arrays.
*/
class ElementsAddressInstruction extends UnaryInstruction {
- ElementsAddressInstruction() { getOpcode() instanceof Opcode::ElementsAddress }
+ ElementsAddressInstruction() { this.getOpcode() instanceof Opcode::ElementsAddress }
/**
* Gets the operand that provides the address of the array object.
*/
- final UnaryOperand getArrayObjectAddressOperand() { result = getAnOperand() }
+ final UnaryOperand getArrayObjectAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the array object.
*/
- final Instruction getArrayObjectAddress() { result = getArrayObjectAddressOperand().getDef() }
+ final Instruction getArrayObjectAddress() {
+ result = this.getArrayObjectAddressOperand().getDef()
+ }
}
/**
@@ -685,7 +694,7 @@ class ElementsAddressInstruction extends UnaryInstruction {
* taken may want to ignore any function that contains an `ErrorInstruction`.
*/
class ErrorInstruction extends Instruction {
- ErrorInstruction() { getOpcode() instanceof Opcode::Error }
+ ErrorInstruction() { this.getOpcode() instanceof Opcode::Error }
}
/**
@@ -695,7 +704,7 @@ class ErrorInstruction extends Instruction {
* an initializer, or whose initializer only partially initializes the variable.
*/
class UninitializedInstruction extends VariableInstruction {
- UninitializedInstruction() { getOpcode() instanceof Opcode::Uninitialized }
+ UninitializedInstruction() { this.getOpcode() instanceof Opcode::Uninitialized }
/**
* Gets the variable that is uninitialized.
@@ -710,7 +719,7 @@ class UninitializedInstruction extends VariableInstruction {
* least one instruction, even when the AST has no semantic effect.
*/
class NoOpInstruction extends Instruction {
- NoOpInstruction() { getOpcode() instanceof Opcode::NoOp }
+ NoOpInstruction() { this.getOpcode() instanceof Opcode::NoOp }
}
/**
@@ -732,32 +741,42 @@ class NoOpInstruction extends Instruction {
* `void`-returning function.
*/
class ReturnInstruction extends Instruction {
- ReturnInstruction() { getOpcode() instanceof ReturnOpcode }
+ ReturnInstruction() { this.getOpcode() instanceof ReturnOpcode }
}
/**
* An instruction that returns control to the caller of the function, without returning a value.
*/
class ReturnVoidInstruction extends ReturnInstruction {
- ReturnVoidInstruction() { getOpcode() instanceof Opcode::ReturnVoid }
+ ReturnVoidInstruction() { this.getOpcode() instanceof Opcode::ReturnVoid }
}
/**
* An instruction that returns control to the caller of the function, including a return value.
*/
class ReturnValueInstruction extends ReturnInstruction {
- ReturnValueInstruction() { getOpcode() instanceof Opcode::ReturnValue }
+ ReturnValueInstruction() { this.getOpcode() instanceof Opcode::ReturnValue }
/**
* Gets the operand that provides the value being returned by the function.
*/
- final LoadOperand getReturnValueOperand() { result = getAnOperand() }
+ final LoadOperand getReturnValueOperand() { result = this.getAnOperand() }
+
+ /**
+ * Gets the operand that provides the address of the value being returned by the function.
+ */
+ final AddressOperand getReturnAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the value being returned by the function, if an
* exact definition is available.
*/
- final Instruction getReturnValue() { result = getReturnValueOperand().getDef() }
+ final Instruction getReturnValue() { result = this.getReturnValueOperand().getDef() }
+
+ /**
+ * Gets the instruction whose result provides the address of the value being returned by the function.
+ */
+ final Instruction getReturnAddress() { result = this.getReturnAddressOperand().getDef() }
}
/**
@@ -770,28 +789,28 @@ class ReturnValueInstruction extends ReturnInstruction {
* that the caller initialized the memory pointed to by the parameter before the call.
*/
class ReturnIndirectionInstruction extends VariableInstruction {
- ReturnIndirectionInstruction() { getOpcode() instanceof Opcode::ReturnIndirection }
+ ReturnIndirectionInstruction() { this.getOpcode() instanceof Opcode::ReturnIndirection }
/**
* Gets the operand that provides the value of the pointed-to memory.
*/
- final SideEffectOperand getSideEffectOperand() { result = getAnOperand() }
+ final SideEffectOperand getSideEffectOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the value of the pointed-to memory, if an exact
* definition is available.
*/
- final Instruction getSideEffect() { result = getSideEffectOperand().getDef() }
+ final Instruction getSideEffect() { result = this.getSideEffectOperand().getDef() }
/**
* Gets the operand that provides the address of the pointed-to memory.
*/
- final AddressOperand getSourceAddressOperand() { result = getAnOperand() }
+ final AddressOperand getSourceAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the pointed-to memory.
*/
- final Instruction getSourceAddress() { result = getSourceAddressOperand().getDef() }
+ final Instruction getSourceAddress() { result = this.getSourceAddressOperand().getDef() }
/**
* Gets the parameter for which this instruction reads the final pointed-to value within the
@@ -826,7 +845,7 @@ class ReturnIndirectionInstruction extends VariableInstruction {
* - `StoreInstruction` - Copies a register operand to a memory result.
*/
class CopyInstruction extends Instruction {
- CopyInstruction() { getOpcode() instanceof CopyOpcode }
+ CopyInstruction() { this.getOpcode() instanceof CopyOpcode }
/**
* Gets the operand that provides the input value of the copy.
@@ -837,16 +856,16 @@ class CopyInstruction extends Instruction {
* Gets the instruction whose result provides the input value of the copy, if an exact definition
* is available.
*/
- final Instruction getSourceValue() { result = getSourceValueOperand().getDef() }
+ final Instruction getSourceValue() { result = this.getSourceValueOperand().getDef() }
}
/**
* An instruction that returns a register result containing a copy of its register operand.
*/
class CopyValueInstruction extends CopyInstruction, UnaryInstruction {
- CopyValueInstruction() { getOpcode() instanceof Opcode::CopyValue }
+ CopyValueInstruction() { this.getOpcode() instanceof Opcode::CopyValue }
- final override UnaryOperand getSourceValueOperand() { result = getAnOperand() }
+ final override UnaryOperand getSourceValueOperand() { result = this.getAnOperand() }
}
/**
@@ -863,47 +882,49 @@ private string getAddressOperandDescription(AddressOperand operand) {
* An instruction that returns a register result containing a copy of its memory operand.
*/
class LoadInstruction extends CopyInstruction {
- LoadInstruction() { getOpcode() instanceof Opcode::Load }
+ LoadInstruction() { this.getOpcode() instanceof Opcode::Load }
final override string getImmediateString() {
- result = getAddressOperandDescription(getSourceAddressOperand())
+ result = getAddressOperandDescription(this.getSourceAddressOperand())
}
/**
* Gets the operand that provides the address of the value being loaded.
*/
- final AddressOperand getSourceAddressOperand() { result = getAnOperand() }
+ final AddressOperand getSourceAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the value being loaded.
*/
- final Instruction getSourceAddress() { result = getSourceAddressOperand().getDef() }
+ final Instruction getSourceAddress() { result = this.getSourceAddressOperand().getDef() }
- final override LoadOperand getSourceValueOperand() { result = getAnOperand() }
+ final override LoadOperand getSourceValueOperand() { result = this.getAnOperand() }
}
/**
* An instruction that returns a memory result containing a copy of its register operand.
*/
class StoreInstruction extends CopyInstruction {
- StoreInstruction() { getOpcode() instanceof Opcode::Store }
+ StoreInstruction() { this.getOpcode() instanceof Opcode::Store }
final override string getImmediateString() {
- result = getAddressOperandDescription(getDestinationAddressOperand())
+ result = getAddressOperandDescription(this.getDestinationAddressOperand())
}
/**
* Gets the operand that provides the address of the location to which the value will be stored.
*/
- final AddressOperand getDestinationAddressOperand() { result = getAnOperand() }
+ final AddressOperand getDestinationAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the location to which the value will
* be stored, if an exact definition is available.
*/
- final Instruction getDestinationAddress() { result = getDestinationAddressOperand().getDef() }
+ final Instruction getDestinationAddress() {
+ result = this.getDestinationAddressOperand().getDef()
+ }
- final override StoreValueOperand getSourceValueOperand() { result = getAnOperand() }
+ final override StoreValueOperand getSourceValueOperand() { result = this.getAnOperand() }
}
/**
@@ -911,27 +932,27 @@ class StoreInstruction extends CopyInstruction {
* operand.
*/
class ConditionalBranchInstruction extends Instruction {
- ConditionalBranchInstruction() { getOpcode() instanceof Opcode::ConditionalBranch }
+ ConditionalBranchInstruction() { this.getOpcode() instanceof Opcode::ConditionalBranch }
/**
* Gets the operand that provides the Boolean condition controlling the branch.
*/
- final ConditionOperand getConditionOperand() { result = getAnOperand() }
+ final ConditionOperand getConditionOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the Boolean condition controlling the branch.
*/
- final Instruction getCondition() { result = getConditionOperand().getDef() }
+ final Instruction getCondition() { result = this.getConditionOperand().getDef() }
/**
* Gets the instruction to which control will flow if the condition is true.
*/
- final Instruction getTrueSuccessor() { result = getSuccessor(EdgeKind::trueEdge()) }
+ final Instruction getTrueSuccessor() { result = this.getSuccessor(EdgeKind::trueEdge()) }
/**
* Gets the instruction to which control will flow if the condition is false.
*/
- final Instruction getFalseSuccessor() { result = getSuccessor(EdgeKind::falseEdge()) }
+ final Instruction getFalseSuccessor() { result = this.getSuccessor(EdgeKind::falseEdge()) }
}
/**
@@ -943,14 +964,14 @@ class ConditionalBranchInstruction extends Instruction {
* successors.
*/
class ExitFunctionInstruction extends Instruction {
- ExitFunctionInstruction() { getOpcode() instanceof Opcode::ExitFunction }
+ ExitFunctionInstruction() { this.getOpcode() instanceof Opcode::ExitFunction }
}
/**
* An instruction whose result is a constant value.
*/
class ConstantInstruction extends ConstantValueInstruction {
- ConstantInstruction() { getOpcode() instanceof Opcode::Constant }
+ ConstantInstruction() { this.getOpcode() instanceof Opcode::Constant }
}
/**
@@ -959,7 +980,7 @@ class ConstantInstruction extends ConstantValueInstruction {
class IntegerConstantInstruction extends ConstantInstruction {
IntegerConstantInstruction() {
exists(IRType resultType |
- resultType = getResultIRType() and
+ resultType = this.getResultIRType() and
(resultType instanceof IRIntegerType or resultType instanceof IRBooleanType)
)
}
@@ -969,7 +990,7 @@ class IntegerConstantInstruction extends ConstantInstruction {
* An instruction whose result is a constant value of floating-point type.
*/
class FloatConstantInstruction extends ConstantInstruction {
- FloatConstantInstruction() { getResultIRType() instanceof IRFloatingPointType }
+ FloatConstantInstruction() { this.getResultIRType() instanceof IRFloatingPointType }
}
/**
@@ -978,7 +999,9 @@ class FloatConstantInstruction extends ConstantInstruction {
class StringConstantInstruction extends VariableInstruction {
override IRStringLiteral var;
- final override string getImmediateString() { result = Language::getStringLiteralText(getValue()) }
+ final override string getImmediateString() {
+ result = Language::getStringLiteralText(this.getValue())
+ }
/**
* Gets the string literal whose address is returned by this instruction.
@@ -990,37 +1013,37 @@ class StringConstantInstruction extends VariableInstruction {
* An instruction whose result is computed from two operands.
*/
class BinaryInstruction extends Instruction {
- BinaryInstruction() { getOpcode() instanceof BinaryOpcode }
+ BinaryInstruction() { this.getOpcode() instanceof BinaryOpcode }
/**
* Gets the left operand of this binary instruction.
*/
- final LeftOperand getLeftOperand() { result = getAnOperand() }
+ final LeftOperand getLeftOperand() { result = this.getAnOperand() }
/**
* Gets the right operand of this binary instruction.
*/
- final RightOperand getRightOperand() { result = getAnOperand() }
+ final RightOperand getRightOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the value of the left operand of this binary
* instruction.
*/
- final Instruction getLeft() { result = getLeftOperand().getDef() }
+ final Instruction getLeft() { result = this.getLeftOperand().getDef() }
/**
* Gets the instruction whose result provides the value of the right operand of this binary
* instruction.
*/
- final Instruction getRight() { result = getRightOperand().getDef() }
+ final Instruction getRight() { result = this.getRightOperand().getDef() }
/**
* Holds if this instruction's operands are `op1` and `op2`, in either order.
*/
final predicate hasOperands(Operand op1, Operand op2) {
- op1 = getLeftOperand() and op2 = getRightOperand()
+ op1 = this.getLeftOperand() and op2 = this.getRightOperand()
or
- op1 = getRightOperand() and op2 = getLeftOperand()
+ op1 = this.getRightOperand() and op2 = this.getLeftOperand()
}
}
@@ -1028,7 +1051,7 @@ class BinaryInstruction extends Instruction {
* An instruction that computes the result of an arithmetic operation.
*/
class ArithmeticInstruction extends Instruction {
- ArithmeticInstruction() { getOpcode() instanceof ArithmeticOpcode }
+ ArithmeticInstruction() { this.getOpcode() instanceof ArithmeticOpcode }
}
/**
@@ -1050,7 +1073,7 @@ class UnaryArithmeticInstruction extends ArithmeticInstruction, UnaryInstruction
* performed according to IEEE-754.
*/
class AddInstruction extends BinaryArithmeticInstruction {
- AddInstruction() { getOpcode() instanceof Opcode::Add }
+ AddInstruction() { this.getOpcode() instanceof Opcode::Add }
}
/**
@@ -1061,7 +1084,7 @@ class AddInstruction extends BinaryArithmeticInstruction {
* according to IEEE-754.
*/
class SubInstruction extends BinaryArithmeticInstruction {
- SubInstruction() { getOpcode() instanceof Opcode::Sub }
+ SubInstruction() { this.getOpcode() instanceof Opcode::Sub }
}
/**
@@ -1072,7 +1095,7 @@ class SubInstruction extends BinaryArithmeticInstruction {
* performed according to IEEE-754.
*/
class MulInstruction extends BinaryArithmeticInstruction {
- MulInstruction() { getOpcode() instanceof Opcode::Mul }
+ MulInstruction() { this.getOpcode() instanceof Opcode::Mul }
}
/**
@@ -1083,7 +1106,7 @@ class MulInstruction extends BinaryArithmeticInstruction {
* to IEEE-754.
*/
class DivInstruction extends BinaryArithmeticInstruction {
- DivInstruction() { getOpcode() instanceof Opcode::Div }
+ DivInstruction() { this.getOpcode() instanceof Opcode::Div }
}
/**
@@ -1093,7 +1116,7 @@ class DivInstruction extends BinaryArithmeticInstruction {
* division by zero or integer overflow is undefined.
*/
class RemInstruction extends BinaryArithmeticInstruction {
- RemInstruction() { getOpcode() instanceof Opcode::Rem }
+ RemInstruction() { this.getOpcode() instanceof Opcode::Rem }
}
/**
@@ -1104,14 +1127,14 @@ class RemInstruction extends BinaryArithmeticInstruction {
* is performed according to IEEE-754.
*/
class NegateInstruction extends UnaryArithmeticInstruction {
- NegateInstruction() { getOpcode() instanceof Opcode::Negate }
+ NegateInstruction() { this.getOpcode() instanceof Opcode::Negate }
}
/**
* An instruction that computes the result of a bitwise operation.
*/
class BitwiseInstruction extends Instruction {
- BitwiseInstruction() { getOpcode() instanceof BitwiseOpcode }
+ BitwiseInstruction() { this.getOpcode() instanceof BitwiseOpcode }
}
/**
@@ -1130,7 +1153,7 @@ class UnaryBitwiseInstruction extends BitwiseInstruction, UnaryInstruction { }
* Both operands must have the same integer type, which will also be the result type.
*/
class BitAndInstruction extends BinaryBitwiseInstruction {
- BitAndInstruction() { getOpcode() instanceof Opcode::BitAnd }
+ BitAndInstruction() { this.getOpcode() instanceof Opcode::BitAnd }
}
/**
@@ -1139,7 +1162,7 @@ class BitAndInstruction extends BinaryBitwiseInstruction {
* Both operands must have the same integer type, which will also be the result type.
*/
class BitOrInstruction extends BinaryBitwiseInstruction {
- BitOrInstruction() { getOpcode() instanceof Opcode::BitOr }
+ BitOrInstruction() { this.getOpcode() instanceof Opcode::BitOr }
}
/**
@@ -1148,7 +1171,7 @@ class BitOrInstruction extends BinaryBitwiseInstruction {
* Both operands must have the same integer type, which will also be the result type.
*/
class BitXorInstruction extends BinaryBitwiseInstruction {
- BitXorInstruction() { getOpcode() instanceof Opcode::BitXor }
+ BitXorInstruction() { this.getOpcode() instanceof Opcode::BitXor }
}
/**
@@ -1159,7 +1182,7 @@ class BitXorInstruction extends BinaryBitwiseInstruction {
* rightmost bits are zero-filled.
*/
class ShiftLeftInstruction extends BinaryBitwiseInstruction {
- ShiftLeftInstruction() { getOpcode() instanceof Opcode::ShiftLeft }
+ ShiftLeftInstruction() { this.getOpcode() instanceof Opcode::ShiftLeft }
}
/**
@@ -1172,7 +1195,7 @@ class ShiftLeftInstruction extends BinaryBitwiseInstruction {
* of the left operand.
*/
class ShiftRightInstruction extends BinaryBitwiseInstruction {
- ShiftRightInstruction() { getOpcode() instanceof Opcode::ShiftRight }
+ ShiftRightInstruction() { this.getOpcode() instanceof Opcode::ShiftRight }
}
/**
@@ -1183,7 +1206,7 @@ class PointerArithmeticInstruction extends BinaryInstruction {
int elementSize;
PointerArithmeticInstruction() {
- getOpcode() instanceof PointerArithmeticOpcode and
+ this.getOpcode() instanceof PointerArithmeticOpcode and
elementSize = Raw::getInstructionElementSize(this)
}
@@ -1206,7 +1229,7 @@ class PointerArithmeticInstruction extends BinaryInstruction {
* An instruction that adds or subtracts an integer offset from a pointer.
*/
class PointerOffsetInstruction extends PointerArithmeticInstruction {
- PointerOffsetInstruction() { getOpcode() instanceof PointerOffsetOpcode }
+ PointerOffsetInstruction() { this.getOpcode() instanceof PointerOffsetOpcode }
}
/**
@@ -1217,7 +1240,7 @@ class PointerOffsetInstruction extends PointerArithmeticInstruction {
* overflow is undefined.
*/
class PointerAddInstruction extends PointerOffsetInstruction {
- PointerAddInstruction() { getOpcode() instanceof Opcode::PointerAdd }
+ PointerAddInstruction() { this.getOpcode() instanceof Opcode::PointerAdd }
}
/**
@@ -1228,7 +1251,7 @@ class PointerAddInstruction extends PointerOffsetInstruction {
* pointer underflow is undefined.
*/
class PointerSubInstruction extends PointerOffsetInstruction {
- PointerSubInstruction() { getOpcode() instanceof Opcode::PointerSub }
+ PointerSubInstruction() { this.getOpcode() instanceof Opcode::PointerSub }
}
/**
@@ -1241,31 +1264,31 @@ class PointerSubInstruction extends PointerOffsetInstruction {
* undefined.
*/
class PointerDiffInstruction extends PointerArithmeticInstruction {
- PointerDiffInstruction() { getOpcode() instanceof Opcode::PointerDiff }
+ PointerDiffInstruction() { this.getOpcode() instanceof Opcode::PointerDiff }
}
/**
* An instruction whose result is computed from a single operand.
*/
class UnaryInstruction extends Instruction {
- UnaryInstruction() { getOpcode() instanceof UnaryOpcode }
+ UnaryInstruction() { this.getOpcode() instanceof UnaryOpcode }
/**
* Gets the sole operand of this instruction.
*/
- final UnaryOperand getUnaryOperand() { result = getAnOperand() }
+ final UnaryOperand getUnaryOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the sole operand of this instruction.
*/
- final Instruction getUnary() { result = getUnaryOperand().getDef() }
+ final Instruction getUnary() { result = this.getUnaryOperand().getDef() }
}
/**
* An instruction that converts the value of its operand to a value of a different type.
*/
class ConvertInstruction extends UnaryInstruction {
- ConvertInstruction() { getOpcode() instanceof Opcode::Convert }
+ ConvertInstruction() { this.getOpcode() instanceof Opcode::Convert }
}
/**
@@ -1279,7 +1302,7 @@ class ConvertInstruction extends UnaryInstruction {
* `as` expression.
*/
class CheckedConvertOrNullInstruction extends UnaryInstruction {
- CheckedConvertOrNullInstruction() { getOpcode() instanceof Opcode::CheckedConvertOrNull }
+ CheckedConvertOrNullInstruction() { this.getOpcode() instanceof Opcode::CheckedConvertOrNull }
}
/**
@@ -1293,7 +1316,7 @@ class CheckedConvertOrNullInstruction extends UnaryInstruction {
* expression.
*/
class CheckedConvertOrThrowInstruction extends UnaryInstruction {
- CheckedConvertOrThrowInstruction() { getOpcode() instanceof Opcode::CheckedConvertOrThrow }
+ CheckedConvertOrThrowInstruction() { this.getOpcode() instanceof Opcode::CheckedConvertOrThrow }
}
/**
@@ -1306,7 +1329,7 @@ class CheckedConvertOrThrowInstruction extends UnaryInstruction {
* the most-derived object.
*/
class CompleteObjectAddressInstruction extends UnaryInstruction {
- CompleteObjectAddressInstruction() { getOpcode() instanceof Opcode::CompleteObjectAddress }
+ CompleteObjectAddressInstruction() { this.getOpcode() instanceof Opcode::CompleteObjectAddress }
}
/**
@@ -1351,7 +1374,7 @@ class InheritanceConversionInstruction extends UnaryInstruction {
* An instruction that converts from the address of a derived class to the address of a base class.
*/
class ConvertToBaseInstruction extends InheritanceConversionInstruction {
- ConvertToBaseInstruction() { getOpcode() instanceof ConvertToBaseOpcode }
+ ConvertToBaseInstruction() { this.getOpcode() instanceof ConvertToBaseOpcode }
}
/**
@@ -1361,7 +1384,9 @@ class ConvertToBaseInstruction extends InheritanceConversionInstruction {
* If the operand holds a null address, the result is a null address.
*/
class ConvertToNonVirtualBaseInstruction extends ConvertToBaseInstruction {
- ConvertToNonVirtualBaseInstruction() { getOpcode() instanceof Opcode::ConvertToNonVirtualBase }
+ ConvertToNonVirtualBaseInstruction() {
+ this.getOpcode() instanceof Opcode::ConvertToNonVirtualBase
+ }
}
/**
@@ -1371,7 +1396,7 @@ class ConvertToNonVirtualBaseInstruction extends ConvertToBaseInstruction {
* If the operand holds a null address, the result is a null address.
*/
class ConvertToVirtualBaseInstruction extends ConvertToBaseInstruction {
- ConvertToVirtualBaseInstruction() { getOpcode() instanceof Opcode::ConvertToVirtualBase }
+ ConvertToVirtualBaseInstruction() { this.getOpcode() instanceof Opcode::ConvertToVirtualBase }
}
/**
@@ -1381,7 +1406,7 @@ class ConvertToVirtualBaseInstruction extends ConvertToBaseInstruction {
* If the operand holds a null address, the result is a null address.
*/
class ConvertToDerivedInstruction extends InheritanceConversionInstruction {
- ConvertToDerivedInstruction() { getOpcode() instanceof Opcode::ConvertToDerived }
+ ConvertToDerivedInstruction() { this.getOpcode() instanceof Opcode::ConvertToDerived }
}
/**
@@ -1390,7 +1415,7 @@ class ConvertToDerivedInstruction extends InheritanceConversionInstruction {
* The operand must have an integer type, which will also be the result type.
*/
class BitComplementInstruction extends UnaryBitwiseInstruction {
- BitComplementInstruction() { getOpcode() instanceof Opcode::BitComplement }
+ BitComplementInstruction() { this.getOpcode() instanceof Opcode::BitComplement }
}
/**
@@ -1399,14 +1424,14 @@ class BitComplementInstruction extends UnaryBitwiseInstruction {
* The operand must have a Boolean type, which will also be the result type.
*/
class LogicalNotInstruction extends UnaryInstruction {
- LogicalNotInstruction() { getOpcode() instanceof Opcode::LogicalNot }
+ LogicalNotInstruction() { this.getOpcode() instanceof Opcode::LogicalNot }
}
/**
* An instruction that compares two numeric operands.
*/
class CompareInstruction extends BinaryInstruction {
- CompareInstruction() { getOpcode() instanceof CompareOpcode }
+ CompareInstruction() { this.getOpcode() instanceof CompareOpcode }
}
/**
@@ -1417,7 +1442,7 @@ class CompareInstruction extends BinaryInstruction {
* unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareEQInstruction extends CompareInstruction {
- CompareEQInstruction() { getOpcode() instanceof Opcode::CompareEQ }
+ CompareEQInstruction() { this.getOpcode() instanceof Opcode::CompareEQ }
}
/**
@@ -1428,14 +1453,14 @@ class CompareEQInstruction extends CompareInstruction {
* `left == right`. Floating-point comparison is performed according to IEEE-754.
*/
class CompareNEInstruction extends CompareInstruction {
- CompareNEInstruction() { getOpcode() instanceof Opcode::CompareNE }
+ CompareNEInstruction() { this.getOpcode() instanceof Opcode::CompareNE }
}
/**
* An instruction that does a relative comparison of two values, such as `<` or `>=`.
*/
class RelationalInstruction extends CompareInstruction {
- RelationalInstruction() { getOpcode() instanceof RelationalOpcode }
+ RelationalInstruction() { this.getOpcode() instanceof RelationalOpcode }
/**
* Gets the operand on the "greater" (or "greater-or-equal") side
@@ -1467,11 +1492,11 @@ class RelationalInstruction extends CompareInstruction {
* are unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareLTInstruction extends RelationalInstruction {
- CompareLTInstruction() { getOpcode() instanceof Opcode::CompareLT }
+ CompareLTInstruction() { this.getOpcode() instanceof Opcode::CompareLT }
- override Instruction getLesser() { result = getLeft() }
+ override Instruction getLesser() { result = this.getLeft() }
- override Instruction getGreater() { result = getRight() }
+ override Instruction getGreater() { result = this.getRight() }
override predicate isStrict() { any() }
}
@@ -1484,11 +1509,11 @@ class CompareLTInstruction extends RelationalInstruction {
* are unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareGTInstruction extends RelationalInstruction {
- CompareGTInstruction() { getOpcode() instanceof Opcode::CompareGT }
+ CompareGTInstruction() { this.getOpcode() instanceof Opcode::CompareGT }
- override Instruction getLesser() { result = getRight() }
+ override Instruction getLesser() { result = this.getRight() }
- override Instruction getGreater() { result = getLeft() }
+ override Instruction getGreater() { result = this.getLeft() }
override predicate isStrict() { any() }
}
@@ -1502,11 +1527,11 @@ class CompareGTInstruction extends RelationalInstruction {
* are unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareLEInstruction extends RelationalInstruction {
- CompareLEInstruction() { getOpcode() instanceof Opcode::CompareLE }
+ CompareLEInstruction() { this.getOpcode() instanceof Opcode::CompareLE }
- override Instruction getLesser() { result = getLeft() }
+ override Instruction getLesser() { result = this.getLeft() }
- override Instruction getGreater() { result = getRight() }
+ override Instruction getGreater() { result = this.getRight() }
override predicate isStrict() { none() }
}
@@ -1520,11 +1545,11 @@ class CompareLEInstruction extends RelationalInstruction {
* are unordered. Floating-point comparison is performed according to IEEE-754.
*/
class CompareGEInstruction extends RelationalInstruction {
- CompareGEInstruction() { getOpcode() instanceof Opcode::CompareGE }
+ CompareGEInstruction() { this.getOpcode() instanceof Opcode::CompareGE }
- override Instruction getLesser() { result = getRight() }
+ override Instruction getLesser() { result = this.getRight() }
- override Instruction getGreater() { result = getLeft() }
+ override Instruction getGreater() { result = this.getLeft() }
override predicate isStrict() { none() }
}
@@ -1543,78 +1568,78 @@ class CompareGEInstruction extends RelationalInstruction {
* of any case edge.
*/
class SwitchInstruction extends Instruction {
- SwitchInstruction() { getOpcode() instanceof Opcode::Switch }
+ SwitchInstruction() { this.getOpcode() instanceof Opcode::Switch }
/** Gets the operand that provides the integer value controlling the switch. */
- final ConditionOperand getExpressionOperand() { result = getAnOperand() }
+ final ConditionOperand getExpressionOperand() { result = this.getAnOperand() }
/** Gets the instruction whose result provides the integer value controlling the switch. */
- final Instruction getExpression() { result = getExpressionOperand().getDef() }
+ final Instruction getExpression() { result = this.getExpressionOperand().getDef() }
/** Gets the successor instructions along the case edges of the switch. */
- final Instruction getACaseSuccessor() { exists(CaseEdge edge | result = getSuccessor(edge)) }
+ final Instruction getACaseSuccessor() { exists(CaseEdge edge | result = this.getSuccessor(edge)) }
/** Gets the successor instruction along the default edge of the switch, if any. */
- final Instruction getDefaultSuccessor() { result = getSuccessor(EdgeKind::defaultEdge()) }
+ final Instruction getDefaultSuccessor() { result = this.getSuccessor(EdgeKind::defaultEdge()) }
}
/**
* An instruction that calls a function.
*/
class CallInstruction extends Instruction {
- CallInstruction() { getOpcode() instanceof Opcode::Call }
+ CallInstruction() { this.getOpcode() instanceof Opcode::Call }
final override string getImmediateString() {
- result = getStaticCallTarget().toString()
+ result = this.getStaticCallTarget().toString()
or
- not exists(getStaticCallTarget()) and result = "?"
+ not exists(this.getStaticCallTarget()) and result = "?"
}
/**
* Gets the operand the specifies the target function of the call.
*/
- final CallTargetOperand getCallTargetOperand() { result = getAnOperand() }
+ final CallTargetOperand getCallTargetOperand() { result = this.getAnOperand() }
/**
* Gets the `Instruction` that computes the target function of the call. This is usually a
* `FunctionAddress` instruction, but can also be an arbitrary instruction that produces a
* function pointer.
*/
- final Instruction getCallTarget() { result = getCallTargetOperand().getDef() }
+ final Instruction getCallTarget() { result = this.getCallTargetOperand().getDef() }
/**
* Gets all of the argument operands of the call, including the `this` pointer, if any.
*/
- final ArgumentOperand getAnArgumentOperand() { result = getAnOperand() }
+ final ArgumentOperand getAnArgumentOperand() { result = this.getAnOperand() }
/**
* Gets the `Function` that the call targets, if this is statically known.
*/
final Language::Function getStaticCallTarget() {
- result = getCallTarget().(FunctionAddressInstruction).getFunctionSymbol()
+ result = this.getCallTarget().(FunctionAddressInstruction).getFunctionSymbol()
}
/**
* Gets all of the arguments of the call, including the `this` pointer, if any.
*/
- final Instruction getAnArgument() { result = getAnArgumentOperand().getDef() }
+ final Instruction getAnArgument() { result = this.getAnArgumentOperand().getDef() }
/**
* Gets the `this` pointer argument operand of the call, if any.
*/
- final ThisArgumentOperand getThisArgumentOperand() { result = getAnOperand() }
+ final ThisArgumentOperand getThisArgumentOperand() { result = this.getAnOperand() }
/**
* Gets the `this` pointer argument of the call, if any.
*/
- final Instruction getThisArgument() { result = getThisArgumentOperand().getDef() }
+ final Instruction getThisArgument() { result = this.getThisArgumentOperand().getDef() }
/**
* Gets the argument operand at the specified index.
*/
pragma[noinline]
final PositionalArgumentOperand getPositionalArgumentOperand(int index) {
- result = getAnOperand() and
+ result = this.getAnOperand() and
result.getIndex() = index
}
@@ -1623,7 +1648,7 @@ class CallInstruction extends Instruction {
*/
pragma[noinline]
final Instruction getPositionalArgument(int index) {
- result = getPositionalArgumentOperand(index).getDef()
+ result = this.getPositionalArgumentOperand(index).getDef()
}
/**
@@ -1631,16 +1656,16 @@ class CallInstruction extends Instruction {
*/
pragma[noinline]
final ArgumentOperand getArgumentOperand(int index) {
- index >= 0 and result = getPositionalArgumentOperand(index)
+ index >= 0 and result = this.getPositionalArgumentOperand(index)
or
- index = -1 and result = getThisArgumentOperand()
+ index = -1 and result = this.getThisArgumentOperand()
}
/**
* Gets the argument at the specified index, or `this` if `index` is `-1`.
*/
pragma[noinline]
- final Instruction getArgument(int index) { result = getArgumentOperand(index).getDef() }
+ final Instruction getArgument(int index) { result = this.getArgumentOperand(index).getDef() }
/**
* Gets the number of arguments of the call, including the `this` pointer, if any.
@@ -1665,7 +1690,7 @@ class CallInstruction extends Instruction {
* An instruction representing a side effect of a function call.
*/
class SideEffectInstruction extends Instruction {
- SideEffectInstruction() { getOpcode() instanceof SideEffectOpcode }
+ SideEffectInstruction() { this.getOpcode() instanceof SideEffectOpcode }
/**
* Gets the instruction whose execution causes this side effect.
@@ -1680,7 +1705,7 @@ class SideEffectInstruction extends Instruction {
* accessed by that call.
*/
class CallSideEffectInstruction extends SideEffectInstruction {
- CallSideEffectInstruction() { getOpcode() instanceof Opcode::CallSideEffect }
+ CallSideEffectInstruction() { this.getOpcode() instanceof Opcode::CallSideEffect }
}
/**
@@ -1691,7 +1716,7 @@ class CallSideEffectInstruction extends SideEffectInstruction {
* call target cannot write to escaped memory.
*/
class CallReadSideEffectInstruction extends SideEffectInstruction {
- CallReadSideEffectInstruction() { getOpcode() instanceof Opcode::CallReadSideEffect }
+ CallReadSideEffectInstruction() { this.getOpcode() instanceof Opcode::CallReadSideEffect }
}
/**
@@ -1699,33 +1724,33 @@ class CallReadSideEffectInstruction extends SideEffectInstruction {
* specific parameter.
*/
class ReadSideEffectInstruction extends SideEffectInstruction, IndexedInstruction {
- ReadSideEffectInstruction() { getOpcode() instanceof ReadSideEffectOpcode }
+ ReadSideEffectInstruction() { this.getOpcode() instanceof ReadSideEffectOpcode }
/** Gets the operand for the value that will be read from this instruction, if known. */
- final SideEffectOperand getSideEffectOperand() { result = getAnOperand() }
+ final SideEffectOperand getSideEffectOperand() { result = this.getAnOperand() }
/** Gets the value that will be read from this instruction, if known. */
- final Instruction getSideEffect() { result = getSideEffectOperand().getDef() }
+ final Instruction getSideEffect() { result = this.getSideEffectOperand().getDef() }
/** Gets the operand for the address from which this instruction may read. */
- final AddressOperand getArgumentOperand() { result = getAnOperand() }
+ final AddressOperand getArgumentOperand() { result = this.getAnOperand() }
/** Gets the address from which this instruction may read. */
- final Instruction getArgumentDef() { result = getArgumentOperand().getDef() }
+ final Instruction getArgumentDef() { result = this.getArgumentOperand().getDef() }
}
/**
* An instruction representing the read of an indirect parameter within a function call.
*/
class IndirectReadSideEffectInstruction extends ReadSideEffectInstruction {
- IndirectReadSideEffectInstruction() { getOpcode() instanceof Opcode::IndirectReadSideEffect }
+ IndirectReadSideEffectInstruction() { this.getOpcode() instanceof Opcode::IndirectReadSideEffect }
}
/**
* An instruction representing the read of an indirect buffer parameter within a function call.
*/
class BufferReadSideEffectInstruction extends ReadSideEffectInstruction {
- BufferReadSideEffectInstruction() { getOpcode() instanceof Opcode::BufferReadSideEffect }
+ BufferReadSideEffectInstruction() { this.getOpcode() instanceof Opcode::BufferReadSideEffect }
}
/**
@@ -1733,18 +1758,18 @@ class BufferReadSideEffectInstruction extends ReadSideEffectInstruction {
*/
class SizedBufferReadSideEffectInstruction extends ReadSideEffectInstruction {
SizedBufferReadSideEffectInstruction() {
- getOpcode() instanceof Opcode::SizedBufferReadSideEffect
+ this.getOpcode() instanceof Opcode::SizedBufferReadSideEffect
}
/**
* Gets the operand that holds the number of bytes read from the buffer.
*/
- final BufferSizeOperand getBufferSizeOperand() { result = getAnOperand() }
+ final BufferSizeOperand getBufferSizeOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the number of bytes read from the buffer.
*/
- final Instruction getBufferSize() { result = getBufferSizeOperand().getDef() }
+ final Instruction getBufferSize() { result = this.getBufferSizeOperand().getDef() }
}
/**
@@ -1752,17 +1777,17 @@ class SizedBufferReadSideEffectInstruction extends ReadSideEffectInstruction {
* specific parameter.
*/
class WriteSideEffectInstruction extends SideEffectInstruction, IndexedInstruction {
- WriteSideEffectInstruction() { getOpcode() instanceof WriteSideEffectOpcode }
+ WriteSideEffectInstruction() { this.getOpcode() instanceof WriteSideEffectOpcode }
/**
* Get the operand that holds the address of the memory to be written.
*/
- final AddressOperand getDestinationAddressOperand() { result = getAnOperand() }
+ final AddressOperand getDestinationAddressOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the address of the memory to be written.
*/
- Instruction getDestinationAddress() { result = getDestinationAddressOperand().getDef() }
+ Instruction getDestinationAddress() { result = this.getDestinationAddressOperand().getDef() }
}
/**
@@ -1770,7 +1795,7 @@ class WriteSideEffectInstruction extends SideEffectInstruction, IndexedInstructi
*/
class IndirectMustWriteSideEffectInstruction extends WriteSideEffectInstruction {
IndirectMustWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::IndirectMustWriteSideEffect
+ this.getOpcode() instanceof Opcode::IndirectMustWriteSideEffect
}
}
@@ -1780,7 +1805,7 @@ class IndirectMustWriteSideEffectInstruction extends WriteSideEffectInstruction
*/
class BufferMustWriteSideEffectInstruction extends WriteSideEffectInstruction {
BufferMustWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::BufferMustWriteSideEffect
+ this.getOpcode() instanceof Opcode::BufferMustWriteSideEffect
}
}
@@ -1790,18 +1815,18 @@ class BufferMustWriteSideEffectInstruction extends WriteSideEffectInstruction {
*/
class SizedBufferMustWriteSideEffectInstruction extends WriteSideEffectInstruction {
SizedBufferMustWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::SizedBufferMustWriteSideEffect
+ this.getOpcode() instanceof Opcode::SizedBufferMustWriteSideEffect
}
/**
* Gets the operand that holds the number of bytes written to the buffer.
*/
- final BufferSizeOperand getBufferSizeOperand() { result = getAnOperand() }
+ final BufferSizeOperand getBufferSizeOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the number of bytes written to the buffer.
*/
- final Instruction getBufferSize() { result = getBufferSizeOperand().getDef() }
+ final Instruction getBufferSize() { result = this.getBufferSizeOperand().getDef() }
}
/**
@@ -1812,7 +1837,7 @@ class SizedBufferMustWriteSideEffectInstruction extends WriteSideEffectInstructi
*/
class IndirectMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
IndirectMayWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::IndirectMayWriteSideEffect
+ this.getOpcode() instanceof Opcode::IndirectMayWriteSideEffect
}
}
@@ -1822,7 +1847,9 @@ class IndirectMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
* Unlike `BufferWriteSideEffectInstruction`, the buffer might not be completely overwritten.
*/
class BufferMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
- BufferMayWriteSideEffectInstruction() { getOpcode() instanceof Opcode::BufferMayWriteSideEffect }
+ BufferMayWriteSideEffectInstruction() {
+ this.getOpcode() instanceof Opcode::BufferMayWriteSideEffect
+ }
}
/**
@@ -1832,18 +1859,18 @@ class BufferMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
*/
class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstruction {
SizedBufferMayWriteSideEffectInstruction() {
- getOpcode() instanceof Opcode::SizedBufferMayWriteSideEffect
+ this.getOpcode() instanceof Opcode::SizedBufferMayWriteSideEffect
}
/**
* Gets the operand that holds the number of bytes written to the buffer.
*/
- final BufferSizeOperand getBufferSizeOperand() { result = getAnOperand() }
+ final BufferSizeOperand getBufferSizeOperand() { result = this.getAnOperand() }
/**
* Gets the instruction whose result provides the number of bytes written to the buffer.
*/
- final Instruction getBufferSize() { result = getBufferSizeOperand().getDef() }
+ final Instruction getBufferSize() { result = this.getBufferSizeOperand().getDef() }
}
/**
@@ -1852,80 +1879,80 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio
*/
class InitializeDynamicAllocationInstruction extends SideEffectInstruction {
InitializeDynamicAllocationInstruction() {
- getOpcode() instanceof Opcode::InitializeDynamicAllocation
+ this.getOpcode() instanceof Opcode::InitializeDynamicAllocation
}
/**
* Gets the operand that represents the address of the allocation this instruction is initializing.
*/
- final AddressOperand getAllocationAddressOperand() { result = getAnOperand() }
+ final AddressOperand getAllocationAddressOperand() { result = this.getAnOperand() }
/**
* Gets the address for the allocation this instruction is initializing.
*/
- final Instruction getAllocationAddress() { result = getAllocationAddressOperand().getDef() }
+ final Instruction getAllocationAddress() { result = this.getAllocationAddressOperand().getDef() }
}
/**
* An instruction representing a GNU or MSVC inline assembly statement.
*/
class InlineAsmInstruction extends Instruction {
- InlineAsmInstruction() { getOpcode() instanceof Opcode::InlineAsm }
+ InlineAsmInstruction() { this.getOpcode() instanceof Opcode::InlineAsm }
}
/**
* An instruction that throws an exception.
*/
class ThrowInstruction extends Instruction {
- ThrowInstruction() { getOpcode() instanceof ThrowOpcode }
+ ThrowInstruction() { this.getOpcode() instanceof ThrowOpcode }
}
/**
* An instruction that throws a new exception.
*/
class ThrowValueInstruction extends ThrowInstruction {
- ThrowValueInstruction() { getOpcode() instanceof Opcode::ThrowValue }
+ ThrowValueInstruction() { this.getOpcode() instanceof Opcode::ThrowValue }
/**
* Gets the address operand of the exception thrown by this instruction.
*/
- final AddressOperand getExceptionAddressOperand() { result = getAnOperand() }
+ final AddressOperand getExceptionAddressOperand() { result = this.getAnOperand() }
/**
* Gets the address of the exception thrown by this instruction.
*/
- final Instruction getExceptionAddress() { result = getExceptionAddressOperand().getDef() }
+ final Instruction getExceptionAddress() { result = this.getExceptionAddressOperand().getDef() }
/**
* Gets the operand for the exception thrown by this instruction.
*/
- final LoadOperand getExceptionOperand() { result = getAnOperand() }
+ final LoadOperand getExceptionOperand() { result = this.getAnOperand() }
/**
* Gets the exception thrown by this instruction.
*/
- final Instruction getException() { result = getExceptionOperand().getDef() }
+ final Instruction getException() { result = this.getExceptionOperand().getDef() }
}
/**
* An instruction that re-throws the current exception.
*/
class ReThrowInstruction extends ThrowInstruction {
- ReThrowInstruction() { getOpcode() instanceof Opcode::ReThrow }
+ ReThrowInstruction() { this.getOpcode() instanceof Opcode::ReThrow }
}
/**
* An instruction that exits the current function by propagating an exception.
*/
class UnwindInstruction extends Instruction {
- UnwindInstruction() { getOpcode() instanceof Opcode::Unwind }
+ UnwindInstruction() { this.getOpcode() instanceof Opcode::Unwind }
}
/**
* An instruction that starts a `catch` handler.
*/
class CatchInstruction extends Instruction {
- CatchInstruction() { getOpcode() instanceof CatchOpcode }
+ CatchInstruction() { this.getOpcode() instanceof CatchOpcode }
}
/**
@@ -1935,7 +1962,7 @@ class CatchByTypeInstruction extends CatchInstruction {
Language::LanguageType exceptionType;
CatchByTypeInstruction() {
- getOpcode() instanceof Opcode::CatchByType and
+ this.getOpcode() instanceof Opcode::CatchByType and
exceptionType = Raw::getInstructionExceptionType(this)
}
@@ -1951,21 +1978,21 @@ class CatchByTypeInstruction extends CatchInstruction {
* An instruction that catches any exception.
*/
class CatchAnyInstruction extends CatchInstruction {
- CatchAnyInstruction() { getOpcode() instanceof Opcode::CatchAny }
+ CatchAnyInstruction() { this.getOpcode() instanceof Opcode::CatchAny }
}
/**
* An instruction that initializes all escaped memory.
*/
class AliasedDefinitionInstruction extends Instruction {
- AliasedDefinitionInstruction() { getOpcode() instanceof Opcode::AliasedDefinition }
+ AliasedDefinitionInstruction() { this.getOpcode() instanceof Opcode::AliasedDefinition }
}
/**
* An instruction that consumes all escaped memory on exit from the function.
*/
class AliasedUseInstruction extends Instruction {
- AliasedUseInstruction() { getOpcode() instanceof Opcode::AliasedUse }
+ AliasedUseInstruction() { this.getOpcode() instanceof Opcode::AliasedUse }
}
/**
@@ -1979,7 +2006,7 @@ class AliasedUseInstruction extends Instruction {
* runtime.
*/
class PhiInstruction extends Instruction {
- PhiInstruction() { getOpcode() instanceof Opcode::Phi }
+ PhiInstruction() { this.getOpcode() instanceof Opcode::Phi }
/**
* Gets all of the instruction's `PhiInputOperand`s, representing the values that flow from each predecessor block.
@@ -2047,29 +2074,29 @@ class PhiInstruction extends Instruction {
* https://link.springer.com/content/pdf/10.1007%2F3-540-61053-7_66.pdf.
*/
class ChiInstruction extends Instruction {
- ChiInstruction() { getOpcode() instanceof Opcode::Chi }
+ ChiInstruction() { this.getOpcode() instanceof Opcode::Chi }
/**
* Gets the operand that represents the previous state of all memory that might be aliased by the
* memory write.
*/
- final ChiTotalOperand getTotalOperand() { result = getAnOperand() }
+ final ChiTotalOperand getTotalOperand() { result = this.getAnOperand() }
/**
* Gets the operand that represents the previous state of all memory that might be aliased by the
* memory write.
*/
- final Instruction getTotal() { result = getTotalOperand().getDef() }
+ final Instruction getTotal() { result = this.getTotalOperand().getDef() }
/**
* Gets the operand that represents the new value written by the memory write.
*/
- final ChiPartialOperand getPartialOperand() { result = getAnOperand() }
+ final ChiPartialOperand getPartialOperand() { result = this.getAnOperand() }
/**
* Gets the operand that represents the new value written by the memory write.
*/
- final Instruction getPartial() { result = getPartialOperand().getDef() }
+ final Instruction getPartial() { result = this.getPartialOperand().getDef() }
/**
* Gets the bit range `[startBit, endBit)` updated by the partial operand of this `ChiInstruction`, relative to the start address of the total operand.
@@ -2093,7 +2120,7 @@ class ChiInstruction extends Instruction {
* or `Switch` instruction where that particular edge is infeasible.
*/
class UnreachedInstruction extends Instruction {
- UnreachedInstruction() { getOpcode() instanceof Opcode::Unreached }
+ UnreachedInstruction() { this.getOpcode() instanceof Opcode::Unreached }
}
/**
@@ -2106,7 +2133,7 @@ class BuiltInOperationInstruction extends Instruction {
Language::BuiltInOperation operation;
BuiltInOperationInstruction() {
- getOpcode() instanceof BuiltInOperationOpcode and
+ this.getOpcode() instanceof BuiltInOperationOpcode and
operation = Raw::getInstructionBuiltInOperation(this)
}
@@ -2122,9 +2149,9 @@ class BuiltInOperationInstruction extends Instruction {
* actual operation is specified by the `getBuiltInOperation()` predicate.
*/
class BuiltInInstruction extends BuiltInOperationInstruction {
- BuiltInInstruction() { getOpcode() instanceof Opcode::BuiltIn }
+ BuiltInInstruction() { this.getOpcode() instanceof Opcode::BuiltIn }
- final override string getImmediateString() { result = getBuiltInOperation().toString() }
+ final override string getImmediateString() { result = this.getBuiltInOperation().toString() }
}
/**
@@ -2135,7 +2162,7 @@ class BuiltInInstruction extends BuiltInOperationInstruction {
* to the `...` parameter.
*/
class VarArgsStartInstruction extends UnaryInstruction {
- VarArgsStartInstruction() { getOpcode() instanceof Opcode::VarArgsStart }
+ VarArgsStartInstruction() { this.getOpcode() instanceof Opcode::VarArgsStart }
}
/**
@@ -2145,7 +2172,7 @@ class VarArgsStartInstruction extends UnaryInstruction {
* a result.
*/
class VarArgsEndInstruction extends UnaryInstruction {
- VarArgsEndInstruction() { getOpcode() instanceof Opcode::VarArgsEnd }
+ VarArgsEndInstruction() { this.getOpcode() instanceof Opcode::VarArgsEnd }
}
/**
@@ -2155,7 +2182,7 @@ class VarArgsEndInstruction extends UnaryInstruction {
* argument.
*/
class VarArgInstruction extends UnaryInstruction {
- VarArgInstruction() { getOpcode() instanceof Opcode::VarArg }
+ VarArgInstruction() { this.getOpcode() instanceof Opcode::VarArg }
}
/**
@@ -2166,7 +2193,7 @@ class VarArgInstruction extends UnaryInstruction {
* argument of the `...` parameter.
*/
class NextVarArgInstruction extends UnaryInstruction {
- NextVarArgInstruction() { getOpcode() instanceof Opcode::NextVarArg }
+ NextVarArgInstruction() { this.getOpcode() instanceof Opcode::NextVarArg }
}
/**
@@ -2180,5 +2207,5 @@ class NextVarArgInstruction extends UnaryInstruction {
* The result is the address of the newly allocated object.
*/
class NewObjInstruction extends Instruction {
- NewObjInstruction() { getOpcode() instanceof Opcode::NewObj }
+ NewObjInstruction() { this.getOpcode() instanceof Opcode::NewObj }
}
diff --git a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll
index d7cf89ca9aa..85d217bd361 100644
--- a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll
+++ b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll
@@ -46,12 +46,12 @@ class Operand extends TStageOperand {
/**
* Gets the location of the source code for this operand.
*/
- final Language::Location getLocation() { result = getUse().getLocation() }
+ final Language::Location getLocation() { result = this.getUse().getLocation() }
/**
* Gets the function that contains this operand.
*/
- final IRFunction getEnclosingIRFunction() { result = getUse().getEnclosingIRFunction() }
+ final IRFunction getEnclosingIRFunction() { result = this.getUse().getEnclosingIRFunction() }
/**
* Gets the `Instruction` that consumes this operand.
@@ -74,7 +74,7 @@ class Operand extends TStageOperand {
*/
final Instruction getDef() {
result = this.getAnyDef() and
- getDefinitionOverlap() instanceof MustExactlyOverlap
+ this.getDefinitionOverlap() instanceof MustExactlyOverlap
}
/**
@@ -82,7 +82,7 @@ class Operand extends TStageOperand {
*
* Gets the `Instruction` that consumes this operand.
*/
- deprecated final Instruction getUseInstruction() { result = getUse() }
+ deprecated final Instruction getUseInstruction() { result = this.getUse() }
/**
* DEPRECATED: use `getAnyDef` or `getDef`. The exact replacement for this
@@ -91,7 +91,7 @@ class Operand extends TStageOperand {
*
* Gets the `Instruction` whose result is the value of the operand.
*/
- deprecated final Instruction getDefinitionInstruction() { result = getAnyDef() }
+ deprecated final Instruction getDefinitionInstruction() { result = this.getAnyDef() }
/**
* Gets the overlap relationship between the operand's definition and its use.
@@ -101,7 +101,9 @@ class Operand extends TStageOperand {
/**
* Holds if the result of the definition instruction does not exactly overlap this use.
*/
- final predicate isDefinitionInexact() { not getDefinitionOverlap() instanceof MustExactlyOverlap }
+ final predicate isDefinitionInexact() {
+ not this.getDefinitionOverlap() instanceof MustExactlyOverlap
+ }
/**
* Gets a prefix to use when dumping the operand in an operand list.
@@ -121,7 +123,7 @@ class Operand extends TStageOperand {
* For example: `this:r3_5`
*/
final string getDumpString() {
- result = getDumpLabel() + getInexactSpecifier() + getDefinitionId()
+ result = this.getDumpLabel() + this.getInexactSpecifier() + this.getDefinitionId()
}
/**
@@ -129,9 +131,9 @@ class Operand extends TStageOperand {
* definition is not modeled in SSA.
*/
private string getDefinitionId() {
- result = getAnyDef().getResultId()
+ result = this.getAnyDef().getResultId()
or
- not exists(getAnyDef()) and result = "m?"
+ not exists(this.getAnyDef()) and result = "m?"
}
/**
@@ -140,7 +142,7 @@ class Operand extends TStageOperand {
* the empty string.
*/
private string getInexactSpecifier() {
- if isDefinitionInexact() then result = "~" else result = ""
+ if this.isDefinitionInexact() then result = "~" else result = ""
}
/**
@@ -155,7 +157,7 @@ class Operand extends TStageOperand {
* the definition type, such as in the case of a partial read or a read from a pointer that
* has been cast to a different type.
*/
- Language::LanguageType getLanguageType() { result = getAnyDef().getResultLanguageType() }
+ Language::LanguageType getLanguageType() { result = this.getAnyDef().getResultLanguageType() }
/**
* Gets the language-neutral type of the value consumed by this operand. This is usually the same
@@ -164,7 +166,7 @@ class Operand extends TStageOperand {
* from the definition type, such as in the case of a partial read or a read from a pointer that
* has been cast to a different type.
*/
- final IRType getIRType() { result = getLanguageType().getIRType() }
+ final IRType getIRType() { result = this.getLanguageType().getIRType() }
/**
* Gets the type of the value consumed by this operand. This is usually the same as the
@@ -173,7 +175,7 @@ class Operand extends TStageOperand {
* the definition type, such as in the case of a partial read or a read from a pointer that
* has been cast to a different type.
*/
- final Language::Type getType() { getLanguageType().hasType(result, _) }
+ final Language::Type getType() { this.getLanguageType().hasType(result, _) }
/**
* Holds if the value consumed by this operand is a glvalue. If this
@@ -182,13 +184,13 @@ class Operand extends TStageOperand {
* not hold, the value of the operand represents a value whose type is
* given by `getType()`.
*/
- final predicate isGLValue() { getLanguageType().hasType(_, true) }
+ final predicate isGLValue() { this.getLanguageType().hasType(_, true) }
/**
* Gets the size of the value consumed by this operand, in bytes. If the operand does not have
* a known constant size, this predicate does not hold.
*/
- final int getSize() { result = getLanguageType().getByteSize() }
+ final int getSize() { result = this.getLanguageType().getByteSize() }
}
/**
@@ -205,7 +207,7 @@ class MemoryOperand extends Operand {
/**
* Gets the kind of memory access performed by the operand.
*/
- MemoryAccessKind getMemoryAccess() { result = getUse().getOpcode().getReadMemoryAccess() }
+ MemoryAccessKind getMemoryAccess() { result = this.getUse().getOpcode().getReadMemoryAccess() }
/**
* Holds if the memory access performed by this operand will not always read from every bit in the
@@ -215,7 +217,7 @@ class MemoryOperand extends Operand {
* conservative estimate of the memory that might actually be accessed at runtime (for example,
* the global side effects of a function call).
*/
- predicate hasMayReadMemoryAccess() { getUse().getOpcode().hasMayReadMemoryAccess() }
+ predicate hasMayReadMemoryAccess() { this.getUse().getOpcode().hasMayReadMemoryAccess() }
/**
* Returns the operand that holds the memory address from which the current operand loads its
@@ -223,8 +225,8 @@ class MemoryOperand extends Operand {
* is `r1`.
*/
final AddressOperand getAddressOperand() {
- getMemoryAccess().usesAddressOperand() and
- result.getUse() = getUse()
+ this.getMemoryAccess().usesAddressOperand() and
+ result.getUse() = this.getUse()
}
}
@@ -294,7 +296,7 @@ class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand, TNonPhiMemoryOpe
result = unique(Instruction defInstr | hasDefinition(defInstr, _))
}
- final override Overlap getDefinitionOverlap() { hasDefinition(_, result) }
+ final override Overlap getDefinitionOverlap() { this.hasDefinition(_, result) }
pragma[noinline]
private predicate hasDefinition(Instruction defInstr, Overlap overlap) {
@@ -449,13 +451,17 @@ class PhiInputOperand extends MemoryOperand, TPhiOperand {
final override Overlap getDefinitionOverlap() { result = overlap }
- final override int getDumpSortOrder() { result = 11 + getPredecessorBlock().getDisplayIndex() }
-
- final override string getDumpLabel() {
- result = "from " + getPredecessorBlock().getDisplayIndex().toString() + ":"
+ final override int getDumpSortOrder() {
+ result = 11 + this.getPredecessorBlock().getDisplayIndex()
}
- final override string getDumpId() { result = getPredecessorBlock().getDisplayIndex().toString() }
+ final override string getDumpLabel() {
+ result = "from " + this.getPredecessorBlock().getDisplayIndex().toString() + ":"
+ }
+
+ final override string getDumpId() {
+ result = this.getPredecessorBlock().getDisplayIndex().toString()
+ }
/**
* Gets the predecessor block from which this value comes.
diff --git a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/AliasAnalysisImports.qll b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/AliasAnalysisImports.qll
index e0bf271dcc7..b0eb5ec98cb 100644
--- a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/AliasAnalysisImports.qll
+++ b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/AliasAnalysisImports.qll
@@ -36,7 +36,7 @@ module AliasModels {
* Holds if this is the input value of the parameter with index `index`.
* DEPRECATED: Use `isParameter(index)` instead.
*/
- deprecated final predicate isInParameter(ParameterIndex index) { isParameter(index) }
+ deprecated final predicate isInParameter(ParameterIndex index) { this.isParameter(index) }
/**
* Holds if this is the input value pointed to by a pointer parameter to a function, or the input
@@ -63,7 +63,7 @@ module AliasModels {
* DEPRECATED: Use `isParameterDeref(index)` instead.
*/
deprecated final predicate isInParameterPointer(ParameterIndex index) {
- isParameterDeref(index)
+ this.isParameterDeref(index)
}
/**
@@ -86,7 +86,7 @@ module AliasModels {
* function.
* DEPRECATED: Use `isQualifierObject()` instead.
*/
- deprecated final predicate isInQualifier() { isQualifierObject() }
+ deprecated final predicate isInQualifier() { this.isQualifierObject() }
/**
* Holds if this is the input value of the `this` pointer of an instance member function.
@@ -184,7 +184,7 @@ module AliasModels {
* DEPRECATED: Use `isParameterDeref(index)` instead.
*/
deprecated final predicate isOutParameterPointer(ParameterIndex index) {
- isParameterDeref(index)
+ this.isParameterDeref(index)
}
/**
@@ -207,7 +207,7 @@ module AliasModels {
* function.
* DEPRECATED: Use `isQualifierObject()` instead.
*/
- deprecated final predicate isOutQualifier() { isQualifierObject() }
+ deprecated final predicate isOutQualifier() { this.isQualifierObject() }
/**
* Holds if this is the value returned by a function.
@@ -232,7 +232,7 @@ module AliasModels {
* Holds if this is the value returned by a function.
* DEPRECATED: Use `isReturnValue()` instead.
*/
- deprecated final predicate isOutReturnValue() { isReturnValue() }
+ deprecated final predicate isOutReturnValue() { this.isReturnValue() }
/**
* Holds if this is the output value pointed to by the return value of a function, if the function
@@ -260,7 +260,7 @@ module AliasModels {
* function returns a reference.
* DEPRECATED: Use `isReturnValueDeref()` instead.
*/
- deprecated final predicate isOutReturnPointer() { isReturnValueDeref() }
+ deprecated final predicate isOutReturnPointer() { this.isReturnValueDeref() }
/**
* Holds if `i >= 0` and `isParameterDeref(i)` holds for this is the value, or
diff --git a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/SSAConsistency.ql b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/SSAConsistency.ql
index cee1274eb19..d4d8e29fb31 100644
--- a/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/SSAConsistency.ql
+++ b/repo-tests/codeql/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/SSAConsistency.ql
@@ -2,7 +2,7 @@
* @name Unaliased SSA Consistency Check
* @description Performs consistency checks on the SSA construction. This query should have no results.
* @kind table
- * @id csharp/unaliased-ssa-consistency-check
+ * @id cs/unaliased-ssa-consistency-check
*/
import SSAConsistency
diff --git a/repo-tests/codeql/csharp/ql/src/experimental/ir/internal/IRGuards.qll b/repo-tests/codeql/csharp/ql/src/experimental/ir/internal/IRGuards.qll
index d01dcbed1e1..2a530b71357 100644
--- a/repo-tests/codeql/csharp/ql/src/experimental/ir/internal/IRGuards.qll
+++ b/repo-tests/codeql/csharp/ql/src/experimental/ir/internal/IRGuards.qll
@@ -107,7 +107,7 @@ private predicate impliesValue(
wholeIsTrue = true and partIsTrue = true and part = blo.getAnOperand()
or
wholeIsTrue = true and
- impliesValue(blo.getAnOperand().(BinaryLogicalOperation), part, partIsTrue, true)
+ impliesValue(blo.getAnOperand(), part, partIsTrue, true)
)
or
blo instanceof LogicalOrExpr and
@@ -115,7 +115,7 @@ private predicate impliesValue(
wholeIsTrue = false and partIsTrue = false and part = blo.getAnOperand()
or
wholeIsTrue = false and
- impliesValue(blo.getAnOperand().(BinaryLogicalOperation), part, partIsTrue, false)
+ impliesValue(blo.getAnOperand(), part, partIsTrue, false)
)
}
@@ -139,7 +139,7 @@ private class GuardConditionFromBinaryLogicalOperator extends GuardCondition {
override predicate comparesLt(Expr left, Expr right, int k, boolean isLessThan, boolean testIsTrue) {
exists(boolean partIsTrue, GuardCondition part |
- impliesValue(this.(BinaryLogicalOperation), part, partIsTrue, testIsTrue)
+ impliesValue(this, part, partIsTrue, testIsTrue)
|
part.comparesLt(left, right, k, isLessThan, partIsTrue)
)
@@ -147,13 +147,13 @@ private class GuardConditionFromBinaryLogicalOperator extends GuardCondition {
override predicate ensuresLt(Expr left, Expr right, int k, BasicBlock block, boolean isLessThan) {
exists(boolean testIsTrue |
- comparesLt(left, right, k, isLessThan, testIsTrue) and this.controls(block, testIsTrue)
+ this.comparesLt(left, right, k, isLessThan, testIsTrue) and this.controls(block, testIsTrue)
)
}
override predicate comparesEq(Expr left, Expr right, int k, boolean areEqual, boolean testIsTrue) {
exists(boolean partIsTrue, GuardCondition part |
- impliesValue(this.(BinaryLogicalOperation), part, partIsTrue, testIsTrue)
+ impliesValue(this, part, partIsTrue, testIsTrue)
|
part.comparesEq(left, right, k, areEqual, partIsTrue)
)
@@ -161,7 +161,7 @@ private class GuardConditionFromBinaryLogicalOperator extends GuardCondition {
override predicate ensuresEq(Expr left, Expr right, int k, BasicBlock block, boolean areEqual) {
exists(boolean testIsTrue |
- comparesEq(left, right, k, areEqual, testIsTrue) and this.controls(block, testIsTrue)
+ this.comparesEq(left, right, k, areEqual, testIsTrue) and this.controls(block, testIsTrue)
)
}
}
@@ -326,9 +326,9 @@ class IRGuardCondition extends Instruction {
cached
predicate controlsEdge(IRBlock pred, IRBlock succ, boolean testIsTrue) {
pred.getASuccessor() = succ and
- controls(pred, testIsTrue)
+ this.controls(pred, testIsTrue)
or
- hasBranchEdge(succ, testIsTrue) and
+ this.hasBranchEdge(succ, testIsTrue) and
branch.getCondition() = this and
branch.getBlock() = pred
}
diff --git a/repo-tests/codeql/csharp/ql/src/printAst.ql b/repo-tests/codeql/csharp/ql/src/printAst.ql
index dd27069d84d..380f4b4024b 100644
--- a/repo-tests/codeql/csharp/ql/src/printAst.ql
+++ b/repo-tests/codeql/csharp/ql/src/printAst.ql
@@ -2,7 +2,7 @@
* @name Print AST
* @description Outputs a representation of a file's Abstract Syntax Tree. This
* query is used by the VS Code extension.
- * @id csharp/print-ast
+ * @id cs/print-ast
* @kind graph
* @tags ide-contextual-queries/print-ast
*/
diff --git a/repo-tests/codeql/java/ql/lib/config/semmlecode.dbscheme b/repo-tests/codeql/java/ql/lib/config/semmlecode.dbscheme
index 017ac1ed2df..89a76edebff 100755
--- a/repo-tests/codeql/java/ql/lib/config/semmlecode.dbscheme
+++ b/repo-tests/codeql/java/ql/lib/config/semmlecode.dbscheme
@@ -393,7 +393,7 @@ typeVars(
string nodeName: string ref,
int pos: int ref,
int kind: int ref, // deprecated
- int parentid: @typeorcallable ref
+ int parentid: @classorinterfaceorcallable ref
);
wildcards(
@@ -414,7 +414,7 @@ typeBounds(
typeArgs(
int argumentid: @reftype ref,
int pos: int ref,
- int parentid: @typeorcallable ref
+ int parentid: @classorinterfaceorcallable ref
);
isParameterized(
@@ -487,7 +487,7 @@ hasModifier(
imports(
unique int id: @import,
- int holder: @typeorpackage ref,
+ int holder: @classorinterfaceorpackage ref,
string name: string ref,
int kind: int ref
);
@@ -857,10 +857,9 @@ javadocText(
@javadocParent = @javadoc | @javadocTag;
@javadocElement = @javadocTag | @javadocText;
-@typeorpackage = @type | @package;
-
-@typeorcallable = @type | @callable;
@classorinterface = @interface | @class;
+@classorinterfaceorpackage = @classorinterface | @package;
+@classorinterfaceorcallable = @classorinterface | @callable;
@boundedtype = @typevariable | @wildcard;
@reftype = @classorinterface | @array | @boundedtype;
@classorarray = @class | @array;
diff --git a/repo-tests/codeql/java/ql/lib/external/ExternalArtifact.qll b/repo-tests/codeql/java/ql/lib/external/ExternalArtifact.qll
index 5359b99c7c8..2e782a6a4da 100644
--- a/repo-tests/codeql/java/ql/lib/external/ExternalArtifact.qll
+++ b/repo-tests/codeql/java/ql/lib/external/ExternalArtifact.qll
@@ -3,24 +3,25 @@ import java
class ExternalData extends @externalDataElement {
string getDataPath() { externalData(this, result, _, _) }
- string getQueryPath() { result = getDataPath().regexpReplaceAll("\\.[^.]*$", ".ql") }
+ string getQueryPath() { result = this.getDataPath().regexpReplaceAll("\\.[^.]*$", ".ql") }
int getNumFields() { result = 1 + max(int i | externalData(this, _, i, _) | i) }
string getField(int index) { externalData(this, _, index, result) }
- int getFieldAsInt(int index) { result = getField(index).toInt() }
+ int getFieldAsInt(int index) { result = this.getField(index).toInt() }
- float getFieldAsFloat(int index) { result = getField(index).toFloat() }
+ float getFieldAsFloat(int index) { result = this.getField(index).toFloat() }
- date getFieldAsDate(int index) { result = getField(index).toDate() }
+ date getFieldAsDate(int index) { result = this.getField(index).toDate() }
- string toString() { result = getQueryPath() + ": " + buildTupleString(0) }
+ string toString() { result = this.getQueryPath() + ": " + this.buildTupleString(0) }
private string buildTupleString(int start) {
- start = getNumFields() - 1 and result = getField(start)
+ start = this.getNumFields() - 1 and result = this.getField(start)
or
- start < getNumFields() - 1 and result = getField(start) + "," + buildTupleString(start + 1)
+ start < this.getNumFields() - 1 and
+ result = this.getField(start) + "," + this.buildTupleString(start + 1)
}
}
@@ -33,7 +34,7 @@ class DefectExternalData extends ExternalData {
this.getNumFields() = 2
}
- string getURL() { result = getField(0) }
+ string getURL() { result = this.getField(0) }
- string getMessage() { result = getField(1) }
+ string getMessage() { result = this.getField(1) }
}
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/FileSystem.qll b/repo-tests/codeql/java/ql/lib/semmle/code/FileSystem.qll
index 6c252d569e9..cace20f63e1 100755
--- a/repo-tests/codeql/java/ql/lib/semmle/code/FileSystem.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/FileSystem.qll
@@ -47,7 +47,7 @@ class Container extends @container, Top {
*/
string getRelativePath() {
exists(string absPath, string pref |
- absPath = getAbsolutePath() and sourceLocationPrefix(pref)
+ absPath = this.getAbsolutePath() and sourceLocationPrefix(pref)
|
absPath = pref and result = ""
or
@@ -74,7 +74,7 @@ class Container extends @container, Top {
*
*/
string getBaseName() {
- result = getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1)
+ result = this.getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1)
}
/**
@@ -100,7 +100,9 @@ class Container extends @container, Top {
* | "/tmp/x.tar.gz" | "gz" |
*
*/
- string getExtension() { result = getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3) }
+ string getExtension() {
+ result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3)
+ }
/**
* Gets the stem of this container, that is, the prefix of its base name up to
@@ -119,7 +121,9 @@ class Container extends @container, Top {
* | "/tmp/x.tar.gz" | "x.tar" |
*
*/
- string getStem() { result = getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1) }
+ string getStem() {
+ result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1)
+ }
/** Gets the parent container of this file or folder, if any. */
Container getParentContainer() { containerparent(result, this) }
@@ -128,20 +132,20 @@ class Container extends @container, Top {
Container getAChildContainer() { this = result.getParentContainer() }
/** Gets a file in this container. */
- File getAFile() { result = getAChildContainer() }
+ File getAFile() { result = this.getAChildContainer() }
/** Gets the file in this container that has the given `baseName`, if any. */
File getFile(string baseName) {
- result = getAFile() and
+ result = this.getAFile() and
result.getBaseName() = baseName
}
/** Gets a sub-folder in this container. */
- Folder getAFolder() { result = getAChildContainer() }
+ Folder getAFolder() { result = this.getAChildContainer() }
/** Gets the sub-folder in this container that has the given `baseName`, if any. */
Folder getFolder(string baseName) {
- result = getAFolder() and
+ result = this.getAFolder() and
result.getBaseName() = baseName
}
@@ -152,7 +156,7 @@ class Container extends @container, Top {
* to provide a different result. To get the absolute path of any `Container`, call
* `Container.getAbsolutePath()` directly.
*/
- override string toString() { result = getAbsolutePath() }
+ override string toString() { result = this.getAbsolutePath() }
}
/** A folder. */
@@ -160,7 +164,7 @@ class Folder extends Container, @folder {
override string getAbsolutePath() { folders(this, result) }
/** Gets the URL of this folder. */
- override string getURL() { result = "folder://" + getAbsolutePath() }
+ override string getURL() { result = "folder://" + this.getAbsolutePath() }
override string getAPrimaryQlClass() { result = "Folder" }
}
@@ -183,7 +187,7 @@ class File extends Container, @file {
* A Java archive file with a ".jar" extension.
*/
class JarFile extends File {
- JarFile() { getExtension() = "jar" }
+ JarFile() { this.getExtension() = "jar" }
/**
* Gets the main attribute with the specified `key`
@@ -195,13 +199,17 @@ class JarFile extends File {
* Gets the "Specification-Version" main attribute
* from this JAR file's manifest.
*/
- string getSpecificationVersion() { result = getManifestMainAttribute("Specification-Version") }
+ string getSpecificationVersion() {
+ result = this.getManifestMainAttribute("Specification-Version")
+ }
/**
* Gets the "Implementation-Version" main attribute
* from this JAR file's manifest.
*/
- string getImplementationVersion() { result = getManifestMainAttribute("Implementation-Version") }
+ string getImplementationVersion() {
+ result = this.getManifestMainAttribute("Implementation-Version")
+ }
/**
* Gets the per-entry attribute for the specified `entry` and `key`
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/Location.qll b/repo-tests/codeql/java/ql/lib/semmle/code/Location.qll
index 2af4f8712e0..d90a189acb7 100755
--- a/repo-tests/codeql/java/ql/lib/semmle/code/Location.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/Location.qll
@@ -63,10 +63,10 @@ class Top extends @top {
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
- hasLocationInfoAux(filepath, startline, startcolumn, endline, endcolumn)
+ this.hasLocationInfoAux(filepath, startline, startcolumn, endline, endcolumn)
or
exists(string outFilepath, int outStartline, int outEndline |
- hasLocationInfoAux(outFilepath, outStartline, _, outEndline, _) and
+ this.hasLocationInfoAux(outFilepath, outStartline, _, outEndline, _) and
hasSmapLocationInfo(filepath, startline, startcolumn, endline, endcolumn, outFilepath,
outStartline, outEndline)
)
@@ -103,7 +103,7 @@ class Top extends @top {
/**
* Gets a comma-separated list of the names of the primary CodeQL classes to which this element belongs.
*/
- final string getPrimaryQlClasses() { result = concat(getAPrimaryQlClass(), ",") }
+ final string getPrimaryQlClasses() { result = concat(this.getAPrimaryQlClass(), ",") }
/**
* Gets the name of a primary CodeQL class to which this element belongs.
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/SMAP.qll b/repo-tests/codeql/java/ql/lib/semmle/code/SMAP.qll
index 006f5ad5e38..575d54f92de 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/SMAP.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/SMAP.qll
@@ -52,6 +52,6 @@ predicate hasSmapLocationInfo(
smap(inputFile, isl, outputFile, osl) and
smap(inputFile, iel - 1, outputFile, oel) and
isc = 1 and
- iec = 0
+ iec = 1
)
}
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/Annotation.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/Annotation.qll
index 2ee7c6403b4..342a2bd6e0d 100755
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/Annotation.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/Annotation.qll
@@ -51,7 +51,7 @@ class Annotation extends @annotation, Expr {
Expr getValue(string name) { filteredAnnotValue(this, this.getAnnotationElement(name), result) }
/** Gets the element being annotated. */
- Element getTarget() { result = getAnnotatedElement() }
+ Element getTarget() { result = this.getAnnotatedElement() }
override string toString() { result = this.getType().getName() }
@@ -67,8 +67,8 @@ class Annotation extends @annotation, Expr {
* expression defined for the value.
*/
Expr getAValue(string name) {
- getType().getAnnotationElement(name).getType() instanceof Array and
- exists(Expr value | value = getValue(name) |
+ this.getType().getAnnotationElement(name).getType() instanceof Array and
+ exists(Expr value | value = this.getValue(name) |
if value instanceof ArrayInit then result = value.(ArrayInit).getAnInit() else result = value
)
}
@@ -104,7 +104,7 @@ class Annotatable extends Element {
/** Holds if this element has the specified annotation. */
predicate hasAnnotation(string package, string name) {
- exists(AnnotationType at | at = getAnAnnotation().getType() |
+ exists(AnnotationType at | at = this.getAnAnnotation().getType() |
at.nestedName() = name and at.getPackage().getName() = package
)
}
@@ -118,7 +118,7 @@ class Annotatable extends Element {
* annotation attached to it for the specified `category`.
*/
predicate suppressesWarningsAbout(string category) {
- category = getAnAnnotation().(SuppressWarningsAnnotation).getASuppressedWarning()
+ category = this.getAnAnnotation().(SuppressWarningsAnnotation).getASuppressedWarning()
or
this.(Member).getDeclaringType().suppressesWarningsAbout(category)
or
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/ControlFlowGraph.qll
index ff60abb4e73..14738dda96f 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/ControlFlowGraph.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/ControlFlowGraph.qll
@@ -150,7 +150,7 @@ private module ControlFlowGraphImpl {
* `TypeThrowable` which results in both `TypeError` and `TypeRuntimeException`.
*/
UncheckedThrowableType getAnUncheckedSubtype() {
- result = this.(UncheckedThrowableType)
+ result = this
or
result instanceof TypeError and this instanceof TypeThrowable
or
@@ -528,13 +528,13 @@ private module ControlFlowGraphImpl {
/** Gets the first child node, if any. */
ControlFlowNode firstChild() {
- result = getChildNode(-1)
+ result = this.getChildNode(-1)
or
- result = getChildNode(0) and not exists(getChildNode(-1))
+ result = this.getChildNode(0) and not exists(this.getChildNode(-1))
}
/** Holds if this CFG node has any child nodes. */
- predicate isLeafNode() { not exists(getChildNode(_)) }
+ predicate isLeafNode() { not exists(this.getChildNode(_)) }
/** Holds if this node can finish with a `normalCompletion`. */
predicate mayCompleteNormally() {
@@ -1222,10 +1222,10 @@ class ConditionNode extends ControlFlowNode {
ControlFlowNode getABranchSuccessor(boolean branch) { result = branchSuccessor(this, branch) }
/** Gets a true-successor of the `ConditionNode`. */
- ControlFlowNode getATrueSuccessor() { result = getABranchSuccessor(true) }
+ ControlFlowNode getATrueSuccessor() { result = this.getABranchSuccessor(true) }
/** Gets a false-successor of the `ConditionNode`. */
- ControlFlowNode getAFalseSuccessor() { result = getABranchSuccessor(false) }
+ ControlFlowNode getAFalseSuccessor() { result = this.getABranchSuccessor(false) }
/** Gets the condition of this `ConditionNode`. This is equal to the node itself. */
Expr getCondition() { result = this }
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/Conversions.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/Conversions.qll
index 9d55f1297fc..b7cd80c4906 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/Conversions.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/Conversions.qll
@@ -27,7 +27,7 @@ abstract class ConversionSite extends Expr {
/**
* Whether this conversion site actually induces a conversion.
*/
- predicate isTrivial() { getConversionTarget() = getConversionSource() }
+ predicate isTrivial() { this.getConversionTarget() = this.getConversionSource() }
/**
* Whether this conversion is implicit.
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/Element.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/Element.qll
index 12a08f8eb9d..14e48fc0d40 100755
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/Element.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/Element.qll
@@ -34,10 +34,10 @@ class Element extends @element, Top {
* Elements pertaining to source files may include generated elements
* not visible in source code, such as implicit default constructors.
*/
- predicate fromSource() { getCompilationUnit().getExtension() = "java" }
+ predicate fromSource() { this.getCompilationUnit().getExtension() = "java" }
/** Gets the compilation unit that this element belongs to. */
- CompilationUnit getCompilationUnit() { result = getFile() }
+ CompilationUnit getCompilationUnit() { result = this.getFile() }
/** Cast this element to a `Documentable`. */
Documentable getDoc() { result = this }
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/Expr.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/Expr.qll
index b83f5332756..8aad29dec09 100755
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/Expr.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/Expr.qll
@@ -86,13 +86,15 @@ class Expr extends ExprParent, @expr {
* explicit constructor invocation statement.
*/
- getEnclosingCallable().isStatic()
+ this.getEnclosingCallable().isStatic()
or
- getParent+() instanceof ThisConstructorInvocationStmt
+ this.getParent+() instanceof ThisConstructorInvocationStmt
or
- getParent+() instanceof SuperConstructorInvocationStmt
+ this.getParent+() instanceof SuperConstructorInvocationStmt
or
- exists(LambdaExpr lam | lam.asMethod() = getEnclosingCallable() and lam.isInStaticContext())
+ exists(LambdaExpr lam |
+ lam.asMethod() = this.getEnclosingCallable() and lam.isInStaticContext()
+ )
}
/** Holds if this expression is parenthesized. */
@@ -116,7 +118,7 @@ private predicate primitiveOrString(Type t) {
*/
class CompileTimeConstantExpr extends Expr {
CompileTimeConstantExpr() {
- primitiveOrString(getType()) and
+ primitiveOrString(this.getType()) and
(
// Literals of primitive type and literals of type `String`.
this instanceof Literal
@@ -164,7 +166,7 @@ class CompileTimeConstantExpr extends Expr {
*/
pragma[nomagic]
string getStringValue() {
- result = this.(StringLiteral).getRepresentedString()
+ result = this.(StringLiteral).getValue()
or
result =
this.(AddExpr).getLeftOperand().(CompileTimeConstantExpr).getStringValue() +
@@ -296,18 +298,15 @@ class CompileTimeConstantExpr extends Expr {
*
* Note that this does not handle the following cases:
*
- * - values of type `long`,
- * - `char` literals.
+ * - values of type `long`.
*/
cached
int getIntValue() {
exists(IntegralType t | this.getType() = t | t.getName().toLowerCase() != "long") and
(
- exists(string lit | lit = this.(Literal).getValue() |
- // `char` literals may get parsed incorrectly, so disallow.
- not this instanceof CharacterLiteral and
- result = lit.toInt()
- )
+ result = this.(IntegerLiteral).getIntValue()
+ or
+ result = this.(CharacterLiteral).getCodePointValue()
or
exists(CastExpr cast, int val |
cast = this and val = cast.getExpr().(CompileTimeConstantExpr).getIntValue()
@@ -425,9 +424,9 @@ class ArrayCreationExpr extends Expr, @arraycreationexpr {
* Gets the size of the first dimension, if it can be statically determined.
*/
int getFirstDimensionSize() {
- if exists(getInit())
- then result = getInit().getSize()
- else result = getDimension(0).(CompileTimeConstantExpr).getIntValue()
+ if exists(this.getInit())
+ then result = this.getInit().getSize()
+ else result = this.getDimension(0).(CompileTimeConstantExpr).getIntValue()
}
/** Gets a printable representation of this expression. */
@@ -463,7 +462,7 @@ class ArrayInit extends Expr, @arrayinit {
* Gets the number of expressions in this initializer, that is, the size the
* created array will have.
*/
- int getSize() { result = count(getAnInit()) }
+ int getSize() { result = count(this.getAnInit()) }
/** Gets a printable representation of this expression. */
override string toString() { result = "{...}" }
@@ -632,9 +631,9 @@ class Literal extends Expr, @literal {
class BooleanLiteral extends Literal, @booleanliteral {
/** Gets the boolean representation of this literal. */
boolean getBooleanValue() {
- result = true and getValue() = "true"
+ result = true and this.getValue() = "true"
or
- result = false and getValue() = "false"
+ result = false and this.getValue() = "false"
}
override string getAPrimaryQlClass() { result = "BooleanLiteral" }
@@ -657,7 +656,7 @@ class BooleanLiteral extends Literal, @booleanliteral {
*/
class IntegerLiteral extends Literal, @integerliteral {
/** Gets the int representation of this literal. */
- int getIntValue() { result = getValue().toInt() }
+ int getIntValue() { result = this.getValue().toInt() }
override string getAPrimaryQlClass() { result = "IntegerLiteral" }
}
@@ -693,7 +692,7 @@ class FloatingPointLiteral extends Literal, @floatingpointliteral {
* Gets the value of this literal as CodeQL 64-bit `float`. The value will
* be parsed as Java 32-bit `float` and then converted to a CodeQL `float`.
*/
- float getFloatValue() { result = getValue().toFloat() }
+ float getFloatValue() { result = this.getValue().toFloat() }
override string getAPrimaryQlClass() { result = "FloatingPointLiteral" }
}
@@ -709,7 +708,7 @@ class DoubleLiteral extends Literal, @doubleliteral {
* Gets the value of this literal as CodeQL 64-bit `float`. The result will
* have the same effective value as the Java `double` literal.
*/
- float getDoubleValue() { result = getValue().toFloat() }
+ float getDoubleValue() { result = this.getValue().toFloat() }
override string getAPrimaryQlClass() { result = "DoubleLiteral" }
}
@@ -717,6 +716,22 @@ class DoubleLiteral extends Literal, @doubleliteral {
/** A character literal. For example, `'\n'`. */
class CharacterLiteral extends Literal, @characterliteral {
override string getAPrimaryQlClass() { result = "CharacterLiteral" }
+
+ /**
+ * Gets a string which consists of the single character represented by
+ * this literal.
+ *
+ * Unicode surrogate characters (U+D800 to U+DFFF) have the replacement character
+ * U+FFFD as result instead.
+ */
+ override string getValue() { result = super.getValue() }
+
+ /**
+ * Gets the Unicode code point value of the character represented by
+ * this literal. The result is the same as if the Java code had cast
+ * the character to an `int`.
+ */
+ int getCodePointValue() { result.toUnicode() = this.getValue() }
}
/**
@@ -730,12 +745,24 @@ class CharacterLiteral extends Literal, @characterliteral {
*/
class StringLiteral extends Literal, @stringliteral {
/**
+ * Gets the string represented by this string literal, that is, the content
+ * of the literal without enclosing quotes and with escape sequences translated.
+ *
+ * Unpaired Unicode surrogate characters (U+D800 to U+DFFF) are replaced with the
+ * replacement character U+FFFD.
+ */
+ override string getValue() { result = super.getValue() }
+
+ /**
+ * DEPRECATED: This predicate will be removed in a future version because
+ * it is just an alias for `getValue()`; that predicate should be used instead.
+ *
* Gets the literal string without the quotes.
*/
- string getRepresentedString() { result = getValue() }
+ deprecated string getRepresentedString() { result = this.getValue() }
/** Holds if this string literal is a text block (`""" ... """`). */
- predicate isTextBlock() { getLiteral().matches("\"\"\"%") }
+ predicate isTextBlock() { this.getLiteral().matches("\"\"\"%") }
override string getAPrimaryQlClass() { result = "StringLiteral" }
}
@@ -1184,7 +1211,7 @@ class LambdaExpr extends FunctionalExpr, @lambdaexpr {
* Gets the implicit method corresponding to this lambda expression.
* The parameters of the lambda expression are the parameters of this method.
*/
- override Method asMethod() { result = getAnonymousClass().getAMethod() }
+ override Method asMethod() { result = this.getAnonymousClass().getAMethod() }
/** Holds if the body of this lambda is an expression. */
predicate hasExprBody() { lambdaKind(this, 0) }
@@ -1194,11 +1221,11 @@ class LambdaExpr extends FunctionalExpr, @lambdaexpr {
/** Gets the body of this lambda expression, if it is an expression. */
Expr getExprBody() {
- hasExprBody() and result = asMethod().getBody().getAChild().(ReturnStmt).getResult()
+ this.hasExprBody() and result = this.asMethod().getBody().getAChild().(ReturnStmt).getResult()
}
/** Gets the body of this lambda expression, if it is a statement. */
- BlockStmt getStmtBody() { hasStmtBody() and result = asMethod().getBody() }
+ BlockStmt getStmtBody() { this.hasStmtBody() and result = this.asMethod().getBody() }
/** Gets a printable representation of this expression. */
override string toString() { result = "...->..." }
@@ -1223,7 +1250,29 @@ class MemberRefExpr extends FunctionalExpr, @memberref {
* (if the reference is to a constructor) or an array creation expression (if the reference
* is to an array constructor).
*/
- override Method asMethod() { result = getAnonymousClass().getAMethod() }
+ override Method asMethod() { result = this.getAnonymousClass().getAMethod() }
+
+ /**
+ * Gets the receiver type whose member this expression refers to. The result might not be
+ * the type which actually declares the member. For example, for the member reference `ArrayList::toString`,
+ * this predicate has the result `java.util.ArrayList`, the type explicitly referred to, while
+ * `getReferencedCallable` will have `java.util.AbstractCollection.toString` as result, which `ArrayList` inherits.
+ */
+ RefType getReceiverType() {
+ exists(Stmt stmt, Expr resultExpr |
+ stmt = asMethod().getBody().(SingletonBlock).getStmt() and
+ (
+ resultExpr = stmt.(ReturnStmt).getResult()
+ or
+ // Note: Currently never an ExprStmt, but might change once https://github.com/github/codeql/issues/3605 is fixed
+ resultExpr = stmt.(ExprStmt).getExpr()
+ )
+ |
+ result = resultExpr.(MethodAccess).getReceiverType() or
+ result = resultExpr.(ClassInstanceExpr).getConstructedType() or
+ result = resultExpr.(ArrayCreationExpr).getType()
+ )
+ }
/**
* Gets the method or constructor referenced by this member reference expression.
@@ -1274,16 +1323,16 @@ class ConditionalExpr extends Expr, @conditionalexpr {
* it is `getFalseExpr()`.
*/
Expr getBranchExpr(boolean branch) {
- branch = true and result = getTrueExpr()
+ branch = true and result = this.getTrueExpr()
or
- branch = false and result = getFalseExpr()
+ branch = false and result = this.getFalseExpr()
}
/**
* Gets the expressions that is evaluated by one of the branches (`true`
* or `false` branch) of this conditional expression.
*/
- Expr getABranchExpr() { result = getBranchExpr(_) }
+ Expr getABranchExpr() { result = this.getBranchExpr(_) }
/** Gets a printable representation of this expression. */
override string toString() { result = "...?...:..." }
@@ -1308,7 +1357,7 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr {
* Gets a case of this `switch` expression,
* which may be either a normal `case` or a `default`.
*/
- SwitchCase getACase() { result = getAConstCase() or result = getDefaultCase() }
+ SwitchCase getACase() { result = this.getAConstCase() or result = this.getDefaultCase() }
/** Gets a (non-default) `case` of this `switch` expression. */
ConstCase getAConstCase() { result.getParent() = this }
@@ -1321,7 +1370,7 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr {
/** Gets a result expression of this `switch` expression. */
Expr getAResult() {
- result = getACase().getRuleExpression()
+ result = this.getACase().getRuleExpression()
or
exists(YieldStmt yield | yield.(JumpStmt).getTarget() = this and result = yield.getValue())
}
@@ -1336,21 +1385,17 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr {
class InstanceOfExpr extends Expr, @instanceofexpr {
/** Gets the expression on the left-hand side of the `instanceof` operator. */
Expr getExpr() {
- if isPattern()
- then result = getLocalVariableDeclExpr().getInit()
+ if this.isPattern()
+ then result = this.getLocalVariableDeclExpr().getInit()
else result.isNthChildOf(this, 0)
}
/**
- * PREVIEW FEATURE in Java 14. Subject to removal in a future release.
- *
* Holds if this `instanceof` expression uses pattern matching.
*/
- predicate isPattern() { exists(getLocalVariableDeclExpr()) }
+ predicate isPattern() { exists(this.getLocalVariableDeclExpr()) }
/**
- * PREVIEW FEATURE in Java 14. Subject to removal in a future release.
- *
* Gets the local variable declaration of this `instanceof` expression if pattern matching is used.
*/
LocalVariableDeclExpr getLocalVariableDeclExpr() { result.isNthChildOf(this, 0) }
@@ -1359,7 +1404,7 @@ class InstanceOfExpr extends Expr, @instanceofexpr {
Expr getTypeName() { result.isNthChildOf(this, 1) }
/** Gets the type this `instanceof` expression checks for. */
- RefType getCheckedType() { result = getTypeName().getType() }
+ RefType getCheckedType() { result = this.getTypeName().getType() }
/** Gets a printable representation of this expression. */
override string toString() { result = "...instanceof..." }
@@ -1457,7 +1502,7 @@ class TypeLiteral extends Expr, @typeliteral {
* Gets the type this type literal refers to. For example for `String.class` the
* result is the type representing `String`.
*/
- Type getReferencedType() { result = getTypeName().getType() }
+ Type getReferencedType() { result = this.getTypeName().getType() }
/** Gets a printable representation of this expression. */
override string toString() { result = this.getTypeName().toString() + ".class" }
@@ -1482,15 +1527,15 @@ abstract class InstanceAccess extends Expr {
* This never holds for accesses in lambda expressions as they cannot access
* their own instance directly.
*/
- predicate isOwnInstanceAccess() { not isEnclosingInstanceAccess(_) }
+ predicate isOwnInstanceAccess() { not this.isEnclosingInstanceAccess(_) }
/** Holds if this instance access is to an enclosing instance of type `t`. */
predicate isEnclosingInstanceAccess(RefType t) {
- t = getQualifier().getType().(RefType).getSourceDeclaration() and
- t != getEnclosingCallable().getDeclaringType()
+ t = this.getQualifier().getType().(RefType).getSourceDeclaration() and
+ t != this.getEnclosingCallable().getDeclaringType()
or
- not exists(getQualifier()) and
- exists(LambdaExpr lam | lam.asMethod() = getEnclosingCallable() |
+ not exists(this.getQualifier()) and
+ exists(LambdaExpr lam | lam.asMethod() = this.getEnclosingCallable() |
t = lam.getAnonymousClass().getEnclosingType()
)
}
@@ -1538,7 +1583,7 @@ class VarAccess extends Expr, @varaccess {
Expr getQualifier() { result.getParent() = this }
/** Holds if this variable access has a qualifier. */
- predicate hasQualifier() { exists(getQualifier()) }
+ predicate hasQualifier() { exists(this.getQualifier()) }
/** Gets the variable accessed by this variable access. */
Variable getVariable() { variableBinding(this, result) }
@@ -1580,11 +1625,11 @@ class VarAccess extends Expr, @varaccess {
*/
predicate isLocal() {
// The access has no qualifier, or...
- not hasQualifier()
+ not this.hasQualifier()
or
// the qualifier is either `this` or `A.this`, where `A` is the enclosing type, or
// the qualifier is either `super` or `A.super`, where `A` is the enclosing type.
- getQualifier().(InstanceAccess).isOwnInstanceAccess()
+ this.getQualifier().(InstanceAccess).isOwnInstanceAccess()
}
override string getAPrimaryQlClass() { result = "VarAccess" }
@@ -1626,7 +1671,7 @@ class MethodAccess extends Expr, Call, @methodaccess {
override Expr getQualifier() { result.isNthChildOf(this, -1) }
/** Holds if this method access has a qualifier. */
- predicate hasQualifier() { exists(getQualifier()) }
+ predicate hasQualifier() { exists(this.getQualifier()) }
/** Gets an argument supplied to the method that is invoked using this method access. */
override Expr getAnArgument() { result.getIndex() >= 0 and result.getParent() = this }
@@ -1663,9 +1708,9 @@ class MethodAccess extends Expr, Call, @methodaccess {
* the enclosing type if there is no qualifier.
*/
RefType getReceiverType() {
- result = getQualifier().getType()
+ result = this.getQualifier().getType()
or
- not hasQualifier() and result = getEnclosingCallable().getDeclaringType()
+ not this.hasQualifier() and result = this.getEnclosingCallable().getDeclaringType()
}
/**
@@ -1841,7 +1886,7 @@ class Call extends ExprParent, @caller {
Callable getCallee() { callableBinding(this, result) }
/** Gets the callable invoking this call. */
- Callable getCaller() { result = getEnclosingCallable() }
+ Callable getCaller() { result = this.getEnclosingCallable() }
}
/** A polymorphic call to an instance method. */
@@ -2042,14 +2087,14 @@ class Argument extends Expr {
}
/** Holds if this argument is part of an implicit varargs array. */
- predicate isVararg() { isNthVararg(_) }
+ predicate isVararg() { this.isNthVararg(_) }
/**
* Holds if this argument is part of an implicit varargs array at the
* given array index.
*/
predicate isNthVararg(int arrayindex) {
- not isExplicitVarargsArray() and
+ not this.isExplicitVarargsArray() and
exists(Callable tgt |
call.getCallee() = tgt and
tgt.isVarargs() and
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/Generics.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/Generics.qll
index a15c47b1f8f..e1bf32d475b 100755
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/Generics.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/Generics.qll
@@ -38,7 +38,7 @@ import Type
*
* For example, `X` in `class X { }`.
*/
-class GenericType extends RefType {
+class GenericType extends ClassOrInterface {
GenericType() { typeVars(_, _, _, _, this) }
/**
@@ -69,12 +69,12 @@ class GenericType extends RefType {
/**
* Gets a type parameter of this generic type.
*/
- TypeVariable getATypeParameter() { result = getTypeParameter(_) }
+ TypeVariable getATypeParameter() { result = this.getTypeParameter(_) }
/**
* Gets the number of type parameters of this generic type.
*/
- int getNumberOfTypeParameters() { result = strictcount(getATypeParameter()) }
+ int getNumberOfTypeParameters() { result = strictcount(this.getATypeParameter()) }
override string getAPrimaryQlClass() { result = "GenericType" }
}
@@ -107,7 +107,7 @@ abstract class BoundedType extends RefType, @boundedtype {
TypeBound getATypeBound() { result.getBoundedType() = this }
/** Gets the first type bound for this type, if any. */
- TypeBound getFirstTypeBound() { result = getATypeBound() and result.getPosition() = 0 }
+ TypeBound getFirstTypeBound() { result = this.getATypeBound() and result.getPosition() = 0 }
/**
* Gets an upper type bound of this type, or `Object`
@@ -123,9 +123,9 @@ abstract class BoundedType extends RefType, @boundedtype {
/** Gets a transitive upper bound for this type that is not itself a bounded type. */
RefType getAnUltimateUpperBoundType() {
- result = getUpperBoundType() and not result instanceof BoundedType
+ result = this.getUpperBoundType() and not result instanceof BoundedType
or
- result = getUpperBoundType().(BoundedType).getAnUltimateUpperBoundType()
+ result = this.getUpperBoundType().(BoundedType).getAnUltimateUpperBoundType()
}
override string getAPrimaryQlClass() { result = "BoundedType" }
@@ -139,7 +139,7 @@ abstract class BoundedType extends RefType, @boundedtype {
*/
class TypeVariable extends BoundedType, @typevariable {
/** Gets the generic type that is parameterized by this type parameter, if any. */
- RefType getGenericType() { typeVars(this, _, _, _, result) }
+ GenericType getGenericType() { typeVars(this, _, _, _, result) }
/** Gets the generic callable that is parameterized by this type parameter, if any. */
GenericCallable getGenericCallable() { typeVars(this, _, _, _, result) }
@@ -168,8 +168,8 @@ class TypeVariable extends BoundedType, @typevariable {
/** Gets the lexically enclosing package of this type parameter, if any. */
override Package getPackage() {
- result = getGenericType().getPackage() or
- result = getGenericCallable().getDeclaringType().getPackage()
+ result = this.getGenericType().getPackage() or
+ result = this.getGenericCallable().getDeclaringType().getPackage()
}
/** Finds a type that was supplied for this parameter. */
@@ -190,9 +190,9 @@ class TypeVariable extends BoundedType, @typevariable {
/** Finds a non-typevariable type that was transitively supplied for this parameter. */
RefType getAnUltimatelySuppliedType() {
- result = getASuppliedType() and not result instanceof TypeVariable
+ result = this.getASuppliedType() and not result instanceof TypeVariable
or
- result = getASuppliedType().(TypeVariable).getAnUltimatelySuppliedType()
+ result = this.getASuppliedType().(TypeVariable).getAnUltimatelySuppliedType()
}
override string getAPrimaryQlClass() { result = "TypeVariable" }
@@ -261,7 +261,7 @@ class Wildcard extends BoundedType, @wildcard {
* Holds if this is the unconstrained wildcard `?`.
*/
predicate isUnconstrained() {
- not hasLowerBound() and
+ not this.hasLowerBound() and
wildcards(this, "?", _)
}
@@ -321,7 +321,7 @@ class TypeBound extends @typebound {
* For example, `List` is a parameterization of
* the generic type `List`, where `E` is a type parameter.
*/
-class ParameterizedType extends RefType {
+class ParameterizedType extends ClassOrInterface {
ParameterizedType() {
typeArgs(_, _, this) or
typeVars(_, _, _, _, this)
@@ -367,7 +367,9 @@ class ParameterizedType extends RefType {
}
/** Holds if this type originates from source code. */
- override predicate fromSource() { typeVars(_, _, _, _, this) and RefType.super.fromSource() }
+ override predicate fromSource() {
+ typeVars(_, _, _, _, this) and ClassOrInterface.super.fromSource()
+ }
override string getAPrimaryQlClass() { result = "ParameterizedType" }
}
@@ -451,12 +453,12 @@ class GenericCallable extends Callable {
/**
* Gets a type parameter of this generic callable.
*/
- TypeVariable getATypeParameter() { result = getTypeParameter(_) }
+ TypeVariable getATypeParameter() { result = this.getTypeParameter(_) }
/**
* Gets the number of type parameters of this generic callable.
*/
- int getNumberOfTypeParameters() { result = strictcount(getATypeParameter()) }
+ int getNumberOfTypeParameters() { result = strictcount(this.getATypeParameter()) }
}
/**
@@ -484,10 +486,10 @@ class GenericCall extends Call {
/** Gets a type argument of the call for the given `TypeVariable`. */
RefType getATypeArgument(TypeVariable v) {
- result = getAnExplicitTypeArgument(v)
+ result = this.getAnExplicitTypeArgument(v)
or
- not exists(getAnExplicitTypeArgument(v)) and
- result = getAnInferredTypeArgument(v)
+ not exists(this.getAnExplicitTypeArgument(v)) and
+ result = this.getAnInferredTypeArgument(v)
}
}
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/Import.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/Import.qll
index fbcfea4f905..75b9d157d25 100755
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/Import.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/Import.qll
@@ -25,7 +25,7 @@ class ImportType extends Import {
ImportType() { imports(this, _, _, 1) }
/** Gets the imported type. */
- RefType getImportedType() { imports(this, result, _, _) }
+ ClassOrInterface getImportedType() { imports(this, result, _, _) }
override string toString() { result = "import " + this.getImportedType().toString() }
@@ -44,7 +44,7 @@ class ImportOnDemandFromType extends Import {
ImportOnDemandFromType() { imports(this, _, _, 2) }
/** Gets the type from which accessible nested types are imported. */
- RefType getTypeHoldingImport() { imports(this, result, _, _) }
+ ClassOrInterface getTypeHoldingImport() { imports(this, result, _, _) }
/** Gets an imported type. */
NestedType getAnImport() { result.getEnclosingType() = this.getTypeHoldingImport() }
@@ -87,7 +87,7 @@ class ImportStaticOnDemand extends Import {
ImportStaticOnDemand() { imports(this, _, _, 4) }
/** Gets the type from which accessible static members are imported. */
- RefType getTypeHoldingImport() { imports(this, result, _, _) }
+ ClassOrInterface getTypeHoldingImport() { imports(this, result, _, _) }
/** Gets an imported type. */
NestedType getATypeImport() { result.getEnclosingType() = this.getTypeHoldingImport() }
@@ -118,7 +118,7 @@ class ImportStaticTypeMember extends Import {
ImportStaticTypeMember() { imports(this, _, _, 5) }
/** Gets the type from which static members with a given name are imported. */
- RefType getTypeHoldingImport() { imports(this, result, _, _) }
+ ClassOrInterface getTypeHoldingImport() { imports(this, result, _, _) }
/** Gets the name of the imported member(s). */
override string getName() { imports(this, _, result, _) }
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/JDK.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/JDK.qll
index 2e14ab7b898..e497740a489 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/JDK.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/JDK.qll
@@ -19,12 +19,12 @@ class TypeCloneable extends Interface {
/** The class `java.lang.ProcessBuilder`. */
class TypeProcessBuilder extends Class {
- TypeProcessBuilder() { hasQualifiedName("java.lang", "ProcessBuilder") }
+ TypeProcessBuilder() { this.hasQualifiedName("java.lang", "ProcessBuilder") }
}
/** The class `java.lang.Runtime`. */
class TypeRuntime extends Class {
- TypeRuntime() { hasQualifiedName("java.lang", "Runtime") }
+ TypeRuntime() { this.hasQualifiedName("java.lang", "Runtime") }
}
/** The class `java.lang.String`. */
@@ -143,22 +143,22 @@ class ImmutableType extends Type {
// --- Java IO ---
/** The interface `java.io.Serializable`. */
class TypeSerializable extends Interface {
- TypeSerializable() { hasQualifiedName("java.io", "Serializable") }
+ TypeSerializable() { this.hasQualifiedName("java.io", "Serializable") }
}
/** The interface `java.io.ObjectOutput`. */
class TypeObjectOutput extends Interface {
- TypeObjectOutput() { hasQualifiedName("java.io", "ObjectOutput") }
+ TypeObjectOutput() { this.hasQualifiedName("java.io", "ObjectOutput") }
}
/** The type `java.io.ObjectOutputStream`. */
class TypeObjectOutputStream extends RefType {
- TypeObjectOutputStream() { hasQualifiedName("java.io", "ObjectOutputStream") }
+ TypeObjectOutputStream() { this.hasQualifiedName("java.io", "ObjectOutputStream") }
}
/** The type `java.io.ObjectInputStream`. */
class TypeObjectInputStream extends RefType {
- TypeObjectInputStream() { hasQualifiedName("java.io", "ObjectInputStream") }
+ TypeObjectInputStream() { this.hasQualifiedName("java.io", "ObjectInputStream") }
}
/** The class `java.nio.file.Paths`. */
@@ -196,8 +196,8 @@ class ProcessBuilderConstructor extends Constructor, ExecCallable {
*/
class MethodProcessBuilderCommand extends Method, ExecCallable {
MethodProcessBuilderCommand() {
- hasName("command") and
- getDeclaringType() instanceof TypeProcessBuilder
+ this.hasName("command") and
+ this.getDeclaringType() instanceof TypeProcessBuilder
}
override int getAnExecutedArgument() { result = 0 }
@@ -208,8 +208,8 @@ class MethodProcessBuilderCommand extends Method, ExecCallable {
*/
class MethodRuntimeExec extends Method, ExecCallable {
MethodRuntimeExec() {
- hasName("exec") and
- getDeclaringType() instanceof TypeRuntime
+ this.hasName("exec") and
+ this.getDeclaringType() instanceof TypeRuntime
}
override int getAnExecutedArgument() { result = 0 }
@@ -220,8 +220,8 @@ class MethodRuntimeExec extends Method, ExecCallable {
*/
class MethodSystemGetenv extends Method {
MethodSystemGetenv() {
- hasName("getenv") and
- getDeclaringType() instanceof TypeSystem
+ this.hasName("getenv") and
+ this.getDeclaringType() instanceof TypeSystem
}
}
@@ -230,8 +230,8 @@ class MethodSystemGetenv extends Method {
*/
class MethodSystemGetProperty extends Method {
MethodSystemGetProperty() {
- hasName("getProperty") and
- getDeclaringType() instanceof TypeSystem
+ this.hasName("getProperty") and
+ this.getDeclaringType() instanceof TypeSystem
}
}
@@ -239,7 +239,7 @@ class MethodSystemGetProperty extends Method {
* An access to a method named `getProperty` on class `java.lang.System`.
*/
class MethodAccessSystemGetProperty extends MethodAccess {
- MethodAccessSystemGetProperty() { getMethod() instanceof MethodSystemGetProperty }
+ MethodAccessSystemGetProperty() { this.getMethod() instanceof MethodSystemGetProperty }
/**
* Holds if this call has a compile-time constant first argument with the value `propertyName`.
@@ -255,8 +255,11 @@ class MethodAccessSystemGetProperty extends MethodAccess {
*/
class MethodExit extends Method {
MethodExit() {
- hasName("exit") and
- (getDeclaringType() instanceof TypeRuntime or getDeclaringType() instanceof TypeSystem)
+ this.hasName("exit") and
+ (
+ this.getDeclaringType() instanceof TypeRuntime or
+ this.getDeclaringType() instanceof TypeSystem
+ )
}
}
@@ -266,10 +269,10 @@ class MethodExit extends Method {
*/
class WriteObjectMethod extends Method {
WriteObjectMethod() {
- hasName("writeObject") and
+ this.hasName("writeObject") and
(
- getDeclaringType() instanceof TypeObjectOutputStream or
- getDeclaringType() instanceof TypeObjectOutput
+ this.getDeclaringType() instanceof TypeObjectOutputStream or
+ this.getDeclaringType() instanceof TypeObjectOutput
)
}
}
@@ -293,16 +296,16 @@ class ReadObjectMethod extends Method {
/** The method `Class.getName()`. */
class ClassNameMethod extends Method {
ClassNameMethod() {
- hasName("getName") and
- getDeclaringType() instanceof TypeClass
+ this.hasName("getName") and
+ this.getDeclaringType() instanceof TypeClass
}
}
/** The method `Class.getSimpleName()`. */
class ClassSimpleNameMethod extends Method {
ClassSimpleNameMethod() {
- hasName("getSimpleName") and
- getDeclaringType() instanceof TypeClass
+ this.hasName("getSimpleName") and
+ this.getDeclaringType() instanceof TypeClass
}
}
@@ -334,24 +337,24 @@ class MethodMathMax extends Method {
/** The field `System.in`. */
class SystemIn extends Field {
SystemIn() {
- hasName("in") and
- getDeclaringType() instanceof TypeSystem
+ this.hasName("in") and
+ this.getDeclaringType() instanceof TypeSystem
}
}
/** The field `System.out`. */
class SystemOut extends Field {
SystemOut() {
- hasName("out") and
- getDeclaringType() instanceof TypeSystem
+ this.hasName("out") and
+ this.getDeclaringType() instanceof TypeSystem
}
}
/** The field `System.err`. */
class SystemErr extends Field {
SystemErr() {
- hasName("err") and
- getDeclaringType() instanceof TypeSystem
+ this.hasName("err") and
+ this.getDeclaringType() instanceof TypeSystem
}
}
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/JDKAnnotations.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/JDKAnnotations.qll
index 49776a570f2..2dff70c4d8e 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/JDKAnnotations.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/JDKAnnotations.qll
@@ -25,7 +25,7 @@ class SuppressWarningsAnnotation extends Annotation {
}
/** Gets the name of a warning suppressed by this annotation. */
- string getASuppressedWarning() { result = getASuppressedWarningLiteral().getRepresentedString() }
+ string getASuppressedWarning() { result = this.getASuppressedWarningLiteral().getValue() }
}
/** A `@Target` annotation. */
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/JMX.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/JMX.qll
index 77194d24767..16c8736059f 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/JMX.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/JMX.qll
@@ -26,27 +26,27 @@ class MXBean extends ManagedBean {
*/
class RegisteredManagedBeanImpl extends Class {
RegisteredManagedBeanImpl() {
- getAnAncestor() instanceof ManagedBean and
+ this.getAnAncestor() instanceof ManagedBean and
exists(JMXRegistrationCall registerCall | registerCall.getObjectArgument().getType() = this)
}
/**
* Gets a managed bean that this registered bean class implements.
*/
- ManagedBean getAnImplementedManagedBean() { result = getAnAncestor() }
+ ManagedBean getAnImplementedManagedBean() { result = this.getAnAncestor() }
}
/**
* A call that registers an object with the `MBeanServer`, directly or indirectly.
*/
class JMXRegistrationCall extends MethodAccess {
- JMXRegistrationCall() { getCallee() instanceof JMXRegistrationMethod }
+ JMXRegistrationCall() { this.getCallee() instanceof JMXRegistrationMethod }
/**
* Gets the argument that represents the object in the registration call.
*/
Expr getObjectArgument() {
- result = getArgument(getCallee().(JMXRegistrationMethod).getObjectPosition())
+ result = this.getArgument(this.getCallee().(JMXRegistrationMethod).getObjectPosition())
}
}
@@ -59,15 +59,15 @@ class JMXRegistrationCall extends MethodAccess {
class JMXRegistrationMethod extends Method {
JMXRegistrationMethod() {
// A direct registration with the `MBeanServer`.
- getDeclaringType().hasQualifiedName("javax.management", "MBeanServer") and
- getName() = "registerMBean"
+ this.getDeclaringType().hasQualifiedName("javax.management", "MBeanServer") and
+ this.getName() = "registerMBean"
or
// The `MBeanServer` is often wrapped by an application specific management class, so identify
// methods that wrap a call to another `JMXRegistrationMethod`.
exists(JMXRegistrationCall c |
// This must be a call to another JMX registration method, where the object argument is an access
// of one of the parameters of this method.
- c.getObjectArgument().(VarAccess).getVariable() = getAParameter()
+ c.getObjectArgument().(VarAccess).getVariable() = this.getAParameter()
)
}
@@ -76,13 +76,13 @@ class JMXRegistrationMethod extends Method {
*/
int getObjectPosition() {
// Passed as the first argument to `registerMBean`.
- getDeclaringType().hasQualifiedName("javax.management", "MBeanServer") and
- getName() = "registerMBean" and
+ this.getDeclaringType().hasQualifiedName("javax.management", "MBeanServer") and
+ this.getName() = "registerMBean" and
result = 0
or
// Identify the position in this method where the object parameter should be passed.
exists(JMXRegistrationCall c |
- c.getObjectArgument().(VarAccess).getVariable() = getParameter(result)
+ c.getObjectArgument().(VarAccess).getVariable() = this.getParameter(result)
)
}
}
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/Javadoc.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/Javadoc.qll
index 61d978fbd35..8f7b1dbf580 100755
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/Javadoc.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/Javadoc.qll
@@ -14,7 +14,7 @@ class JavadocParent extends @javadocParent, Top {
JavadocElement getChild(int index) { result = this.getAChild() and result.getIndex() = index }
/** Gets the number of documentation elements attached to this parent. */
- int getNumChild() { result = count(getAChild()) }
+ int getNumChild() { result = count(this.getAChild()) }
/** Gets a documentation element with the specified Javadoc tag name. */
JavadocTag getATag(string name) { result = this.getAChild() and result.getTagName() = name }
@@ -33,7 +33,9 @@ class Javadoc extends JavadocParent, @javadoc {
/** Gets the value of the `@author` tag, if any. */
string getAuthor() { result = this.getATag("@author").getChild(0).toString() }
- override string toString() { result = toStringPrefix() + getChild(0) + toStringPostfix() }
+ override string toString() {
+ result = this.toStringPrefix() + this.getChild(0) + this.toStringPostfix()
+ }
private string toStringPrefix() {
if isEolComment(this)
@@ -47,7 +49,7 @@ class Javadoc extends JavadocParent, @javadoc {
if isEolComment(this)
then result = ""
else (
- if strictcount(getAChild()) = 1 then result = " */" else result = " ... */"
+ if strictcount(this.getAChild()) = 1 then result = " */" else result = " ... */"
)
}
@@ -119,10 +121,10 @@ class ThrowsTag extends JavadocTag {
/** A Javadoc `@see` tag. */
class SeeTag extends JavadocTag {
- SeeTag() { getTagName() = "@see" }
+ SeeTag() { this.getTagName() = "@see" }
/** Gets the name of the entity referred to. */
- string getReference() { result = getChild(0).toString() }
+ string getReference() { result = this.getChild(0).toString() }
}
/** A Javadoc `@author` tag. */
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/Maps.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/Maps.qll
index c86cb0ef47a..784db84fb98 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/Maps.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/Maps.qll
@@ -76,11 +76,11 @@ class FreshMap extends ClassInstanceExpr {
* A call to `Map.put(key, value)`.
*/
class MapPutCall extends MethodAccess {
- MapPutCall() { getCallee().(MapMethod).hasName("put") }
+ MapPutCall() { this.getCallee().(MapMethod).hasName("put") }
/** Gets the key argument of this call. */
- Expr getKey() { result = getArgument(0) }
+ Expr getKey() { result = this.getArgument(0) }
/** Gets the value argument of this call. */
- Expr getValue() { result = getArgument(1) }
+ Expr getValue() { result = this.getArgument(1) }
}
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/Member.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/Member.qll
index da136c577f8..bbafde2c9ba 100755
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/Member.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/Member.qll
@@ -21,7 +21,7 @@ class Member extends Element, Annotatable, Modifiable, @member {
RefType getDeclaringType() { declaresMember(result, this) }
/** Gets the qualified name of this member. */
- string getQualifiedName() { result = getDeclaringType().getName() + "." + getName() }
+ string getQualifiedName() { result = this.getDeclaringType().getName() + "." + this.getName() }
/**
* Holds if this member has the specified name and is declared in the
@@ -33,9 +33,9 @@ class Member extends Element, Annotatable, Modifiable, @member {
/** Holds if this member is package protected, that is, neither public nor private nor protected. */
predicate isPackageProtected() {
- not isPrivate() and
- not isProtected() and
- not isPublic()
+ not this.isPrivate() and
+ not this.isProtected() and
+ not this.isPublic()
}
/**
@@ -78,7 +78,7 @@ class Callable extends StmtParent, Member, @callable {
*/
string getMethodDescriptor() {
exists(string return | return = this.getReturnType().getTypeDescriptor() |
- result = "(" + descriptorUpTo(this.getNumberOfParameters()) + ")" + return
+ result = "(" + this.descriptorUpTo(this.getNumberOfParameters()) + ")" + return
)
}
@@ -86,19 +86,19 @@ class Callable extends StmtParent, Member, @callable {
n = 0 and result = ""
or
exists(Parameter p | p = this.getParameter(n - 1) |
- result = descriptorUpTo(n - 1) + p.getType().getTypeDescriptor()
+ result = this.descriptorUpTo(n - 1) + p.getType().getTypeDescriptor()
)
}
/** Holds if this callable calls `target`. */
- predicate calls(Callable target) { exists(getACallSite(target)) }
+ predicate calls(Callable target) { exists(this.getACallSite(target)) }
/**
* Holds if this callable calls `target`
* using a `super(...)` constructor call.
*/
predicate callsSuperConstructor(Constructor target) {
- getACallSite(target) instanceof SuperConstructorInvocationStmt
+ this.getACallSite(target) instanceof SuperConstructorInvocationStmt
}
/**
@@ -106,14 +106,14 @@ class Callable extends StmtParent, Member, @callable {
* using a `this(...)` constructor call.
*/
predicate callsThis(Constructor target) {
- getACallSite(target) instanceof ThisConstructorInvocationStmt
+ this.getACallSite(target) instanceof ThisConstructorInvocationStmt
}
/**
* Holds if this callable calls `target`
* using a `super` method call.
*/
- predicate callsSuper(Method target) { getACallSite(target) instanceof SuperMethodAccess }
+ predicate callsSuper(Method target) { this.getACallSite(target) instanceof SuperMethodAccess }
/**
* Holds if this callable calls `c` using
@@ -165,13 +165,13 @@ class Callable extends StmtParent, Member, @callable {
Field getAnAccessedField() { this.accesses(result) }
/** Gets the type of a formal parameter of this callable. */
- Type getAParamType() { result = getParameterType(_) }
+ Type getAParamType() { result = this.getParameterType(_) }
/** Holds if this callable does not have any formal parameters. */
- predicate hasNoParameters() { not exists(getAParameter()) }
+ predicate hasNoParameters() { not exists(this.getAParameter()) }
/** Gets the number of formal parameters of this callable. */
- int getNumberOfParameters() { result = count(getAParameter()) }
+ int getNumberOfParameters() { result = count(this.getAParameter()) }
/** Gets a formal parameter of this callable. */
Parameter getAParameter() { result.getCallable() = this }
@@ -205,7 +205,7 @@ class Callable extends StmtParent, Member, @callable {
*/
pragma[nomagic]
string paramsString() {
- exists(int n | n = getNumberOfParameters() |
+ exists(int n | n = this.getNumberOfParameters() |
n = 0 and result = "()"
or
n > 0 and result = "(" + this.paramUpTo(n - 1) + ")"
@@ -217,9 +217,9 @@ class Callable extends StmtParent, Member, @callable {
* from left to right, up to (and including) the `n`-th parameter.
*/
private string paramUpTo(int n) {
- n = 0 and result = getParameterType(0).toString()
+ n = 0 and result = this.getParameterType(0).toString()
or
- n > 0 and result = paramUpTo(n - 1) + ", " + getParameterType(n)
+ n > 0 and result = this.paramUpTo(n - 1) + ", " + this.getParameterType(n)
}
/**
@@ -234,7 +234,7 @@ class Callable extends StmtParent, Member, @callable {
Exception getAnException() { exceptions(result, _, this) }
/** Gets an exception type that occurs in the `throws` clause of this callable. */
- RefType getAThrownExceptionType() { result = getAnException().getType() }
+ RefType getAThrownExceptionType() { result = this.getAnException().getType() }
/** Gets a call site that references this callable. */
Call getAReference() { result.getCallee() = this }
@@ -285,7 +285,20 @@ private predicate overrides(Method m1, Method m2) {
or
m2.isProtected()
or
- m2.isPackageProtected() and t1.getPackage() = t2.getPackage()
+ m2.isPackageProtected() and
+ pragma[only_bind_out](t1.getPackage()) = pragma[only_bind_out](t2.getPackage())
+ )
+}
+
+pragma[nomagic]
+private predicate overridesCandidateType(RefType tsup, string sig, RefType t, Method m) {
+ virtualMethodWithSignature(sig, t, m) and
+ t.extendsOrImplements(tsup)
+ or
+ exists(RefType mid |
+ overridesCandidateType(mid, sig, t, m) and
+ mid.extendsOrImplements(tsup) and
+ not virtualMethodWithSignature(sig, mid, _)
)
}
@@ -294,11 +307,10 @@ private predicate overrides(Method m1, Method m2) {
* ignoring any access modifiers. Additionally, this predicate binds
* `t1` to the type declaring `m1` and `t2` to the type declaring `m2`.
*/
-pragma[noopt]
+cached
predicate overridesIgnoringAccess(Method m1, RefType t1, Method m2, RefType t2) {
exists(string sig |
- virtualMethodWithSignature(sig, t1, m1) and
- t1.extendsOrImplements+(t2) and
+ overridesCandidateType(t2, sig, t1, m1) and
virtualMethodWithSignature(sig, t2, m2)
)
}
@@ -392,7 +404,7 @@ class Method extends Callable, @method {
or
// JLS 9.4: Every method declaration in the body of an interface without an
// access modifier is implicitly public.
- getDeclaringType() instanceof Interface and
+ this.getDeclaringType() instanceof Interface and
not this.isPrivate()
or
exists(FunctionalExpr func | func.asMethod() = this)
@@ -413,7 +425,7 @@ class Method extends Callable, @method {
Callable.super.isStrictfp()
or
// JLS 8.1.1.3, JLS 9.1.1.2
- getDeclaringType().isStrictfp()
+ this.getDeclaringType().isStrictfp()
}
/**
@@ -421,8 +433,8 @@ class Method extends Callable, @method {
* nor an initializer method, and hence could be inherited.
*/
predicate isInheritable() {
- not isPrivate() and
- not (isStatic() and getDeclaringType() instanceof Interface) and
+ not this.isPrivate() and
+ not (this.isStatic() and this.getDeclaringType() instanceof Interface) and
not this instanceof InitializerMethod
}
@@ -430,13 +442,13 @@ class Method extends Callable, @method {
* Holds if this method is neither private nor static, and hence
* uses dynamic dispatch.
*/
- predicate isVirtual() { not isPrivate() and not isStatic() }
+ predicate isVirtual() { not this.isPrivate() and not this.isStatic() }
/** Holds if this method can be overridden. */
predicate isOverridable() {
- isVirtual() and
- not isFinal() and
- not getDeclaringType().isFinal()
+ this.isVirtual() and
+ not this.isFinal() and
+ not this.getDeclaringType().isFinal()
}
override string getAPrimaryQlClass() { result = "Method" }
@@ -549,7 +561,7 @@ abstract class InitializerMethod extends Method { }
* field initializations and static initializer blocks.
*/
class StaticInitializer extends InitializerMethod {
- StaticInitializer() { hasName("") }
+ StaticInitializer() { this.hasName("") }
}
/**
@@ -629,7 +641,7 @@ class Field extends Member, ExprParent, @field, Variable {
or
// JLS 9.3: Every field declaration in the body of an interface is
// implicitly public, static, and final
- getDeclaringType() instanceof Interface
+ this.getDeclaringType() instanceof Interface
}
override predicate isStatic() {
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/Modifier.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/Modifier.qll
index 39cbe5a3c29..11317ef8537 100755
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/Modifier.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/Modifier.qll
@@ -25,7 +25,7 @@ abstract class Modifiable extends Element {
* abstract, so `isAbstract()` will hold for them even if `hasModifier("abstract")`
* does not.
*/
- predicate hasModifier(string m) { modifiers(getAModifier(), m) }
+ predicate hasModifier(string m) { modifiers(this.getAModifier(), m) }
/** Holds if this element has no modifier. */
predicate hasNoModifier() { not hasModifier(this, _) }
@@ -34,31 +34,31 @@ abstract class Modifiable extends Element {
Modifier getAModifier() { this = result.getElement() }
/** Holds if this element has an `abstract` modifier or is implicitly abstract. */
- predicate isAbstract() { hasModifier("abstract") }
+ predicate isAbstract() { this.hasModifier("abstract") }
/** Holds if this element has a `static` modifier or is implicitly static. */
- predicate isStatic() { hasModifier("static") }
+ predicate isStatic() { this.hasModifier("static") }
/** Holds if this element has a `final` modifier or is implicitly final. */
- predicate isFinal() { hasModifier("final") }
+ predicate isFinal() { this.hasModifier("final") }
/** Holds if this element has a `public` modifier or is implicitly public. */
- predicate isPublic() { hasModifier("public") }
+ predicate isPublic() { this.hasModifier("public") }
/** Holds if this element has a `protected` modifier. */
- predicate isProtected() { hasModifier("protected") }
+ predicate isProtected() { this.hasModifier("protected") }
/** Holds if this element has a `private` modifier or is implicitly private. */
- predicate isPrivate() { hasModifier("private") }
+ predicate isPrivate() { this.hasModifier("private") }
/** Holds if this element has a `volatile` modifier. */
- predicate isVolatile() { hasModifier("volatile") }
+ predicate isVolatile() { this.hasModifier("volatile") }
/** Holds if this element has a `synchronized` modifier. */
- predicate isSynchronized() { hasModifier("synchronized") }
+ predicate isSynchronized() { this.hasModifier("synchronized") }
/** Holds if this element has a `native` modifier. */
- predicate isNative() { hasModifier("native") }
+ predicate isNative() { this.hasModifier("native") }
/** Holds if this element has a `default` modifier. */
predicate isDefault() { this.hasModifier("default") }
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/PrettyPrintAst.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/PrettyPrintAst.qll
index 45e683a2466..6cb5769184a 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/PrettyPrintAst.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/PrettyPrintAst.qll
@@ -169,27 +169,27 @@ private class PpArrayCreationExpr extends PpAst, ArrayCreationExpr {
override string getPart(int i) {
i = 0 and result = "new "
or
- i = 1 and result = baseType()
+ i = 1 and result = this.baseType()
or
- i = 2 + 3 * dimensionIndex() and result = "["
+ i = 2 + 3 * this.dimensionIndex() and result = "["
or
- i = 4 + 3 * dimensionIndex() and result = "]"
+ i = 4 + 3 * this.dimensionIndex() and result = "]"
or
- i = 4 + 3 * exprDims() + [1 .. nonExprDims()] and result = "[]"
+ i = 4 + 3 * this.exprDims() + [1 .. this.nonExprDims()] and result = "[]"
}
private string baseType() { result = this.getType().(Array).getElementType().toString() }
private int dimensionIndex() { exists(this.getDimension(result)) }
- private int exprDims() { result = max(int j | j = 0 or j = 1 + dimensionIndex()) }
+ private int exprDims() { result = max(int j | j = 0 or j = 1 + this.dimensionIndex()) }
- private int nonExprDims() { result = this.getType().(Array).getDimension() - exprDims() }
+ private int nonExprDims() { result = this.getType().(Array).getDimension() - this.exprDims() }
override PpAst getChild(int i) {
exists(int j | result = this.getDimension(j) and i = 3 + 3 * j)
or
- i = 5 + 3 * exprDims() + nonExprDims() and result = this.getInit()
+ i = 5 + 3 * this.exprDims() + this.nonExprDims() and result = this.getInit()
}
}
@@ -539,27 +539,27 @@ private class PpForStmt extends PpAst, ForStmt {
or
exists(int j | j > 0 and exists(this.getInit(j)) and i = 2 + 2 * j and result = ", ")
or
- i = 1 + lastInitIndex() and result = "; "
+ i = 1 + this.lastInitIndex() and result = "; "
or
- i = 3 + lastInitIndex() and result = "; "
+ i = 3 + this.lastInitIndex() and result = "; "
or
exists(int j |
- j > 0 and exists(this.getUpdate(j)) and i = 3 + lastInitIndex() + 2 * j and result = ", "
+ j > 0 and exists(this.getUpdate(j)) and i = 3 + this.lastInitIndex() + 2 * j and result = ", "
)
or
- i = 1 + lastUpdateIndex() and result = ")"
+ i = 1 + this.lastUpdateIndex() and result = ")"
or
- i = 2 + lastUpdateIndex() and result = " " and this.getStmt() instanceof BlockStmt
+ i = 2 + this.lastUpdateIndex() and result = " " and this.getStmt() instanceof BlockStmt
}
private int lastInitIndex() { result = 3 + 2 * max(int j | exists(this.getInit(j))) }
private int lastUpdateIndex() {
- result = 4 + lastInitIndex() + 2 * max(int j | exists(this.getUpdate(j)))
+ result = 4 + this.lastInitIndex() + 2 * max(int j | exists(this.getUpdate(j)))
}
override predicate newline(int i) {
- i = 2 + lastUpdateIndex() and not this.getStmt() instanceof BlockStmt
+ i = 2 + this.lastUpdateIndex() and not this.getStmt() instanceof BlockStmt
}
override PpAst getChild(int i) {
@@ -567,15 +567,15 @@ private class PpForStmt extends PpAst, ForStmt {
or
exists(int j | result = this.getInit(j) and i = 3 + 2 * j)
or
- i = 2 + lastInitIndex() and result = this.getCondition()
+ i = 2 + this.lastInitIndex() and result = this.getCondition()
or
- exists(int j | result = this.getUpdate(j) and i = 4 + lastInitIndex() + 2 * j)
+ exists(int j | result = this.getUpdate(j) and i = 4 + this.lastInitIndex() + 2 * j)
or
- i = 3 + lastUpdateIndex() and result = this.getStmt()
+ i = 3 + this.lastUpdateIndex() and result = this.getStmt()
}
override predicate indents(int i) {
- i = 3 + lastUpdateIndex() and not this.getStmt() instanceof BlockStmt
+ i = 3 + this.lastUpdateIndex() and not this.getStmt() instanceof BlockStmt
}
}
@@ -654,9 +654,9 @@ private class PpTryStmt extends PpAst, TryStmt {
or
exists(int j | exists(this.getResourceExpr(j)) and i = 3 + 2 * j and result = ";")
or
- i = 2 + lastResourceIndex() and result = ") " and exists(this.getAResource())
+ i = 2 + this.lastResourceIndex() and result = ") " and exists(this.getAResource())
or
- i = 1 + lastCatchIndex() and result = " finally " and exists(this.getFinally())
+ i = 1 + this.lastCatchIndex() and result = " finally " and exists(this.getFinally())
}
private int lastResourceIndex() {
@@ -664,17 +664,17 @@ private class PpTryStmt extends PpAst, TryStmt {
}
private int lastCatchIndex() {
- result = 4 + lastResourceIndex() + max(int j | exists(this.getCatchClause(j)) or j = 0)
+ result = 4 + this.lastResourceIndex() + max(int j | exists(this.getCatchClause(j)) or j = 0)
}
override PpAst getChild(int i) {
exists(int j | i = 2 + 2 * j and result = this.getResource(j))
or
- i = 3 + lastResourceIndex() and result = this.getBlock()
+ i = 3 + this.lastResourceIndex() and result = this.getBlock()
or
- exists(int j | i = 4 + lastResourceIndex() + j and result = this.getCatchClause(j))
+ exists(int j | i = 4 + this.lastResourceIndex() + j and result = this.getCatchClause(j))
or
- i = 2 + lastCatchIndex() and result = this.getFinally()
+ i = 2 + this.lastCatchIndex() and result = this.getFinally()
}
}
@@ -728,11 +728,11 @@ private class PpSwitchCase extends PpAst, SwitchCase {
or
exists(int j | i = 2 * j and j != 0 and result = ", " and exists(this.(ConstCase).getValue(j)))
or
- i = 1 + lastConstCaseValueIndex() and result = ":" and not this.isRule()
+ i = 1 + this.lastConstCaseValueIndex() and result = ":" and not this.isRule()
or
- i = 1 + lastConstCaseValueIndex() and result = " -> " and this.isRule()
+ i = 1 + this.lastConstCaseValueIndex() and result = " -> " and this.isRule()
or
- i = 3 + lastConstCaseValueIndex() and result = ";" and exists(this.getRuleExpression())
+ i = 3 + this.lastConstCaseValueIndex() and result = ";" and exists(this.getRuleExpression())
}
private int lastConstCaseValueIndex() {
@@ -742,9 +742,9 @@ private class PpSwitchCase extends PpAst, SwitchCase {
override PpAst getChild(int i) {
exists(int j | i = 1 + 2 * j and result = this.(ConstCase).getValue(j))
or
- i = 2 + lastConstCaseValueIndex() and result = this.getRuleExpression()
+ i = 2 + this.lastConstCaseValueIndex() and result = this.getRuleExpression()
or
- i = 2 + lastConstCaseValueIndex() and result = this.getRuleStatement()
+ i = 2 + this.lastConstCaseValueIndex() and result = this.getRuleStatement()
}
}
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/PrintAst.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/PrintAst.qll
index d22065177bc..9527787e3a4 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/PrintAst.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/PrintAst.qll
@@ -151,7 +151,7 @@ class PrintAstNode extends TPrintAstNode {
/**
* Gets a child of this node.
*/
- final PrintAstNode getAChild() { result = getChild(_) }
+ final PrintAstNode getAChild() { result = this.getChild(_) }
/**
* Gets the parent of this node, if any.
@@ -169,7 +169,7 @@ class PrintAstNode extends TPrintAstNode {
*/
string getProperty(string key) {
key = "semmle.label" and
- result = toString()
+ result = this.toString()
}
/**
@@ -178,7 +178,7 @@ class PrintAstNode extends TPrintAstNode {
* this.
*/
string getChildEdgeLabel(int childIndex) {
- exists(getChild(childIndex)) and
+ exists(this.getChild(childIndex)) and
result = childIndex.toString()
}
}
@@ -259,7 +259,7 @@ final class AnnotationPartNode extends ExprStmtNode {
override ElementNode getChild(int childIndex) {
result.getElement() =
rank[childIndex](Element ch, string file, int line, int column |
- ch = getAnAnnotationChild() and locationSortKeys(ch, file, line, column)
+ ch = this.getAnAnnotationChild() and locationSortKeys(ch, file, line, column)
|
ch order by file, line, column
)
@@ -352,7 +352,7 @@ private class SingleLocalVarDeclParent extends ExprOrStmt {
LocalVariableDeclExpr getVariable() { result.getParent() = this }
/** Gets the type access of the variable */
- Expr getTypeAccess() { result = getVariable().getTypeAccess() }
+ Expr getTypeAccess() { result = this.getVariable().getTypeAccess() }
}
/**
@@ -460,7 +460,7 @@ final class ClassInterfaceNode extends ElementNode {
childIndex >= 0 and
result.(ElementNode).getElement() =
rank[childIndex](Element e, string file, int line, int column |
- e = getADeclaration() and locationSortKeys(e, file, line, column)
+ e = this.getADeclaration() and locationSortKeys(e, file, line, column)
|
e order by file, line, column
)
@@ -507,7 +507,7 @@ final class CompilationUnitNode extends ElementNode {
childIndex >= 0 and
result.(ElementNode).getElement() =
rank[childIndex](Element e, string file, int line, int column |
- e = getADeclaration() and locationSortKeys(e, file, line, column)
+ e = this.getADeclaration() and locationSortKeys(e, file, line, column)
|
e order by file, line, column
)
@@ -665,7 +665,7 @@ final class GenericTypeNode extends PrintAstNode, TGenericTypeNode {
override Location getLocation() { none() }
override ElementNode getChild(int childIndex) {
- result.getElement().(TypeVariable) = ty.getTypeParameter(childIndex)
+ result.getElement() = ty.getTypeParameter(childIndex)
}
/**
@@ -686,7 +686,7 @@ final class GenericCallableNode extends PrintAstNode, TGenericCallableNode {
override string toString() { result = "(Generic Parameters)" }
override ElementNode getChild(int childIndex) {
- result.getElement().(TypeVariable) = c.getTypeParameter(childIndex)
+ result.getElement() = c.getTypeParameter(childIndex)
}
/**
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/Reflection.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/Reflection.qll
index ac6046824f6..1817eaa729f 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/Reflection.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/Reflection.qll
@@ -55,7 +55,7 @@ abstract private class ReflectiveClassIdentifier extends Expr {
private class ReflectiveClassIdentifierLiteral extends ReflectiveClassIdentifier, TypeLiteral {
override RefType getReflectivelyIdentifiedClass() {
- result = getReferencedType().(RefType).getSourceDeclaration()
+ result = this.getReferencedType().(RefType).getSourceDeclaration()
}
}
@@ -65,21 +65,21 @@ private class ReflectiveClassIdentifierLiteral extends ReflectiveClassIdentifier
class ReflectiveClassIdentifierMethodAccess extends ReflectiveClassIdentifier, MethodAccess {
ReflectiveClassIdentifierMethodAccess() {
// A call to `Class.forName(...)`, from which we can infer `T` in the returned type `Class`.
- getCallee().getDeclaringType() instanceof TypeClass and getCallee().hasName("forName")
+ this.getCallee().getDeclaringType() instanceof TypeClass and this.getCallee().hasName("forName")
or
// A call to `ClassLoader.loadClass(...)`, from which we can infer `T` in the returned type `Class`.
- getCallee().getDeclaringType().hasQualifiedName("java.lang", "ClassLoader") and
- getCallee().hasName("loadClass")
+ this.getCallee().getDeclaringType().hasQualifiedName("java.lang", "ClassLoader") and
+ this.getCallee().hasName("loadClass")
}
/**
* If the argument to this call is a `StringLiteral`, then return that string.
*/
- string getTypeName() { result = getArgument(0).(StringLiteral).getRepresentedString() }
+ string getTypeName() { result = this.getArgument(0).(StringLiteral).getValue() }
override RefType getReflectivelyIdentifiedClass() {
// We only handle cases where the class is specified as a string literal to this call.
- result.getQualifiedName() = getTypeName()
+ result.getQualifiedName() = this.getTypeName()
}
}
@@ -150,7 +150,7 @@ private Type parameterForSubTypes(ParameterizedType type) {
lowerBound = arg.(Wildcard).getLowerBoundType()
|
// `T super Foo` implies that `Foo`, or any super-type of `Foo`, may be represented.
- lowerBound.(RefType).getAnAncestor() = result
+ lowerBound.getAnAncestor() = result
)
)
}
@@ -214,10 +214,10 @@ private predicate expectsEnclosingInstance(RefType r) {
class NewInstance extends MethodAccess {
NewInstance() {
(
- getCallee().getDeclaringType() instanceof TypeClass or
- getCallee().getDeclaringType() instanceof TypeConstructor
+ this.getCallee().getDeclaringType() instanceof TypeClass or
+ this.getCallee().getDeclaringType() instanceof TypeConstructor
) and
- getCallee().hasName("newInstance")
+ this.getCallee().hasName("newInstance")
}
/**
@@ -225,26 +225,26 @@ class NewInstance extends MethodAccess {
* called.
*/
Constructor getInferredConstructor() {
- result = getInferredConstructedType().getAConstructor() and
- if getCallee().getDeclaringType() instanceof TypeClass
+ result = this.getInferredConstructedType().getAConstructor() and
+ if this.getCallee().getDeclaringType() instanceof TypeClass
then result.getNumberOfParameters() = 0
else
- if getNumArgument() = 1 and getArgument(0).getType() instanceof Array
+ if this.getNumArgument() = 1 and this.getArgument(0).getType() instanceof Array
then
// This is a var-args array argument. If array argument is initialized inline, then identify
// the number of arguments specified in the array.
- if exists(getArgument(0).(ArrayCreationExpr).getInit())
+ if exists(this.getArgument(0).(ArrayCreationExpr).getInit())
then
// Count the number of elements in the initializer, and find the matching constructors.
- matchConstructorArguments(result,
- count(getArgument(0).(ArrayCreationExpr).getInit().getAnInit()))
+ this.matchConstructorArguments(result,
+ count(this.getArgument(0).(ArrayCreationExpr).getInit().getAnInit()))
else
// Could be any of the constructors on this class.
any()
else
// No var-args in play, just use the number of arguments to the `newInstance(..)` to determine
// which constructors may be called.
- matchConstructorArguments(result, getNumArgument())
+ this.matchConstructorArguments(result, this.getNumArgument())
}
/**
@@ -273,13 +273,13 @@ class NewInstance extends MethodAccess {
not result instanceof TypeVariable and
(
// If this is called on a `Class` instance, return the inferred type `T`.
- result = inferClassParameterType(getQualifier())
+ result = inferClassParameterType(this.getQualifier())
or
// If this is called on a `Constructor` instance, return the inferred type `T`.
- result = inferConstructorParameterType(getQualifier())
+ result = inferConstructorParameterType(this.getQualifier())
or
// If the result of this is cast to a particular type, then use that type.
- result = getCastInferredConstructedTypes()
+ result = this.getCastInferredConstructedTypes()
)
}
@@ -313,7 +313,7 @@ class ClassMethodAccess extends MethodAccess {
// `TypeVariable`s do not have methods themselves.
not result instanceof TypeVariable and
// If this is called on a `Class` instance, return the inferred type `T`.
- result = inferClassParameterType(getQualifier())
+ result = inferClassParameterType(this.getQualifier())
}
}
@@ -354,13 +354,13 @@ class ReflectiveMethodAccess extends ClassMethodAccess {
if this.getCallee().hasName("getDeclaredMethod")
then
// The method must be declared on the type itself.
- result.getDeclaringType() = getInferredClassType()
+ result.getDeclaringType() = this.getInferredClassType()
else
// The method may be declared on an inferred type or a super-type.
- getInferredClassType().inherits(result)
+ this.getInferredClassType().inherits(result)
) and
// Only consider instances where the method name is provided as a `StringLiteral`.
- result.hasName(getArgument(0).(StringLiteral).getRepresentedString())
+ result.hasName(this.getArgument(0).(StringLiteral).getValue())
}
}
@@ -373,7 +373,9 @@ class ReflectiveAnnotationAccess extends ClassMethodAccess {
/**
* Gets a possible annotation type for this reflective annotation access.
*/
- AnnotationType getAPossibleAnnotationType() { result = inferClassParameterType(getArgument(0)) }
+ AnnotationType getAPossibleAnnotationType() {
+ result = inferClassParameterType(this.getArgument(0))
+ }
}
/**
@@ -391,13 +393,13 @@ class ReflectiveFieldAccess extends ClassMethodAccess {
if this.getCallee().hasName("getDeclaredField")
then
// Declared fields must be on the type itself.
- result.getDeclaringType() = getInferredClassType()
+ result.getDeclaringType() = this.getInferredClassType()
else (
// This field must be public, and be inherited by one of the inferred class types.
result.isPublic() and
- getInferredClassType().inherits(result)
+ this.getInferredClassType().inherits(result)
)
) and
- result.hasName(getArgument(0).(StringLiteral).getRepresentedString())
+ result.hasName(this.getArgument(0).(StringLiteral).getValue())
}
}
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/Statement.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/Statement.qll
index a5f9eb81080..082fb2ab295 100755
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/Statement.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/Statement.qll
@@ -71,7 +71,7 @@ class BlockStmt extends Stmt, @block {
int getNumStmt() { result = count(this.getAStmt()) }
/** Gets the last statement in this block. */
- Stmt getLastStmt() { result = getStmt(getNumStmt() - 1) }
+ Stmt getLastStmt() { result = this.getStmt(this.getNumStmt() - 1) }
override string pp() { result = "{ ... }" }
@@ -93,7 +93,7 @@ class SingletonBlock extends BlockStmt {
SingletonBlock() { this.getNumStmt() = 1 }
/** Gets the single statement in this block. */
- Stmt getStmt() { result = getStmt(0) }
+ Stmt getStmt() { result = this.getStmt(0) }
}
/**
@@ -125,7 +125,7 @@ class IfStmt extends ConditionalStmt, @ifstmt {
* Gets the statement that is executed whenever the condition
* of this branch statement evaluates to `true`.
*/
- deprecated override Stmt getTrueSuccessor() { result = getThen() }
+ deprecated override Stmt getTrueSuccessor() { result = this.getThen() }
/** Gets the `else` branch of this `if` statement. */
Stmt getElse() { result.isNthChildOf(this, 2) }
@@ -155,7 +155,7 @@ class ForStmt extends ConditionalStmt, @forstmt {
/** Gets the initializer expression of the loop at the specified (zero-based) position. */
Expr getInit(int index) {
- result = getAnInit() and
+ result = this.getAnInit() and
index = -1 - result.getIndex()
}
@@ -167,7 +167,7 @@ class ForStmt extends ConditionalStmt, @forstmt {
/** Gets the update expression of this loop at the specified (zero-based) position. */
Expr getUpdate(int index) {
- result = getAnUpdate() and
+ result = this.getAnUpdate() and
index = result.getIndex() - 3
}
@@ -178,7 +178,7 @@ class ForStmt extends ConditionalStmt, @forstmt {
* Gets the statement that is executed whenever the condition
* of this branch statement evaluates to true.
*/
- deprecated override Stmt getTrueSuccessor() { result = getStmt() }
+ deprecated override Stmt getTrueSuccessor() { result = this.getStmt() }
/**
* Gets a variable that is used as an iteration variable: it is defined,
@@ -193,12 +193,12 @@ class ForStmt extends ConditionalStmt, @forstmt {
*/
Variable getAnIterationVariable() {
// Check that the variable is assigned to, incremented or decremented in the update expression, and...
- exists(Expr update | update = getAnUpdate().getAChildExpr*() |
+ exists(Expr update | update = this.getAnUpdate().getAChildExpr*() |
update.(UnaryAssignExpr).getExpr() = result.getAnAccess() or
update = result.getAnAssignedValue()
) and
// ...that it is checked or used in the condition.
- getCondition().getAChildExpr*() = result.getAnAccess()
+ this.getCondition().getAChildExpr*() = result.getAnAccess()
}
override string pp() { result = "for (...;...;...) " + this.getStmt().pp() }
@@ -242,7 +242,7 @@ class WhileStmt extends ConditionalStmt, @whilestmt {
* Gets the statement that is executed whenever the condition
* of this branch statement evaluates to true.
*/
- deprecated override Stmt getTrueSuccessor() { result = getStmt() }
+ deprecated override Stmt getTrueSuccessor() { result = this.getStmt() }
override string pp() { result = "while (...) " + this.getStmt().pp() }
@@ -265,7 +265,7 @@ class DoStmt extends ConditionalStmt, @dostmt {
* Gets the statement that is executed whenever the condition
* of this branch statement evaluates to `true`.
*/
- deprecated override Stmt getTrueSuccessor() { result = getStmt() }
+ deprecated override Stmt getTrueSuccessor() { result = this.getStmt() }
override string pp() { result = "do " + this.getStmt().pp() + " while (...)" }
@@ -343,17 +343,17 @@ class TryStmt extends Stmt, @trystmt {
}
/** Gets a resource in this `try` statement, if any. */
- ExprParent getAResource() { result = getAResourceDecl() or result = getAResourceExpr() }
+ ExprParent getAResource() { result = this.getAResourceDecl() or result = this.getAResourceExpr() }
/** Gets the resource at the specified position in this `try` statement. */
ExprParent getResource(int index) {
- result = getResourceDecl(index) or result = getResourceExpr(index)
+ result = this.getResourceDecl(index) or result = this.getResourceExpr(index)
}
/** Gets a resource variable, if any, either from a resource variable declaration or resource expression. */
Variable getAResourceVariable() {
- result = getAResourceDecl().getAVariable().getVariable() or
- result = getAResourceExpr().getVariable()
+ result = this.getAResourceDecl().getAVariable().getVariable() or
+ result = this.getAResourceExpr().getVariable()
}
override string pp() { result = "try " + this.getBlock().pp() + " catch (...)" }
@@ -381,7 +381,7 @@ class CatchClause extends Stmt, @catchclause {
/** Gets a type caught by this `catch` clause. */
RefType getACaughtType() {
- exists(Expr ta | ta = getVariable().getTypeAccess() |
+ exists(Expr ta | ta = this.getVariable().getTypeAccess() |
result = ta.(TypeAccess).getType() or
result = ta.(UnionTypeAccess).getAnAlternative().getType()
)
@@ -411,7 +411,7 @@ class SwitchStmt extends Stmt, @switchstmt {
* Gets a case of this `switch` statement,
* which may be either a normal `case` or a `default`.
*/
- SwitchCase getACase() { result = getAConstCase() or result = getDefaultCase() }
+ SwitchCase getACase() { result = this.getAConstCase() or result = this.getDefaultCase() }
/** Gets a (non-default) `case` of this `switch` statement. */
ConstCase getAConstCase() { result.getParent() = this }
@@ -550,7 +550,7 @@ class ThrowStmt extends Stmt, @throwstmt {
override string getHalsteadID() { result = "ThrowStmt" }
/** Gets the type of the expression thrown by this `throw` statement. */
- RefType getThrownExceptionType() { result = getExpr().getType() }
+ RefType getThrownExceptionType() { result = this.getExpr().getType() }
/**
* Gets the `catch` clause that catches the exception
@@ -559,15 +559,15 @@ class ThrowStmt extends Stmt, @throwstmt {
* provided such a `catch` exists.
*/
CatchClause getLexicalCatchIfAny() {
- exists(TryStmt try | try = findEnclosing() and result = catchClauseForThis(try))
+ exists(TryStmt try | try = this.findEnclosing() and result = this.catchClauseForThis(try))
}
private Stmt findEnclosing() {
- result = getEnclosingStmt()
+ result = this.getEnclosingStmt()
or
exists(Stmt mid |
- mid = findEnclosing() and
- not exists(this.catchClauseForThis(mid.(TryStmt))) and
+ mid = this.findEnclosing() and
+ not exists(this.catchClauseForThis(mid)) and
result = mid.getEnclosingStmt()
)
}
@@ -575,7 +575,7 @@ class ThrowStmt extends Stmt, @throwstmt {
private CatchClause catchClauseForThis(TryStmt try) {
result = try.getACatchClause() and
result.getEnclosingCallable() = this.getEnclosingCallable() and
- getExpr().getType().(RefType).hasSupertype*(result.getVariable().getType().(RefType)) and
+ this.getExpr().getType().(RefType).hasSupertype*(result.getVariable().getType()) and
not this.getEnclosingStmt+() = result
}
@@ -599,7 +599,7 @@ class JumpStmt extends Stmt {
namestrings(result.getLabel(), _, this)
}
- private Stmt getLabelTarget() { result = getTargetLabel().getStmt() }
+ private Stmt getLabelTarget() { result = this.getTargetLabel().getStmt() }
private Stmt getAPotentialTarget() {
this.getEnclosingStmt+() = result and
@@ -613,20 +613,20 @@ class JumpStmt extends Stmt {
private SwitchExpr getSwitchExprTarget() { result = this.(YieldStmt).getParent+() }
private StmtParent getEnclosingTarget() {
- result = getSwitchExprTarget()
+ result = this.getSwitchExprTarget()
or
- not exists(getSwitchExprTarget()) and
- result = getAPotentialTarget() and
- not exists(Stmt other | other = getAPotentialTarget() | other.getEnclosingStmt+() = result)
+ not exists(this.getSwitchExprTarget()) and
+ result = this.getAPotentialTarget() and
+ not exists(Stmt other | other = this.getAPotentialTarget() | other.getEnclosingStmt+() = result)
}
/**
* Gets the statement or `switch` expression that this `break`, `yield` or `continue` jumps to.
*/
StmtParent getTarget() {
- result = getLabelTarget()
+ result = this.getLabelTarget()
or
- not exists(getLabelTarget()) and result = getEnclosingTarget()
+ not exists(this.getLabelTarget()) and result = this.getEnclosingTarget()
}
}
@@ -714,9 +714,9 @@ class ExprStmt extends Stmt, @exprstmt {
/** Holds if this statement represents a field declaration with an initializer. */
predicate isFieldDecl() {
- getEnclosingCallable() instanceof InitializerMethod and
+ this.getEnclosingCallable() instanceof InitializerMethod and
exists(FieldDeclaration fd, Location fdl, Location sl |
- fdl = fd.getLocation() and sl = getLocation()
+ fdl = fd.getLocation() and sl = this.getLocation()
|
fdl.getFile() = sl.getFile() and
fdl.getStartLine() = sl.getStartLine() and
@@ -775,7 +775,7 @@ class LocalVariableDeclStmt extends Stmt, @localvariabledeclstmt {
}
/** Gets an index of a variable declared in this local variable declaration statement. */
- int getAVariableIndex() { exists(getVariable(result)) }
+ int getAVariableIndex() { exists(this.getVariable(result)) }
override string pp() { result = "var ...;" }
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/StringFormat.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/StringFormat.qll
index cc37ee8212a..2938f5255fa 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/StringFormat.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/StringFormat.qll
@@ -152,15 +152,15 @@ class FormattingCall extends Call {
private Expr getLastArg() {
exists(Expr last | last = this.getArgument(this.getNumArgument() - 1) |
if this.hasExplicitVarargsArray()
- then result = last.(ArrayCreationExpr).getInit().getInit(getVarargsCount() - 1)
+ then result = last.(ArrayCreationExpr).getInit().getInit(this.getVarargsCount() - 1)
else result = last
)
}
/** Holds if this uses the "logger ({})" format syntax and the last argument is a `Throwable`. */
predicate hasTrailingThrowableArgument() {
- getSyntax() = TFmtLogger() and
- getLastArg().getType().(RefType).getASourceSupertype*() instanceof TypeThrowable
+ this.getSyntax() = TFmtLogger() and
+ this.getLastArg().getType().(RefType).getASourceSupertype*() instanceof TypeThrowable
}
/** Gets the argument to this call in the position of the format string */
@@ -171,7 +171,7 @@ class FormattingCall extends Call {
exists(int i |
result = this.getArgument(i) and
i > this.getFormatStringIndex() and
- not hasExplicitVarargsArray()
+ not this.hasExplicitVarargsArray()
)
}
@@ -279,7 +279,7 @@ private predicate formatStringFragment(Expr fmt) {
private predicate formatStringValue(Expr e, string fmtvalue) {
formatStringFragment(e) and
(
- e.(StringLiteral).getRepresentedString() = fmtvalue
+ e.(StringLiteral).getValue() = fmtvalue
or
e.getType() instanceof IntegralType and fmtvalue = "1" // dummy value
or
@@ -318,7 +318,7 @@ private predicate formatStringValue(Expr e, string fmtvalue) {
getprop.hasName("getProperty") and
getprop.getDeclaringType().hasQualifiedName("java.lang", "System") and
getprop.getNumberOfParameters() = 1 and
- ma.getAnArgument().(StringLiteral).getRepresentedString() = prop and
+ ma.getAnArgument().(StringLiteral).getValue() = prop and
(prop = "line.separator" or prop = "file.separator" or prop = "path.separator") and
fmtvalue = "x" // dummy value
)
@@ -433,15 +433,15 @@ private class PrintfFormatString extends FormatString {
override int getMaxFmtSpecIndex() {
result =
max(int ix |
- ix = fmtSpecRefersToSpecificIndex(_) or
- ix = count(int i | fmtSpecRefersToSequentialIndex(i))
+ ix = this.fmtSpecRefersToSpecificIndex(_) or
+ ix = count(int i | this.fmtSpecRefersToSequentialIndex(i))
)
}
override int getASkippedFmtSpecIndex() {
- result in [1 .. getMaxFmtSpecIndex()] and
- result > count(int i | fmtSpecRefersToSequentialIndex(i)) and
- not result = fmtSpecRefersToSpecificIndex(_)
+ result in [1 .. this.getMaxFmtSpecIndex()] and
+ result > count(int i | this.fmtSpecRefersToSequentialIndex(i)) and
+ not result = this.fmtSpecRefersToSpecificIndex(_)
}
private int getFmtSpecRank(int specOffset) {
@@ -449,14 +449,14 @@ private class PrintfFormatString extends FormatString {
}
override int getAnArgUsageOffset(int argNo) {
- argNo = fmtSpecRefersToSpecificIndex(result)
+ argNo = this.fmtSpecRefersToSpecificIndex(result)
or
- result = rank[argNo](int i | fmtSpecRefersToSequentialIndex(i))
+ result = rank[argNo](int i | this.fmtSpecRefersToSequentialIndex(i))
or
- fmtSpecRefersToPrevious(result) and
+ this.fmtSpecRefersToPrevious(result) and
exists(int previousOffset |
- getFmtSpecRank(previousOffset) = getFmtSpecRank(result) - 1 and
- previousOffset = getAnArgUsageOffset(argNo)
+ this.getFmtSpecRank(previousOffset) = this.getFmtSpecRank(result) - 1 and
+ previousOffset = this.getAnArgUsageOffset(argNo)
)
}
}
@@ -479,10 +479,12 @@ private class LoggerFormatString extends FormatString {
private predicate fmtPlaceholder(int i) {
this.charAt(i) = "{" and
this.charAt(i + 1) = "}" and
- not true = isUnescapedBackslash(i - 1)
+ not true = this.isUnescapedBackslash(i - 1)
}
- override int getMaxFmtSpecIndex() { result = count(int i | fmtPlaceholder(i)) }
+ override int getMaxFmtSpecIndex() { result = count(int i | this.fmtPlaceholder(i)) }
- override int getAnArgUsageOffset(int argNo) { result = rank[argNo](int i | fmtPlaceholder(i)) }
+ override int getAnArgUsageOffset(int argNo) {
+ result = rank[argNo](int i | this.fmtPlaceholder(i))
+ }
}
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/Type.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/Type.qll
index 492d1b546cb..1755442545d 100755
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/Type.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/Type.qll
@@ -31,7 +31,7 @@ predicate hasSubtype(RefType t, Type sub) {
arraySubtype(t, sub) and t != sub
or
// Type parameter containment for parameterized types.
- parContainmentSubtype(t, sub) and t != sub
+ parContainmentSubtype(t, sub)
or
// Type variables are subtypes of their upper bounds.
typeVarSubtypeBound(t, sub) and t != sub
@@ -59,19 +59,23 @@ private predicate arraySubtype(Array sup, Array sub) {
* )
* ```
* For performance several transformations are made. First, the `forex` is
- * written as a loop where `typeArgumentsContain(_, pt, psub, n)` encode that
- * the `forex` holds for `i in [0..n]`. Second, the relation is split into two
- * cases depending on whether `pt.getNumberOfTypeArguments()` is 1 or 2+, as
- * this allows us to unroll the loop and collapse the first two iterations. The
- * base case for `typeArgumentsContain` is therefore `n=1` and this allows an
- * improved join order implemented by `contains01`.
+ * written as a loop where `typePrefixContains(ppt, ppsub)` encode that
+ * `ppt` and `ppsub` are prefixes of `pt` and `ptsub` and that
+ * the `forex` holds for `i in [0..n-1]` where `n` is the length of the prefixes.
+ * Second, the recursive case that determines containment of length `n+1`
+ * prefixes is split into three cases depending on whether there is
+ * non-reflexive type parameter containment:
+ * - only in the length `n` prefix,
+ * - only in the `n`th position,
+ * - both in the length `n` prefix and the `n`th position.
*/
private predicate parContainmentSubtype(ParameterizedType pt, ParameterizedType psub) {
- pt != psub and
- typeArgumentsContain(_, pt, psub, pt.getNumberOfTypeArguments() - 1)
- or
- typeArgumentsContain0(_, pt, psub)
+ exists(ParameterizedPrefix ppt, ParameterizedPrefix ppsub |
+ typePrefixContains(ppt, ppsub) and
+ ppt.equals(pt) and
+ ppsub.equals(psub)
+ )
}
/**
@@ -94,100 +98,116 @@ private RefType parameterisationTypeArgumentVarianceCand(
varianceCandidate(t)
}
-/**
- * Holds if every type argument of `s` (up to `n` with `n >= 1`) contains the
- * corresponding type argument of `t`. Both `s` and `t` are constrained to
- * being parameterizations of `g`.
- */
-pragma[nomagic]
-private predicate typeArgumentsContain(
- GenericType g, ParameterizedType s, ParameterizedType t, int n
-) {
- contains01(g, s, t) and n = 1
+private newtype TParameterizedPrefix =
+ TGenericType(GenericType g) or
+ TTypeParam(ParameterizedPrefix pp, RefType t) { prefixMatches(pp, t, _, _) }
+
+/** Holds if `pp` is a length `n` prefix of `pt`. */
+private predicate prefixMatches(ParameterizedPrefix pp, ParameterizedType pt, int n) {
+ pp = TGenericType(pt.getGenericType()) and n = 0
or
- contains(g, s, t, n) and
- typeArgumentsContain(g, s, t, n - 1)
-}
-
-private predicate typeArgumentsContain0(
- GenericType g, ParameterizedType sParm, ParameterizedType tParm
-) {
- exists(RefType s, RefType t |
- containsAux0(g, tParm, s, t) and
- s = parameterisationTypeArgument(g, sParm, 0) and
- s != t
+ exists(ParameterizedPrefix pp0, RefType t |
+ pp = TTypeParam(pp0, t) and prefixMatches(pp0, t, pt, n - 1)
)
}
/**
- * Holds if the `n`-th type argument of `sParm` contain the `n`-th type
- * argument of `tParm` for both `n = 0` and `n = 1`, where both `sParm` and
- * `tParm` are parameterizations of the same generic type `g`.
- *
- * This is equivalent to
- * ```
- * contains(g, sParm, tParm, 0) and
- * contains(g, sParm, tParm, 1)
- * ```
- * except `contains` is restricted to only include `n >= 2`.
+ * Holds if `pp` is a length `n` prefix of `pt` and `t` is the `n`th type
+ * argument of `pt`.
*/
-private predicate contains01(GenericType g, ParameterizedType sParm, ParameterizedType tParm) {
- exists(RefType s0, RefType t0, RefType s1, RefType t1 |
- contains01Aux0(g, tParm, s0, t0, t1) and
- contains01Aux1(g, sParm, s0, s1, t1)
- )
-}
-
-pragma[nomagic]
-private predicate contains01Aux0(
- GenericType g, ParameterizedType tParm, RefType s0, RefType t0, RefType t1
-) {
- typeArgumentContains(g, s0, t0, 0) and
- t0 = parameterisationTypeArgument(g, tParm, 0) and
- t1 = parameterisationTypeArgument(g, tParm, 1)
-}
-
-pragma[nomagic]
-private predicate contains01Aux1(
- GenericType g, ParameterizedType sParm, RefType s0, RefType s1, RefType t1
-) {
- typeArgumentContains(g, s1, t1, 1) and
- s0 = parameterisationTypeArgumentVarianceCand(g, sParm, 0) and
- s1 = parameterisationTypeArgumentVarianceCand(g, sParm, 1)
-}
-
-pragma[nomagic]
-private predicate containsAux0(GenericType g, ParameterizedType tParm, RefType s, RefType t) {
- typeArgumentContains(g, s, t, 0) and
- t = parameterisationTypeArgument(g, tParm, 0) and
- g.getNumberOfTypeParameters() = 1
+private predicate prefixMatches(ParameterizedPrefix pp, RefType t, ParameterizedType pt, int n) {
+ prefixMatches(pp, pt, n) and
+ t = pt.getTypeArgument(n)
}
/**
- * Holds if the `n`-th type argument of `sParm` contain the `n`-th type
- * argument of `tParm`, where both `sParm` and `tParm` are parameterizations of
- * the same generic type `g`. The index `n` is restricted to `n >= 2`, the
- * cases `n < 2` are handled by `contains01`.
- *
- * See JLS 4.5.1, Type Arguments of Parameterized Types.
+ * A prefix of a `ParameterizedType`. This encodes the corresponding
+ * `GenericType` and the first `n` type arguments where `n` is the prefix
+ * length.
*/
-private predicate contains(GenericType g, ParameterizedType sParm, ParameterizedType tParm, int n) {
- exists(RefType s, RefType t |
- containsAux(g, tParm, n, s, t) and
- s = parameterisationTypeArgumentVarianceCand(g, sParm, n)
+private class ParameterizedPrefix extends TParameterizedPrefix {
+ string toString() { result = "ParameterizedPrefix" }
+
+ predicate equals(ParameterizedType pt) { prefixMatches(this, pt, pt.getNumberOfTypeArguments()) }
+
+ /** Holds if this prefix has length `n`, applies to `g`, and equals `TTypeParam(pp, t)`. */
+ predicate split(GenericType g, ParameterizedPrefix pp, RefType t, int n) {
+ this = TTypeParam(pp, t) and
+ (
+ pp = TGenericType(g) and n = 0
+ or
+ pp.split(g, _, _, n - 1)
+ )
+ }
+}
+
+/**
+ * Holds if every type argument of `pps` contains the corresponding type
+ * argument of `ppt`. Both `pps` and `ppt` are constrained to be equal-length
+ * prefixes of parameterizations of the same `GenericType`.
+ */
+pragma[nomagic]
+private predicate typePrefixContains(ParameterizedPrefix pps, ParameterizedPrefix ppt) {
+ // Let `pps = TTypeParam(pps0, s)` and `ppt = TTypeParam(ppt0, t)`.
+ // Case 1: pps0 = ppt0 and typeArgumentContains(_, s, t, _)
+ typePrefixContains_base(pps, ppt)
+ or
+ // Case 2: typePrefixContains(pps0, ppt0) and s = t
+ typePrefixContains_ext_eq(pps, ppt)
+ or
+ // Case 3: typePrefixContains(pps0, ppt0) and typeArgumentContains(_, s, t, _)
+ typePrefixContains_ext_neq(pps, ppt)
+}
+
+private predicate typePrefixContains_base(ParameterizedPrefix pps, ParameterizedPrefix ppt) {
+ exists(ParameterizedPrefix pp, RefType s |
+ pps = TTypeParam(pp, s) and
+ typePrefixContainsAux2(ppt, pp, s)
+ )
+}
+
+private predicate typePrefixContains_ext_eq(ParameterizedPrefix pps, ParameterizedPrefix ppt) {
+ exists(ParameterizedPrefix pps0, ParameterizedPrefix ppt0, RefType t |
+ typePrefixContains(pragma[only_bind_into](pps0), pragma[only_bind_into](ppt0)) and
+ pps = TTypeParam(pragma[only_bind_into](pps0), t) and
+ ppt = TTypeParam(ppt0, t)
+ )
+}
+
+private predicate typePrefixContains_ext_neq(ParameterizedPrefix pps, ParameterizedPrefix ppt) {
+ exists(ParameterizedPrefix ppt0, RefType s |
+ typePrefixContainsAux1(pps, ppt0, s) and
+ typePrefixContainsAux2(ppt, ppt0, s)
)
}
pragma[nomagic]
-private predicate containsAux(GenericType g, ParameterizedType tParm, int n, RefType s, RefType t) {
- typeArgumentContains(g, s, t, n) and
- t = parameterisationTypeArgument(g, tParm, n) and
- n >= 2
+private predicate typePrefixContainsAux1(
+ ParameterizedPrefix pps, ParameterizedPrefix ppt0, RefType s
+) {
+ exists(ParameterizedPrefix pps0 |
+ typePrefixContains(pps0, ppt0) and
+ pps = TTypeParam(pps0, s) and
+ s instanceof Wildcard // manual magic, implied by `typeArgumentContains(_, s, t, _)`
+ )
+}
+
+pragma[nomagic]
+private predicate typePrefixContainsAux2(
+ ParameterizedPrefix ppt, ParameterizedPrefix ppt0, RefType s
+) {
+ exists(GenericType g, int n, RefType t |
+ // Implies `ppt = TTypeParam(ppt0, t)`
+ ppt.split(g, ppt0, t, n) and
+ typeArgumentContains(g, s, t, n)
+ )
}
/**
* Holds if the type argument `s` contains the type argument `t`, where both
* type arguments occur as index `n` in an instantiation of `g`.
+ *
+ * The case `s = t` is not included.
*/
pragma[noinline]
private predicate typeArgumentContains(GenericType g, RefType s, RefType t, int n) {
@@ -205,18 +225,18 @@ private predicate typeArgumentContainsAux2(GenericType g, RefType s, RefType t,
* Holds if the type argument `s` contains the type argument `t`, where both
* type arguments occur as index `n` in some parameterized types.
*
+ * The case `s = t` is not included.
+ *
* See JLS 4.5.1, Type Arguments of Parameterized Types.
*/
private predicate typeArgumentContainsAux1(RefType s, RefType t, int n) {
- exists(int i |
- s = parameterisationTypeArgumentVarianceCand(_, _, i) and
- t = parameterisationTypeArgument(_, _, n) and
- i <= n and
- n <= i
- |
+ s = parameterisationTypeArgumentVarianceCand(_, _, pragma[only_bind_into](n)) and
+ t = parameterisationTypeArgument(_, _, pragma[only_bind_into](n)) and
+ s != t and
+ (
exists(RefType tUpperBound | tUpperBound = t.(Wildcard).getUpperBound().getType() |
// ? extends T <= ? extends S if T <: S
- hasSubtypeStar(s.(Wildcard).getUpperBound().getType(), tUpperBound)
+ hasSubtypeStar1(s.(Wildcard).getUpperBound().getType(), tUpperBound)
or
// ? extends T <= ?
s.(Wildcard).isUnconstrained()
@@ -224,7 +244,7 @@ private predicate typeArgumentContainsAux1(RefType s, RefType t, int n) {
or
exists(RefType tLowerBound | tLowerBound = t.(Wildcard).getLowerBound().getType() |
// ? super T <= ? super S if s <: T
- hasSubtypeStar(tLowerBound, s.(Wildcard).getLowerBound().getType())
+ hasSubtypeStar2(tLowerBound, s.(Wildcard).getLowerBound().getType())
or
// ? super T <= ?
s.(Wildcard).isUnconstrained()
@@ -233,14 +253,14 @@ private predicate typeArgumentContainsAux1(RefType s, RefType t, int n) {
wildcardExtendsObject(s)
)
or
- // T <= T
- s = t
- or
// T <= ? extends T
- hasSubtypeStar(s.(Wildcard).getUpperBound().getType(), t)
+ hasSubtypeStar1(s.(Wildcard).getUpperBound().getType(), t)
or
// T <= ? super T
- hasSubtypeStar(t, s.(Wildcard).getLowerBound().getType())
+ hasSubtypeStar2(t, s.(Wildcard).getLowerBound().getType())
+ // or
+ // T <= T
+ // but this case is handled directly in `typePrefixContains`
)
}
@@ -249,12 +269,38 @@ private predicate wildcardExtendsObject(Wildcard wc) {
wc.getUpperBound().getType() instanceof TypeObject
}
-private predicate hasSubtypeStar(RefType t, RefType sub) {
- sub = t
+// manual magic for `hasSubtypeStar1`
+private predicate getAWildcardUpperBound(RefType t) {
+ t = any(Wildcard w).getUpperBound().getType()
+}
+
+// manual magic for `hasSubtypeStar2`
+private predicate getAWildcardLowerBound(RefType t) {
+ t = any(Wildcard w).getLowerBound().getType()
+}
+
+/**
+ * Holds if `hasSubtype*(t, sub)`, but manual-magic'ed with `getAWildcardUpperBound(t)`.
+ */
+pragma[nomagic]
+private predicate hasSubtypeStar1(RefType t, RefType sub) {
+ sub = t and getAWildcardUpperBound(t)
or
- hasSubtype(t, sub)
+ hasSubtype(t, sub) and getAWildcardUpperBound(t)
or
- exists(RefType mid | hasSubtypeStar(t, mid) and hasSubtype(mid, sub))
+ exists(RefType mid | hasSubtypeStar1(t, mid) and hasSubtype(mid, sub))
+}
+
+/**
+ * Holds if `hasSubtype*(t, sub)`, but manual-magic'ed with `getAWildcardLowerBound(sub)`.
+ */
+pragma[nomagic]
+private predicate hasSubtypeStar2(RefType t, RefType sub) {
+ sub = t and getAWildcardLowerBound(sub)
+ or
+ hasSubtype(t, sub) and getAWildcardLowerBound(sub)
+ or
+ exists(RefType mid | hasSubtype(t, mid) and hasSubtypeStar2(mid, sub))
}
/** Holds if type `t` declares member `m`. */
@@ -379,7 +425,7 @@ class RefType extends Type, Annotatable, Modifiable, @reftype {
}
/** Holds if this type declares any members. */
- predicate hasMember() { exists(getAMember()) }
+ predicate hasMember() { exists(this.getAMember()) }
/** Gets a member declared in this type. */
Member getAMember() { this = result.getDeclaringType() }
@@ -511,7 +557,7 @@ class RefType extends Type, Annotatable, Modifiable, @reftype {
this.getSourceDeclaration().inherits(f)
)
or
- this.hasMethod(m.(Method), _)
+ this.hasMethod(m, _)
}
/** Holds if this is a top-level type, which is not nested inside any other types. */
@@ -545,8 +591,10 @@ class RefType extends Type, Annotatable, Modifiable, @reftype {
* `java.lang.Thread$State`.
*/
string getQualifiedName() {
- exists(string pkgName | pkgName = getPackage().getName() |
- if pkgName = "" then result = nestedName() else result = pkgName + "." + nestedName()
+ exists(string pkgName | pkgName = this.getPackage().getName() |
+ if pkgName = ""
+ then result = this.nestedName()
+ else result = pkgName + "." + this.nestedName()
)
}
@@ -656,7 +704,7 @@ class IntersectionType extends RefType, @class {
/** Gets a textual representation of this type that includes all the intersected types. */
string getLongName() {
- result = superType().toString() + concat(" & " + superInterface().toString())
+ result = this.superType().toString() + concat(" & " + this.superInterface().toString())
}
/** Gets the first bound of this intersection type. */
@@ -690,7 +738,8 @@ class AnonymousClass extends NestedClass {
override string getTypeDescriptor() {
exists(RefType parent | parent = this.getEnclosingType() |
exists(int num |
- num = 1 + count(AnonymousClass other | other.rankInParent(parent) < rankInParent(parent))
+ num =
+ 1 + count(AnonymousClass other | other.rankInParent(parent) < this.rankInParent(parent))
|
exists(string parentWithSemi | parentWithSemi = parent.getTypeDescriptor() |
result = parentWithSemi.prefix(parentWithSemi.length() - 1) + "$" + num + ";"
@@ -760,8 +809,8 @@ class NestedType extends RefType {
/** Gets the nesting depth of this nested type. Top-level types have nesting depth 0. */
int getNestingDepth() {
- if getEnclosingType() instanceof NestedType
- then result = getEnclosingType().(NestedType).getNestingDepth() + 1
+ if this.getEnclosingType() instanceof NestedType
+ then result = this.getEnclosingType().(NestedType).getNestingDepth() + 1
else result = 1
}
@@ -776,7 +825,7 @@ class NestedType extends RefType {
super.isStrictfp()
or
// JLS 8.1.1.3, JLS 9.1.1.2
- getEnclosingType().isStrictfp()
+ this.getEnclosingType().isStrictfp()
}
override predicate isStatic() {
@@ -860,9 +909,9 @@ class ClassOrInterface extends RefType, @classorinterface {
/** Holds if this class or interface is package protected, that is, neither public nor private nor protected. */
predicate isPackageProtected() {
- not isPrivate() and
- not isProtected() and
- not isPublic()
+ not this.isPrivate() and
+ not this.isProtected() and
+ not this.isPublic()
}
}
@@ -948,12 +997,12 @@ class PrimitiveType extends Type, @primitive {
* require an explicit cast.
*/
Literal getADefaultValue() {
- getName() = "boolean" and result.getLiteral() = "false"
+ this.getName() = "boolean" and result.getLiteral() = "false"
or
- getName() = "char" and
+ this.getName() = "char" and
(result.getLiteral() = "'\\0'" or result.getLiteral() = "'\\u0000'")
or
- getName().regexpMatch("(float|double|int|short|byte|long)") and
+ this.getName().regexpMatch("(float|double|int|short|byte|long)") and
result.getLiteral().regexpMatch("0(\\.0)?+[lLfFdD]?+")
}
@@ -1047,7 +1096,7 @@ class EnumType extends Class {
override predicate isFinal() {
// JLS 8.9: An enum declaration is implicitly `final` unless it contains
// at least one enum constant that has a class body.
- not getAnEnumConstant().getAnAssignedValue().getType() instanceof AnonymousClass
+ not this.getAnEnumConstant().getAnAssignedValue().getType() instanceof AnonymousClass
}
}
@@ -1120,7 +1169,10 @@ predicate erasedHaveIntersection(RefType t1, RefType t2) {
t2 = erase(_)
}
-/** An integral type, which may be either a primitive or a boxed type. */
+/**
+ * An integral type, which may be either a primitive or a boxed type.
+ * This includes the types `char` and `Character`.
+ */
class IntegralType extends Type {
IntegralType() {
exists(string name |
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/UnitTests.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/UnitTests.qll
index 1adc88d35f7..e56b9a6dc23 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/UnitTests.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/UnitTests.qll
@@ -38,11 +38,12 @@ class TearDownMethod extends Method {
private class TestRelatedAnnotation extends Annotation {
TestRelatedAnnotation() {
- this.getType().getPackage().hasName("org.testng.annotations") or
- this.getType().getPackage().hasName("org.junit") or
- this.getType().getPackage().hasName("org.junit.runner") or
- this.getType().getPackage().hasName("org.junit.jupiter.api") or
- this.getType().getPackage().hasName("org.junit.jupiter.params")
+ this.getType()
+ .getPackage()
+ .hasName([
+ "org.testng.annotations", "org.junit", "org.junit.runner", "org.junit.jupiter.api",
+ "org.junit.jupiter.params"
+ ])
}
}
@@ -115,7 +116,7 @@ class JUnitJupiterTestMethod extends Method {
* A JUnit `@Ignore` annotation.
*/
class JUnitIgnoreAnnotation extends Annotation {
- JUnitIgnoreAnnotation() { getType().hasQualifiedName("org.junit", "Ignore") }
+ JUnitIgnoreAnnotation() { this.getType().hasQualifiedName("org.junit", "Ignore") }
}
/**
@@ -124,7 +125,7 @@ class JUnitIgnoreAnnotation extends Annotation {
*/
class JUnitIgnoredMethod extends Method {
JUnitIgnoredMethod() {
- getAnAnnotation() instanceof JUnitIgnoreAnnotation
+ this.getAnAnnotation() instanceof JUnitIgnoreAnnotation
or
exists(Class c | c = this.getDeclaringType() |
c.getAnAnnotation() instanceof JUnitIgnoreAnnotation
@@ -136,14 +137,14 @@ class JUnitIgnoredMethod extends Method {
* An annotation in TestNG.
*/
class TestNGAnnotation extends Annotation {
- TestNGAnnotation() { getType().getPackage().hasName("org.testng.annotations") }
+ TestNGAnnotation() { this.getType().getPackage().hasName("org.testng.annotations") }
}
/**
* An annotation of type `org.test.ng.annotations.Test`.
*/
class TestNGTestAnnotation extends TestNGAnnotation {
- TestNGTestAnnotation() { getType().hasName("Test") }
+ TestNGTestAnnotation() { this.getType().hasName("Test") }
}
/**
@@ -158,13 +159,13 @@ class TestNGTestMethod extends Method {
*/
TestNGDataProviderMethod getADataProvider() {
exists(TestNGTestAnnotation testAnnotation |
- testAnnotation = getAnAnnotation() and
+ testAnnotation = this.getAnAnnotation() and
// The data provider must have the same name as the referenced data provider
result.getDataProviderName() =
- testAnnotation.getValue("dataProvider").(StringLiteral).getRepresentedString()
+ testAnnotation.getValue("dataProvider").(StringLiteral).getValue()
|
// Either the data provider should be on the current class, or a supertype
- getDeclaringType().getAnAncestor() = result.getDeclaringType()
+ this.getDeclaringType().getAnAncestor() = result.getDeclaringType()
or
// Or the data provider class should be declared
result.getDeclaringType() =
@@ -190,14 +191,14 @@ class TestMethod extends Method {
* A TestNG annotation used to mark a method that runs "before".
*/
class TestNGBeforeAnnotation extends TestNGAnnotation {
- TestNGBeforeAnnotation() { getType().getName().matches("Before%") }
+ TestNGBeforeAnnotation() { this.getType().getName().matches("Before%") }
}
/**
* A TestNG annotation used to mark a method that runs "after".
*/
class TestNGAfterAnnotation extends TestNGAnnotation {
- TestNGAfterAnnotation() { getType().getName().matches("After%") }
+ TestNGAfterAnnotation() { this.getType().getName().matches("After%") }
}
/**
@@ -205,7 +206,7 @@ class TestNGAfterAnnotation extends TestNGAnnotation {
* them as data provider methods for TestNG.
*/
class TestNGDataProviderAnnotation extends TestNGAnnotation {
- TestNGDataProviderAnnotation() { getType().hasName("DataProvider") }
+ TestNGDataProviderAnnotation() { this.getType().hasName("DataProvider") }
}
/**
@@ -213,7 +214,7 @@ class TestNGDataProviderAnnotation extends TestNGAnnotation {
* them as factory methods for TestNG.
*/
class TestNGFactoryAnnotation extends TestNGAnnotation {
- TestNGFactoryAnnotation() { getType().hasName("Factory") }
+ TestNGFactoryAnnotation() { this.getType().hasName("Factory") }
}
/**
@@ -221,13 +222,13 @@ class TestNGFactoryAnnotation extends TestNGAnnotation {
* which listeners apply to them.
*/
class TestNGListenersAnnotation extends TestNGAnnotation {
- TestNGListenersAnnotation() { getType().hasName("Listeners") }
+ TestNGListenersAnnotation() { this.getType().hasName("Listeners") }
/**
* Gets a listener defined in this annotation.
*/
TestNGListenerImpl getAListener() {
- result = getAValue("value").(TypeLiteral).getReferencedType()
+ result = this.getAValue("value").(TypeLiteral).getReferencedType()
}
}
@@ -235,7 +236,7 @@ class TestNGListenersAnnotation extends TestNGAnnotation {
* A concrete implementation class of one or more of the TestNG listener interfaces.
*/
class TestNGListenerImpl extends Class {
- TestNGListenerImpl() { getAnAncestor().hasQualifiedName("org.testng", "ITestNGListener") }
+ TestNGListenerImpl() { this.getAnAncestor().hasQualifiedName("org.testng", "ITestNGListener") }
}
/**
@@ -246,18 +247,18 @@ class TestNGListenerImpl extends Class {
* an instance of a particular value when running a test method.
*/
class TestNGDataProviderMethod extends Method {
- TestNGDataProviderMethod() { getAnAnnotation() instanceof TestNGDataProviderAnnotation }
+ TestNGDataProviderMethod() { this.getAnAnnotation() instanceof TestNGDataProviderAnnotation }
/**
* Gets the name associated with this data provider.
*/
string getDataProviderName() {
result =
- getAnAnnotation()
+ this.getAnAnnotation()
.(TestNGDataProviderAnnotation)
.getValue("name")
.(StringLiteral)
- .getRepresentedString()
+ .getValue()
}
}
@@ -268,7 +269,7 @@ class TestNGDataProviderMethod extends Method {
* This factory callable is used to generate instances of parameterized test classes.
*/
class TestNGFactoryCallable extends Callable {
- TestNGFactoryCallable() { getAnAnnotation() instanceof TestNGFactoryAnnotation }
+ TestNGFactoryCallable() { this.getAnAnnotation() instanceof TestNGFactoryAnnotation }
}
/**
@@ -276,7 +277,7 @@ class TestNGFactoryCallable extends Callable {
*/
class ParameterizedJUnitTest extends Class {
ParameterizedJUnitTest() {
- getAnAnnotation()
+ this.getAnAnnotation()
.(RunWithAnnotation)
.getRunner()
.(Class)
@@ -289,7 +290,7 @@ class ParameterizedJUnitTest extends Class {
*/
class JUnitCategoryAnnotation extends Annotation {
JUnitCategoryAnnotation() {
- getType().hasQualifiedName("org.junit.experimental.categories", "Category")
+ this.getType().hasQualifiedName("org.junit.experimental.categories", "Category")
}
/**
@@ -297,7 +298,7 @@ class JUnitCategoryAnnotation extends Annotation {
*/
Type getACategory() {
exists(TypeLiteral literal, Expr value |
- value = getValue("value") and
+ value = this.getValue("value") and
(
literal = value or
literal = value.(ArrayCreationExpr).getInit().getAnInit()
@@ -313,7 +314,7 @@ class JUnitCategoryAnnotation extends Annotation {
*/
class JUnitTheoryTest extends Class {
JUnitTheoryTest() {
- getAnAnnotation()
+ this.getAnAnnotation()
.(RunWithAnnotation)
.getRunner()
.(Class)
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/Variable.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/Variable.qll
index 439ee5d3f6b..530ddd4eae7 100755
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/Variable.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/Variable.qll
@@ -47,12 +47,12 @@ class LocalVariableDecl extends @localvar, LocalScopeVariable {
override Callable getCallable() { result = this.getParent().getEnclosingCallable() }
/** Gets the callable in which this declaration occurs. */
- Callable getEnclosingCallable() { result = getCallable() }
+ Callable getEnclosingCallable() { result = this.getCallable() }
override string toString() { result = this.getType().getName() + " " + this.getName() }
/** Gets the initializer expression of this local variable declaration. */
- override Expr getInitializer() { result = getDeclExpr().getInit() }
+ override Expr getInitializer() { result = this.getDeclExpr().getInit() }
override string getAPrimaryQlClass() { result = "LocalVariableDecl" }
}
@@ -63,7 +63,7 @@ class Parameter extends Element, @param, LocalScopeVariable {
override Type getType() { params(this, result, _, _, _) }
/** Holds if the parameter is never assigned a value in the body of the callable. */
- predicate isEffectivelyFinal() { not exists(getAnAssignedValue()) }
+ predicate isEffectivelyFinal() { not exists(this.getAnAssignedValue()) }
/** Gets the (zero-based) index of this formal parameter. */
int getPosition() { params(this, _, result, _, _) }
@@ -87,8 +87,8 @@ class Parameter extends Element, @param, LocalScopeVariable {
* Varargs parameters will have no results for this method.
*/
Expr getAnArgument() {
- not isVarargs() and
- result = getACallArgument(getPosition())
+ not this.isVarargs() and
+ result = this.getACallArgument(this.getPosition())
}
pragma[noinline]
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/arithmetic/Overflow.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/arithmetic/Overflow.qll
index 7227c6da398..4e9eb75ec13 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/arithmetic/Overflow.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/arithmetic/Overflow.qll
@@ -2,9 +2,9 @@ import java
/** A subclass of `PrimitiveType` with width-based ordering methods. */
class OrdPrimitiveType extends PrimitiveType {
- predicate widerThan(OrdPrimitiveType that) { getWidthRank() > that.getWidthRank() }
+ predicate widerThan(OrdPrimitiveType that) { this.getWidthRank() > that.getWidthRank() }
- predicate widerThanOrEqualTo(OrdPrimitiveType that) { getWidthRank() >= that.getWidthRank() }
+ predicate widerThanOrEqualTo(OrdPrimitiveType that) { this.getWidthRank() >= that.getWidthRank() }
OrdPrimitiveType maxType(OrdPrimitiveType that) {
this.widerThan(that) and result = this
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/controlflow/BasicBlocks.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/controlflow/BasicBlocks.qll
index c0b227ba9ba..5f1ed3438b5 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/controlflow/BasicBlocks.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/controlflow/BasicBlocks.qll
@@ -25,13 +25,13 @@ class BasicBlock extends ControlFlowNode {
/** Gets an immediate successor of this basic block. */
cached
- BasicBlock getABBSuccessor() { result = getLastNode().getASuccessor() }
+ BasicBlock getABBSuccessor() { result = this.getLastNode().getASuccessor() }
/** Gets an immediate predecessor of this basic block. */
BasicBlock getABBPredecessor() { result.getABBSuccessor() = this }
/** Gets a control-flow node contained in this basic block. */
- ControlFlowNode getANode() { result = getNode(_) }
+ ControlFlowNode getANode() { result = this.getNode(_) }
/** Gets the control-flow node at a specific (zero-indexed) position in this basic block. */
cached
@@ -39,7 +39,7 @@ class BasicBlock extends ControlFlowNode {
result = this and pos = 0
or
exists(ControlFlowNode mid, int mid_pos | pos = mid_pos + 1 |
- getNode(mid_pos) = mid and
+ this.getNode(mid_pos) = mid and
mid.getASuccessor() = result and
not result instanceof BasicBlock
)
@@ -49,11 +49,11 @@ class BasicBlock extends ControlFlowNode {
ControlFlowNode getFirstNode() { result = this }
/** Gets the last control-flow node in this basic block. */
- ControlFlowNode getLastNode() { result = getNode(length() - 1) }
+ ControlFlowNode getLastNode() { result = this.getNode(this.length() - 1) }
/** Gets the number of control-flow nodes contained in this basic block. */
cached
- int length() { result = strictcount(getANode()) }
+ int length() { result = strictcount(this.getANode()) }
/** Holds if this basic block strictly dominates `node`. */
predicate bbStrictlyDominates(BasicBlock node) { bbStrictlyDominates(this, node) }
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/controlflow/UnreachableBlocks.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/controlflow/UnreachableBlocks.qll
index 5ce27fda434..8e9d90769f3 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/controlflow/UnreachableBlocks.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/controlflow/UnreachableBlocks.qll
@@ -12,13 +12,13 @@ import semmle.code.java.controlflow.Guards
*/
class ConstantField extends Field {
ConstantField() {
- getType() instanceof ImmutableType and
+ this.getType() instanceof ImmutableType and
// Assigned once
- count(getAnAssignedValue()) = 1 and
+ count(this.getAnAssignedValue()) = 1 and
// And that assignment is either in the appropriate initializer, or, for instance fields on
// classes with one constructor, in the constructor.
- forall(FieldWrite fa | fa = getAnAccess() |
- if isStatic()
+ forall(FieldWrite fa | fa = this.getAnAccess() |
+ if this.isStatic()
then fa.getEnclosingCallable() instanceof StaticInitializer
else (
// Defined in the instance initializer.
@@ -26,7 +26,7 @@ class ConstantField extends Field {
or
// It can be defined in the constructor if there is only one constructor.
fa.getEnclosingCallable() instanceof Constructor and
- count(getDeclaringType().getAConstructor()) = 1
+ count(this.getDeclaringType().getAConstructor()) = 1
)
)
}
@@ -36,7 +36,7 @@ class ConstantField extends Field {
*
* Note: although this value is constant, we may not be able to statically determine the value.
*/
- ConstantExpr getConstantValue() { result = getAnAssignedValue() }
+ ConstantExpr getConstantValue() { result = this.getAnAssignedValue() }
}
/**
@@ -162,18 +162,18 @@ class ConstSwitchStmt extends SwitchStmt {
/** Gets the `ConstCase` that matches, if any. */
ConstCase getMatchingConstCase() {
- result = getAConstCase() and
+ result = this.getAConstCase() and
// Only handle the int case for now
- result.getValue().(ConstantExpr).getIntValue() = getExpr().(ConstantExpr).getIntValue()
+ result.getValue().(ConstantExpr).getIntValue() = this.getExpr().(ConstantExpr).getIntValue()
}
/** Gets the matching case, if it can be deduced. */
SwitchCase getMatchingCase() {
// Must be a value we can deduce
- exists(getExpr().(ConstantExpr).getIntValue()) and
- if exists(getMatchingConstCase())
- then result = getMatchingConstCase()
- else result = getDefaultCase()
+ exists(this.getExpr().(ConstantExpr).getIntValue()) and
+ if exists(this.getMatchingConstCase())
+ then result = this.getMatchingConstCase()
+ else result = this.getDefaultCase()
}
/**
@@ -184,8 +184,8 @@ class ConstSwitchStmt extends SwitchStmt {
SwitchCase getAFailingCase() {
exists(SwitchCase matchingCase |
// We must have found the matching case, otherwise we can't deduce which cases are not matched
- matchingCase = getMatchingCase() and
- result = getACase() and
+ matchingCase = this.getMatchingCase() and
+ result = this.getACase() and
result != matchingCase
)
}
@@ -208,7 +208,7 @@ class UnreachableBasicBlock extends BasicBlock {
or
// This block is not reachable in the CFG, and is not a callable, a body of a callable, an
// expression in an annotation, an expression in an assert statement, or a catch clause.
- forall(BasicBlock bb | bb = getABBPredecessor() | bb instanceof UnreachableBasicBlock) and
+ forall(BasicBlock bb | bb = this.getABBPredecessor() | bb instanceof UnreachableBasicBlock) and
not exists(Callable c | c.getBody() = this) and
not this instanceof Callable and
not exists(Annotation a | a.getAChildExpr*() = this) and
@@ -231,12 +231,12 @@ class UnreachableBasicBlock extends BasicBlock {
* An unreachable expression is an expression contained in an `UnreachableBasicBlock`.
*/
class UnreachableExpr extends Expr {
- UnreachableExpr() { getBasicBlock() instanceof UnreachableBasicBlock }
+ UnreachableExpr() { this.getBasicBlock() instanceof UnreachableBasicBlock }
}
/**
* An unreachable statement is a statement contained in an `UnreachableBasicBlock`.
*/
class UnreachableStmt extends Stmt {
- UnreachableStmt() { getBasicBlock() instanceof UnreachableBasicBlock }
+ UnreachableStmt() { this.getBasicBlock() instanceof UnreachableBasicBlock }
}
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/controlflow/unreachableblocks/ExcludeDebuggingProfilingLogging.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/controlflow/unreachableblocks/ExcludeDebuggingProfilingLogging.qll
index a9f75924c55..3494e813c74 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/controlflow/unreachableblocks/ExcludeDebuggingProfilingLogging.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/controlflow/unreachableblocks/ExcludeDebuggingProfilingLogging.qll
@@ -17,16 +17,11 @@ import semmle.code.java.controlflow.UnreachableBlocks
class ExcludeDebuggingProfilingLogging extends ExcludedConstantField {
ExcludeDebuggingProfilingLogging() {
exists(string validFieldName |
- validFieldName = "debug" or
- validFieldName = "profiling" or
- validFieldName = "profile" or
- validFieldName = "time" or
- validFieldName = "verbose" or
- validFieldName = "report" or
- validFieldName = "dbg" or
- validFieldName = "timing" or
- validFieldName = "assert" or
- validFieldName = "log"
+ validFieldName =
+ [
+ "debug", "profiling", "profile", "time", "verbose", "report", "dbg", "timing", "assert",
+ "log"
+ ]
|
getName().regexpMatch(".*(?i)" + validFieldName + ".*")
) and
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll
index cfbcd16f75e..70858ce2911 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll
@@ -79,9 +79,11 @@ private module Frameworks {
private import internal.ContainerFlow
private import semmle.code.java.frameworks.android.Android
private import semmle.code.java.frameworks.android.Intent
+ private import semmle.code.java.frameworks.android.SQLite
private import semmle.code.java.frameworks.android.XssSinks
private import semmle.code.java.frameworks.ApacheHttp
private import semmle.code.java.frameworks.apache.Collections
+ private import semmle.code.java.frameworks.apache.IO
private import semmle.code.java.frameworks.apache.Lang
private import semmle.code.java.frameworks.Flexjson
private import semmle.code.java.frameworks.guava.Guava
@@ -95,6 +97,8 @@ private module Frameworks {
private import semmle.code.java.frameworks.Optional
private import semmle.code.java.frameworks.Stream
private import semmle.code.java.frameworks.Strings
+ private import semmle.code.java.frameworks.ratpack.Ratpack
+ private import semmle.code.java.frameworks.ratpack.RatpackExec
private import semmle.code.java.frameworks.spring.SpringCache
private import semmle.code.java.frameworks.spring.SpringHttp
private import semmle.code.java.frameworks.spring.SpringUtil
@@ -104,6 +108,7 @@ private module Frameworks {
private import semmle.code.java.frameworks.spring.SpringBeans
private import semmle.code.java.frameworks.spring.SpringWebMultipart
private import semmle.code.java.frameworks.spring.SpringWebUtil
+ private import semmle.code.java.security.AndroidIntentRedirection
private import semmle.code.java.security.ResponseSplitting
private import semmle.code.java.security.InformationLeak
private import semmle.code.java.security.GroovyInjection
@@ -114,8 +119,6 @@ private module Frameworks {
private import semmle.code.java.security.OgnlInjection
private import semmle.code.java.security.XPath
private import semmle.code.java.security.XsltInjection
- private import semmle.code.java.frameworks.android.Android
- private import semmle.code.java.frameworks.android.SQLite
private import semmle.code.java.frameworks.Jdbc
private import semmle.code.java.frameworks.SpringJdbc
private import semmle.code.java.frameworks.MyBatis
@@ -320,33 +323,11 @@ private predicate summaryModelCsv(string row) {
"org.apache.commons.codec;BinaryDecoder;true;decode;(byte[]);;Argument[0];ReturnValue;taint",
"org.apache.commons.codec;StringEncoder;true;encode;(String);;Argument[0];ReturnValue;taint",
"org.apache.commons.codec;StringDecoder;true;decode;(String);;Argument[0];ReturnValue;taint",
- "org.apache.commons.io;IOUtils;false;buffer;;;Argument[0];ReturnValue;taint",
- "org.apache.commons.io;IOUtils;false;readLines;;;Argument[0];ReturnValue;taint",
- "org.apache.commons.io;IOUtils;false;readFully;(InputStream,int);;Argument[0];ReturnValue;taint",
- "org.apache.commons.io;IOUtils;false;toBufferedInputStream;;;Argument[0];ReturnValue;taint",
- "org.apache.commons.io;IOUtils;false;toBufferedReader;;;Argument[0];ReturnValue;taint",
- "org.apache.commons.io;IOUtils;false;toByteArray;;;Argument[0];ReturnValue;taint",
- "org.apache.commons.io;IOUtils;false;toCharArray;;;Argument[0];ReturnValue;taint",
- "org.apache.commons.io;IOUtils;false;toInputStream;;;Argument[0];ReturnValue;taint",
- "org.apache.commons.io;IOUtils;false;toString;;;Argument[0];ReturnValue;taint",
"java.net;URLDecoder;false;decode;;;Argument[0];ReturnValue;taint",
"java.net;URI;false;create;;;Argument[0];ReturnValue;taint",
"javax.xml.transform.sax;SAXSource;false;sourceToInputSource;;;Argument[0];ReturnValue;taint",
// arg to arg
"java.lang;System;false;arraycopy;;;Argument[0];Argument[2];taint",
- "org.apache.commons.io;IOUtils;false;copy;;;Argument[0];Argument[1];taint",
- "org.apache.commons.io;IOUtils;false;copyLarge;;;Argument[0];Argument[1];taint",
- "org.apache.commons.io;IOUtils;false;read;;;Argument[0];Argument[1];taint",
- "org.apache.commons.io;IOUtils;false;readFully;(InputStream,byte[]);;Argument[0];Argument[1];taint",
- "org.apache.commons.io;IOUtils;false;readFully;(InputStream,byte[],int,int);;Argument[0];Argument[1];taint",
- "org.apache.commons.io;IOUtils;false;readFully;(InputStream,ByteBuffer);;Argument[0];Argument[1];taint",
- "org.apache.commons.io;IOUtils;false;readFully;(ReadableByteChannel,ByteBuffer);;Argument[0];Argument[1];taint",
- "org.apache.commons.io;IOUtils;false;readFully;(Reader,char[]);;Argument[0];Argument[1];taint",
- "org.apache.commons.io;IOUtils;false;readFully;(Reader,char[],int,int);;Argument[0];Argument[1];taint",
- "org.apache.commons.io;IOUtils;false;write;;;Argument[0];Argument[1];taint",
- "org.apache.commons.io;IOUtils;false;writeChunked;;;Argument[0];Argument[1];taint",
- "org.apache.commons.io;IOUtils;false;writeLines;;;Argument[0];Argument[2];taint",
- "org.apache.commons.io;IOUtils;false;writeLines;;;Argument[1];Argument[2];taint",
// constructor flow
"java.io;File;false;File;;;Argument[0];Argument[-1];taint",
"java.io;File;false;File;;;Argument[1];Argument[-1];taint",
@@ -371,7 +352,11 @@ private predicate summaryModelCsv(string row) {
"java.io;StringReader;false;StringReader;;;Argument[0];Argument[-1];taint",
"java.io;CharArrayReader;false;CharArrayReader;;;Argument[0];Argument[-1];taint",
"java.io;BufferedReader;false;BufferedReader;;;Argument[0];Argument[-1];taint",
- "java.io;InputStreamReader;false;InputStreamReader;;;Argument[0];Argument[-1];taint"
+ "java.io;InputStreamReader;false;InputStreamReader;;;Argument[0];Argument[-1];taint",
+ "java.io;OutputStream;true;write;(byte[]);;Argument[0];Argument[-1];taint",
+ "java.io;OutputStream;true;write;(byte[],int,int);;Argument[0];Argument[-1];taint",
+ "java.io;OutputStream;true;write;(int);;Argument[0];Argument[-1];taint",
+ "java.io;FilterOutputStream;true;FilterOutputStream;(OutputStream);;Argument[0];Argument[-1];taint"
]
}
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/FlowSources.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/FlowSources.qll
index a8f15a103c8..d8d02f995a6 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/FlowSources.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/FlowSources.qll
@@ -45,8 +45,8 @@ private class RmiMethodParameterSource extends RemoteFlowSource {
exists(RemoteCallableMethod method |
method.getAParameter() = this.asParameter() and
(
- getType() instanceof PrimitiveType or
- getType() instanceof TypeString
+ this.getType() instanceof PrimitiveType or
+ this.getType() instanceof TypeString
)
)
}
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/FlowSteps.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/FlowSteps.qll
index ae9c88fb118..ec62ec42c44 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/FlowSteps.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/FlowSteps.qll
@@ -11,6 +11,7 @@ private import semmle.code.java.dataflow.DataFlow
*/
private module Frameworks {
private import semmle.code.java.frameworks.jackson.JacksonSerializability
+ private import semmle.code.java.frameworks.android.AsyncTask
private import semmle.code.java.frameworks.android.Intent
private import semmle.code.java.frameworks.android.SQLite
private import semmle.code.java.frameworks.Guice
@@ -64,6 +65,20 @@ class AdditionalTaintStep extends Unit {
abstract predicate step(DataFlow::Node node1, DataFlow::Node node2);
}
+/**
+ * A unit class for adding additional value steps.
+ *
+ * Extend this class to add additional value-preserving steps that should apply
+ * to all data flow configurations.
+ */
+class AdditionalValueStep extends Unit {
+ /**
+ * Holds if the step from `node1` to `node2` is a value-preserving step and
+ * should apply to all data flow configurations.
+ */
+ abstract predicate step(DataFlow::Node node1, DataFlow::Node node2);
+}
+
/**
* A method or constructor that preserves taint.
*
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/SSA.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/SSA.qll
index 9a2a1df0915..dbcaafd3071 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/SSA.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/SSA.qll
@@ -97,7 +97,7 @@ class SsaSourceVariable extends TSsaSourceVariable {
else result = c.getName() + "(..)." + v.getName()
)
or
- result = this.(SsaSourceField).ppQualifier() + "." + getVariable().toString()
+ result = this.(SsaSourceField).ppQualifier() + "." + this.getVariable().toString()
}
/**
@@ -117,7 +117,7 @@ class SsaSourceVariable extends TSsaSourceVariable {
Location getLocation() {
exists(LocalScopeVariable v | this = TLocalVar(_, v) and result = v.getLocation())
or
- this instanceof SsaSourceField and result = getFirstAccess().getLocation()
+ this instanceof SsaSourceField and result = this.getFirstAccess().getLocation()
}
/** Gets the type of this variable. */
@@ -140,7 +140,7 @@ class SsaSourceField extends SsaSourceVariable {
}
/** Gets the field corresponding to this named field. */
- Field getField() { result = getVariable() }
+ Field getField() { result = this.getVariable() }
/** Gets a string representation of the qualifier. */
string ppQualifier() {
@@ -155,8 +155,8 @@ class SsaSourceField extends SsaSourceVariable {
/** Holds if the field itself or any of the fields part of the qualifier are volatile. */
predicate isVolatile() {
- getField().isVolatile() or
- getQualifier().(SsaSourceField).isVolatile()
+ this.getField().isVolatile() or
+ this.getQualifier().(SsaSourceField).isVolatile()
}
}
@@ -932,10 +932,10 @@ class SsaVariable extends TSsaVariable {
string toString() { none() }
/** Gets the source location for this element. */
- Location getLocation() { result = getCFGNode().getLocation() }
+ Location getLocation() { result = this.getCFGNode().getLocation() }
/** Gets the `BasicBlock` in which this SSA variable is defined. */
- BasicBlock getBasicBlock() { result = getCFGNode().getBasicBlock() }
+ BasicBlock getBasicBlock() { result = this.getCFGNode().getBasicBlock() }
/** Gets an access of this SSA variable. */
RValue getAUse() {
@@ -989,14 +989,16 @@ class SsaUpdate extends SsaVariable {
/** An SSA variable that is defined by a `VariableUpdate`. */
class SsaExplicitUpdate extends SsaUpdate, TSsaCertainUpdate {
SsaExplicitUpdate() {
- exists(VariableUpdate upd | upd = this.getCFGNode() and getDestVar(upd) = getSourceVariable())
+ exists(VariableUpdate upd |
+ upd = this.getCFGNode() and getDestVar(upd) = this.getSourceVariable()
+ )
}
- override string toString() { result = "SSA def(" + getSourceVariable() + ")" }
+ override string toString() { result = "SSA def(" + this.getSourceVariable() + ")" }
/** Gets the `VariableUpdate` defining the SSA variable. */
VariableUpdate getDefiningExpr() {
- result = this.getCFGNode() and getDestVar(result) = getSourceVariable()
+ result = this.getCFGNode() and getDestVar(result) = this.getSourceVariable()
}
}
@@ -1010,22 +1012,22 @@ class SsaImplicitUpdate extends SsaUpdate {
SsaImplicitUpdate() { not this instanceof SsaExplicitUpdate }
override string toString() {
- result = "SSA impl upd[" + getKind() + "](" + getSourceVariable() + ")"
+ result = "SSA impl upd[" + this.getKind() + "](" + this.getSourceVariable() + ")"
}
private string getKind() {
this = TSsaUntracked(_, _) and result = "untracked"
or
- certainVariableUpdate(getSourceVariable().getQualifier(), getCFGNode(), _, _) and
+ certainVariableUpdate(this.getSourceVariable().getQualifier(), this.getCFGNode(), _, _) and
result = "explicit qualifier"
or
- if uncertainVariableUpdate(getSourceVariable().getQualifier(), getCFGNode(), _, _)
+ if uncertainVariableUpdate(this.getSourceVariable().getQualifier(), this.getCFGNode(), _, _)
then
- if exists(getANonLocalUpdate())
+ if exists(this.getANonLocalUpdate())
then result = "nonlocal + nonlocal qualifier"
else result = "nonlocal qualifier"
else (
- exists(getANonLocalUpdate()) and result = "nonlocal"
+ exists(this.getANonLocalUpdate()) and result = "nonlocal"
)
}
@@ -1034,9 +1036,9 @@ class SsaImplicitUpdate extends SsaUpdate {
*/
FieldWrite getANonLocalUpdate() {
exists(SsaSourceField f, Callable setter |
- f = getSourceVariable() and
+ f = this.getSourceVariable() and
relevantFieldUpdate(setter, f.getField(), result) and
- updatesNamedField(getCFGNode(), f, setter)
+ updatesNamedField(this.getCFGNode(), f, setter)
)
}
@@ -1049,8 +1051,8 @@ class SsaImplicitUpdate extends SsaUpdate {
*/
predicate assignsUnknownValue() {
this = TSsaUntracked(_, _) or
- certainVariableUpdate(getSourceVariable().getQualifier(), getCFGNode(), _, _) or
- uncertainVariableUpdate(getSourceVariable().getQualifier(), getCFGNode(), _, _)
+ certainVariableUpdate(this.getSourceVariable().getQualifier(), this.getCFGNode(), _, _) or
+ uncertainVariableUpdate(this.getSourceVariable().getQualifier(), this.getCFGNode(), _, _)
}
}
@@ -1072,30 +1074,31 @@ class SsaUncertainImplicitUpdate extends SsaImplicitUpdate, TSsaUncertainUpdate
* includes initial values of parameters, fields, and closure variables.
*/
class SsaImplicitInit extends SsaVariable, TSsaEntryDef {
- override string toString() { result = "SSA init(" + getSourceVariable() + ")" }
+ override string toString() { result = "SSA init(" + this.getSourceVariable() + ")" }
/** Holds if this is a closure variable that captures the value of `capturedvar`. */
predicate captures(SsaVariable capturedvar) {
- ssaDefReachesCapture(_, capturedvar, getSourceVariable())
+ ssaDefReachesCapture(_, capturedvar, this.getSourceVariable())
}
/**
* Holds if the SSA variable is a parameter defined by its initial value in the callable.
*/
predicate isParameterDefinition(Parameter p) {
- getSourceVariable() = TLocalVar(p.getCallable(), p) and p.getCallable().getBody() = getCFGNode()
+ this.getSourceVariable() = TLocalVar(p.getCallable(), p) and
+ p.getCallable().getBody() = this.getCFGNode()
}
}
/** An SSA phi node. */
class SsaPhiNode extends SsaVariable, TSsaPhiNode {
- override string toString() { result = "SSA phi(" + getSourceVariable() + ")" }
+ override string toString() { result = "SSA phi(" + this.getSourceVariable() + ")" }
/** Gets an input to the phi node defining the SSA variable. */
SsaVariable getAPhiInput() {
exists(BasicBlock phiPred, TrackedVar v |
- v = getSourceVariable() and
- getCFGNode().(BasicBlock).getABBPredecessor() = phiPred and
+ v = this.getSourceVariable() and
+ this.getCFGNode().(BasicBlock).getABBPredecessor() = phiPred and
ssaDefReachesEndOfBlock(v, result, phiPred)
)
}
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/StringPrefixes.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/StringPrefixes.qll
new file mode 100644
index 00000000000..755deaba532
--- /dev/null
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/StringPrefixes.qll
@@ -0,0 +1,178 @@
+/**
+ * Provides classes and predicates for identifying expressions that may be appended to an interesting prefix.
+ *
+ * To use this library, extend the abstract class `InterestingPrefix` to have the library identify expressions that
+ * may be appended to it, then check `InterestingPrefix.getAnAppendedExpression(Expr)` to get your results.
+ *
+ * For example, to identify expressions that may follow "foo:" in some string, we could define:
+ *
+ * ```
+ * private class FooPrefix extends InterestingPrefix {
+ * int offset;
+ * FooPrefix() { this.getStringValue().substring("foo:") = offset };
+ * override int getOffset() { result = offset }
+ * };
+ *
+ * predicate mayFollowFoo(Expr e) { e = any(FooPrefix fp).getAnAppendedExpression() }
+ * ```
+ *
+ * This will identify all the `suffix` expressions in contexts such as:
+ *
+ * ```
+ * "foo:" + suffix1
+ * "barfoo:" + suffix2
+ * stringBuilder.append("foo:").append(suffix3);
+ * String.format("%sfoo:%s", notSuffix, suffix4);
+ * ```
+ */
+
+import java
+private import semmle.code.java.dataflow.TaintTracking
+private import semmle.code.java.StringFormat
+
+/**
+ * A string constant that contains a prefix whose possibly-appended strings are
+ * returned by `getAnAppendedExpression`.
+ *
+ * Extend this class to specify prefixes whose possibly-appended strings should be analysed.
+ */
+abstract class InterestingPrefix extends CompileTimeConstantExpr {
+ /**
+ * Gets the offset in this constant string where the interesting prefix begins.
+ */
+ abstract int getOffset();
+
+ /**
+ * Gets an expression that may follow this prefix in a derived string.
+ */
+ Expr getAnAppendedExpression() { mayFollowInterestingPrefix(this, result) }
+}
+
+private Expr getAnInterestingPrefix(InterestingPrefix root) {
+ result = root
+ or
+ result.(AddExpr).getAnOperand() = getAnInterestingPrefix(root)
+}
+
+private class StringBuilderAppend extends MethodAccess {
+ StringBuilderAppend() {
+ this.getMethod().getDeclaringType() instanceof StringBuildingType and
+ this.getMethod().hasName("append")
+ }
+}
+
+private class StringBuilderConstructorOrAppend extends Call {
+ StringBuilderConstructorOrAppend() {
+ this instanceof StringBuilderAppend or
+ this.(ClassInstanceExpr).getConstructedType() instanceof StringBuildingType
+ }
+}
+
+private Expr getQualifier(Expr e) { result = e.(MethodAccess).getQualifier() }
+
+/**
+ * An extension of `StringBuilderVar` that also accounts for strings appended in StringBuilder/Buffer's constructor
+ * and in `append` calls chained onto the constructor call.
+ *
+ * The original `StringBuilderVar` doesn't care about these because it is designed to model taint, and
+ * in taint rules terms these are not needed, as the connection between construction, appends and the
+ * eventual `toString` is more obvious.
+ */
+private class StringBuilderVarExt extends StringBuilderVar {
+ /**
+ * Returns a first assignment after this StringBuilderVar is first assigned.
+ *
+ * For example, for `StringBuilder sbv = new StringBuilder("1").append("2"); sbv.append("3").append("4");`
+ * this returns the append of `"3"`.
+ */
+ private StringBuilderAppend getAFirstAppendAfterAssignment() {
+ result = this.getAnAppend() and not result = this.getNextAppend(_)
+ }
+
+ /**
+ * Gets the next `append` after `prev`, where `prev` is, perhaps after some more `append` or other
+ * chained calls, assigned to this `StringBuilderVar`.
+ */
+ private StringBuilderAppend getNextAssignmentChainedAppend(StringBuilderConstructorOrAppend prev) {
+ getQualifier*(result) = this.getAnAssignedValue() and
+ result.getQualifier() = prev
+ }
+
+ /**
+ * Get a constructor call or `append` call that contributes a string to this string builder.
+ */
+ StringBuilderConstructorOrAppend getAConstructorOrAppend() {
+ exists(this.getNextAssignmentChainedAppend(result)) or
+ result = this.getAnAssignedValue() or
+ result = this.getAnAppend()
+ }
+
+ /**
+ * Like `StringBuilderVar.getNextAppend`, except including appends and constructors directly
+ * assigned to this `StringBuilderVar`.
+ */
+ private StringBuilderAppend getNextAppendIncludingAssignmentChains(
+ StringBuilderConstructorOrAppend prev
+ ) {
+ result = this.getNextAssignmentChainedAppend(prev)
+ or
+ prev = this.getAnAssignedValue() and
+ result = this.getAFirstAppendAfterAssignment()
+ or
+ result = this.getNextAppend(prev)
+ }
+
+ /**
+ * Implements `StringBuilderVarExt.getNextAppendIncludingAssignmentChains+(prev)`.
+ */
+ pragma[nomagic]
+ StringBuilderAppend getSubsequentAppendIncludingAssignmentChains(
+ StringBuilderConstructorOrAppend prev
+ ) {
+ result = this.getNextAppendIncludingAssignmentChains(prev) or
+ result =
+ this.getSubsequentAppendIncludingAssignmentChains(this.getNextAppendIncludingAssignmentChains(prev))
+ }
+}
+
+/**
+ * Holds if `follows` may be concatenated after `prefix`.
+ */
+private predicate mayFollowInterestingPrefix(InterestingPrefix prefix, Expr follows) {
+ // Expressions that come after an interesting prefix in a tree of string additions:
+ follows =
+ any(AddExpr add | add.getLeftOperand() = getAnInterestingPrefix(prefix)).getRightOperand()
+ or
+ // Sanitize expressions that come after an interesting prefix in a sequence of StringBuilder operations:
+ exists(
+ StringBuilderConstructorOrAppend appendSanitizingConstant, StringBuilderAppend subsequentAppend,
+ StringBuilderVarExt v
+ |
+ appendSanitizingConstant = v.getAConstructorOrAppend() and
+ appendSanitizingConstant.getArgument(0) = getAnInterestingPrefix(prefix) and
+ v.getSubsequentAppendIncludingAssignmentChains(appendSanitizingConstant) = subsequentAppend and
+ follows = subsequentAppend.getArgument(0)
+ )
+ or
+ // Sanitize expressions that come after an interesting prefix in the args to a format call:
+ exists(
+ FormattingCall formatCall, FormatString formatString, int prefixOffset, int laterOffset,
+ int sanitizedArg
+ |
+ formatString = unique(FormatString fs | fs = formatCall.getAFormatString()) and
+ (
+ // An interesting prefix argument comes before this:
+ exists(int argIdx |
+ formatCall.getArgumentToBeFormatted(argIdx) = prefix and
+ prefixOffset = formatString.getAnArgUsageOffset(argIdx)
+ )
+ or
+ // The format string itself contains an interesting prefix that precedes subsequent arguments:
+ formatString = prefix.getStringValue() and
+ prefixOffset = prefix.getOffset()
+ ) and
+ laterOffset > prefixOffset and
+ laterOffset = formatString.getAnArgUsageOffset(sanitizedArg) and
+ follows = formatCall.getArgumentToBeFormatted(sanitizedArg)
+ )
+}
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/BaseSSA.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/BaseSSA.qll
index 8193a33bcb3..e0e6e64321f 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/BaseSSA.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/BaseSSA.qll
@@ -484,10 +484,10 @@ class BaseSsaVariable extends TBaseSsaVariable {
string toString() { none() }
- Location getLocation() { result = getCFGNode().getLocation() }
+ Location getLocation() { result = this.getCFGNode().getLocation() }
/** Gets the `BasicBlock` in which this SSA variable is defined. */
- BasicBlock getBasicBlock() { result = getCFGNode().getBasicBlock() }
+ BasicBlock getBasicBlock() { result = this.getCFGNode().getBasicBlock() }
/** Gets an access of this SSA variable. */
RValue getAUse() { ssaDefReachesUse(_, this, result) }
@@ -532,14 +532,16 @@ class BaseSsaVariable extends TBaseSsaVariable {
/** An SSA variable that is defined by a `VariableUpdate`. */
class BaseSsaUpdate extends BaseSsaVariable, TSsaUpdate {
BaseSsaUpdate() {
- exists(VariableUpdate upd | upd = this.getCFGNode() and getDestVar(upd) = getSourceVariable())
+ exists(VariableUpdate upd |
+ upd = this.getCFGNode() and getDestVar(upd) = this.getSourceVariable()
+ )
}
- override string toString() { result = "SSA def(" + getSourceVariable() + ")" }
+ override string toString() { result = "SSA def(" + this.getSourceVariable() + ")" }
/** Gets the `VariableUpdate` defining the SSA variable. */
VariableUpdate getDefiningExpr() {
- result = this.getCFGNode() and getDestVar(result) = getSourceVariable()
+ result = this.getCFGNode() and getDestVar(result) = this.getSourceVariable()
}
}
@@ -548,30 +550,31 @@ class BaseSsaUpdate extends BaseSsaVariable, TSsaUpdate {
* includes initial values of parameters, fields, and closure variables.
*/
class BaseSsaImplicitInit extends BaseSsaVariable, TSsaEntryDef {
- override string toString() { result = "SSA init(" + getSourceVariable() + ")" }
+ override string toString() { result = "SSA init(" + this.getSourceVariable() + ")" }
/** Holds if this is a closure variable that captures the value of `capturedvar`. */
predicate captures(BaseSsaVariable capturedvar) {
- ssaDefReachesCapture(_, capturedvar, getSourceVariable())
+ ssaDefReachesCapture(_, capturedvar, this.getSourceVariable())
}
/**
* Holds if the SSA variable is a parameter defined by its initial value in the callable.
*/
predicate isParameterDefinition(Parameter p) {
- getSourceVariable() = TLocalVar(p.getCallable(), p) and p.getCallable().getBody() = getCFGNode()
+ this.getSourceVariable() = TLocalVar(p.getCallable(), p) and
+ p.getCallable().getBody() = this.getCFGNode()
}
}
/** An SSA phi node. */
class BaseSsaPhiNode extends BaseSsaVariable, TSsaPhiNode {
- override string toString() { result = "SSA phi(" + getSourceVariable() + ")" }
+ override string toString() { result = "SSA phi(" + this.getSourceVariable() + ")" }
/** Gets an input to the phi node defining the SSA variable. */
BaseSsaVariable getAPhiInput() {
exists(BasicBlock phiPred, BaseSsaSourceVariable v |
- v = getSourceVariable() and
- getCFGNode().(BasicBlock).getABBPredecessor() = phiPred and
+ v = this.getSourceVariable() and
+ this.getCFGNode().(BasicBlock).getABBPredecessor() = phiPred and
ssaDefReachesEndOfBlock(v, result, phiPred)
)
}
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/ContainerFlow.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/ContainerFlow.qll
index 6c72bbd451b..79fea09de4c 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/ContainerFlow.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/ContainerFlow.qll
@@ -104,7 +104,9 @@ private class ContainerFlowSummaries extends SummaryModelCsv {
"java.util;Map$Entry;true;setValue;;;Argument[0];MapValue of Argument[-1];value",
"java.lang;Iterable;true;iterator;();;Element of Argument[-1];Element of ReturnValue;value",
"java.lang;Iterable;true;spliterator;();;Element of Argument[-1];Element of ReturnValue;value",
+ "java.lang;Iterable;true;forEach;(Consumer);;Element of Argument[-1];Parameter[0] of Argument[0];value",
"java.util;Iterator;true;next;;;Element of Argument[-1];ReturnValue;value",
+ "java.util;Iterator;true;forEachRemaining;(Consumer);;Element of Argument[-1];Parameter[0] of Argument[0];value",
"java.util;ListIterator;true;previous;;;Element of Argument[-1];ReturnValue;value",
"java.util;ListIterator;true;add;(Object);;Argument[0];Element of Argument[-1];value",
"java.util;ListIterator;true;set;(Object);;Argument[0];Element of Argument[-1];value",
@@ -135,6 +137,8 @@ private class ContainerFlowSummaries extends SummaryModelCsv {
"java.util;Map;true;merge;(Object,Object,BiFunction);;Argument[1];MapValue of Argument[-1];value",
"java.util;Map;true;putAll;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;Map;true;putAll;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value",
+ "java.util;Map;true;forEach;(BiConsumer);;MapKey of Argument[-1];Parameter[0] of Argument[0];value",
+ "java.util;Map;true;forEach;(BiConsumer);;MapValue of Argument[-1];Parameter[1] of Argument[0];value",
"java.util;Collection;true;parallelStream;();;Element of Argument[-1];Element of ReturnValue;value",
"java.util;Collection;true;stream;();;Element of Argument[-1];Element of ReturnValue;value",
"java.util;Collection;true;toArray;;;Element of Argument[-1];ArrayElement of ReturnValue;value",
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowDispatch.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowDispatch.qll
index 0e61dc4582f..91b8b391df5 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowDispatch.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowDispatch.qll
@@ -8,9 +8,9 @@ private import semmle.code.java.dispatch.VirtualDispatch as VirtualDispatch
private module DispatchImpl {
/** Gets a viable implementation of the target of the given `Call`. */
DataFlowCallable viableCallable(DataFlowCall c) {
- result = VirtualDispatch::viableCallable(c.asCall())
+ result.asCallable() = VirtualDispatch::viableCallable(c.asCall())
or
- result.(SummarizedCallable) = c.asCall().getCallee().getSourceDeclaration()
+ result.(SummarizedCallable).asCallable() = c.asCall().getCallee().getSourceDeclaration()
}
/**
@@ -93,31 +93,32 @@ private module DispatchImpl {
* qualifier is a parameter of the enclosing callable `c`.
*/
predicate mayBenefitFromCallContext(DataFlowCall call, DataFlowCallable c) {
- mayBenefitFromCallContext(call.asCall(), c, _)
+ mayBenefitFromCallContext(call.asCall(), c.asCallable(), _)
}
/**
* Gets a viable dispatch target of `call` in the context `ctx`. This is
* restricted to those `call`s for which a context might make a difference.
*/
- Method viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
+ DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
result = viableCallable(call) and
exists(int i, Callable c, Method def, RefType t, boolean exact, MethodAccess ma |
ma = call.asCall() and
mayBenefitFromCallContext(ma, c, i) and
- c = viableCallable(ctx) and
+ c = viableCallable(ctx).asCallable() and
contextArgHasType(ctx.asCall(), i, t, exact) and
ma.getMethod().getSourceDeclaration() = def
|
- exact = true and result = VirtualDispatch::exactMethodImpl(def, t.getSourceDeclaration())
+ exact = true and
+ result.asCallable() = VirtualDispatch::exactMethodImpl(def, t.getSourceDeclaration())
or
exact = false and
exists(RefType t2 |
- result = VirtualDispatch::viableMethodImpl(def, t.getSourceDeclaration(), t2) and
+ result.asCallable() = VirtualDispatch::viableMethodImpl(def, t.getSourceDeclaration(), t2) and
not failsUnification(t, t2)
)
or
- result = def and def instanceof SummarizedCallable
+ result.asCallable() = def and result instanceof SummarizedCallable
)
}
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl2.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl2.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl2.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl2.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl3.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl3.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl3.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl3.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl4.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl4.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl4.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl4.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl5.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl5.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl5.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl5.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl6.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl6.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl6.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl6.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll
index f43a550af57..c28ceabb438 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll
@@ -2,6 +2,42 @@ private import DataFlowImplSpecific::Private
private import DataFlowImplSpecific::Public
import Cached
+module DataFlowImplCommonPublic {
+ private newtype TFlowFeature =
+ TFeatureHasSourceCallContext() or
+ TFeatureHasSinkCallContext() or
+ TFeatureEqualSourceSinkCallContext()
+
+ /** A flow configuration feature for use in `Configuration::getAFeature()`. */
+ class FlowFeature extends TFlowFeature {
+ string toString() { none() }
+ }
+
+ /**
+ * A flow configuration feature that implies that sources have some existing
+ * call context.
+ */
+ class FeatureHasSourceCallContext extends FlowFeature, TFeatureHasSourceCallContext {
+ override string toString() { result = "FeatureHasSourceCallContext" }
+ }
+
+ /**
+ * A flow configuration feature that implies that sinks have some existing
+ * call context.
+ */
+ class FeatureHasSinkCallContext extends FlowFeature, TFeatureHasSinkCallContext {
+ override string toString() { result = "FeatureHasSinkCallContext" }
+ }
+
+ /**
+ * A flow configuration feature that implies that source-sink pairs have some
+ * shared existing call context.
+ */
+ class FeatureEqualSourceSinkCallContext extends FlowFeature, TFeatureEqualSourceSinkCallContext {
+ override string toString() { result = "FeatureEqualSourceSinkCallContext" }
+ }
+}
+
/**
* The cost limits for the `AccessPathFront` to `AccessPathApprox` expansion.
*
@@ -251,7 +287,7 @@ private module Cached {
predicate forceCachingInSameStage() { any() }
cached
- predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = n.getEnclosingCallable() }
+ predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = nodeGetEnclosingCallable(n) }
cached
predicate callEnclosingCallable(DataFlowCall call, DataFlowCallable c) {
@@ -316,9 +352,7 @@ private module Cached {
}
cached
- predicate parameterNode(Node n, DataFlowCallable c, int i) {
- n.(ParameterNode).isParameterOf(c, i)
- }
+ predicate parameterNode(Node p, DataFlowCallable c, int pos) { isParameterNode(p, c, pos) }
cached
predicate argumentNode(Node n, DataFlowCall call, int pos) {
@@ -801,6 +835,9 @@ private module Cached {
exists(Node n | getNodeEnclosingCallable(n) = callable | isUnreachableInCallCached(n, call))
}
+ cached
+ predicate allowParameterReturnInSelfCached(ParamNode p) { allowParameterReturnInSelf(p) }
+
cached
newtype TCallContext =
TAnyCallContext() or
@@ -937,7 +974,7 @@ class CallContextSpecificCall extends CallContextCall, TSpecificCall {
}
override predicate relevantFor(DataFlowCallable callable) {
- recordDataFlowCallSite(getCall(), callable)
+ recordDataFlowCallSite(this.getCall(), callable)
}
override predicate matchesCall(DataFlowCall call) { call = this.getCall() }
@@ -1257,7 +1294,7 @@ abstract class AccessPathFront extends TAccessPathFront {
TypedContent getHead() { this = TFrontHead(result) }
- predicate isClearedAt(Node n) { clearsContentCached(n, getHead().getContent()) }
+ predicate isClearedAt(Node n) { clearsContentCached(n, this.getHead().getContent()) }
}
class AccessPathFrontNil extends AccessPathFront, TFrontNil {
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplConsistency.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplConsistency.qll
index a55e65a81f6..acf31338f9a 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplConsistency.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplConsistency.qll
@@ -31,7 +31,7 @@ module Consistency {
query predicate uniqueEnclosingCallable(Node n, string msg) {
exists(int c |
n instanceof RelevantNode and
- c = count(n.getEnclosingCallable()) and
+ c = count(nodeGetEnclosingCallable(n)) and
c != 1 and
msg = "Node should have one enclosing callable but has " + c + "."
)
@@ -85,13 +85,13 @@ module Consistency {
}
query predicate parameterCallable(ParameterNode p, string msg) {
- exists(DataFlowCallable c | p.isParameterOf(c, _) and c != p.getEnclosingCallable()) and
+ exists(DataFlowCallable c | isParameterNode(p, c, _) and c != nodeGetEnclosingCallable(p)) and
msg = "Callable mismatch for parameter."
}
query predicate localFlowIsLocal(Node n1, Node n2, string msg) {
simpleLocalFlowStep(n1, n2) and
- n1.getEnclosingCallable() != n2.getEnclosingCallable() and
+ nodeGetEnclosingCallable(n1) != nodeGetEnclosingCallable(n2) and
msg = "Local flow step does not preserve enclosing callable."
}
@@ -106,7 +106,7 @@ module Consistency {
query predicate unreachableNodeCCtx(Node n, DataFlowCall call, string msg) {
isUnreachableInCall(n, call) and
exists(DataFlowCallable c |
- c = n.getEnclosingCallable() and
+ c = nodeGetEnclosingCallable(n) and
not viableCallable(call) = c
) and
msg = "Call context for isUnreachableInCall is inconsistent with call graph."
@@ -120,7 +120,7 @@ module Consistency {
n.(ArgumentNode).argumentOf(call, _) and
msg = "ArgumentNode and call does not share enclosing callable."
) and
- n.getEnclosingCallable() != call.getEnclosingCallable()
+ nodeGetEnclosingCallable(n) != call.getEnclosingCallable()
}
// This predicate helps the compiler forget that in some languages
@@ -151,7 +151,7 @@ module Consistency {
}
query predicate postIsInSameCallable(PostUpdateNode n, string msg) {
- n.getEnclosingCallable() != n.getPreUpdateNode().getEnclosingCallable() and
+ nodeGetEnclosingCallable(n) != nodeGetEnclosingCallable(n.getPreUpdateNode()) and
msg = "PostUpdateNode does not share callable with its pre-update node."
}
@@ -175,6 +175,7 @@ module Consistency {
query predicate postWithInFlow(Node n, string msg) {
isPostUpdateNode(n) and
+ not clearsContent(n, _) and
simpleLocalFlowStep(_, n) and
msg = "PostUpdateNode should not be the target of local flow."
}
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForSerializability.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForSerializability.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForSerializability.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForSerializability.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowNodes.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowNodes.qll
index 622ef595792..3509b38108e 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowNodes.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowNodes.qll
@@ -14,14 +14,14 @@ newtype TNode =
not e.getParent*() instanceof Annotation
} or
TExplicitParameterNode(Parameter p) {
- exists(p.getCallable().getBody()) or p.getCallable() instanceof SummarizedCallable
+ exists(p.getCallable().getBody()) or p.getCallable() = any(SummarizedCallable sc).asCallable()
} or
TImplicitVarargsArray(Call c) {
c.getCallee().isVarargs() and
not exists(Argument arg | arg.getCall() = c and arg.isExplicitVarargsArray())
} or
TInstanceParameterNode(Callable c) {
- (exists(c.getBody()) or c instanceof SummarizedCallable) and
+ (exists(c.getBody()) or c = any(SummarizedCallable sc).asCallable()) and
not c.isStatic()
} or
TImplicitInstanceAccess(InstanceAccessExt ia) { not ia.isExplicit(_) } or
@@ -44,7 +44,8 @@ newtype TNode =
} or
TSummaryInternalNode(SummarizedCallable c, FlowSummaryImpl::Private::SummaryNodeState state) {
FlowSummaryImpl::Private::summaryNodeRange(c, state)
- }
+ } or
+ TFieldValueNode(Field f)
private predicate explicitInstanceArgument(Call call, Expr instarg) {
call instanceof MethodAccess and
@@ -94,19 +95,12 @@ module Public {
result = this.(MallocNode).getClassInstanceExpr().getType()
or
result = this.(ImplicitPostUpdateNode).getPreUpdateNode().getType()
+ or
+ result = this.(FieldValueNode).getField().getType()
}
/** Gets the callable in which this node occurs. */
- Callable getEnclosingCallable() {
- result = this.asExpr().getEnclosingCallable() or
- result = this.asParameter().getCallable() or
- result = this.(ImplicitVarargsArray).getCall().getEnclosingCallable() or
- result = this.(InstanceParameterNode).getCallable() or
- result = this.(ImplicitInstanceAccess).getInstanceAccess().getEnclosingCallable() or
- result = this.(MallocNode).getClassInstanceExpr().getEnclosingCallable() or
- result = this.(ImplicitPostUpdateNode).getPreUpdateNode().getEnclosingCallable() or
- this = TSummaryInternalNode(result, _)
- }
+ Callable getEnclosingCallable() { result = nodeGetEnclosingCallable(this).asCallable() }
private Type getImprovedTypeBound() {
exprTypeFlow(this.asExpr(), result, _) or
@@ -117,9 +111,9 @@ module Public {
* Gets an upper bound on the type of this node.
*/
Type getTypeBound() {
- result = getImprovedTypeBound()
+ result = this.getImprovedTypeBound()
or
- result = getType() and not exists(getImprovedTypeBound())
+ result = this.getType() and not exists(this.getImprovedTypeBound())
}
/**
@@ -132,7 +126,7 @@ module Public {
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
- getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
}
@@ -257,6 +251,18 @@ module Public {
abstract Node getPreUpdateNode();
}
+ /**
+ * A node representing the value of a field.
+ */
+ class FieldValueNode extends Node, TFieldValueNode {
+ /** Gets the field corresponding to this node. */
+ Field getField() { this = TFieldValueNode(result) }
+
+ override string toString() { result = getField().toString() }
+
+ override Location getLocation() { result = getField().getLocation() }
+ }
+
/**
* Gets the node that occurs as the qualifier of `fa`.
*/
@@ -288,9 +294,9 @@ private class NewExpr extends PostUpdateNode, TExprNode {
* A `PostUpdateNode` that is not a `ClassInstanceExpr`.
*/
abstract private class ImplicitPostUpdateNode extends PostUpdateNode {
- override Location getLocation() { result = getPreUpdateNode().getLocation() }
+ override Location getLocation() { result = this.getPreUpdateNode().getLocation() }
- override string toString() { result = getPreUpdateNode().toString() + " [post update]" }
+ override string toString() { result = this.getPreUpdateNode().toString() + " [post update]" }
}
private class ExplicitExprPostUpdate extends ImplicitPostUpdateNode, TExplicitExprPostUpdate {
@@ -304,6 +310,24 @@ private class ImplicitExprPostUpdate extends ImplicitPostUpdateNode, TImplicitEx
}
module Private {
+ /** Gets the callable in which this node occurs. */
+ DataFlowCallable nodeGetEnclosingCallable(Node n) {
+ result.asCallable() = n.asExpr().getEnclosingCallable() or
+ result.asCallable() = n.asParameter().getCallable() or
+ result.asCallable() = n.(ImplicitVarargsArray).getCall().getEnclosingCallable() or
+ result.asCallable() = n.(InstanceParameterNode).getCallable() or
+ result.asCallable() = n.(ImplicitInstanceAccess).getInstanceAccess().getEnclosingCallable() or
+ result.asCallable() = n.(MallocNode).getClassInstanceExpr().getEnclosingCallable() or
+ result = nodeGetEnclosingCallable(n.(ImplicitPostUpdateNode).getPreUpdateNode()) or
+ n = TSummaryInternalNode(result, _) or
+ result.asFieldScope() = n.(FieldValueNode).getField()
+ }
+
+ /** Holds if `p` is a `ParameterNode` of `c` with position `pos`. */
+ predicate isParameterNode(ParameterNode p, DataFlowCallable c, int pos) {
+ p.isParameterOf(c.asCallable(), pos)
+ }
+
/**
* A data flow node that occurs as the argument of a call and is passed as-is
* to the callable. Arguments that are wrapped in an implicit varargs array
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll
index d35610ecf4a..4473adccb58 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll
@@ -5,6 +5,7 @@ private import DataFlowDispatch
private import semmle.code.java.controlflow.Guards
private import semmle.code.java.dataflow.SSA
private import ContainerFlow
+private import semmle.code.java.dataflow.FlowSteps
private import semmle.code.java.dataflow.FlowSummary
private import FlowSummaryImpl as FlowSummaryImpl
import DataFlowNodes::Private
@@ -32,12 +33,18 @@ OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) {
/**
* Holds if data can flow from `node1` to `node2` through a static field.
*/
-private predicate staticFieldStep(ExprNode node1, ExprNode node2) {
+private predicate staticFieldStep(Node node1, Node node2) {
+ exists(Field f |
+ f.isStatic() and
+ f.getAnAssignedValue() = node1.asExpr() and
+ node2.(FieldValueNode).getField() = f
+ )
+ or
exists(Field f, FieldRead fr |
f.isStatic() and
- f.getAnAssignedValue() = node1.getExpr() and
+ node1.(FieldValueNode).getField() = f and
fr.getField() = f and
- fr = node2.getExpr() and
+ fr = node2.asExpr() and
hasNonlocalValue(fr)
)
}
@@ -67,9 +74,14 @@ private predicate variableCaptureStep(Node node1, ExprNode node2) {
* variable capture.
*/
predicate jumpStep(Node node1, Node node2) {
- staticFieldStep(node1, node2) or
- variableCaptureStep(node1, node2) or
+ staticFieldStep(node1, node2)
+ or
+ variableCaptureStep(node1, node2)
+ or
variableCaptureStep(node1.(PostUpdateNode).getPreUpdateNode(), node2)
+ or
+ any(AdditionalValueStep a).step(node1, node2) and
+ node1.getEnclosingCallable() != node2.getEnclosingCallable()
}
/**
@@ -139,11 +151,7 @@ predicate readStep(Node node1, Content f, Node node2) {
*/
predicate clearsContent(Node n, Content c) {
c instanceof FieldContent and
- (
- n = any(PostUpdateNode pun | storeStep(_, c, pun)).getPreUpdateNode()
- or
- FlowSummaryImpl::Private::Steps::summaryStoresIntoArg(c, n)
- )
+ n = any(PostUpdateNode pun | storeStep(_, c, pun)).getPreUpdateNode()
or
FlowSummaryImpl::Private::Steps::summaryClearsContent(n, c)
}
@@ -209,7 +217,30 @@ class CastNode extends ExprNode {
CastNode() { this.getExpr() instanceof CastExpr }
}
-class DataFlowCallable = Callable;
+private newtype TDataFlowCallable =
+ TCallable(Callable c) or
+ TFieldScope(Field f)
+
+class DataFlowCallable extends TDataFlowCallable {
+ Callable asCallable() { this = TCallable(result) }
+
+ Field asFieldScope() { this = TFieldScope(result) }
+
+ RefType getDeclaringType() {
+ result = asCallable().getDeclaringType() or
+ result = asFieldScope().getDeclaringType()
+ }
+
+ string toString() {
+ result = asCallable().toString() or
+ result = "Field scope: " + asFieldScope().toString()
+ }
+
+ Location getLocation() {
+ result = asCallable().getLocation() or
+ result = asFieldScope().getLocation()
+ }
+}
class DataFlowExpr = Expr;
@@ -255,7 +286,9 @@ class SrcCall extends DataFlowCall, TCall {
SrcCall() { this = TCall(call) }
- override DataFlowCallable getEnclosingCallable() { result = call.getEnclosingCallable() }
+ override DataFlowCallable getEnclosingCallable() {
+ result.asCallable() = call.getEnclosingCallable()
+ }
override string toString() { result = call.toString() }
@@ -341,7 +374,11 @@ predicate isImmutableOrUnobservable(Node n) {
}
/** Holds if `n` should be hidden from path explanations. */
-predicate nodeIsHidden(Node n) { n instanceof SummaryNode }
+predicate nodeIsHidden(Node n) {
+ n instanceof SummaryNode
+ or
+ n.(ParameterNode).isParameterOf(any(SummarizedCallable c).asCallable(), _)
+}
class LambdaCallKind = Method; // the "apply" method in the functional interface
@@ -349,10 +386,10 @@ class LambdaCallKind = Method; // the "apply" method in the functional interface
predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) {
exists(ClassInstanceExpr func, Interface t, FunctionalInterface interface |
creation.asExpr() = func and
- func.getAnonymousClass().getAMethod() = c and
+ func.getAnonymousClass().getAMethod() = c.asCallable() and
func.getConstructedType().extendsOrImplements+(t) and
t.getSourceDeclaration() = interface and
- c.(Method).overridesOrInstantiates+(pragma[only_bind_into](kind)) and
+ c.asCallable().(Method).overridesOrInstantiates+(pragma[only_bind_into](kind)) and
pragma[only_bind_into](kind) = interface.getRunMethod().getSourceDeclaration()
)
}
@@ -369,3 +406,14 @@ predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
/** Extra data-flow steps needed for lambda flow analysis. */
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() }
+
+/**
+ * Holds if flow is allowed to pass from parameter `p` and back to itself as a
+ * side-effect, resulting in a summary from `p` to itself.
+ *
+ * One example would be to allow flow like `p.foo = p.bar;`, which is disallowed
+ * by default as a heuristic.
+ */
+predicate allowParameterReturnInSelf(ParameterNode p) {
+ FlowSummaryImpl::Private::summaryAllowParameterReturnInSelf(p)
+}
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll
index 462430b2b5a..1ae6749877d 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll
@@ -101,6 +101,8 @@ predicate hasNonlocalValue(FieldRead fr) {
predicate localFlowStep(Node node1, Node node2) {
simpleLocalFlowStep(node1, node2)
or
+ adjacentUseUse(node1.asExpr(), node2.asExpr())
+ or
// Simple flow through library code is included in the exposed local
// step relation, even though flow is technically inter-procedural
FlowSummaryImpl::Private::Steps::summaryThroughStep(node1, node2, true)
@@ -131,7 +133,8 @@ predicate simpleLocalFlowStep(Node node1, Node node2) {
adjacentUseUse(node1.asExpr(), node2.asExpr()) and
not exists(FieldRead fr |
hasNonlocalValue(fr) and fr.getField().isStatic() and fr = node1.asExpr()
- )
+ ) and
+ not FlowSummaryImpl::Private::Steps::summaryClearsContentArg(node1, _)
or
ThisFlow::adjacentThisRefs(node1, node2)
or
@@ -155,6 +158,10 @@ predicate simpleLocalFlowStep(Node node1, Node node2) {
)
or
FlowSummaryImpl::Private::Steps::summaryLocalStep(node1, node2, true)
+ or
+ any(AdditionalValueStep a).step(node1, node2) and
+ pragma[only_bind_out](node1.getEnclosingCallable()) =
+ pragma[only_bind_out](node2.getEnclosingCallable())
}
private newtype TContent =
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll
index 83076558ec4..5955285bd6f 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll
@@ -261,7 +261,10 @@ module Private {
private newtype TSummaryNodeState =
TSummaryNodeInputState(SummaryComponentStack s) { inputState(_, s) } or
- TSummaryNodeOutputState(SummaryComponentStack s) { outputState(_, s) }
+ TSummaryNodeOutputState(SummaryComponentStack s) { outputState(_, s) } or
+ TSummaryNodeClearsContentState(int i, boolean post) {
+ any(SummarizedCallable sc).clearsContent(i, _) and post in [false, true]
+ }
/**
* A state used to break up (complex) flow summaries into atomic flow steps.
@@ -308,6 +311,12 @@ module Private {
this = TSummaryNodeOutputState(s) and
result = "to write: " + s
)
+ or
+ exists(int i, boolean post, string postStr |
+ this = TSummaryNodeClearsContentState(i, post) and
+ (if post = true then postStr = " (post)" else postStr = "") and
+ result = "clear: " + i + postStr
+ )
}
}
@@ -329,6 +338,11 @@ module Private {
not parameterReadState(c, state, _)
or
state.isOutputState(c, _)
+ or
+ exists(int i |
+ c.clearsContent(i, _) and
+ state = TSummaryNodeClearsContentState(i, _)
+ )
}
pragma[noinline]
@@ -364,6 +378,8 @@ module Private {
parameterReadState(c, _, i)
or
isParameterPostUpdate(_, c, i)
+ or
+ c.clearsContent(i, _)
}
private predicate callbackOutput(
@@ -436,6 +452,12 @@ module Private {
)
)
)
+ or
+ exists(SummarizedCallable c, int i, ParamNode p |
+ n = summaryNode(c, TSummaryNodeClearsContentState(i, false)) and
+ p.isParameterOf(c, i) and
+ result = getNodeType(p)
+ )
}
/** Holds if summary node `out` contains output of kind `rk` from call `c`. */
@@ -461,6 +483,9 @@ module Private {
exists(SummarizedCallable c, int i |
isParameterPostUpdate(post, c, i) and
pre.(ParamNode).isParameterOf(c, i)
+ or
+ pre = summaryNode(c, TSummaryNodeClearsContentState(i, false)) and
+ post = summaryNode(c, TSummaryNodeClearsContentState(i, true))
)
or
exists(SummarizedCallable callable, SummaryComponentStack s |
@@ -478,6 +503,17 @@ module Private {
)
}
+ /**
+ * Holds if flow is allowed to pass from parameter `p`, to a return
+ * node, and back out to `p`.
+ */
+ predicate summaryAllowParameterReturnInSelf(ParamNode p) {
+ exists(SummarizedCallable c, int i |
+ c.clearsContent(i, _) and
+ p.isParameterOf(c, i)
+ )
+ }
+
/** Provides a compilation of flow summaries to atomic data-flow steps. */
module Steps {
/**
@@ -504,11 +540,21 @@ module Private {
// for `StringBuilder.append(x)` with a specified value flow from qualifier to
// return value and taint flow from argument 0 to the qualifier, then this
// allows us to infer taint flow from argument 0 to the return value.
- succ instanceof ParamNode and summaryPostUpdateNode(pred, succ) and preservesValue = true
+ succ instanceof ParamNode and
+ summaryPostUpdateNode(pred, succ) and
+ preservesValue = true
or
// Similarly we would like to chain together summaries where values get passed
// into callbacks along the way.
- pred instanceof ArgNode and summaryPostUpdateNode(succ, pred) and preservesValue = true
+ pred instanceof ArgNode and
+ summaryPostUpdateNode(succ, pred) and
+ preservesValue = true
+ or
+ exists(SummarizedCallable c, int i |
+ pred.(ParamNode).isParameterOf(c, i) and
+ succ = summaryNode(c, TSummaryNodeClearsContentState(i, _)) and
+ preservesValue = true
+ )
}
/**
@@ -536,10 +582,39 @@ module Private {
}
/**
- * Holds if values stored inside content `c` are cleared when passed as
- * input of type `input` in `call`.
+ * Holds if values stored inside content `c` are cleared at `n`. `n` is a
+ * synthesized summary node, so in order for values to be cleared at calls
+ * to the relevant method, it is important that flow does not pass over
+ * the argument, either via use-use flow or def-use flow.
+ *
+ * Example:
+ *
+ * ```
+ * a.b = taint;
+ * a.clearB(); // assume we have a flow summary for `clearB` that clears `b` on the qualifier
+ * sink(a.b);
+ * ```
+ *
+ * In the above, flow should not pass from `a` on the first line (or the second
+ * line) to `a` on the third line. Instead, there will be synthesized flow from
+ * `a` on line 2 to the post-update node for `a` on that line (via an intermediate
+ * node where field `b` is cleared).
*/
- predicate summaryClearsContent(ArgNode arg, Content c) {
+ predicate summaryClearsContent(Node n, Content c) {
+ exists(SummarizedCallable sc, int i |
+ n = summaryNode(sc, TSummaryNodeClearsContentState(i, true)) and
+ sc.clearsContent(i, c)
+ )
+ }
+
+ /**
+ * Holds if values stored inside content `c` are cleared inside a
+ * callable to which `arg` is an argument.
+ *
+ * In such cases, it is important to prevent use-use flow out of
+ * `arg` (see comment for `summaryClearsContent`).
+ */
+ predicate summaryClearsContentArg(ArgNode arg, Content c) {
exists(DataFlowCall call, int i |
viableCallable(call).(SummarizedCallable).clearsContent(i, c) and
arg.argumentOf(call, i)
@@ -599,25 +674,6 @@ module Private {
ret.getKind() = rk
)
}
-
- /**
- * Holds if data is written into content `c` of argument `arg` using a flow summary.
- *
- * Depending on the type of `c`, this predicate may be relevant to include in the
- * definition of `clearsContent()`.
- */
- predicate summaryStoresIntoArg(Content c, Node arg) {
- exists(ParamUpdateReturnKind rk, ReturnNodeExt ret, PostUpdateNode out |
- exists(DataFlowCall call, SummarizedCallable callable |
- getNodeEnclosingCallable(ret) = callable and
- viableCallable(call) = callable and
- summaryStoreStep(_, c, ret) and
- ret.getKind() = pragma[only_bind_into](rk) and
- out = rk.getAnOutNode(call) and
- arg = out.getPreUpdateNode()
- )
- )
- }
}
/**
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll
index e4c3091f05e..a3d5d64a766 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll
@@ -30,7 +30,7 @@ DataFlowType getContentType(Content c) { result = c.getType() }
/** Gets the return type of kind `rk` for callable `c`. */
DataFlowType getReturnType(SummarizedCallable c, ReturnKind rk) {
- result = getErasedRepr(c.getReturnType()) and
+ result = getErasedRepr(c.asCallable().getReturnType()) and
exists(rk)
}
@@ -62,7 +62,7 @@ predicate summaryElement(DataFlowCallable c, string input, string output, string
string namespace, string type, boolean subtypes, string name, string signature, string ext
|
summaryModel(namespace, type, subtypes, name, signature, ext, input, output, kind) and
- c = interpretElement(namespace, type, subtypes, name, signature, ext)
+ c.asCallable() = interpretElement(namespace, type, subtypes, name, signature, ext)
)
}
@@ -119,7 +119,7 @@ class InterpretNode extends TInterpretNode {
DataFlowCall asCall() { result.asCall() = this.asElement() }
/** Gets the callable that this node corresponds to, if any. */
- DataFlowCallable asCallable() { result = this.asElement() }
+ DataFlowCallable asCallable() { result.asCallable() = this.asElement() }
/** Gets the target of this call, if any. */
Callable getCallTarget() { result = this.asCall().asCall().getCallee().getSourceDeclaration() }
diff --git a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll
index e92ad541a51..bab6025b4a6 100644
--- a/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll
+++ b/repo-tests/codeql/java/ql/lib/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll
@@ -285,11 +285,11 @@ private predicate taintPreservingQualifierToMethod(Method m) {
private class StringReplaceMethod extends TaintPreservingCallable {
StringReplaceMethod() {
- getDeclaringType() instanceof TypeString and
+ this.getDeclaringType() instanceof TypeString and
(
- hasName("replace") or
- hasName("replaceAll") or
- hasName("replaceFirst")
+ this.hasName("replace") or
+ this.hasName("replaceAll") or
+ this.hasName("replaceFirst")
)
}
@@ -300,8 +300,8 @@ private predicate unsafeEscape(MethodAccess ma) {
// Removing `",
+ "", "", "", "",
+ "", "",
+ "", "", "",
+ "", "", "",
+ "", ""
+ ]
+ }
+}
+
+/**
+ * Holds if `regexp` matches some HTML tags, but misses some HTML tags that it should match.
+ *
+ * When adding a new case to this predicate, make sure the test string used in `matches(..)` calls are present in `HTMLMatchingRegExp::test` / `HTMLMatchingRegExp::testWithGroups`.
+ */
+predicate isBadRegexpFilter(HTMLMatchingRegExp regexp, string msg) {
+ // CVE-2021-33829 - matching both "" and "", but in different capture groups
+ regexp.matches("") and
+ regexp.matches("") and
+ exists(int a, int b | a != b |
+ regexp.fillsCaptureGroup("", a) and
+ // might be ambigously parsed (matching both capture groups), and that is ok here.
+ regexp.fillsCaptureGroup("", b) and
+ not regexp.fillsCaptureGroup("", a) and
+ msg =
+ "Comments ending with --> are matched differently from comments ending with --!>. The first is matched with capture group "
+ + a + " and comments ending with --!> are matched with capture group " +
+ strictconcat(int i | regexp.fillsCaptureGroup("", i) | i.toString(), ", ") +
+ "."
+ )
+ or
+ // CVE-2020-17480 - matching "" and other tags, but not "".
+ exists(int group, int other |
+ group != other and
+ regexp.fillsCaptureGroup("", group) and
+ regexp.fillsCaptureGroup("", other) and
+ not regexp.matches("") and
+ not regexp.fillsCaptureGroup("", any(int i | i != group)) and
+ not regexp.fillsCaptureGroup("", group) and
+ not regexp.fillsCaptureGroup("", group) and
+ not regexp.fillsCaptureGroup("") and
+ regexp.matches("") and
+ not regexp.matches("") and
+ (
+ not regexp.matches("") and
+ msg = "This regular expression matches , but not "
+ or
+ not regexp.matches("") and
+ msg = "This regular expression matches , but not "
+ )
+ or
+ regexp.matches("") and
+ regexp.matches("") and
+ not regexp.matches("") and
+ not regexp.matches("") and
+ msg = "This regular expression does not match script tags where the attribute uses single-quotes."
+ or
+ regexp.matches("") and
+ regexp.matches("") and
+ not regexp.matches("") and
+ not regexp.matches("") and
+ msg = "This regular expression does not match script tags where the attribute uses double-quotes."
+ or
+ regexp.matches("") and
+ regexp.matches("") and
+ not regexp.matches("") and
+ not regexp.matches("") and
+ not regexp.matches("") and
+ msg = "This regular expression does not match script tags where tabs are used between attributes."
+ or
+ regexp.matches("") and
+ not RegExpFlags::isIgnoreCase(regexp) and
+ not regexp.matches("") and
+ not regexp.matches("") and
+ (
+ not regexp.matches("") and
+ msg = "This regular expression does not match upper case ") and
+ regexp.matches("") and
+ msg = "This regular expression does not match mixed case ") and
+ not regexp.matches("") and
+ not regexp.matches("") and
+ (
+ not regexp.matches("") and
+ msg = "This regular expression does not match script end tags like ."
+ or
+ not regexp.matches("") and
+ msg = "This regular expression does not match script end tags like ."
+ or
+ not regexp.matches("") and
+ msg = "This regular expression does not match script end tags like ."
+ )
+}
diff --git a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/CryptoAlgorithms.qll b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/CryptoAlgorithms.qll
index a5bfd6696be..7bc05ac7eb4 100644
--- a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/CryptoAlgorithms.qll
+++ b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/CryptoAlgorithms.qll
@@ -15,75 +15,38 @@
*/
private module AlgorithmNames {
predicate isStrongHashingAlgorithm(string name) {
- name = "DSA" or
- name = "ED25519" or
- name = "ES256" or
- name = "ECDSA256" or
- name = "ES384" or
- name = "ECDSA384" or
- name = "ES512" or
- name = "ECDSA512" or
- name = "SHA2" or
- name = "SHA224" or
- name = "SHA256" or
- name = "SHA384" or
- name = "SHA512" or
- name = "SHA3" or
- name = "SHA3224" or
- name = "SHA3256" or
- name = "SHA3384" or
- name = "SHA3512"
+ name =
+ [
+ "DSA", "ED25519", "ES256", "ECDSA256", "ES384", "ECDSA384", "ES512", "ECDSA512", "SHA2",
+ "SHA224", "SHA256", "SHA384", "SHA512", "SHA3", "SHA3224", "SHA3256", "SHA3384", "SHA3512"
+ ]
}
predicate isWeakHashingAlgorithm(string name) {
- name = "HAVEL128" or
- name = "MD2" or
- name = "MD4" or
- name = "MD5" or
- name = "PANAMA" or
- name = "RIPEMD" or
- name = "RIPEMD128" or
- name = "RIPEMD256" or
- name = "RIPEMD160" or
- name = "RIPEMD320" or
- name = "SHA0" or
- name = "SHA1"
+ name =
+ [
+ "HAVEL128", "MD2", "MD4", "MD5", "PANAMA", "RIPEMD", "RIPEMD128", "RIPEMD256", "RIPEMD160",
+ "RIPEMD320", "SHA0", "SHA1"
+ ]
}
predicate isStrongEncryptionAlgorithm(string name) {
- name = "AES" or
- name = "AES128" or
- name = "AES192" or
- name = "AES256" or
- name = "AES512" or
- name = "RSA" or
- name = "RABBIT" or
- name = "BLOWFISH"
+ name = ["AES", "AES128", "AES192", "AES256", "AES512", "RSA", "RABBIT", "BLOWFISH"]
}
predicate isWeakEncryptionAlgorithm(string name) {
- name = "DES" or
- name = "3DES" or
- name = "TRIPLEDES" or
- name = "TDEA" or
- name = "TRIPLEDEA" or
- name = "ARC2" or
- name = "RC2" or
- name = "ARC4" or
- name = "RC4" or
- name = "ARCFOUR" or
- name = "ARC5" or
- name = "RC5"
+ name =
+ [
+ "DES", "3DES", "TRIPLEDES", "TDEA", "TRIPLEDEA", "ARC2", "RC2", "ARC4", "RC4", "ARCFOUR",
+ "ARC5", "RC5"
+ ]
}
predicate isStrongPasswordHashingAlgorithm(string name) {
- name = "ARGON2" or
- name = "PBKDF2" or
- name = "BCRYPT" or
- name = "SCRYPT"
+ name = ["ARGON2", "PBKDF2", "BCRYPT", "SCRYPT"]
}
- predicate isWeakPasswordHashingAlgorithm(string name) { none() }
+ predicate isWeakPasswordHashingAlgorithm(string name) { name = "EVPKDF" }
}
private import AlgorithmNames
@@ -122,11 +85,13 @@ abstract class CryptographicAlgorithm extends TCryptographicAlgorithm {
/**
* Holds if the name of this algorithm matches `name` modulo case,
- * white space, dashes, and underscores.
+ * white space, dashes, underscores, and anything after a dash in the name
+ * (to ignore modes of operation, such as CBC or ECB).
*/
bindingset[name]
predicate matchesName(string name) {
- name.toUpperCase().regexpReplaceAll("[-_ ]", "") = getName()
+ [name.toUpperCase(), name.toUpperCase().regexpCapture("^(\\w+)(?:-.*)?$", 1)]
+ .regexpReplaceAll("[-_ ]", "") = getName()
}
/**
diff --git a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/SensitiveActions.qll b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/SensitiveActions.qll
index eb692c3b440..54803fcf857 100644
--- a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/SensitiveActions.qll
+++ b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/SensitiveActions.qll
@@ -14,11 +14,14 @@ import semmle.javascript.security.internal.SensitiveDataHeuristics
private import HeuristicNames
/** An expression that might contain sensitive data. */
+cached
abstract class SensitiveExpr extends Expr {
/** Gets a human-readable description of this expression for use in alert messages. */
+ cached
abstract string describe();
/** Gets a classification of the kind of sensitive data this expression might contain. */
+ cached
abstract SensitiveDataClassification getClassification();
}
diff --git a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/TaintedUrlSuffix.qll b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/TaintedUrlSuffix.qll
index a8151180f9b..c83a3bf85af 100644
--- a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/TaintedUrlSuffix.qll
+++ b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/TaintedUrlSuffix.qll
@@ -29,20 +29,11 @@ module TaintedUrlSuffix {
/** Holds for `pred -> succ` is a step of form `x -> x.p` */
private predicate isSafeLocationProp(DataFlow::PropRead read) {
// Ignore properties that refer to the scheme, domain, port, auth, or path.
- exists(string name | name = read.getPropertyName() |
- name = "protocol" or
- name = "scheme" or
- name = "host" or
- name = "hostname" or
- name = "domain" or
- name = "origin" or
- name = "port" or
- name = "path" or
- name = "pathname" or
- name = "username" or
- name = "password" or
- name = "auth"
- )
+ read.getPropertyName() =
+ [
+ "protocol", "scheme", "host", "hostname", "domain", "origin", "port", "path", "pathname",
+ "username", "password", "auth"
+ ]
}
/**
diff --git a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/UselessUseOfCat.qll b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/UselessUseOfCat.qll
index 604a8182e96..a9a1c3ace73 100644
--- a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/UselessUseOfCat.qll
+++ b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/UselessUseOfCat.qll
@@ -303,14 +303,11 @@ module PrettyPrintCatCall {
bindingset[str]
private string createSimplifiedStringConcat(string str) {
// Remove an initial ""+ (e.g. in `""+file`)
- if str.prefix(5) = "\"\" + "
+ if str.matches("\"\" + %")
then result = str.suffix(5)
else
// prettify `${newpath}` to just newpath
- if
- str.prefix(3) = "`${" and
- str.suffix(str.length() - 2) = "}`" and
- not str.suffix(3).matches("%{%")
+ if str.matches("`${%") and str.matches("%}`") and not str.suffix(3).matches("%{%")
then result = str.prefix(str.length() - 2).suffix(3)
else result = str
}
@@ -320,7 +317,7 @@ module PrettyPrintCatCall {
*/
string createFileThatIsReadFromCommandList(CommandCall call) {
exists(DataFlow::ArrayCreationNode array, DataFlow::Node element |
- array = call.getArgumentList().(DataFlow::ArrayCreationNode) and
+ array = call.getArgumentList() and
array.getSize() = 1 and
element = array.getElement(0)
|
diff --git a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll
index 65d3b44dafa..f426dd050c8 100644
--- a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll
+++ b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll
@@ -88,12 +88,7 @@ module ClientSideUrlRedirect {
class LocationSink extends Sink, DataFlow::ValueNode {
LocationSink() {
// A call to a `window.navigate` or `window.open`
- exists(string name |
- name = "navigate" or
- name = "open" or
- name = "openDialog" or
- name = "showModalDialog"
- |
+ exists(string name | name = ["navigate", "open", "openDialog", "showModalDialog"] |
this = DataFlow::globalVarRef(name).getACall().getArgument(0)
)
or
@@ -102,7 +97,7 @@ module ClientSideUrlRedirect {
locationCall = DOM::locationRef().getAMethodCall(name) and
this = locationCall.getArgument(0)
|
- name = "replace" or name = "assign"
+ name = ["replace", "assign"]
)
or
// An assignment to `location`
@@ -113,7 +108,7 @@ module ClientSideUrlRedirect {
pw = DOM::locationRef().getAPropertyWrite(propName) and
this = pw.getRhs()
|
- propName = "href" or propName = "protocol" or propName = "hostname"
+ propName = ["href", "protocol", "hostname"]
)
or
// A redirection using the AngularJS `$location` service
@@ -153,9 +148,8 @@ module ClientSideUrlRedirect {
*/
class SrcAttributeUrlSink extends ScriptUrlSink, DataFlow::ValueNode {
SrcAttributeUrlSink() {
- exists(DOM::AttributeDefinition attr, string eltName |
- attr.getElement().getName() = eltName and
- (eltName = "script" or eltName = "iframe") and
+ exists(DOM::AttributeDefinition attr |
+ attr.getElement().getName() = ["script", "iframe"] and
attr.getName() = "src" and
this = attr.getValueNode()
)
diff --git a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/DOM.qll b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/DOM.qll
index 204f04e9c52..5528aa257d1 100644
--- a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/DOM.qll
+++ b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/DOM.qll
@@ -88,14 +88,7 @@ class DomMethodCallExpr extends MethodCallExpr {
name = "setAttributeNS" and argPos = 2
) and
// restrict to potentially dangerous attributes
- exists(string attr |
- attr = "action" or
- attr = "formaction" or
- attr = "href" or
- attr = "src" or
- attr = "xlink:href" or
- attr = "data"
- |
+ exists(string attr | attr = ["action", "formaction", "href", "src", "xlink:href", "data"] |
getArgument(argPos - 1).getStringValue().toLowerCase() = attr
)
)
diff --git a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/LoopBoundInjectionCustomizations.qll b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/LoopBoundInjectionCustomizations.qll
index 8e3857d049b..9a44bf2d41f 100644
--- a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/LoopBoundInjectionCustomizations.qll
+++ b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/LoopBoundInjectionCustomizations.qll
@@ -115,66 +115,18 @@ module LoopBoundInjection {
* Holds if `name` is a method from lodash vulnerable to a DoS attack if called with a tainted object.
*/
predicate loopableLodashMethod(string name) {
- name = "chunk" or
- name = "compact" or
- name = "difference" or
- name = "differenceBy" or
- name = "differenceWith" or
- name = "drop" or
- name = "dropRight" or
- name = "dropRightWhile" or
- name = "dropWhile" or
- name = "fill" or
- name = "findIndex" or
- name = "findLastIndex" or
- name = "flatten" or
- name = "flattenDeep" or
- name = "flattenDepth" or
- name = "initial" or
- name = "intersection" or
- name = "intersectionBy" or
- name = "intersectionWith" or
- name = "join" or
- name = "remove" or
- name = "reverse" or
- name = "slice" or
- name = "sortedUniq" or
- name = "sortedUniqBy" or
- name = "tail" or
- name = "union" or
- name = "unionBy" or
- name = "unionWith" or
- name = "uniqBy" or
- name = "unzip" or
- name = "unzipWith" or
- name = "without" or
- name = "zip" or
- name = "zipObject" or
- name = "zipObjectDeep" or
- name = "zipWith" or
- name = "countBy" or
- name = "each" or
- name = "forEach" or
- name = "eachRight" or
- name = "forEachRight" or
- name = "filter" or
- name = "find" or
- name = "findLast" or
- name = "flatMap" or
- name = "flatMapDeep" or
- name = "flatMapDepth" or
- name = "forEach" or
- name = "forEachRight" or
- name = "groupBy" or
- name = "invokeMap" or
- name = "keyBy" or
- name = "map" or
- name = "orderBy" or
- name = "partition" or
- name = "reduce" or
- name = "reduceRight" or
- name = "reject" or
- name = "sortBy"
+ name =
+ [
+ "chunk", "compact", "difference", "differenceBy", "differenceWith", "drop", "dropRight",
+ "dropRightWhile", "dropWhile", "fill", "findIndex", "findLastIndex", "flatten",
+ "flattenDeep", "flattenDepth", "initial", "intersection", "intersectionBy",
+ "intersectionWith", "join", "remove", "reverse", "slice", "sortedUniq", "sortedUniqBy",
+ "tail", "union", "unionBy", "unionWith", "uniqBy", "unzip", "unzipWith", "without", "zip",
+ "zipObject", "zipObjectDeep", "zipWith", "countBy", "each", "forEach", "eachRight",
+ "forEachRight", "filter", "find", "findLast", "flatMap", "flatMapDeep", "flatMapDepth",
+ "forEach", "forEachRight", "groupBy", "invokeMap", "keyBy", "map", "orderBy", "partition",
+ "reduce", "reduceRight", "reject", "sortBy"
+ ]
}
/**
diff --git a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/PrototypePollutingAssignmentCustomizations.qll b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/PrototypePollutingAssignmentCustomizations.qll
index df48d455eb8..f7868c290aa 100644
--- a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/PrototypePollutingAssignmentCustomizations.qll
+++ b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/PrototypePollutingAssignmentCustomizations.qll
@@ -13,7 +13,12 @@ module PrototypePollutingAssignment {
/**
* A data flow source for untrusted data from which the special `__proto__` property name may be arise.
*/
- abstract class Source extends DataFlow::Node { }
+ abstract class Source extends DataFlow::Node {
+ /**
+ * Gets a string that describes the type of source.
+ */
+ abstract string describe();
+ }
/**
* A data flow sink for prototype-polluting assignments or untrusted property names.
@@ -44,6 +49,8 @@ module PrototypePollutingAssignment {
this = any(DataFlow::PropWrite write).getBase()
or
this = any(ExtendCall c).getDestinationOperand()
+ or
+ this = any(DeleteExpr del).getOperand().flow().(DataFlow::PropRef).getBase()
}
override DataFlow::FlowLabel getAFlowLabel() { result instanceof ObjectPrototype }
@@ -52,5 +59,18 @@ module PrototypePollutingAssignment {
/** A remote flow source or location.{hash,search} as a taint source. */
private class DefaultSource extends Source {
DefaultSource() { this instanceof RemoteFlowSource }
+
+ override string describe() { result = "user controlled input" }
+ }
+
+ import semmle.javascript.PackageExports as Exports
+
+ /**
+ * A parameter of an exported function, seen as a source prototype-polluting assignment.
+ */
+ class ExternalInputSource extends Source, DataFlow::SourceNode {
+ ExternalInputSource() { this = Exports::getALibraryInputParameter() }
+
+ override string describe() { result = "library input" }
}
}
diff --git a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/PrototypePollutingAssignmentQuery.qll b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/PrototypePollutingAssignmentQuery.qll
index 2c55bc6a8eb..495bb51bf81 100644
--- a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/PrototypePollutingAssignmentQuery.qll
+++ b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/PrototypePollutingAssignmentQuery.qll
@@ -10,7 +10,8 @@
private import javascript
private import semmle.javascript.DynamicPropertyAccess
private import semmle.javascript.dataflow.InferredTypes
-private import PrototypePollutingAssignmentCustomizations::PrototypePollutingAssignment
+import PrototypePollutingAssignmentCustomizations::PrototypePollutingAssignment
+private import semmle.javascript.filters.ClassifyFiles as ClassifyFiles
// Materialize flow labels
private class ConcreteObjectPrototype extends ObjectPrototype {
@@ -31,7 +32,27 @@ class Configuration extends TaintTracking::Configuration {
node instanceof Sanitizer
or
// Concatenating with a string will in practice prevent the string `__proto__` from arising.
- node instanceof StringOps::ConcatenationRoot
+ exists(StringOps::ConcatenationRoot root | node = root |
+ // Exclude the string coercion `"" + node` from this filter.
+ not node.(StringOps::ConcatenationNode).isCoercion()
+ )
+ or
+ node instanceof DataFlow::ThisNode
+ or
+ // Stop at .replace() calls that likely prevent __proto__ from arising
+ exists(StringReplaceCall replace |
+ node = replace and
+ replace.getAReplacedString() = ["_", "p", "r", "o", "t"] and
+ // Replacing with "_" is likely to be exploitable
+ not replace.getRawReplacement().getStringValue() = "_" and
+ (
+ replace.isGlobal()
+ or
+ // Non-global replace with a non-empty string can also prevent __proto__ by
+ // inserting a chunk of text that doesn't fit anywhere in __proto__
+ not replace.getRawReplacement().getStringValue() = ""
+ )
+ )
}
override predicate isAdditionalFlowStep(
@@ -62,6 +83,29 @@ class Configuration extends TaintTracking::Configuration {
inlbl.isTaint() and
outlbl instanceof ObjectPrototype
)
+ or
+ DataFlow::localFieldStep(pred, succ) and inlbl = outlbl
+ }
+
+ override predicate hasFlowPath(DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink) {
+ super.hasFlowPath(source, sink) and
+ // require that there is a path without unmatched return steps
+ DataFlow::hasPathWithoutUnmatchedReturn(source, sink) and
+ // filter away paths that start with library inputs and end with a write to a fixed property.
+ not exists(ExternalInputSource src, Sink snk, DataFlow::PropWrite write |
+ source.getNode() = src and sink.getNode() = snk
+ |
+ snk = write.getBase() and
+ (
+ // fixed property name
+ exists(write.getPropertyName())
+ or
+ // non-string property name (likely number)
+ exists(Expr prop | prop = write.getPropertyNameExpr() |
+ not prop.analyze().getAType() = TTString()
+ )
+ )
+ )
}
override predicate isLabeledBarrier(DataFlow::Node node, DataFlow::FlowLabel lbl) {
@@ -78,7 +122,8 @@ class Configuration extends TaintTracking::Configuration {
guard instanceof InstanceofCheck or
guard instanceof IsArrayCheck or
guard instanceof TypeofCheck or
- guard instanceof EqualityCheck
+ guard instanceof EqualityCheck or
+ guard instanceof IncludesCheck
}
}
@@ -91,7 +136,8 @@ private DataFlow::SourceNode prototypeLessObject(DataFlow::TypeTracker t) {
t.start() and
// We assume the argument to Object.create is not Object.prototype, since most
// users wouldn't bother to call Object.create in that case.
- result = DataFlow::globalVarRef("Object").getAMemberCall("create")
+ result = DataFlow::globalVarRef("Object").getAMemberCall("create") and
+ not result.getFile() instanceof TestFile
or
// Allow use of SharedFlowSteps to track a bit further
exists(DataFlow::Node mid |
@@ -102,6 +148,14 @@ private DataFlow::SourceNode prototypeLessObject(DataFlow::TypeTracker t) {
exists(DataFlow::TypeTracker t2 | result = prototypeLessObject(t2).track(t2, t))
}
+/**
+ * A test file.
+ * Objects created in such files are ignored in the `prototypeLessObject` predicate.
+ */
+private class TestFile extends File {
+ TestFile() { ClassifyFiles::isTestFile(this) }
+}
+
/** Holds if `Object.prototype` has a member named `prop`. */
private predicate isPropertyPresentOnObjectPrototype(string prop) {
exists(ExternalInstanceMemberDecl decl |
@@ -198,3 +252,15 @@ private class EqualityCheck extends TaintTracking::SanitizerGuardNode, DataFlow:
outcome = astNode.getPolarity().booleanNot()
}
}
+
+/**
+ * Sanitizer guard of the form `x.includes("__proto__")`.
+ */
+private class IncludesCheck extends TaintTracking::LabeledSanitizerGuardNode, InclusionTest {
+ IncludesCheck() { this.getContainedNode().mayHaveStringValue("__proto__") }
+
+ override predicate sanitizes(boolean outcome, Expr e) {
+ e = getContainerNode().asExpr() and
+ outcome = getPolarity().booleanNot()
+ }
+}
diff --git a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/SqlInjectionCustomizations.qll b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/SqlInjectionCustomizations.qll
index 4c40ac4d2a5..e3bf02362a4 100644
--- a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/SqlInjectionCustomizations.qll
+++ b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/SqlInjectionCustomizations.qll
@@ -41,4 +41,35 @@ module SqlInjection {
class GraphqlInjectionSink extends Sink {
GraphqlInjectionSink() { this instanceof GraphQL::GraphQLString }
}
+
+ /**
+ * An LDAPjs sink.
+ */
+ class LdapJSSink extends Sink {
+ LdapJSSink() {
+ // A distinguished name (DN) used in a call to the client API.
+ this = any(LdapJS::ClientCall call).getArgument(0)
+ or
+ // A search options object, which contains a filter and a baseDN.
+ this = any(LdapJS::SearchOptions opt).getARhs()
+ or
+ // A call to "parseDN", which parses a DN from a string.
+ this = LdapJS::ldapjs().getMember("parseDN").getACall().getArgument(0)
+ }
+ }
+
+ import semmle.javascript.security.IncompleteBlacklistSanitizer as IncompleteBlacklistSanitizer
+
+ /**
+ * A chain of replace calls that replaces all unsafe chars for ldap injection.
+ * For simplicity it's used as a sanitizer for all of `js/sql-injection`.
+ */
+ class LdapStringSanitizer extends Sanitizer,
+ IncompleteBlacklistSanitizer::StringReplaceCallSequence {
+ LdapStringSanitizer() {
+ forall(string char | char = ["*", "(", ")", "\\", "/"] |
+ this.getAMember().getAReplacedString() = char
+ )
+ }
+ }
}
diff --git a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/SqlInjectionQuery.qll b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/SqlInjectionQuery.qll
index 0adbf564fb0..35d838cbff8 100644
--- a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/SqlInjectionQuery.qll
+++ b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/SqlInjectionQuery.qll
@@ -24,4 +24,11 @@ class Configuration extends TaintTracking::Configuration {
super.isSanitizer(node) or
node instanceof Sanitizer
}
+
+ override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
+ exists(LdapJS::TaintPreservingLdapFilterStep filter |
+ pred = filter.getInput() and
+ succ = filter.getOutput()
+ )
+ }
}
diff --git a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll
index 13ac5d358c9..3faee47f867 100644
--- a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll
+++ b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll
@@ -445,17 +445,25 @@ module TaintedPath {
/**
* An expression of form `x.includes("..")` or similar.
*/
- class ContainsDotDotSanitizer extends BarrierGuardNode {
- StringOps::Includes contains;
-
- ContainsDotDotSanitizer() {
- this = contains and
- isDotDotSlashPrefix(contains.getSubstring())
- }
+ class ContainsDotDotSanitizer extends BarrierGuardNode instanceof StringOps::Includes {
+ ContainsDotDotSanitizer() { isDotDotSlashPrefix(super.getSubstring()) }
override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel label) {
- e = contains.getBaseString().asExpr() and
- outcome = contains.getPolarity().booleanNot() and
+ e = super.getBaseString().asExpr() and
+ outcome = super.getPolarity().booleanNot() and
+ label.(Label::PosixPath).canContainDotDotSlash() // can still be bypassed by normalized absolute path
+ }
+ }
+
+ /**
+ * An expression of form `x.matches(/\.\./)` or similar.
+ */
+ class ContainsDotDotRegExpSanitizer extends BarrierGuardNode instanceof StringOps::RegExpTest {
+ ContainsDotDotRegExpSanitizer() { super.getRegExp().getAMatchedString() = [".", "..", "../"] }
+
+ override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel label) {
+ e = super.getStringOperand().asExpr() and
+ outcome = super.getPolarity().booleanNot() and
label.(Label::PosixPath).canContainDotDotSlash() // can still be bypassed by normalized absolute path
}
}
@@ -751,13 +759,11 @@ module TaintedPath {
exists(mcn.getAnArgument().asExpr().getIntValue())
or
exists(string argumentlessMethodName |
- argumentlessMethodName = "toLocaleLowerCase" or
- argumentlessMethodName = "toLocaleUpperCase" or
- argumentlessMethodName = "toLowerCase" or
- argumentlessMethodName = "toUpperCase" or
- argumentlessMethodName = "trim" or
- argumentlessMethodName = "trimLeft" or
- argumentlessMethodName = "trimRight"
+ argumentlessMethodName =
+ [
+ "toLocaleLowerCase", "toLocaleUpperCase", "toLowerCase", "toUpperCase", "trim",
+ "trimLeft", "trimRight"
+ ]
|
name = argumentlessMethodName
)
diff --git a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/TypeConfusionThroughParameterTamperingCustomizations.qll b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/TypeConfusionThroughParameterTamperingCustomizations.qll
index ab237733acc..3364b704dc1 100644
--- a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/TypeConfusionThroughParameterTamperingCustomizations.qll
+++ b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/TypeConfusionThroughParameterTamperingCustomizations.qll
@@ -39,11 +39,7 @@ module TypeConfusionThroughParameterTampering {
private class StringArrayAmbiguousMethodCall extends Sink {
StringArrayAmbiguousMethodCall() {
exists(string name, DataFlow::MethodCallNode mc |
- name = "concat" or
- name = "includes" or
- name = "indexOf" or
- name = "lastIndexOf" or
- name = "slice"
+ name = ["concat", "includes", "indexOf", "lastIndexOf", "slice"]
|
mc.calls(this, name) and
// ignore patterns that are innocent in practice
diff --git a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeShellCommandConstructionCustomizations.qll b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeShellCommandConstructionCustomizations.qll
index 20b69168474..79ec58e6b82 100644
--- a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeShellCommandConstructionCustomizations.qll
+++ b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeShellCommandConstructionCustomizations.qll
@@ -50,14 +50,14 @@ module UnsafeShellCommandConstruction {
/**
* A parameter of an exported function, seen as a source for shell command constructed from library input.
*/
- class ExternalInputSource extends Source, DataFlow::ParameterNode {
+ class ExternalInputSource extends Source, DataFlow::SourceNode {
ExternalInputSource() {
this = Exports::getALibraryInputParameter() and
not (
// looks to be on purpose.
- this.getName() = ["cmd", "command"]
+ this.(DataFlow::ParameterNode).getName() = ["cmd", "command"]
or
- this.getName().regexpMatch(".*(Cmd|Command)$") // ends with "Cmd" or "Command"
+ this.(DataFlow::ParameterNode).getName().regexpMatch(".*(Cmd|Command)$") // ends with "Cmd" or "Command"
)
}
}
diff --git a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/Xss.qll b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/Xss.qll
index 03628f299a2..68026a02de4 100644
--- a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/Xss.qll
+++ b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/dataflow/Xss.qll
@@ -526,9 +526,7 @@ module ReflectedXss {
* ```
*/
predicate isLocalHeaderDefinition(HTTP::HeaderDefinition header) {
- exists(ReachableBasicBlock headerBlock |
- headerBlock = header.getBasicBlock().(ReachableBasicBlock)
- |
+ exists(ReachableBasicBlock headerBlock | headerBlock = header.getBasicBlock() |
1 =
strictcount(HTTP::ResponseSendArgument sender |
sender.getRouteHandler() = header.getRouteHandler() and
diff --git a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/internal/SensitiveDataHeuristics.qll b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/internal/SensitiveDataHeuristics.qll
index 589c37120b9..2adce57db11 100644
--- a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/internal/SensitiveDataHeuristics.qll
+++ b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/internal/SensitiveDataHeuristics.qll
@@ -58,7 +58,7 @@ module HeuristicNames {
*/
string maybeAccountInfo() {
result = "(?is).*acc(ou)?nt.*" or
- result = "(?is).*(puid|username|userid).*" or
+ result = "(?is).*(puid|username|userid|session(id|key)).*" or
result = "(?s).*([uU]|^|_|[a-z](?=U))([uU][iI][dD]).*"
}
diff --git a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/performance/PolynomialReDoSCustomizations.qll b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/performance/PolynomialReDoSCustomizations.qll
index 9574e6e6376..ca01ef566ab 100644
--- a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/performance/PolynomialReDoSCustomizations.qll
+++ b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/performance/PolynomialReDoSCustomizations.qll
@@ -47,10 +47,8 @@ module PolynomialReDoS {
* A remote input to a server, seen as a source for polynomial
* regular expression denial-of-service vulnerabilities.
*/
- class RequestInputAccessAsSource extends Source {
- RequestInputAccessAsSource() { this instanceof HTTP::RequestInputAccess }
-
- override string getKind() { result = this.(HTTP::RequestInputAccess).getKind() }
+ class RequestInputAccessAsSource extends Source instanceof HTTP::RequestInputAccess {
+ override string getKind() { result = HTTP::RequestInputAccess.super.getKind() }
}
/**
@@ -67,14 +65,7 @@ module PolynomialReDoS {
|
this = mcn.getArgument(0) and
regexp = mcn.getReceiver() and
- (
- name = "match" or
- name = "split" or
- name = "matchAll" or
- name = "replace" or
- name = "replaceAll" or
- name = "search"
- )
+ name = ["match", "split", "matchAll", "replace", "replaceAll", "search"]
or
this = mcn.getReceiver() and
regexp = mcn.getArgument(0) and
@@ -133,7 +124,7 @@ module PolynomialReDoS {
/**
* A parameter of an exported function, seen as a source for polynomial-redos.
*/
- class ExternalInputSource extends Source, DataFlow::ParameterNode {
+ class ExternalInputSource extends Source, DataFlow::SourceNode {
ExternalInputSource() { this = Exports::getALibraryInputParameter() }
override string getKind() { result = "library" }
diff --git a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/performance/ReDoSUtil.qll b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/performance/ReDoSUtil.qll
index 12b7559615d..8b443992c0b 100644
--- a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/performance/ReDoSUtil.qll
+++ b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/performance/ReDoSUtil.qll
@@ -477,7 +477,7 @@ private module CharacterClasses {
result = ["0", "9"]
or
cc.getValue() = "s" and
- result = [" "]
+ result = " "
or
cc.getValue() = "w" and
result = ["a", "Z", "_", "0", "9"]
@@ -490,7 +490,7 @@ private module CharacterClasses {
result = "9"
or
cc.getValue() = "s" and
- result = [" "]
+ result = " "
or
cc.getValue() = "w" and
result = "a"
@@ -542,7 +542,7 @@ private State before(RegExpTerm t) { result = Match(t, 0) }
/**
* Gets a state the NFA may be in after matching `t`.
*/
-private State after(RegExpTerm t) {
+State after(RegExpTerm t) {
exists(RegExpAlt alt | t = alt.getAChild() | result = after(alt))
or
exists(RegExpSequence seq, int i | t = seq.getChild(i) |
@@ -671,7 +671,7 @@ RegExpRoot getRoot(RegExpTerm term) {
/**
* A state in the NFA.
*/
-private newtype TState =
+newtype TState =
/**
* A state representing that the NFA is about to match a term.
* `i` is used to index into multi-char literals.
@@ -801,29 +801,26 @@ InputSymbol getAnInputSymbolMatching(string char) {
result = Any()
}
+/**
+ * Holds if `state` is a start state.
+ */
+predicate isStartState(State state) {
+ state = mkMatch(any(RegExpRoot r))
+ or
+ exists(RegExpCaret car | state = after(car))
+}
+
/**
* Predicates for constructing a prefix string that leads to a given state.
*/
private module PrefixConstruction {
- /**
- * Holds if `state` starts the string matched by the regular expression.
- */
- private predicate isStartState(State state) {
- state instanceof StateInPumpableRegexp and
- (
- state = Match(any(RegExpRoot r), _)
- or
- exists(RegExpCaret car | state = after(car))
- )
- }
-
/**
* Holds if `state` is the textually last start state for the regular expression.
*/
private predicate lastStartState(State state) {
exists(RegExpRoot root |
state =
- max(State s, Location l |
+ max(StateInPumpableRegexp s, Location l |
isStartState(s) and getRoot(s.getRepr()) = root and l = s.getRepr().getLocation()
|
s
diff --git a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/performance/RegExpTreeView.qll b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/performance/RegExpTreeView.qll
index f896e44f5e9..3174c993720 100644
--- a/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/performance/RegExpTreeView.qll
+++ b/repo-tests/codeql/javascript/ql/lib/semmle/javascript/security/performance/RegExpTreeView.qll
@@ -20,12 +20,7 @@ module RegExpFlags {
/**
* Holds if `root` has the `i` flag for case-insensitive matching.
*/
- predicate isIgnoreCase(RegExpTerm root) {
- root.isRootTerm() and
- exists(DataFlow::RegExpCreationNode node | node.getRoot() = root |
- RegExp::isIgnoreCase(node.getFlags())
- )
- }
+ predicate isIgnoreCase(RegExpTerm root) { RegExp::isIgnoreCase(getFlags(root)) }
/**
* Gets the flags for `root`, or the empty string if `root` has no flags.
@@ -38,15 +33,14 @@ module RegExpFlags {
not exists(node.getFlags()) and
result = ""
)
+ or
+ exists(RegExpPatternSource source | source.getRegExpTerm() = root |
+ result = source.getARegExpObject().(DataFlow::RegExpCreationNode).getFlags()
+ )
}
/**
* Holds if `root` has the `s` flag for multi-line matching.
*/
- predicate isDotAll(RegExpTerm root) {
- root.isRootTerm() and
- exists(DataFlow::RegExpCreationNode node | node.getRoot() = root |
- RegExp::isDotAll(node.getFlags())
- )
- }
+ predicate isDotAll(RegExpTerm root) { RegExp::isDotAll(getFlags(root)) }
}
diff --git a/repo-tests/codeql/javascript/ql/lib/semmlecode.javascript.dbscheme b/repo-tests/codeql/javascript/ql/lib/semmlecode.javascript.dbscheme
index e54b35a8a12..8320e9d13aa 100644
--- a/repo-tests/codeql/javascript/ql/lib/semmlecode.javascript.dbscheme
+++ b/repo-tests/codeql/javascript/ql/lib/semmlecode.javascript.dbscheme
@@ -855,7 +855,7 @@ regexpterm (unique int id: @regexpterm,
int idx: int ref,
varchar(900) tostring: string ref);
-@regexpparent = @regexpterm | @regexp_literal | @string_literal;
+@regexpparent = @regexpterm | @regexp_literal | @string_literal | @add_expr;
case @regexpterm.kind of
0 = @regexp_alt
diff --git a/repo-tests/codeql/javascript/ql/src/AngularJS/IncompatibleService.ql b/repo-tests/codeql/javascript/ql/src/AngularJS/IncompatibleService.ql
index 7e6daafd72b..4fb290cd56d 100644
--- a/repo-tests/codeql/javascript/ql/src/AngularJS/IncompatibleService.ql
+++ b/repo-tests/codeql/javascript/ql/src/AngularJS/IncompatibleService.ql
@@ -71,13 +71,7 @@ predicate isCompatibleRequestedService(InjectableFunctionServiceRequest request,
isRunMethod(request) or
isControllerFunction(request)
) and
- (
- kind = "value" or
- kind = "service" or
- kind = "factory" or
- kind = "constant" or
- kind = "provider-value"
- )
+ kind = ["value", "service", "factory", "constant", "provider-value"]
or
isControllerFunction(request) and
kind = "controller-only"
diff --git a/repo-tests/codeql/javascript/ql/src/Declarations/MixedStaticInstanceThisAccess.ql b/repo-tests/codeql/javascript/ql/src/Declarations/MixedStaticInstanceThisAccess.ql
index c41b514737a..a8f771706f1 100644
--- a/repo-tests/codeql/javascript/ql/src/Declarations/MixedStaticInstanceThisAccess.ql
+++ b/repo-tests/codeql/javascript/ql/src/Declarations/MixedStaticInstanceThisAccess.ql
@@ -12,7 +12,7 @@
import javascript
/** Holds if `base` declares or inherits method `m` with the given `name`. */
-predicate hasMethod(ClassDefinition base, string name, MethodDefinition m) {
+predicate hasMethod(ClassDefinition base, string name, MethodDeclaration m) {
m = base.getMethod(name) or
hasMethod(base.getSuperClassDefinition(), name, m)
}
@@ -22,7 +22,7 @@ predicate hasMethod(ClassDefinition base, string name, MethodDefinition m) {
* where `fromMethod` and `toMethod` are of kind `fromKind` and `toKind`, respectively.
*/
predicate isLocalMethodAccess(
- PropAccess access, MethodDefinition fromMethod, string fromKind, MethodDefinition toMethod,
+ PropAccess access, MethodDefinition fromMethod, string fromKind, MethodDeclaration toMethod,
string toKind
) {
hasMethod(fromMethod.getDeclaringClass(), access.getPropertyName(), toMethod) and
@@ -32,7 +32,7 @@ predicate isLocalMethodAccess(
toKind = getKind(toMethod)
}
-string getKind(MethodDefinition m) {
+string getKind(MethodDeclaration m) {
if m.isStatic() then result = "static" else result = "instance"
}
diff --git a/repo-tests/codeql/javascript/ql/src/React/UnsupportedStateUpdateInLifecycleMethod.ql b/repo-tests/codeql/javascript/ql/src/React/UnsupportedStateUpdateInLifecycleMethod.ql
index d1798ee51e8..bf47712e3e6 100644
--- a/repo-tests/codeql/javascript/ql/src/React/UnsupportedStateUpdateInLifecycleMethod.ql
+++ b/repo-tests/codeql/javascript/ql/src/React/UnsupportedStateUpdateInLifecycleMethod.ql
@@ -76,13 +76,11 @@ class StateUpdateVolatileMethod extends Function {
// - componentsWillMount
// - componentsDidMount
exists(ReactComponent c |
- methodName = "componentDidUnmount" or
- methodName = "componentDidUpdate" or
- methodName = "componentWillUpdate" or
- methodName = "getDefaultProps" or
- methodName = "getInitialState" or
- methodName = "render" or
- methodName = "shouldComponentUpdate"
+ methodName =
+ [
+ "componentDidUnmount", "componentDidUpdate", "componentWillUpdate", "getDefaultProps",
+ "getInitialState", "render", "shouldComponentUpdate"
+ ]
|
this = c.getInstanceMethod(methodName)
)
diff --git a/repo-tests/codeql/javascript/ql/src/Security/CWE-089/SqlInjection.ql b/repo-tests/codeql/javascript/ql/src/Security/CWE-089/SqlInjection.ql
index cd68319c429..671b5c8d9db 100644
--- a/repo-tests/codeql/javascript/ql/src/Security/CWE-089/SqlInjection.ql
+++ b/repo-tests/codeql/javascript/ql/src/Security/CWE-089/SqlInjection.ql
@@ -9,6 +9,8 @@
* @id js/sql-injection
* @tags security
* external/cwe/cwe-089
+ * external/cwe/cwe-090
+ * external/cwe/cwe-943
*/
import javascript
diff --git a/repo-tests/codeql/javascript/ql/src/Security/CWE-1004/ClientExposedCookie.ql b/repo-tests/codeql/javascript/ql/src/Security/CWE-1004/ClientExposedCookie.ql
new file mode 100644
index 00000000000..ad7b3f7f0b0
--- /dev/null
+++ b/repo-tests/codeql/javascript/ql/src/Security/CWE-1004/ClientExposedCookie.ql
@@ -0,0 +1,20 @@
+/**
+ * @name Sensitive server cookie exposed to the client
+ * @description Sensitive cookies set by a server can be read by the client if the `httpOnly` flag is not set.
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity 5.0
+ * @precision high
+ * @id js/client-exposed-cookie
+ * @tags security
+ * external/cwe/cwe-1004
+ */
+
+import javascript
+
+from CookieWrites::CookieWrite cookie
+where
+ cookie.isSensitive() and
+ cookie.isServerSide() and
+ not cookie.isHttpOnly()
+select cookie, "Sensitive server cookie is missing 'httpOnly' flag."
diff --git a/repo-tests/codeql/javascript/ql/src/Security/CWE-116/BadTagFilter.ql b/repo-tests/codeql/javascript/ql/src/Security/CWE-116/BadTagFilter.ql
new file mode 100644
index 00000000000..609690982bb
--- /dev/null
+++ b/repo-tests/codeql/javascript/ql/src/Security/CWE-116/BadTagFilter.ql
@@ -0,0 +1,19 @@
+/**
+ * @name Bad HTML filtering regexp
+ * @description Matching HTML tags using regular expressions is hard to do right, and can easily lead to security issues.
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity 7.8
+ * @precision high
+ * @id js/bad-tag-filter
+ * @tags correctness
+ * security
+ * external/cwe/cwe-116
+ * external/cwe/cwe-020
+ */
+
+import semmle.javascript.security.BadTagFilterQuery
+
+from HTMLMatchingRegExp regexp, string msg
+where msg = min(string m | isBadRegexpFilter(regexp, m) | m order by m.length(), m) // there might be multiple, we arbitrarily pick the shortest one
+select regexp, msg
diff --git a/repo-tests/codeql/javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql b/repo-tests/codeql/javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql
index 08bb355799a..4e1a66ab22c 100644
--- a/repo-tests/codeql/javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql
+++ b/repo-tests/codeql/javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql
@@ -79,14 +79,11 @@ predicate allBackslashesEscaped(DataFlow::Node nd) {
or
// flow through string methods
exists(DataFlow::MethodCallNode mc, string m |
- m = "replace" or
- m = "replaceAll" or
- m = "slice" or
- m = "substr" or
- m = "substring" or
- m = "toLowerCase" or
- m = "toUpperCase" or
- m = "trim"
+ m =
+ [
+ "replace", "replaceAll", "slice", "substr", "substring", "toLowerCase", "toUpperCase",
+ "trim"
+ ]
|
mc = nd and m = mc.getMethodName() and allBackslashesEscaped(mc.getReceiver())
)
diff --git a/repo-tests/codeql/javascript/ql/src/Security/CWE-209/StackTraceExposure.ql b/repo-tests/codeql/javascript/ql/src/Security/CWE-209/StackTraceExposure.ql
index 49d14daf6c9..a7c1d8ca778 100644
--- a/repo-tests/codeql/javascript/ql/src/Security/CWE-209/StackTraceExposure.ql
+++ b/repo-tests/codeql/javascript/ql/src/Security/CWE-209/StackTraceExposure.ql
@@ -10,6 +10,7 @@
* @id js/stack-trace-exposure
* @tags security
* external/cwe/cwe-209
+ * external/cwe/cwe-497
*/
import javascript
diff --git a/repo-tests/codeql/javascript/ql/src/Security/CWE-295/DisablingCertificateValidation.ql b/repo-tests/codeql/javascript/ql/src/Security/CWE-295/DisablingCertificateValidation.ql
index 0b24235801d..7547d4e31c1 100644
--- a/repo-tests/codeql/javascript/ql/src/Security/CWE-295/DisablingCertificateValidation.ql
+++ b/repo-tests/codeql/javascript/ql/src/Security/CWE-295/DisablingCertificateValidation.ql
@@ -8,6 +8,7 @@
* @id js/disabling-certificate-validation
* @tags security
* external/cwe/cwe-295
+ * external/cwe/cwe-297
*/
import javascript
diff --git a/repo-tests/codeql/javascript/ql/src/Security/CWE-312/CleartextLogging.ql b/repo-tests/codeql/javascript/ql/src/Security/CWE-312/CleartextLogging.ql
index 00916c4293d..7d45d2e8b23 100644
--- a/repo-tests/codeql/javascript/ql/src/Security/CWE-312/CleartextLogging.ql
+++ b/repo-tests/codeql/javascript/ql/src/Security/CWE-312/CleartextLogging.ql
@@ -11,6 +11,7 @@
* external/cwe/cwe-312
* external/cwe/cwe-315
* external/cwe/cwe-359
+ * external/cwe/cwe-532
*/
import javascript
diff --git a/repo-tests/codeql/javascript/ql/src/Security/CWE-326/InsufficientKeySize.ql b/repo-tests/codeql/javascript/ql/src/Security/CWE-326/InsufficientKeySize.ql
new file mode 100644
index 00000000000..4b95b594d6e
--- /dev/null
+++ b/repo-tests/codeql/javascript/ql/src/Security/CWE-326/InsufficientKeySize.ql
@@ -0,0 +1,36 @@
+/**
+ * @name Use of a weak cryptographic key
+ * @description Using a weak cryptographic key can allow an attacker to compromise security.
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity 7.5
+ * @precision high
+ * @id js/insufficient-key-size
+ * @tags security
+ * external/cwe/cwe-326
+ */
+
+import javascript
+
+from CryptographicKeyCreation key, int size, string msg, string algo
+where
+ size = key.getSize() and
+ (
+ algo = key.getAlgorithm() + " "
+ or
+ not exists(key.getAlgorithm()) and algo = ""
+ ) and
+ (
+ size < 128 and
+ key.isSymmetricKey() and
+ msg =
+ "Creation of an symmetric " + algo + "key uses " + size +
+ " bits, which is below 128 and considered breakable."
+ or
+ size < 2048 and
+ not key.isSymmetricKey() and
+ msg =
+ "Creation of an asymmetric " + algo + "key uses " + size +
+ " bits, which is below 2048 and considered breakable."
+ )
+select key, msg
diff --git a/repo-tests/codeql/javascript/ql/src/Security/CWE-346/CorsMisconfigurationForCredentials.ql b/repo-tests/codeql/javascript/ql/src/Security/CWE-346/CorsMisconfigurationForCredentials.ql
index 1b0acebea15..a5f6c8aeffb 100644
--- a/repo-tests/codeql/javascript/ql/src/Security/CWE-346/CorsMisconfigurationForCredentials.ql
+++ b/repo-tests/codeql/javascript/ql/src/Security/CWE-346/CorsMisconfigurationForCredentials.ql
@@ -9,6 +9,7 @@
* @tags security
* external/cwe/cwe-346
* external/cwe/cwe-639
+ * external/cwe/cwe-942
*/
import javascript
diff --git a/repo-tests/codeql/javascript/ql/src/Security/CWE-384/SessionFixation.ql b/repo-tests/codeql/javascript/ql/src/Security/CWE-384/SessionFixation.ql
new file mode 100644
index 00000000000..aec00f22d45
--- /dev/null
+++ b/repo-tests/codeql/javascript/ql/src/Security/CWE-384/SessionFixation.ql
@@ -0,0 +1,58 @@
+/**
+ * @name Failure to abandon session
+ * @description Reusing an existing session as a different user could allow
+ * an attacker to access someone else's account by using
+ * their session.
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity 5
+ * @precision medium
+ * @id js/session-fixation
+ * @tags security
+ * external/cwe/cwe-384
+ */
+
+import javascript
+
+/**
+ * Holds if `setup` uses express-session (or similar) to log in a user.
+ */
+pragma[inline]
+predicate isLoginSetup(Express::RouteSetup setup) {
+ // either some path that contains "login" with a write to `req.session`
+ setup.getPath().matches("%login%") and
+ exists(
+ setup
+ .getARouteHandler()
+ .(Express::RouteHandler)
+ .getARequestSource()
+ .ref()
+ .getAPropertyRead("session")
+ .getAPropertyWrite()
+ )
+ or
+ // or an authentication method is used (e.g. `passport.authenticate`)
+ setup.getARouteHandler().(DataFlow::CallNode).getCalleeName() = "authenticate"
+}
+
+/**
+ * Holds if `handler` regenerates its session using `req.session.regenerate`.
+ */
+pragma[inline]
+predicate regeneratesSession(Express::RouteSetup setup) {
+ exists(
+ setup
+ .getARouteHandler()
+ .(Express::RouteHandler)
+ .getARequestSource()
+ .ref()
+ .getAPropertyRead("session")
+ .getAPropertyRead("regenerate")
+ )
+}
+
+from Express::RouteSetup setup
+where
+ isLoginSetup(setup) and
+ not regeneratesSession(setup)
+select setup, "Route handler does not invalidate session following login"
diff --git a/repo-tests/codeql/javascript/ql/src/Security/CWE-598/SensitiveGetQuery.ql b/repo-tests/codeql/javascript/ql/src/Security/CWE-598/SensitiveGetQuery.ql
new file mode 100644
index 00000000000..f42b6b79ccf
--- /dev/null
+++ b/repo-tests/codeql/javascript/ql/src/Security/CWE-598/SensitiveGetQuery.ql
@@ -0,0 +1,27 @@
+/**
+ * @name Sensitive data read from GET request
+ * @description Placing sensitive data in a GET request increases the risk of
+ * the data being exposed to an attacker.
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity 6.5
+ * @precision high
+ * @id js/sensitive-get-query
+ * @tags security
+ * external/cwe/cwe-598
+ */
+
+import javascript
+
+from
+ Express::RouteSetup setup, Express::RouteHandler handler, Express::RequestInputAccess input,
+ SensitiveExpr sensitive
+where
+ setup.getRequestMethod() = "GET" and
+ handler = setup.getARouteHandler() and
+ input.getRouteHandler() = handler and
+ input.getKind() = "parameter" and
+ input.(DataFlow::SourceNode).flowsToExpr(sensitive) and
+ not sensitive.getClassification() = SensitiveDataClassification::id()
+select input, "$@ for GET requests uses query parameter as sensitive data.", handler,
+ "Route handler"
diff --git a/repo-tests/codeql/javascript/ql/src/Security/CWE-614/ClearTextCookie.ql b/repo-tests/codeql/javascript/ql/src/Security/CWE-614/ClearTextCookie.ql
new file mode 100644
index 00000000000..5c03aa45858
--- /dev/null
+++ b/repo-tests/codeql/javascript/ql/src/Security/CWE-614/ClearTextCookie.ql
@@ -0,0 +1,21 @@
+/**
+ * @name Clear text transmission of sensitive cookie
+ * @description Sending sensitive information in a cookie without requring SSL encryption
+ * can expose the cookie to an attacker.
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity 5.0
+ * @precision high
+ * @id js/clear-text-cookie
+ * @tags security
+ * external/cwe/cwe-614
+ * external/cwe/cwe-311
+ * external/cwe/cwe-312
+ * external/cwe/cwe-319
+ */
+
+import javascript
+
+from CookieWrites::CookieWrite cookie
+where cookie.isSensitive() and not cookie.isSecure()
+select cookie, "Sensitive cookie sent without enforcing SSL encryption"
diff --git a/repo-tests/codeql/javascript/ql/src/Security/CWE-730/ServerCrash.ql b/repo-tests/codeql/javascript/ql/src/Security/CWE-730/ServerCrash.ql
index 7c16287d48c..336cc2abf70 100644
--- a/repo-tests/codeql/javascript/ql/src/Security/CWE-730/ServerCrash.ql
+++ b/repo-tests/codeql/javascript/ql/src/Security/CWE-730/ServerCrash.ql
@@ -104,7 +104,7 @@ class AsyncSentinelCall extends DataFlow::CallNode {
exists(DataFlow::FunctionNode node | node.getAstNode() = asyncCallee |
// manual models
exists(string memberName |
- not "Sync" = memberName.suffix(memberName.length() - 4) and
+ not memberName.matches("%Sync") and
this = NodeJSLib::FS::moduleMember(memberName).getACall() and
node = this.getCallback([1 .. 2])
)
diff --git a/repo-tests/codeql/javascript/ql/src/Security/CWE-915/PrototypePollutingAssignment.ql b/repo-tests/codeql/javascript/ql/src/Security/CWE-915/PrototypePollutingAssignment.ql
index b68a75b5b3b..0557b575adb 100644
--- a/repo-tests/codeql/javascript/ql/src/Security/CWE-915/PrototypePollutingAssignment.ql
+++ b/repo-tests/codeql/javascript/ql/src/Security/CWE-915/PrototypePollutingAssignment.ql
@@ -24,4 +24,4 @@ from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink, source, sink,
"This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@.",
- source.getNode(), "here"
+ source.getNode(), source.getNode().(Source).describe()
diff --git a/repo-tests/codeql/javascript/ql/src/Statements/UseOfReturnlessFunction.ql b/repo-tests/codeql/javascript/ql/src/Statements/UseOfReturnlessFunction.ql
index 117e22eb867..08928946eb5 100644
--- a/repo-tests/codeql/javascript/ql/src/Statements/UseOfReturnlessFunction.ql
+++ b/repo-tests/codeql/javascript/ql/src/Statements/UseOfReturnlessFunction.ql
@@ -111,16 +111,11 @@ predicate callToVoidFunction(DataFlow::CallNode call, Function func) {
* and the callback is expected to return a value.
*/
predicate hasNonVoidCallbackMethod(string name) {
- name = "every" or
- name = "filter" or
- name = "find" or
- name = "findIndex" or
- name = "flatMap" or
- name = "map" or
- name = "reduce" or
- name = "reduceRight" or
- name = "some" or
- name = "sort"
+ name =
+ [
+ "every", "filter", "find", "findIndex", "flatMap", "map", "reduce", "reduceRight", "some",
+ "sort"
+ ]
}
DataFlow::SourceNode array(DataFlow::TypeTracker t) {
diff --git a/repo-tests/codeql/javascript/ql/src/experimental/Security/CWE-090/LdapInjection.ql b/repo-tests/codeql/javascript/ql/src/experimental/Security/CWE-090/LdapInjection.ql
deleted file mode 100644
index f19d1ac6125..00000000000
--- a/repo-tests/codeql/javascript/ql/src/experimental/Security/CWE-090/LdapInjection.ql
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * @name LDAP query built from user-controlled sources
- * @description Building an LDAP query from user-controlled sources is vulnerable to insertion of
- * malicious LDAP code by the user.
- * @kind path-problem
- * @problem.severity error
- * @precision high
- * @id javascript/ldap-injection
- * @tags security
- * external/cwe/cwe-090
- */
-
-import javascript
-import DataFlow::PathGraph
-import LdapInjection::LdapInjection
-
-from LdapInjectionConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
-where config.hasFlowPath(source, sink)
-select sink.getNode(), source, sink, "$@ might include code from $@.",
- sink.getNode().(Sink).getQueryCall(), "LDAP query call", source.getNode(), "user-provided value"
diff --git a/repo-tests/codeql/javascript/ql/src/experimental/Security/CWE-090/LdapInjection.qll b/repo-tests/codeql/javascript/ql/src/experimental/Security/CWE-090/LdapInjection.qll
deleted file mode 100644
index 406a350d2d3..00000000000
--- a/repo-tests/codeql/javascript/ql/src/experimental/Security/CWE-090/LdapInjection.qll
+++ /dev/null
@@ -1,25 +0,0 @@
-import javascript
-
-module LdapInjection {
- import LdapInjectionCustomizations::LdapInjection
-
- /**
- * A taint-tracking configuration for reasoning about LDAP injection vulnerabilities.
- */
- class LdapInjectionConfiguration extends TaintTracking::Configuration {
- LdapInjectionConfiguration() { this = "LdapInjection" }
-
- override predicate isSource(DataFlow::Node source) { source instanceof Source }
-
- override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
-
- override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
-
- override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
- exists(LdapjsParseFilter filter |
- pred = filter.getArgument(0) and
- succ = filter
- )
- }
- }
-}
diff --git a/repo-tests/codeql/javascript/ql/src/experimental/Security/CWE-090/LdapInjectionCustomizations.qll b/repo-tests/codeql/javascript/ql/src/experimental/Security/CWE-090/LdapInjectionCustomizations.qll
deleted file mode 100644
index cb0292a5c4d..00000000000
--- a/repo-tests/codeql/javascript/ql/src/experimental/Security/CWE-090/LdapInjectionCustomizations.qll
+++ /dev/null
@@ -1,73 +0,0 @@
-/**
- * Provides default sources, sinks and sanitizers for reasoning about
- * LDAP injection vulnerabilities, as well as extension points for
- * adding your own.
- */
-
-import javascript
-
-module LdapInjection {
- import Ldapjs::Ldapjs
-
- /**
- * A data flow source for LDAP injection vulnerabilities.
- */
- abstract class Source extends DataFlow::Node { }
-
- /**
- * A data flow sink for LDAP injection vulnerabilities.
- */
- abstract class Sink extends DataFlow::Node {
- /**
- * Gets the LDAP query call that the sink flows into.
- */
- abstract DataFlow::Node getQueryCall();
- }
-
- /**
- * A sanitizer for LDAP injection vulnerabilities.
- */
- abstract class Sanitizer extends DataFlow::Node { }
-
- /**
- * A source of remote user input, considered as a flow source for LDAP injection.
- */
- class RemoteSource extends Source {
- RemoteSource() { this instanceof RemoteFlowSource }
- }
-
- /**
- * An LDAP filter for an API call that executes an operation against the LDAP server.
- */
- class LdapjsSearchFilterAsSink extends Sink {
- LdapjsSearchFilterAsSink() { this instanceof LdapjsSearchFilter }
-
- override DataFlow::InvokeNode getQueryCall() {
- result = this.(LdapjsSearchFilter).getQueryCall()
- }
- }
-
- /**
- * An LDAP DN argument for an API call that executes an operation against the LDAP server.
- */
- class LdapjsDNArgumentAsSink extends Sink {
- LdapjsDNArgumentAsSink() { this instanceof LdapjsDNArgument }
-
- override DataFlow::InvokeNode getQueryCall() { result = this.(LdapjsDNArgument).getQueryCall() }
- }
-
- /**
- * A call to a function whose name suggests that it escapes LDAP search query parameter.
- */
- class FilterOrDNSanitizationCall extends Sanitizer, DataFlow::CallNode {
- FilterOrDNSanitizationCall() {
- exists(string sanitize, string input |
- sanitize = "(?:escape|saniti[sz]e|validate|filter)" and
- input = "[Ii]nput?"
- |
- this.getCalleeName()
- .regexpMatch("(?i)(" + sanitize + input + ")" + "|(" + input + sanitize + ")")
- )
- }
- }
-}
diff --git a/repo-tests/codeql/javascript/ql/src/experimental/Security/CWE-090/Ldapjs.qll b/repo-tests/codeql/javascript/ql/src/experimental/Security/CWE-090/Ldapjs.qll
deleted file mode 100644
index 5cefa9a517d..00000000000
--- a/repo-tests/codeql/javascript/ql/src/experimental/Security/CWE-090/Ldapjs.qll
+++ /dev/null
@@ -1,92 +0,0 @@
-/**
- * Provides classes for working with [ldapjs](https://github.com/ldapjs/node-ldapjs) (Client only)
- */
-
-import javascript
-
-module Ldapjs {
- /**
- * Gets a method name on an LDAPjs client that accepts a DN as the first argument.
- */
- private string getLdapjsClientDNMethodName() {
- result = ["add", "bind", "compare", "del", "modify", "modifyDN", "search"]
- }
-
- /**
- * Gets a data flow source node for an LDAP client.
- */
- abstract class LdapClient extends DataFlow::SourceNode { }
-
- /**
- * Gets a data flow source node for the ldapjs library.
- */
- private DataFlow::SourceNode ldapjs() { result = DataFlow::moduleImport("ldapjs") }
-
- /**
- * Gets a data flow source node for the ldapjs client.
- */
- class LdapjsClient extends LdapClient {
- LdapjsClient() { this = ldapjs().getAMemberCall("createClient") }
- }
-
- /**
- * Gets a data flow node for the client `search` options.
- */
- class LdapjsSearchOptions extends DataFlow::SourceNode {
- DataFlow::CallNode queryCall;
-
- LdapjsSearchOptions() {
- queryCall = any(LdapjsClient client).getAMemberCall("search") and
- this = queryCall.getArgument(1).getALocalSource()
- }
-
- /**
- * Gets the LDAP query call that these options are used in.
- */
- DataFlow::InvokeNode getQueryCall() { result = queryCall }
- }
-
- /**
- * A filter used in a `search` operation against the LDAP server.
- */
- class LdapjsSearchFilter extends DataFlow::Node {
- LdapjsSearchOptions options;
-
- LdapjsSearchFilter() { this = options.getAPropertyWrite("filter").getRhs() }
-
- /**
- * Gets the LDAP query call that this filter is used in.
- */
- DataFlow::InvokeNode getQueryCall() { result = options.getQueryCall() }
- }
-
- /**
- * A call to the ldapjs Client API methods.
- */
- class LdapjsClientAPICall extends DataFlow::CallNode {
- LdapjsClientAPICall() {
- this = any(LdapjsClient client).getAMemberCall(getLdapjsClientDNMethodName())
- }
- }
-
- /**
- * A distinguished name (DN) used in a Client API call against the LDAP server.
- */
- class LdapjsDNArgument extends DataFlow::Node {
- LdapjsClientAPICall queryCall;
-
- LdapjsDNArgument() { this = queryCall.getArgument(0) }
-
- /**
- * Gets the LDAP query call that this DN is used in.
- */
- DataFlow::InvokeNode getQueryCall() { result = queryCall }
- }
-
- /**
- * Ldapjs parseFilter method call.
- */
- class LdapjsParseFilter extends DataFlow::CallNode {
- LdapjsParseFilter() { this = ldapjs().getAMemberCall("parseFilter") }
- }
-}
diff --git a/repo-tests/codeql/javascript/ql/src/experimental/Security/CWE-1004/CookieWithoutHttpOnly.ql b/repo-tests/codeql/javascript/ql/src/experimental/Security/CWE-1004/CookieWithoutHttpOnly.ql
deleted file mode 100644
index bcce52ed8ec..00000000000
--- a/repo-tests/codeql/javascript/ql/src/experimental/Security/CWE-1004/CookieWithoutHttpOnly.ql
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * @name 'HttpOnly' attribute is not set to true
- * @description Omitting the 'HttpOnly' attribute for security sensitive cookie data allows
- * malicious JavaScript to steal it in case of XSS vulnerabilities. Always set
- * 'HttpOnly' to 'true' for authentication related cookies to make them
- * inaccessible from JavaScript.
- * @kind problem
- * @problem.severity warning
- * @precision high
- * @id js/cookie-httponly-not-set
- * @tags security
- * external/cwe/cwe-1004
- */
-
-import javascript
-import experimental.semmle.javascript.security.InsecureCookie::Cookie
-
-from Cookie cookie
-where cookie.isAuthNotHttpOnly()
-select cookie, "Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie."
diff --git a/repo-tests/codeql/javascript/ql/src/experimental/Security/CWE-614/InsecureCookie.ql b/repo-tests/codeql/javascript/ql/src/experimental/Security/CWE-614/InsecureCookie.ql
deleted file mode 100644
index 789a7ec53e2..00000000000
--- a/repo-tests/codeql/javascript/ql/src/experimental/Security/CWE-614/InsecureCookie.ql
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * @name Failure to set secure cookies
- * @description Insecure cookies may be sent in cleartext, which makes them vulnerable to
- * interception.
- * @kind problem
- * @problem.severity error
- * @precision high
- * @id js/insecure-cookie
- * @tags security
- * external/cwe/cwe-614
- */
-
-import javascript
-import experimental.semmle.javascript.security.InsecureCookie::Cookie
-
-from Cookie cookie
-where not cookie.isSecure()
-select cookie, "Cookie is added to response without the 'secure' flag being set to true"
diff --git a/repo-tests/codeql/javascript/ql/src/experimental/Security/CWE-918/SSRF.ql b/repo-tests/codeql/javascript/ql/src/experimental/Security/CWE-918/SSRF.ql
new file mode 100644
index 00000000000..f13f5fae14e
--- /dev/null
+++ b/repo-tests/codeql/javascript/ql/src/experimental/Security/CWE-918/SSRF.ql
@@ -0,0 +1,19 @@
+/**
+ * @id javascript/ssrf
+ * @kind path-problem
+ * @name Uncontrolled data used in network request
+ * @description Sending network requests with user-controlled data as part of the URL allows for request forgery attacks.
+ * @problem.severity error
+ * @precision medium
+ * @tags security
+ * external/cwe/cwe-918
+ */
+
+import javascript
+import SSRF
+import DataFlow::PathGraph
+
+from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node request
+where
+ cfg.hasFlowPath(source, sink) and request = sink.getNode().(RequestForgery::Sink).getARequest()
+select sink, source, sink, "The URL of this request depends on a user-provided value"
diff --git a/repo-tests/codeql/javascript/ql/src/experimental/Security/CWE-918/SSRF.qll b/repo-tests/codeql/javascript/ql/src/experimental/Security/CWE-918/SSRF.qll
new file mode 100644
index 00000000000..cba9b1f5154
--- /dev/null
+++ b/repo-tests/codeql/javascript/ql/src/experimental/Security/CWE-918/SSRF.qll
@@ -0,0 +1,154 @@
+import javascript
+import semmle.javascript.security.dataflow.RequestForgeryCustomizations
+import semmle.javascript.security.dataflow.UrlConcatenation
+
+class Configuration extends TaintTracking::Configuration {
+ Configuration() { this = "SSRF" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof RequestForgery::Source }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof RequestForgery::Sink }
+
+ override predicate isSanitizer(DataFlow::Node node) {
+ super.isSanitizer(node) or
+ node instanceof RequestForgery::Sanitizer
+ }
+
+ private predicate hasSanitizingSubstring(DataFlow::Node nd) {
+ nd.getStringValue().regexpMatch(".*[?#].*")
+ or
+ hasSanitizingSubstring(StringConcatenation::getAnOperand(nd))
+ or
+ hasSanitizingSubstring(nd.getAPredecessor())
+ }
+
+ private predicate strictSanitizingPrefixEdge(DataFlow::Node source, DataFlow::Node sink) {
+ exists(DataFlow::Node operator, int n |
+ StringConcatenation::taintStep(source, sink, operator, n) and
+ hasSanitizingSubstring(StringConcatenation::getOperand(operator, [0 .. n - 1]))
+ )
+ }
+
+ override predicate isSanitizerEdge(DataFlow::Node source, DataFlow::Node sink) {
+ strictSanitizingPrefixEdge(source, sink)
+ }
+
+ override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode nd) {
+ nd instanceof IntegerCheck or
+ nd instanceof ValidatorCheck or
+ nd instanceof TernaryOperatorSanitizerGuard
+ }
+}
+
+/**
+ * This sanitizers models the next example:
+ * let valid = req.params.id ? Number.isInteger(req.params.id) : false
+ * if (valid) { sink(req.params.id) }
+ *
+ * This sanitizer models this way of using ternary operators,
+ * when the sanitizer guard is used as any of the branches
+ * instead of being used as the condition.
+ *
+ * This sanitizer sanitize the corresponding if statement branch.
+ */
+class TernaryOperatorSanitizer extends RequestForgery::Sanitizer {
+ TernaryOperatorSanitizer() {
+ exists(
+ TaintTracking::SanitizerGuardNode guard, IfStmt ifStmt, DataFlow::Node taintedInput,
+ boolean outcome, Stmt r, DataFlow::Node falseNode
+ |
+ ifStmt.getCondition().flow().getAPredecessor+() = guard and
+ ifStmt.getCondition().flow().getAPredecessor+() = falseNode and
+ falseNode.asExpr().(BooleanLiteral).mayHaveBooleanValue(false) and
+ not ifStmt.getCondition() instanceof LogicalBinaryExpr and
+ guard.sanitizes(outcome, taintedInput.asExpr()) and
+ (
+ outcome = true and r = ifStmt.getThen() and not ifStmt.getCondition() instanceof LogNotExpr
+ or
+ outcome = false and r = ifStmt.getElse() and not ifStmt.getCondition() instanceof LogNotExpr
+ or
+ outcome = false and r = ifStmt.getThen() and ifStmt.getCondition() instanceof LogNotExpr
+ or
+ outcome = true and r = ifStmt.getElse() and ifStmt.getCondition() instanceof LogNotExpr
+ ) and
+ r.getFirstControlFlowNode()
+ .getBasicBlock()
+ .(ReachableBasicBlock)
+ .dominates(this.getBasicBlock())
+ )
+ }
+}
+
+/**
+ * This sanitizer guard is another way of modeling the example from above
+ * In this case:
+ * let valid = req.params.id ? Number.isInteger(req.params.id) : false
+ * if (!valid) { return }
+ * sink(req.params.id)
+ *
+ * The previous sanitizer is not enough,
+ * because we are sanitizing the entire if statement branch
+ * but we need to sanitize the use of this variable from now on.
+ *
+ * Thats why we model this sanitizer guard which says that
+ * the result of the ternary operator execution is a sanitizer guard.
+ */
+class TernaryOperatorSanitizerGuard extends TaintTracking::SanitizerGuardNode {
+ TaintTracking::SanitizerGuardNode originalGuard;
+
+ TernaryOperatorSanitizerGuard() {
+ this.getAPredecessor+().asExpr().(BooleanLiteral).mayHaveBooleanValue(false) and
+ this.getAPredecessor+() = originalGuard and
+ not this.asExpr() instanceof LogicalBinaryExpr
+ }
+
+ override predicate sanitizes(boolean outcome, Expr e) {
+ not this.asExpr() instanceof LogNotExpr and
+ originalGuard.sanitizes(outcome, e)
+ or
+ exists(boolean originalOutcome |
+ this.asExpr() instanceof LogNotExpr and
+ originalGuard.sanitizes(originalOutcome, e) and
+ (
+ originalOutcome = true and outcome = false
+ or
+ originalOutcome = false and outcome = true
+ )
+ )
+ }
+}
+
+/**
+ * Number.isInteger is a sanitizer guard because a number can't be used to exploit a SSRF.
+ */
+class IntegerCheck extends TaintTracking::SanitizerGuardNode, DataFlow::CallNode {
+ IntegerCheck() { this = DataFlow::globalVarRef("Number").getAMemberCall("isInteger") }
+
+ override predicate sanitizes(boolean outcome, Expr e) {
+ outcome = true and
+ e = getArgument(0).asExpr()
+ }
+}
+
+/**
+ * ValidatorCheck identifies if exists a call to validator's library methods.
+ * validator is a library which has a variety of input-validation functions. We are interesed in
+ * checking that source is a number (any type of number) or an alphanumeric value.
+ */
+class ValidatorCheck extends TaintTracking::SanitizerGuardNode, DataFlow::CallNode {
+ ValidatorCheck() {
+ exists(DataFlow::SourceNode mod, string method |
+ mod = DataFlow::moduleImport("validator") and
+ this = mod.getAChainedMethodCall(method) and
+ method in [
+ "isAlphanumeric", "isAlpha", "isDecimal", "isFloat", "isHexadecimal", "isHexColor",
+ "isInt", "isNumeric", "isOctal", "isUUID"
+ ]
+ )
+ }
+
+ override predicate sanitizes(boolean outcome, Expr e) {
+ outcome = true and
+ e = getArgument(0).asExpr()
+ }
+}
diff --git a/repo-tests/codeql/javascript/ql/src/experimental/semmle/javascript/security/InsecureCookie.qll b/repo-tests/codeql/javascript/ql/src/experimental/semmle/javascript/security/InsecureCookie.qll
deleted file mode 100644
index 0b3406c9dd9..00000000000
--- a/repo-tests/codeql/javascript/ql/src/experimental/semmle/javascript/security/InsecureCookie.qll
+++ /dev/null
@@ -1,323 +0,0 @@
-/**
- * Provides classes for reasoning about cookies added to response without the 'secure' or 'httponly' flag being set.
- * - A cookie without the 'secure' flag being set can be intercepted and read by a malicious user.
- * - A cookie without the 'httponly' flag being set can be read by maliciously injected JavaScript.
- */
-
-import javascript
-
-module Cookie {
- /**
- * `secure` property of the cookie options.
- */
- string secureFlag() { result = "secure" }
-
- /**
- * `httpOnly` property of the cookie options.
- */
- string httpOnlyFlag() { result = "httpOnly" }
-
- /**
- * Abstract class to represent different cases of insecure cookie settings.
- */
- abstract class Cookie extends DataFlow::Node {
- /**
- * Gets the name of the middleware/library used to set the cookie.
- */
- abstract string getKind();
-
- /**
- * Gets the options used to set this cookie, if any.
- */
- abstract DataFlow::Node getCookieOptionsArgument();
-
- /**
- * Holds if this cookie is secure.
- */
- abstract predicate isSecure();
-
- /**
- * Holds if this cookie is HttpOnly.
- */
- abstract predicate isHttpOnly();
-
- /**
- * Holds if the cookie is authentication sensitive and lacks HttpOnly.
- */
- abstract predicate isAuthNotHttpOnly();
- }
-
- /**
- * Holds if the expression is a variable with a sensitive name.
- */
- private predicate isAuthVariable(DataFlow::Node expr) {
- exists(string val |
- (
- val = expr.getStringValue() or
- val = expr.asExpr().(VarAccess).getName() or
- val = expr.(DataFlow::PropRead).getPropertyName()
- ) and
- regexpMatchAuth(val)
- )
- or
- isAuthVariable(expr.getAPredecessor())
- }
-
- /**
- * Holds if `val` looks related to authentication, without being an anti-forgery token.
- */
- bindingset[val]
- private predicate regexpMatchAuth(string val) {
- val.regexpMatch("(?i).*(session|login|token|user|auth|credential).*") and
- not val.regexpMatch("(?i).*(xsrf|csrf|forgery).*")
- }
-
- /**
- * A cookie set using the `express` module `cookie-session` (https://github.com/expressjs/cookie-session).
- */
- class InsecureCookieSession extends ExpressLibraries::CookieSession::MiddlewareInstance, Cookie {
- override string getKind() { result = "cookie-session" }
-
- override DataFlow::SourceNode getCookieOptionsArgument() { result.flowsTo(getArgument(0)) }
-
- private DataFlow::Node getCookieFlagValue(string flag) {
- result = this.getCookieOptionsArgument().getAPropertyWrite(flag).getRhs()
- }
-
- override predicate isSecure() {
- // The flag `secure` is set to `false` by default for HTTP, `true` by default for HTTPS (https://github.com/expressjs/cookie-session#cookie-options).
- // A cookie is secure if the `secure` flag is not explicitly set to `false`.
- not getCookieFlagValue(secureFlag()).mayHaveBooleanValue(false)
- }
-
- override predicate isAuthNotHttpOnly() {
- not isHttpOnly() // It is a session cookie, likely auth sensitive
- }
-
- override predicate isHttpOnly() {
- // The flag `httpOnly` is set to `true` by default (https://github.com/expressjs/cookie-session#cookie-options).
- // A cookie is httpOnly if the `httpOnly` flag is not explicitly set to `false`.
- not getCookieFlagValue(httpOnlyFlag()).mayHaveBooleanValue(false)
- }
- }
-
- /**
- * A cookie set using the `express` module `express-session` (https://github.com/expressjs/session).
- */
- class InsecureExpressSessionCookie extends ExpressLibraries::ExpressSession::MiddlewareInstance,
- Cookie {
- override string getKind() { result = "express-session" }
-
- override DataFlow::SourceNode getCookieOptionsArgument() { result = this.getOption("cookie") }
-
- private DataFlow::Node getCookieFlagValue(string flag) {
- result = this.getCookieOptionsArgument().getAPropertyWrite(flag).getRhs()
- }
-
- override predicate isSecure() {
- // The flag `secure` is not set by default (https://github.com/expressjs/session#Cookiesecure).
- // The default value for cookie options is { path: '/', httpOnly: true, secure: false, maxAge: null }.
- // A cookie is secure if there are the cookie options with the `secure` flag set to `true` or to `auto`.
- getCookieFlagValue(secureFlag()).mayHaveBooleanValue(true) or
- getCookieFlagValue(secureFlag()).mayHaveStringValue("auto")
- }
-
- override predicate isAuthNotHttpOnly() {
- not isHttpOnly() // It is a session cookie, likely auth sensitive
- }
-
- override predicate isHttpOnly() {
- // The flag `httpOnly` is set by default (https://github.com/expressjs/session#Cookiesecure).
- // The default value for cookie options is { path: '/', httpOnly: true, secure: false, maxAge: null }.
- // A cookie is httpOnly if the `httpOnly` flag is not explicitly set to `false`.
- not getCookieFlagValue(httpOnlyFlag()).mayHaveBooleanValue(false)
- }
- }
-
- /**
- * A cookie set using `response.cookie` from `express` module (https://expressjs.com/en/api.html#res.cookie).
- */
- class InsecureExpressCookieResponse extends Cookie, DataFlow::MethodCallNode {
- InsecureExpressCookieResponse() { this.calls(any(Express::ResponseExpr r).flow(), "cookie") }
-
- override string getKind() { result = "response.cookie" }
-
- override DataFlow::SourceNode getCookieOptionsArgument() {
- result = this.getLastArgument().getALocalSource()
- }
-
- private DataFlow::Node getCookieFlagValue(string flag) {
- result = this.getCookieOptionsArgument().getAPropertyWrite(flag).getRhs()
- }
-
- override predicate isSecure() {
- // A cookie is secure if there are cookie options with the `secure` flag set to `true`.
- // The default is `false`.
- getCookieFlagValue(secureFlag()).mayHaveBooleanValue(true)
- }
-
- override predicate isAuthNotHttpOnly() {
- isAuthVariable(this.getArgument(0)) and
- not isHttpOnly()
- }
-
- override predicate isHttpOnly() {
- // A cookie is httpOnly if there are cookie options with the `httpOnly` flag set to `true`.
- // The default is `false`.
- getCookieFlagValue(httpOnlyFlag()).mayHaveBooleanValue(true)
- }
- }
-
- private class AttributeToSetCookieHeaderTrackingConfig extends TaintTracking::Configuration {
- AttributeToSetCookieHeaderTrackingConfig() { this = "AttributeToSetCookieHeaderTrackingConfig" }
-
- override predicate isSource(DataFlow::Node source) {
- exists(string s | source.mayHaveStringValue(s))
- }
-
- override predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof TemplateLiteral }
- }
-
- private class SensitiveNameToSetCookieHeaderTrackingConfig extends TaintTracking::Configuration {
- SensitiveNameToSetCookieHeaderTrackingConfig() {
- this = "SensitiveNameToSetCookieHeaderTrackingConfig"
- }
-
- override predicate isSource(DataFlow::Node source) { isAuthVariable(source) }
-
- override predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof TemplateLiteral }
- }
-
- /**
- * A cookie set using `Set-Cookie` header of an `HTTP` response.
- * (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie).
- * In case an array is passed `setHeader("Set-Cookie", [...]` it sets multiple cookies.
- * Each array element has its own attributes.
- */
- class InsecureSetCookieHeader extends Cookie {
- InsecureSetCookieHeader() {
- this.asExpr() = any(HTTP::SetCookieHeader setCookie).getHeaderArgument()
- }
-
- override string getKind() { result = "set-cookie header" }
-
- override DataFlow::Node getCookieOptionsArgument() {
- if this.asExpr() instanceof ArrayExpr
- then result.asExpr() = this.asExpr().(ArrayExpr).getAnElement()
- else result.asExpr() = this.asExpr()
- }
-
- /**
- * A cookie is secure if the `secure` flag is specified in the cookie definition.
- * The default is `false`.
- */
- override predicate isSecure() { allHaveCookieAttribute("secure") }
-
- /**
- * A cookie is httpOnly if the `httpOnly` flag is specified in the cookie definition.
- * The default is `false`.
- */
- override predicate isHttpOnly() { allHaveCookieAttribute(httpOnlyFlag()) }
-
- /**
- * The predicate holds only if all elements have the specified attribute.
- */
- bindingset[attribute]
- private predicate allHaveCookieAttribute(string attribute) {
- forall(DataFlow::Node n | n = getCookieOptionsArgument() |
- exists(string s |
- n.mayHaveStringValue(s) and
- hasCookieAttribute(s, attribute)
- )
- or
- exists(AttributeToSetCookieHeaderTrackingConfig cfg, DataFlow::Node source |
- cfg.hasFlow(source, n) and
- exists(string attr |
- source.mayHaveStringValue(attr) and
- attr.regexpMatch("(?i).*\\b" + attribute + "\\b.*")
- )
- )
- )
- }
-
- /**
- * The predicate holds only if any element has a sensitive name and
- * doesn't have the `httpOnly` flag.
- */
- override predicate isAuthNotHttpOnly() {
- exists(DataFlow::Node n | n = getCookieOptionsArgument() |
- exists(string s |
- n.mayHaveStringValue(s) and
- (
- not hasCookieAttribute(s, httpOnlyFlag()) and
- regexpMatchAuth(getCookieName(s))
- )
- )
- or
- not exists(AttributeToSetCookieHeaderTrackingConfig cfg, DataFlow::Node source |
- cfg.hasFlow(source, n) and
- exists(string attr |
- source.mayHaveStringValue(attr) and
- attr.regexpMatch("(?i).*\\b" + httpOnlyFlag() + "\\b.*")
- )
- ) and
- exists(SensitiveNameToSetCookieHeaderTrackingConfig cfg | cfg.hasFlow(_, n))
- )
- }
-
- /**
- * Gets cookie name from a `Set-Cookie` header value.
- * The header value always starts with `=` optionally followed by attributes:
- * `=; Domain=; Secure; HttpOnly`
- */
- bindingset[s]
- private string getCookieName(string s) { result = s.regexpCapture("\\s*([^=\\s]*)\\s*=.*", 1) }
-
- /**
- * Holds if the `Set-Cookie` header value contains the specified attribute
- * 1. The attribute is case insensitive
- * 2. It always starts with a pair `=`.
- * If the attribute is present there must be `;` after the pair.
- * Other attributes like `Domain=`, `Path=`, etc. may come after the pair:
- * `=; Domain=; Secure; HttpOnly`
- * See `https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie`
- */
- bindingset[s, attribute]
- private predicate hasCookieAttribute(string s, string attribute) {
- s.regexpMatch("(?i).*;\\s*" + attribute + "\\s*;?.*$")
- }
- }
-
- /**
- * A cookie set using `js-cookie` library (https://github.com/js-cookie/js-cookie).
- */
- class InsecureJsCookie extends Cookie {
- InsecureJsCookie() {
- this =
- [
- DataFlow::globalVarRef("Cookie"),
- DataFlow::globalVarRef("Cookie").getAMemberCall("noConflict"),
- DataFlow::moduleImport("js-cookie")
- ].getAMemberCall("set")
- }
-
- override string getKind() { result = "js-cookie" }
-
- override DataFlow::SourceNode getCookieOptionsArgument() {
- result = this.(DataFlow::CallNode).getAnArgument().getALocalSource()
- }
-
- DataFlow::Node getCookieFlagValue(string flag) {
- result = this.getCookieOptionsArgument().getAPropertyWrite(flag).getRhs()
- }
-
- override predicate isSecure() {
- // A cookie is secure if there are cookie options with the `secure` flag set to `true`.
- getCookieFlagValue(secureFlag()).mayHaveBooleanValue(true)
- }
-
- override predicate isAuthNotHttpOnly() { none() }
-
- override predicate isHttpOnly() { none() } // js-cookie is browser side library and doesn't support HttpOnly
- }
-}
diff --git a/repo-tests/codeql/javascript/ql/src/meta/Consistency.ql b/repo-tests/codeql/javascript/ql/src/meta/Consistency.ql
index b87c256f609..3e2ec0c412a 100644
--- a/repo-tests/codeql/javascript/ql/src/meta/Consistency.ql
+++ b/repo-tests/codeql/javascript/ql/src/meta/Consistency.ql
@@ -37,22 +37,12 @@ predicate exprWithoutEnclosingStmt(Expr e) {
* `"3 results for toString()"`.
*/
predicate uniqueness_error(int number, string what, string problem) {
- (
- what = "toString" or
- what = "getLocation" or
- what = "getTopLevel" or
- what = "getEnclosingStmt" or
- what = "getContainer" or
- what = "getEnclosingContainer" or
- what = "getEntry" or
- what = "getExit" or
- what = "getFirstControlFlowNode" or
- what = "getOuterScope" or
- what = "getScopeElement" or
- what = "getBaseName" or
- what = "getOperator" or
- what = "getTest"
- ) and
+ what =
+ [
+ "toString", "getLocation", "getTopLevel", "getEnclosingStmt", "getContainer",
+ "getEnclosingContainer", "getEntry", "getExit", "getFirstControlFlowNode", "getOuterScope",
+ "getScopeElement", "getBaseName", "getOperator", "getTest"
+ ] and
(
number = 0 and problem = "no results for " + what + "()"
or
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/ApiGraphs.qll b/repo-tests/codeql/python/ql/lib/semmle/python/ApiGraphs.qll
index 62afe4ef865..78146ad18c8 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/ApiGraphs.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/ApiGraphs.qll
@@ -55,7 +55,7 @@ module API {
/**
* Gets a call to the function represented by this API component.
*/
- DataFlow::CallCfgNode getACall() { result = getReturn().getAnImmediateUse() }
+ DataFlow::CallCfgNode getACall() { result = this.getReturn().getAnImmediateUse() }
/**
* Gets a node representing member `m` of this API component.
@@ -67,21 +67,21 @@ module API {
*/
bindingset[m]
bindingset[result]
- Node getMember(string m) { result = getASuccessor(Label::member(m)) }
+ Node getMember(string m) { result = this.getASuccessor(Label::member(m)) }
/**
* Gets a node representing a member of this API component where the name of the member is
* not known statically.
*/
- Node getUnknownMember() { result = getASuccessor(Label::unknownMember()) }
+ Node getUnknownMember() { result = this.getASuccessor(Label::unknownMember()) }
/**
* Gets a node representing a member of this API component where the name of the member may
* or may not be known statically.
*/
Node getAMember() {
- result = getASuccessor(Label::member(_)) or
- result = getUnknownMember()
+ result = this.getASuccessor(Label::member(_)) or
+ result = this.getUnknownMember()
}
/**
@@ -90,23 +90,25 @@ module API {
* This predicate may have multiple results when there are multiple invocations of this API component.
* Consider using `getACall()` if there is a need to distinguish between individual calls.
*/
- Node getReturn() { result = getASuccessor(Label::return()) }
+ Node getReturn() { result = this.getASuccessor(Label::return()) }
/**
* Gets a node representing a subclass of the class represented by this node.
*/
- Node getASubclass() { result = getASuccessor(Label::subclass()) }
+ Node getASubclass() { result = this.getASuccessor(Label::subclass()) }
/**
* Gets a node representing the result from awaiting this node.
*/
- Node getAwaited() { result = getASuccessor(Label::await()) }
+ Node getAwaited() { result = this.getASuccessor(Label::await()) }
/**
* Gets a string representation of the lexicographically least among all shortest access paths
* from the root to this node.
*/
- string getPath() { result = min(string p | p = getAPath(Impl::distanceFromRoot(this)) | p) }
+ string getPath() {
+ result = min(string p | p = this.getAPath(Impl::distanceFromRoot(this)) | p)
+ }
/**
* Gets a node such that there is an edge in the API graph between this node and the other
@@ -124,13 +126,13 @@ module API {
* Gets a node such that there is an edge in the API graph between this node and the other
* one.
*/
- Node getAPredecessor() { result = getAPredecessor(_) }
+ Node getAPredecessor() { result = this.getAPredecessor(_) }
/**
* Gets a node such that there is an edge in the API graph between that other node and
* this one.
*/
- Node getASuccessor() { result = getASuccessor(_) }
+ Node getASuccessor() { result = this.getASuccessor(_) }
/**
* Gets the data-flow node that gives rise to this node, if any.
@@ -147,11 +149,11 @@ module API {
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
- getInducingNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ this.getInducingNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
or
// For nodes that do not have a meaningful location, `path` is the empty string and all other
// parameters are zero.
- not exists(getInducingNode()) and
+ not exists(this.getInducingNode()) and
filepath = "" and
startline = 0 and
startcolumn = 0 and
@@ -202,7 +204,7 @@ module API {
or
this = Impl::MkModuleImport(_) and type = "ModuleImport "
|
- result = type + getPath()
+ result = type + this.getPath()
or
not exists(this.getPath()) and result = type + "with no path"
)
@@ -302,6 +304,8 @@ module API {
* API graph node for the prefix `foo`), in accordance with the usual semantics of Python.
*/
+ private import semmle.python.internal.Awaited
+
cached
newtype TApiNode =
/** The root of the API graph. */
@@ -387,7 +391,7 @@ module API {
or
// Python 3 only
result in [
- "ascii", "breakpoint", "bytes", "exec",
+ "ascii", "breakpoint", "bytes", "exec", "aiter", "anext",
// Exceptions
"BlockingIOError", "BrokenPipeError", "ChildProcessError", "ConnectionAbortedError",
"ConnectionError", "ConnectionRefusedError", "ConnectionResetError", "FileExistsError",
@@ -516,10 +520,9 @@ module API {
)
or
// awaiting
- exists(Await await, DataFlow::Node awaitedValue |
+ exists(DataFlow::Node awaitedValue |
lbl = Label::await() and
- ref.asExpr() = await and
- await.getValue() = awaitedValue.asExpr() and
+ ref = awaited(awaitedValue) and
pred.flowsTo(awaitedValue)
)
)
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/Concepts.qll b/repo-tests/codeql/python/ql/lib/semmle/python/Concepts.qll
index 5517347e692..5cac9bbc207 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/Concepts.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/Concepts.qll
@@ -326,9 +326,47 @@ module CodeExecution {
}
}
+/**
+ * A data-flow node that constructs an SQL statement.
+ * Often, it is worthy of an alert if an SQL statement is constructed such that
+ * executing it would be a security risk.
+ *
+ * If it is important that the SQL statement is indeed executed, then use `SQLExecution`.
+ *
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `SqlConstruction::Range` instead.
+ */
+class SqlConstruction extends DataFlow::Node {
+ SqlConstruction::Range range;
+
+ SqlConstruction() { this = range }
+
+ /** Gets the argument that specifies the SQL statements to be constructed. */
+ DataFlow::Node getSql() { result = range.getSql() }
+}
+
+/** Provides a class for modeling new SQL execution APIs. */
+module SqlConstruction {
+ /**
+ * A data-flow node that constructs an SQL statement.
+ * Often, it is worthy of an alert if an SQL statement is constructed such that
+ * executing it would be a security risk.
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `SqlExecution` instead.
+ */
+ abstract class Range extends DataFlow::Node {
+ /** Gets the argument that specifies the SQL statements to be constructed. */
+ abstract DataFlow::Node getSql();
+ }
+}
+
/**
* A data-flow node that executes SQL statements.
*
+ * If the context of interest is such that merely constructing an SQL statement
+ * would be valuabe to report, then consider using `SqlConstruction`.
+ *
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `SqlExecution::Range` instead.
*/
@@ -346,6 +384,9 @@ module SqlExecution {
/**
* A data-flow node that executes SQL statements.
*
+ * If the context of interest is such that merely constructing an SQL statement
+ * would be valuabe to report, then consider using `SqlConstruction`.
+ *
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `SqlExecution` instead.
*/
@@ -355,6 +396,53 @@ module SqlExecution {
}
}
+/**
+ * A data-flow node that executes a regular expression.
+ *
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `RegexExecution::Range` instead.
+ */
+class RegexExecution extends DataFlow::Node {
+ RegexExecution::Range range;
+
+ RegexExecution() { this = range }
+
+ /** Gets the data flow node for the regex being executed by this node. */
+ DataFlow::Node getRegex() { result = range.getRegex() }
+
+ /** Gets a dataflow node for the string to be searched or matched against. */
+ DataFlow::Node getString() { result = range.getString() }
+
+ /**
+ * Gets the name of this regex execution, typically the name of an executing method.
+ * This is used for nice alert messages and should include the module if possible.
+ */
+ string getName() { result = range.getName() }
+}
+
+/** Provides classes for modeling new regular-expression execution APIs. */
+module RegexExecution {
+ /**
+ * A data-flow node that executes a regular expression.
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `RegexExecution` instead.
+ */
+ abstract class Range extends DataFlow::Node {
+ /** Gets the data flow node for the regex being executed by this node. */
+ abstract DataFlow::Node getRegex();
+
+ /** Gets a dataflow node for the string to be searched or matched against. */
+ abstract DataFlow::Node getString();
+
+ /**
+ * Gets the name of this regex execution, typically the name of an executing method.
+ * This is used for nice alert messages and should include the module if possible.
+ */
+ abstract string getName();
+ }
+}
+
/**
* A data-flow node that escapes meta-characters, which could be used to prevent
* injection attacks.
@@ -411,6 +499,9 @@ module Escaping {
/** Gets the escape-kind for escaping a string so it can safely be included in HTML. */
string getHtmlKind() { result = "html" }
+
+ /** Gets the escape-kind for escaping a string so it can safely be included in HTML. */
+ string getRegexKind() { result = "regex" }
// TODO: If adding an XML kind, update the modeling of the `MarkupSafe` PyPI package.
//
// Technically it claims to escape for both HTML and XML, but for now we don't have
@@ -427,6 +518,14 @@ class HtmlEscaping extends Escaping {
HtmlEscaping() { range.getKind() = Escaping::getHtmlKind() }
}
+/**
+ * An escape of a string so it can be safely included in
+ * the body of a regex.
+ */
+class RegexEscaping extends Escaping {
+ RegexEscaping() { range.getKind() = Escaping::getRegexKind() }
+}
+
/** Provides classes for modeling HTTP-related APIs. */
module HTTP {
import semmle.python.web.HttpConstants
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/Exprs.qll b/repo-tests/codeql/python/ql/lib/semmle/python/Exprs.qll
index 98c24b126a4..5afa651de22 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/Exprs.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/Exprs.qll
@@ -17,7 +17,7 @@ class Expr extends Expr_, AstNode {
* Whether this expression defines variable `v`
* If doing dataflow, then consider using SsaVariable.getDefinition() for more precision.
*/
- predicate defines(Variable v) { this.getASubExpression+().defines(v) }
+ predicate defines(Variable v) { this.getASubExpression().defines(v) }
/** Whether this expression may have a side effect (as determined purely from its syntax) */
predicate hasSideEffects() {
@@ -240,7 +240,7 @@ class Call extends Call_ {
/** Gets the tuple (*) argument of this call, provided there is exactly one. */
Expr getStarArg() {
count(this.getStarargs()) < 2 and
- result = getStarargs()
+ result = this.getStarargs()
}
}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/Files.qll b/repo-tests/codeql/python/ql/lib/semmle/python/Files.qll
index 66dfca681cc..99570fc4d7a 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/Files.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/Files.qll
@@ -256,7 +256,7 @@ abstract class Container extends @container {
*
*/
string getBaseName() {
- result = getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1)
+ result = this.getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1)
}
/**
@@ -282,7 +282,9 @@ abstract class Container extends @container {
* | "/tmp/x.tar.gz" | "gz" |
*
*/
- string getExtension() { result = getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3) }
+ string getExtension() {
+ result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3)
+ }
/**
* Gets the stem of this container, that is, the prefix of its base name up to
@@ -301,7 +303,9 @@ abstract class Container extends @container {
* | "/tmp/x.tar.gz" | "x.tar" |
*
*/
- string getStem() { result = getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1) }
+ string getStem() {
+ result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1)
+ }
File getFile(string baseName) {
result = this.getAFile() and
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/Flow.qll b/repo-tests/codeql/python/ql/lib/semmle/python/Flow.qll
index 65246927872..ab3d0a5f393 100755
--- a/repo-tests/codeql/python/ql/lib/semmle/python/Flow.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/Flow.qll
@@ -851,9 +851,9 @@ class ForNode extends ControlFlowNode {
/** Holds if this `for` statement causes iteration over `sequence` storing each step of the iteration in `target` */
predicate iterates(ControlFlowNode target, ControlFlowNode sequence) {
- sequence = getSequence() and
- target = possibleTarget() and
- not target = unrolledSuffix().possibleTarget()
+ sequence = this.getSequence() and
+ target = this.possibleTarget() and
+ not target = this.unrolledSuffix().possibleTarget()
}
/** Gets the sequence node for this `for` statement. */
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/Frameworks.qll b/repo-tests/codeql/python/ql/lib/semmle/python/Frameworks.qll
index b3ff235c3ee..4a25ebf8869 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/Frameworks.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/Frameworks.qll
@@ -6,13 +6,18 @@
// `docs/codeql/support/reusables/frameworks.rst`
private import semmle.python.frameworks.Aioch
private import semmle.python.frameworks.Aiohttp
+private import semmle.python.frameworks.Aiomysql
+private import semmle.python.frameworks.Aiopg
+private import semmle.python.frameworks.Asyncpg
private import semmle.python.frameworks.ClickhouseDriver
private import semmle.python.frameworks.Cryptodome
private import semmle.python.frameworks.Cryptography
private import semmle.python.frameworks.Dill
private import semmle.python.frameworks.Django
private import semmle.python.frameworks.Fabric
+private import semmle.python.frameworks.FastApi
private import semmle.python.frameworks.Flask
+private import semmle.python.frameworks.FlaskAdmin
private import semmle.python.frameworks.FlaskSqlAlchemy
private import semmle.python.frameworks.Idna
private import semmle.python.frameworks.Invoke
@@ -23,11 +28,16 @@ private import semmle.python.frameworks.Mysql
private import semmle.python.frameworks.MySQLdb
private import semmle.python.frameworks.Peewee
private import semmle.python.frameworks.Psycopg2
+private import semmle.python.frameworks.Pydantic
private import semmle.python.frameworks.PyMySQL
+private import semmle.python.frameworks.RestFramework
private import semmle.python.frameworks.Rsa
+private import semmle.python.frameworks.RuamelYaml
private import semmle.python.frameworks.Simplejson
private import semmle.python.frameworks.SqlAlchemy
+private import semmle.python.frameworks.Starlette
private import semmle.python.frameworks.Stdlib
+private import semmle.python.frameworks.Toml
private import semmle.python.frameworks.Tornado
private import semmle.python.frameworks.Twisted
private import semmle.python.frameworks.Ujson
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/Import.qll b/repo-tests/codeql/python/ql/lib/semmle/python/Import.qll
index 40c1c27a851..9620b01e4c6 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/Import.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/Import.qll
@@ -31,7 +31,7 @@ class ImportExpr extends ImportExpr_ {
// relative imports are no longer allowed in Python 3
major_version() < 3 and
// and can be explicitly turned off in later versions of Python 2
- not getEnclosingModule().hasFromFuture("absolute_import")
+ not this.getEnclosingModule().hasFromFuture("absolute_import")
}
/**
@@ -53,8 +53,8 @@ class ImportExpr extends ImportExpr_ {
* the name of the topmost module that will be imported.
*/
private string relativeTopName() {
- getLevel() = -1 and
- result = basePackageName(1) + "." + this.getTopName() and
+ this.getLevel() = -1 and
+ result = this.basePackageName(1) + "." + this.getTopName() and
valid_module_name(result)
}
@@ -62,7 +62,7 @@ class ImportExpr extends ImportExpr_ {
if this.getLevel() <= 0
then result = this.getTopName()
else (
- result = basePackageName(this.getLevel()) and
+ result = this.basePackageName(this.getLevel()) and
valid_module_name(result)
)
}
@@ -73,17 +73,17 @@ class ImportExpr extends ImportExpr_ {
* which may not be the name of the module.
*/
string bottomModuleName() {
- result = relativeTopName() + this.remainderOfName()
+ result = this.relativeTopName() + this.remainderOfName()
or
- not exists(relativeTopName()) and
+ not exists(this.relativeTopName()) and
result = this.qualifiedTopName() + this.remainderOfName()
}
/** Gets the name of topmost module or package being imported */
string topModuleName() {
- result = relativeTopName()
+ result = this.relativeTopName()
or
- not exists(relativeTopName()) and
+ not exists(this.relativeTopName()) and
result = this.qualifiedTopName()
}
@@ -94,7 +94,7 @@ class ImportExpr extends ImportExpr_ {
*/
string getImportedModuleName() {
exists(string bottomName | bottomName = this.bottomModuleName() |
- if this.isTop() then result = topModuleName() else result = bottomName
+ if this.isTop() then result = this.topModuleName() else result = bottomName
)
}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/Module.qll b/repo-tests/codeql/python/ql/lib/semmle/python/Module.qll
index 8f9344f60c0..6baf41b4a03 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/Module.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/Module.qll
@@ -86,13 +86,13 @@ class Module extends Module_, Scope, AstNode {
/** Gets the package containing this module (or parent package if this is a package) */
Module getPackage() {
this.getName().matches("%.%") and
- result.getName() = getName().regexpReplaceAll("\\.[^.]*$", "")
+ result.getName() = this.getName().regexpReplaceAll("\\.[^.]*$", "")
}
/** Gets the name of the package containing this module */
string getPackageName() {
this.getName().matches("%.%") and
- result = getName().regexpReplaceAll("\\.[^.]*$", "")
+ result = this.getName().regexpReplaceAll("\\.[^.]*$", "")
}
/** Gets the metrics for this module */
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/PrintAst.qll b/repo-tests/codeql/python/ql/lib/semmle/python/PrintAst.qll
index e06e8bc9ffb..d76285ee3fa 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/PrintAst.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/PrintAst.qll
@@ -52,8 +52,7 @@ private newtype TPrintAstNode =
TStmtListNode(StmtList list) {
shouldPrint(list.getAnItem(), _) and
not list = any(Module mod).getBody() and
- not forall(AstNode child | child = list.getAnItem() | isNotNeeded(child)) and
- exists(list.getAnItem())
+ not forall(AstNode child | child = list.getAnItem() | isNotNeeded(child))
} or
TRegExpTermNode(RegExpTerm term) {
exists(StrConst str | term.getRootTerm() = getParsedRegExp(str) and shouldPrint(str, _))
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/RegexTreeView.qll b/repo-tests/codeql/python/ql/lib/semmle/python/RegexTreeView.qll
index ad1949e4bc4..8d2a59b5fa3 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/RegexTreeView.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/RegexTreeView.qll
@@ -49,16 +49,17 @@ newtype TRegExpParent =
* or another regular expression term.
*/
class RegExpParent extends TRegExpParent {
+ /** Gets a textual representation of this element. */
string toString() { result = "RegExpParent" }
/** Gets the `i`th child term. */
abstract RegExpTerm getChild(int i);
/** Gets a child term . */
- RegExpTerm getAChild() { result = getChild(_) }
+ RegExpTerm getAChild() { result = this.getChild(_) }
/** Gets the number of child terms. */
- int getNumChild() { result = count(getAChild()) }
+ int getNumChild() { result = count(this.getAChild()) }
/** Gets the associated regex. */
abstract Regex getRegex();
@@ -72,14 +73,18 @@ class RegExpLiteral extends TRegExpLiteral, RegExpParent {
override RegExpTerm getChild(int i) { i = 0 and result.getRegex() = re and result.isRootTerm() }
+ /** Holds if dot, `.`, matches all characters, including newlines. */
predicate isDotAll() { re.getAMode() = "DOTALL" }
+ /** Holds if this regex matching is case-insensitive for this regex. */
predicate isIgnoreCase() { re.getAMode() = "IGNORECASE" }
+ /** Get a string representing all modes for this regex. */
string getFlags() { result = concat(string mode | mode = re.getAMode() | mode, " | ") }
override Regex getRegex() { result = re }
+ /** Gets the primary QL class for this regex. */
string getPrimaryQLClass() { result = "RegExpLiteral" }
}
@@ -117,7 +122,7 @@ class RegExpTerm extends RegExpParent {
RegExpTerm getRootTerm() {
this.isRootTerm() and result = this
or
- result = getParent().(RegExpTerm).getRootTerm()
+ result = this.getParent().(RegExpTerm).getRootTerm()
}
/**
@@ -196,7 +201,7 @@ class RegExpTerm extends RegExpParent {
/** Gets the regular expression term that is matched (textually) before this one, if any. */
RegExpTerm getPredecessor() {
- exists(RegExpTerm parent | parent = getParent() |
+ exists(RegExpTerm parent | parent = this.getParent() |
result = parent.(RegExpSequence).previousElement(this)
or
not exists(parent.(RegExpSequence).previousElement(this)) and
@@ -207,7 +212,7 @@ class RegExpTerm extends RegExpParent {
/** Gets the regular expression term that is matched (textually) after this one, if any. */
RegExpTerm getSuccessor() {
- exists(RegExpTerm parent | parent = getParent() |
+ exists(RegExpTerm parent | parent = this.getParent() |
result = parent.(RegExpSequence).nextElement(this)
or
not exists(parent.(RegExpSequence).nextElement(this)) and
@@ -246,8 +251,10 @@ class RegExpQuantifier extends RegExpTerm, TRegExpQuantifier {
result.getEnd() = part_end
}
+ /** Hols if this term may match an unlimited number of times. */
predicate mayRepeatForever() { may_repeat_forever = true }
+ /** Gets the qualifier for this term. That is e.g "?" for "a?". */
string getQualifier() { result = re.getText().substring(part_end, end) }
override string getPrimaryQLClass() { result = "RegExpQuantifier" }
@@ -322,8 +329,10 @@ class RegExpRange extends RegExpQuantifier {
RegExpRange() { re.multiples(part_end, end, lower, upper) }
+ /** Gets the string defining the upper bound of this range, if any. */
string getUpper() { result = upper }
+ /** Gets the string defining the lower bound of this range, if any. */
string getLower() { result = lower }
/**
@@ -358,7 +367,7 @@ class RegExpSequence extends RegExpTerm, TRegExpSequence {
override RegExpTerm getChild(int i) { result = seqChild(re, start, end, i) }
/** Gets the element preceding `element` in this sequence. */
- RegExpTerm previousElement(RegExpTerm element) { element = nextElement(result) }
+ RegExpTerm previousElement(RegExpTerm element) { element = this.nextElement(result) }
/** Gets the element following `element` in this sequence. */
RegExpTerm nextElement(RegExpTerm element) {
@@ -458,17 +467,20 @@ class RegExpEscape extends RegExpNormalChar {
or
this.getUnescaped() = "t" and result = "\t"
or
- // TODO: Find a way to include a formfeed character
- // this.getUnescaped() = "f" and result = ""
- // or
- isUnicode() and
- result = getUnicode()
+ this.getUnescaped() = "f" and result = 12.toUnicode()
+ or
+ this.getUnescaped() = "v" and result = 11.toUnicode()
+ or
+ this.isUnicode() and
+ result = this.getUnicode()
}
+ /** Holds if this terms name is given by the part following the escape character. */
predicate isIdentityEscape() { not this.getUnescaped() in ["n", "r", "t", "f"] }
override string getPrimaryQLClass() { result = "RegExpEscape" }
+ /** Gets the part of the term following the escape character. That is e.g. "w" if the term is "\w". */
string getUnescaped() { result = this.getText().suffix(1) }
/**
@@ -479,7 +491,7 @@ class RegExpEscape extends RegExpNormalChar {
/**
* Holds if this is a unicode escape.
*/
- private predicate isUnicode() { getText().prefix(2) = ["\\u", "\\U"] }
+ private predicate isUnicode() { this.getText().prefix(2) = ["\\u", "\\U"] }
/**
* Gets the unicode char for this escape.
@@ -524,6 +536,13 @@ private int toHex(string hex) {
result = 15 and hex = ["f", "F"]
}
+/**
+ * A word boundary, that is, a regular expression term of the form `\b`.
+ */
+class RegExpWordBoundary extends RegExpEscape {
+ RegExpWordBoundary() { this.getUnescaped() = "b" }
+}
+
/**
* A character class escape in a regular expression.
* That is, an escaped charachter that denotes multiple characters.
@@ -536,15 +555,8 @@ private int toHex(string hex) {
* ```
*/
class RegExpCharacterClassEscape extends RegExpEscape {
- // string value;
- RegExpCharacterClassEscape() {
- // value = re.getText().substring(start + 1, end) and
- // value in ["d", "D", "s", "S", "w", "W"]
- this.getValue() in ["d", "D", "s", "S", "w", "W"]
- }
+ RegExpCharacterClassEscape() { this.getValue() in ["d", "D", "s", "S", "w", "W"] }
- /** Gets the name of the character class; for example, `w` for `\w`. */
- // override string getValue() { result = value }
override RegExpTerm getChild(int i) { none() }
override string getPrimaryQLClass() { result = "RegExpCharacterClassEscape" }
@@ -563,19 +575,22 @@ class RegExpCharacterClassEscape extends RegExpEscape {
class RegExpCharacterClass extends RegExpTerm, TRegExpCharacterClass {
RegExpCharacterClass() { this = TRegExpCharacterClass(re, start, end) }
+ /** Holds if this character class is inverted, matching the opposite of its content. */
predicate isInverted() { re.getChar(start + 1) = "^" }
+ /** Gets the `i`th char inside this charater class. */
string getCharThing(int i) { result = re.getChar(i + start) }
+ /** Holds if this character class can match anything. */
predicate isUniversalClass() {
// [^]
- isInverted() and not exists(getAChild())
+ this.isInverted() and not exists(this.getAChild())
or
// [\w\W] and similar
- not isInverted() and
+ not this.isInverted() and
exists(string cce1, string cce2 |
- cce1 = getAChild().(RegExpCharacterClassEscape).getValue() and
- cce2 = getAChild().(RegExpCharacterClassEscape).getValue()
+ cce1 = this.getAChild().(RegExpCharacterClassEscape).getValue() and
+ cce2 = this.getAChild().(RegExpCharacterClassEscape).getValue()
|
cce1 != cce2 and cce1.toLowerCase() = cce2.toLowerCase()
)
@@ -620,6 +635,7 @@ class RegExpCharacterRange extends RegExpTerm, TRegExpCharacterRange {
re.charRange(_, start, lower_end, upper_start, end)
}
+ /** Holds if this range goes from `lo` to `hi`, in effect is `lo-hi`. */
predicate isRange(string lo, string hi) {
lo = re.getText().substring(start, lower_end) and
hi = re.getText().substring(upper_start, end)
@@ -653,8 +669,13 @@ class RegExpCharacterRange extends RegExpTerm, TRegExpCharacterRange {
class RegExpNormalChar extends RegExpTerm, TRegExpNormalChar {
RegExpNormalChar() { this = TRegExpNormalChar(re, start, end) }
+ /**
+ * Holds if this constant represents a valid Unicode character (as opposed
+ * to a surrogate code point that does not correspond to a character by itself.)
+ */
predicate isCharacter() { any() }
+ /** Gets the string representation of the char matched by this term. */
string getValue() { result = re.getText().substring(start, end) }
override RegExpTerm getChild(int i) { none() }
@@ -684,15 +705,15 @@ class RegExpConstant extends RegExpTerm {
qstart <= start and end <= qend
) and
value = this.(RegExpNormalChar).getValue()
- // This will never hold
- // or
- // this = TRegExpSpecialChar(re, start, end) and
- // re.inCharSet(start) and
- // value = this.(RegExpSpecialChar).getChar()
}
+ /**
+ * Holds if this constant represents a valid Unicode character (as opposed
+ * to a surrogate code point that does not correspond to a character by itself.)
+ */
predicate isCharacter() { any() }
+ /** Gets the string matched by this constant term. */
string getValue() { result = value }
override RegExpTerm getChild(int i) { none() }
@@ -731,10 +752,6 @@ class RegExpGroup extends RegExpTerm, TRegExpGroup {
/** Gets the name of this capture group, if any. */
string getName() { result = re.getGroupName(start, end) }
- predicate isCharacter() { any() }
-
- string getValue() { result = re.getText().substring(start, end) }
-
override RegExpTerm getChild(int i) {
result.getRegex() = re and
i = 0 and
@@ -762,8 +779,13 @@ class RegExpSpecialChar extends RegExpTerm, TRegExpSpecialChar {
re.specialCharacter(start, end, char)
}
+ /**
+ * Holds if this constant represents a valid Unicode character (as opposed
+ * to a surrogate code point that does not correspond to a character by itself.)
+ */
predicate isCharacter() { any() }
+ /** Gets the char for this term. */
string getChar() { result = char }
override RegExpTerm getChild(int i) { none() }
@@ -828,8 +850,6 @@ class RegExpCaret extends RegExpSpecialChar {
class RegExpZeroWidthMatch extends RegExpGroup {
RegExpZeroWidthMatch() { re.zeroWidthMatch(start, end) }
- override predicate isCharacter() { any() }
-
override RegExpTerm getChild(int i) { none() }
override string getPrimaryQLClass() { result = "RegExpZeroWidthMatch" }
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/SpecialMethods.qll b/repo-tests/codeql/python/ql/lib/semmle/python/SpecialMethods.qll
index 2218791794e..b548e7a52a3 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/SpecialMethods.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/SpecialMethods.qll
@@ -107,7 +107,7 @@ class SpecialMethodCallNode extends PotentialSpecialMethodCallNode {
SpecialMethodCallNode() {
exists(SpecialMethod::Potential pot |
- this.(SpecialMethod::Potential) = pot and
+ this = pot and
pot.getSelf().pointsTo().getClass().lookup(pot.getSpecialMethodName()) = resolvedSpecialMethod
)
}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/Stmts.qll b/repo-tests/codeql/python/ql/lib/semmle/python/Stmts.qll
index 56612ef7283..3dc71e9d990 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/Stmts.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/Stmts.qll
@@ -94,13 +94,13 @@ class AugAssign extends AugAssign_ {
* Gets the target of this augmented assignment statement.
* That is, the `a` in `a += b`.
*/
- Expr getTarget() { result = this.getOperation().(BinaryExpr).getLeft() }
+ Expr getTarget() { result = this.getOperation().getLeft() }
/**
* Gets the value of this augmented assignment statement.
* That is, the `b` in `a += b`.
*/
- Expr getValue() { result = this.getOperation().(BinaryExpr).getRight() }
+ Expr getValue() { result = this.getOperation().getRight() }
override Stmt getASubStatement() { none() }
}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/concepts/CryptoAlgorithms.qll b/repo-tests/codeql/python/ql/lib/semmle/python/concepts/CryptoAlgorithms.qll
index a5bfd6696be..7bc05ac7eb4 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/concepts/CryptoAlgorithms.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/concepts/CryptoAlgorithms.qll
@@ -15,75 +15,38 @@
*/
private module AlgorithmNames {
predicate isStrongHashingAlgorithm(string name) {
- name = "DSA" or
- name = "ED25519" or
- name = "ES256" or
- name = "ECDSA256" or
- name = "ES384" or
- name = "ECDSA384" or
- name = "ES512" or
- name = "ECDSA512" or
- name = "SHA2" or
- name = "SHA224" or
- name = "SHA256" or
- name = "SHA384" or
- name = "SHA512" or
- name = "SHA3" or
- name = "SHA3224" or
- name = "SHA3256" or
- name = "SHA3384" or
- name = "SHA3512"
+ name =
+ [
+ "DSA", "ED25519", "ES256", "ECDSA256", "ES384", "ECDSA384", "ES512", "ECDSA512", "SHA2",
+ "SHA224", "SHA256", "SHA384", "SHA512", "SHA3", "SHA3224", "SHA3256", "SHA3384", "SHA3512"
+ ]
}
predicate isWeakHashingAlgorithm(string name) {
- name = "HAVEL128" or
- name = "MD2" or
- name = "MD4" or
- name = "MD5" or
- name = "PANAMA" or
- name = "RIPEMD" or
- name = "RIPEMD128" or
- name = "RIPEMD256" or
- name = "RIPEMD160" or
- name = "RIPEMD320" or
- name = "SHA0" or
- name = "SHA1"
+ name =
+ [
+ "HAVEL128", "MD2", "MD4", "MD5", "PANAMA", "RIPEMD", "RIPEMD128", "RIPEMD256", "RIPEMD160",
+ "RIPEMD320", "SHA0", "SHA1"
+ ]
}
predicate isStrongEncryptionAlgorithm(string name) {
- name = "AES" or
- name = "AES128" or
- name = "AES192" or
- name = "AES256" or
- name = "AES512" or
- name = "RSA" or
- name = "RABBIT" or
- name = "BLOWFISH"
+ name = ["AES", "AES128", "AES192", "AES256", "AES512", "RSA", "RABBIT", "BLOWFISH"]
}
predicate isWeakEncryptionAlgorithm(string name) {
- name = "DES" or
- name = "3DES" or
- name = "TRIPLEDES" or
- name = "TDEA" or
- name = "TRIPLEDEA" or
- name = "ARC2" or
- name = "RC2" or
- name = "ARC4" or
- name = "RC4" or
- name = "ARCFOUR" or
- name = "ARC5" or
- name = "RC5"
+ name =
+ [
+ "DES", "3DES", "TRIPLEDES", "TDEA", "TRIPLEDEA", "ARC2", "RC2", "ARC4", "RC4", "ARCFOUR",
+ "ARC5", "RC5"
+ ]
}
predicate isStrongPasswordHashingAlgorithm(string name) {
- name = "ARGON2" or
- name = "PBKDF2" or
- name = "BCRYPT" or
- name = "SCRYPT"
+ name = ["ARGON2", "PBKDF2", "BCRYPT", "SCRYPT"]
}
- predicate isWeakPasswordHashingAlgorithm(string name) { none() }
+ predicate isWeakPasswordHashingAlgorithm(string name) { name = "EVPKDF" }
}
private import AlgorithmNames
@@ -122,11 +85,13 @@ abstract class CryptographicAlgorithm extends TCryptographicAlgorithm {
/**
* Holds if the name of this algorithm matches `name` modulo case,
- * white space, dashes, and underscores.
+ * white space, dashes, underscores, and anything after a dash in the name
+ * (to ignore modes of operation, such as CBC or ECB).
*/
bindingset[name]
predicate matchesName(string name) {
- name.toUpperCase().regexpReplaceAll("[-_ ]", "") = getName()
+ [name.toUpperCase(), name.toUpperCase().regexpCapture("^(\\w+)(?:-.*)?$", 1)]
+ .regexpReplaceAll("[-_ ]", "") = getName()
}
/**
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl.qll b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl2.qll b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl2.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl2.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl2.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl3.qll b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl3.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl3.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl3.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl4.qll b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl4.qll
index 4ca06c93362..08030e0b35b 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl4.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl4.qll
@@ -10,6 +10,7 @@
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
/**
* A configuration of interprocedural data flow analysis. This defines
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
*/
int fieldFlowBranchLimit() { result = 2 }
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
/**
* Holds if data may flow from `source` to `sink` for this configuration.
*/
@@ -110,12 +127,12 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowTo(Node sink) { hasFlow(_, sink) }
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
- predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) }
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
/**
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
@@ -244,6 +261,8 @@ private class ParamNodeEx extends NodeEx {
}
int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
}
private class RetNodeEx extends NodeEx {
@@ -347,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -363,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
- not fullBarrier(node2, config)
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
}
@@ -399,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
*/
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
private module Stage1 {
class ApApprox = Unit;
@@ -419,7 +454,7 @@ private module Stage1 {
not fullBarrier(node, config) and
(
sourceNode(node, config) and
- cc = false
+ if hasSourceCallCtx(config) then cc = true else cc = false
or
exists(NodeEx mid |
fwdFlow(mid, cc, config) and
@@ -549,7 +584,7 @@ private module Stage1 {
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
fwdFlow(node, config) and
sinkNode(node, config) and
- toReturn = false
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
or
exists(NodeEx mid |
localFlowStep(node, mid, config) and
@@ -744,8 +779,12 @@ private module Stage1 {
returnFlowCallableNodeCand(c, kind, config) and
p.getEnclosingCallable() = c and
exists(ap) and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -931,6 +970,8 @@ private module Stage2 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -998,7 +1039,7 @@ private module Stage2 {
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1209,7 +1250,7 @@ private module Stage2 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -1394,8 +1435,12 @@ private module Stage2 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -1606,6 +1651,8 @@ private module Stage3 {
Cc ccNone() { result = false }
+ CcCall ccSomeCall() { result = true }
+
private class LocalCc = Unit;
bindingset[call, c, outercc]
@@ -1687,7 +1734,7 @@ private module Stage3 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -1898,7 +1945,7 @@ private module Stage3 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2083,8 +2130,12 @@ private module Stage3 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2352,6 +2403,8 @@ private module Stage4 {
Cc ccNone() { result instanceof CallContextAny }
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
private class LocalCc = LocalCallContext;
bindingset[call, c, outercc]
@@ -2447,7 +2500,7 @@ private module Stage4 {
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
flowCand(node, _, config) and
sourceNode(node, config) and
- cc = ccNone() and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
argAp = apNone() and
ap = getApNil(node)
or
@@ -2658,7 +2711,7 @@ private module Stage4 {
) {
fwdFlow(node, _, _, ap, config) and
sinkNode(node, config) and
- toReturn = false and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
returnAp = apNone() and
ap instanceof ApNil
or
@@ -2843,8 +2896,12 @@ private module Stage4 {
fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
kind = ret.getKind() and
p.getPosition() = pos and
- // we don't expect a parameter to return stored in itself
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
)
}
@@ -2917,6 +2974,8 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
int getParameterPos() { p.isParameterOf(_, result) }
+ ParamNodeEx getParamNode() { result = p }
+
override string toString() { result = p + ": " + ap }
predicate hasLocationInfo(
@@ -3044,7 +3103,11 @@ private newtype TPathNode =
// A PathNode is introduced by a source ...
Stage4::revFlow(node, config) and
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap = TAccessPathNil(node.getDataFlowType())
or
@@ -3056,17 +3119,10 @@ private newtype TPathNode =
)
} or
TPathNodeSink(NodeEx node, Configuration config) {
- sinkNode(node, pragma[only_bind_into](config)) and
- Stage4::revFlow(node, pragma[only_bind_into](config)) and
- (
- // A sink that is also a source ...
- sourceNode(node, config)
- or
- // ... or a sink that can be reached from a source
- exists(PathNodeMid mid |
- pathStep(mid, node, _, _, TAccessPathNil(_)) and
- pragma[only_bind_into](config) = mid.getConfiguration()
- )
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
)
}
@@ -3170,7 +3226,7 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
}
override string toString() {
- result = "[" + this.toStringImpl(true) + length().toString() + ")]"
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
or
result = "[" + this.toStringImpl(false)
}
@@ -3309,9 +3365,11 @@ abstract private class PathNodeImpl extends PathNode {
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
}
- override string toString() { result = this.getNodeEx().toString() + ppAp() }
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
- override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() }
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
@@ -3379,24 +3437,48 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
override PathNodeImpl getASuccessorImpl() {
// an intermediate step to another intermediate node
- result = getSuccMid()
+ result = this.getSuccMid()
or
- // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
- exists(PathNodeMid mid, PathNodeSink sink |
- mid = getSuccMid() and
- mid.getNodeEx() = sink.getNodeEx() and
- mid.getAp() instanceof AccessPathNil and
- sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
- result = sink
- )
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
sourceNode(node, config) and
- cc instanceof CallContextAny and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
sc instanceof SummaryCtxNone and
ap instanceof AccessPathNil
}
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
}
/**
@@ -3460,7 +3542,7 @@ private predicate pathStep(
exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
sc = mid.getSummaryCtx()
or
- pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp()
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
or
pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
or
@@ -3537,18 +3619,20 @@ private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc)
*/
pragma[noinline]
private predicate pathIntoArg(
- PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
) {
exists(ArgNode arg |
arg = mid.getNodeEx().asNode() and
cc = mid.getCallContext() and
arg.argumentOf(call, i) and
ap = mid.getAp() and
- apa = ap.getApprox()
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
)
}
-pragma[noinline]
+pragma[nomagic]
private predicate parameterCand(
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
) {
@@ -3561,12 +3645,14 @@ private predicate parameterCand(
pragma[nomagic]
private predicate pathIntoCallable0(
PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
- AccessPath ap
+ AccessPath ap, Configuration config
) {
exists(AccessPathApprox apa |
- pathIntoArg(mid, i, outercc, call, ap, apa) and
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
callable = resolveCall(call, outercc) and
- parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration())
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
)
}
@@ -3575,18 +3661,23 @@ private predicate pathIntoCallable0(
* before and after entering the callable are `outercc` and `innercc`,
* respectively.
*/
+pragma[nomagic]
private predicate pathIntoCallable(
PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
- DataFlowCall call
+ DataFlowCall call, Configuration config
) {
exists(int i, DataFlowCallable callable, AccessPath ap |
- pathIntoCallable0(mid, callable, i, outercc, call, ap) and
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
p.isParameterOf(callable, i) and
(
sc = TSummaryCtxSome(p, ap)
or
not exists(TSummaryCtxSome(p, ap)) and
- sc = TSummaryCtxNone()
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
)
|
if recordDataFlowCallSite(call, callable)
@@ -3610,18 +3701,23 @@ private predicate paramFlowsThrough(
ap = mid.getAp() and
apa = ap.getApprox() and
pos = sc.getParameterPos() and
- not kind.(ParamUpdateReturnKind).getPosition() = pos
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
)
}
pragma[nomagic]
private predicate pathThroughCallable0(
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
- AccessPathApprox apa
+ AccessPathApprox apa, Configuration config
) {
exists(CallContext innercc, SummaryCtx sc |
- pathIntoCallable(mid, _, cc, innercc, sc, call) and
- paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration()))
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
)
}
@@ -3631,9 +3727,9 @@ private predicate pathThroughCallable0(
*/
pragma[noinline]
private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
- exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa |
- pathThroughCallable0(call, mid, kind, cc, ap, apa) and
- out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration()))
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
)
}
@@ -3644,13 +3740,15 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
- PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
- pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
- pathIntoCallable(arg, par, _, innercc, sc, _) and
- paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _,
- unbindConf(arg.getConfiguration()))
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
}
/**
@@ -3683,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
- apout = ret.getAp() and
- not ret.isHidden()
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
)
}
@@ -3693,11 +3800,12 @@ private module Subpaths {
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
- predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
- subpaths03(arg, p, ret, o, apout) and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImplCommon.qll b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImplCommon.qll
index f43a550af57..c28ceabb438 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImplCommon.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImplCommon.qll
@@ -2,6 +2,42 @@ private import DataFlowImplSpecific::Private
private import DataFlowImplSpecific::Public
import Cached
+module DataFlowImplCommonPublic {
+ private newtype TFlowFeature =
+ TFeatureHasSourceCallContext() or
+ TFeatureHasSinkCallContext() or
+ TFeatureEqualSourceSinkCallContext()
+
+ /** A flow configuration feature for use in `Configuration::getAFeature()`. */
+ class FlowFeature extends TFlowFeature {
+ string toString() { none() }
+ }
+
+ /**
+ * A flow configuration feature that implies that sources have some existing
+ * call context.
+ */
+ class FeatureHasSourceCallContext extends FlowFeature, TFeatureHasSourceCallContext {
+ override string toString() { result = "FeatureHasSourceCallContext" }
+ }
+
+ /**
+ * A flow configuration feature that implies that sinks have some existing
+ * call context.
+ */
+ class FeatureHasSinkCallContext extends FlowFeature, TFeatureHasSinkCallContext {
+ override string toString() { result = "FeatureHasSinkCallContext" }
+ }
+
+ /**
+ * A flow configuration feature that implies that source-sink pairs have some
+ * shared existing call context.
+ */
+ class FeatureEqualSourceSinkCallContext extends FlowFeature, TFeatureEqualSourceSinkCallContext {
+ override string toString() { result = "FeatureEqualSourceSinkCallContext" }
+ }
+}
+
/**
* The cost limits for the `AccessPathFront` to `AccessPathApprox` expansion.
*
@@ -251,7 +287,7 @@ private module Cached {
predicate forceCachingInSameStage() { any() }
cached
- predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = n.getEnclosingCallable() }
+ predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = nodeGetEnclosingCallable(n) }
cached
predicate callEnclosingCallable(DataFlowCall call, DataFlowCallable c) {
@@ -316,9 +352,7 @@ private module Cached {
}
cached
- predicate parameterNode(Node n, DataFlowCallable c, int i) {
- n.(ParameterNode).isParameterOf(c, i)
- }
+ predicate parameterNode(Node p, DataFlowCallable c, int pos) { isParameterNode(p, c, pos) }
cached
predicate argumentNode(Node n, DataFlowCall call, int pos) {
@@ -801,6 +835,9 @@ private module Cached {
exists(Node n | getNodeEnclosingCallable(n) = callable | isUnreachableInCallCached(n, call))
}
+ cached
+ predicate allowParameterReturnInSelfCached(ParamNode p) { allowParameterReturnInSelf(p) }
+
cached
newtype TCallContext =
TAnyCallContext() or
@@ -937,7 +974,7 @@ class CallContextSpecificCall extends CallContextCall, TSpecificCall {
}
override predicate relevantFor(DataFlowCallable callable) {
- recordDataFlowCallSite(getCall(), callable)
+ recordDataFlowCallSite(this.getCall(), callable)
}
override predicate matchesCall(DataFlowCall call) { call = this.getCall() }
@@ -1257,7 +1294,7 @@ abstract class AccessPathFront extends TAccessPathFront {
TypedContent getHead() { this = TFrontHead(result) }
- predicate isClearedAt(Node n) { clearsContentCached(n, getHead().getContent()) }
+ predicate isClearedAt(Node n) { clearsContentCached(n, this.getHead().getContent()) }
}
class AccessPathFrontNil extends AccessPathFront, TFrontNil {
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImplConsistency.qll b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImplConsistency.qll
index a55e65a81f6..acf31338f9a 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImplConsistency.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImplConsistency.qll
@@ -31,7 +31,7 @@ module Consistency {
query predicate uniqueEnclosingCallable(Node n, string msg) {
exists(int c |
n instanceof RelevantNode and
- c = count(n.getEnclosingCallable()) and
+ c = count(nodeGetEnclosingCallable(n)) and
c != 1 and
msg = "Node should have one enclosing callable but has " + c + "."
)
@@ -85,13 +85,13 @@ module Consistency {
}
query predicate parameterCallable(ParameterNode p, string msg) {
- exists(DataFlowCallable c | p.isParameterOf(c, _) and c != p.getEnclosingCallable()) and
+ exists(DataFlowCallable c | isParameterNode(p, c, _) and c != nodeGetEnclosingCallable(p)) and
msg = "Callable mismatch for parameter."
}
query predicate localFlowIsLocal(Node n1, Node n2, string msg) {
simpleLocalFlowStep(n1, n2) and
- n1.getEnclosingCallable() != n2.getEnclosingCallable() and
+ nodeGetEnclosingCallable(n1) != nodeGetEnclosingCallable(n2) and
msg = "Local flow step does not preserve enclosing callable."
}
@@ -106,7 +106,7 @@ module Consistency {
query predicate unreachableNodeCCtx(Node n, DataFlowCall call, string msg) {
isUnreachableInCall(n, call) and
exists(DataFlowCallable c |
- c = n.getEnclosingCallable() and
+ c = nodeGetEnclosingCallable(n) and
not viableCallable(call) = c
) and
msg = "Call context for isUnreachableInCall is inconsistent with call graph."
@@ -120,7 +120,7 @@ module Consistency {
n.(ArgumentNode).argumentOf(call, _) and
msg = "ArgumentNode and call does not share enclosing callable."
) and
- n.getEnclosingCallable() != call.getEnclosingCallable()
+ nodeGetEnclosingCallable(n) != call.getEnclosingCallable()
}
// This predicate helps the compiler forget that in some languages
@@ -151,7 +151,7 @@ module Consistency {
}
query predicate postIsInSameCallable(PostUpdateNode n, string msg) {
- n.getEnclosingCallable() != n.getPreUpdateNode().getEnclosingCallable() and
+ nodeGetEnclosingCallable(n) != nodeGetEnclosingCallable(n.getPreUpdateNode()) and
msg = "PostUpdateNode does not share callable with its pre-update node."
}
@@ -175,6 +175,7 @@ module Consistency {
query predicate postWithInFlow(Node n, string msg) {
isPostUpdateNode(n) and
+ not clearsContent(n, _) and
simpleLocalFlowStep(_, n) and
msg = "PostUpdateNode should not be the target of local flow."
}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
index 169ebd191ba..aca3ce15bb2 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
@@ -3,6 +3,12 @@ private import DataFlowPublic
import semmle.python.SpecialMethods
private import semmle.python.essa.SsaCompute
+/** Gets the callable in which this node occurs. */
+DataFlowCallable nodeGetEnclosingCallable(Node n) { result = n.getEnclosingCallable() }
+
+/** Holds if `p` is a `ParameterNode` of `c` with position `pos`. */
+predicate isParameterNode(ParameterNode p, DataFlowCallable c, int pos) { p.isParameterOf(c, pos) }
+
//--------
// Data flow graph
//--------
@@ -172,9 +178,23 @@ module EssaFlow {
// see `with_flow` in `python/ql/src/semmle/python/dataflow/Implementation.qll`
with.getContextExpr() = contextManager.getNode() and
with.getOptionalVars() = var.getNode() and
+ not with.isAsync() and
contextManager.strictlyDominates(var)
)
or
+ // Async with var definition
+ // `async with f(42) as x:`
+ // nodeFrom is `x`, cfg node
+ // nodeTo is `x`, essa var
+ //
+ // This makes the cfg node the local source of the awaited value.
+ exists(With with, ControlFlowNode var |
+ nodeFrom.(CfgNode).getNode() = var and
+ nodeTo.(EssaNode).getVar().getDefinition().(WithDefinition).getDefiningNode() = var and
+ with.getOptionalVars() = var.getNode() and
+ with.isAsync()
+ )
+ or
// Parameter definition
// `def foo(x):`
// nodeFrom is `x`, cfgNode
@@ -610,11 +630,11 @@ class DataFlowLambda extends DataFlowCallable, TLambda {
override string toString() { result = lambda.toString() }
- override CallNode getACall() { result = getCallableValue().getACall() }
+ override CallNode getACall() { result = this.getCallableValue().getACall() }
override Scope getScope() { result = lambda.getEvaluatingScope() }
- override NameNode getParameter(int n) { result = getParameter(getCallableValue(), n) }
+ override NameNode getParameter(int n) { result = getParameter(this.getCallableValue(), n) }
override string getName() { result = "Lambda callable" }
@@ -1345,10 +1365,8 @@ module IterableUnpacking {
}
/** A (possibly recursive) target of an unpacking assignment which is also a sequence. */
- class UnpackingAssignmentSequenceTarget extends UnpackingAssignmentTarget {
- UnpackingAssignmentSequenceTarget() { this instanceof SequenceNode }
-
- ControlFlowNode getElement(int i) { result = this.(SequenceNode).getElement(i) }
+ class UnpackingAssignmentSequenceTarget extends UnpackingAssignmentTarget instanceof SequenceNode {
+ ControlFlowNode getElement(int i) { result = super.getElement(i) }
ControlFlowNode getAnElement() { result = this.getElement(_) }
}
@@ -1664,3 +1682,12 @@ predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { no
/** Extra data-flow steps needed for lambda flow analysis. */
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() }
+
+/**
+ * Holds if flow is allowed to pass from parameter `p` and back to itself as a
+ * side-effect, resulting in a summary from `p` to itself.
+ *
+ * One example would be to allow flow like `p.foo = p.bar;`, which is disallowed
+ * by default as a heuristic.
+ */
+predicate allowParameterReturnInSelf(ParameterNode p) { none() }
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/LocalSources.qll b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/LocalSources.qll
index df1ee7bba16..76cc1573b24 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/LocalSources.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/LocalSources.qll
@@ -62,12 +62,12 @@ class LocalSourceNode extends Node {
/**
* Gets a read of attribute `attrName` on this node.
*/
- AttrRead getAnAttributeRead(string attrName) { result = getAnAttributeReference(attrName) }
+ AttrRead getAnAttributeRead(string attrName) { result = this.getAnAttributeReference(attrName) }
/**
* Gets a write of attribute `attrName` on this node.
*/
- AttrWrite getAnAttributeWrite(string attrName) { result = getAnAttributeReference(attrName) }
+ AttrWrite getAnAttributeWrite(string attrName) { result = this.getAnAttributeReference(attrName) }
/**
* Gets a reference (read or write) of any attribute on this node.
@@ -81,12 +81,12 @@ class LocalSourceNode extends Node {
/**
* Gets a read of any attribute on this node.
*/
- AttrRead getAnAttributeRead() { result = getAnAttributeReference() }
+ AttrRead getAnAttributeRead() { result = this.getAnAttributeReference() }
/**
* Gets a write of any attribute on this node.
*/
- AttrWrite getAnAttributeWrite() { result = getAnAttributeReference() }
+ AttrWrite getAnAttributeWrite() { result = this.getAnAttributeReference() }
/**
* Gets a call to this node.
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/PrintNode.qll b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/PrintNode.qll
index 234019a498c..c7fd7dcecf3 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/PrintNode.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/PrintNode.qll
@@ -58,7 +58,6 @@ string prettyNode(DataFlow::Node node) {
*/
bindingset[node]
string prettyNodeForInlineTest(DataFlow::Node node) {
- exists(node.asExpr()) and
result = prettyExpr(node.asExpr())
or
exists(Expr e | e = node.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr() |
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/TaintTrackingPrivate.qll b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/TaintTrackingPrivate.qll
index 313b5e3410b..13a576de9d0 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/TaintTrackingPrivate.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/TaintTrackingPrivate.qll
@@ -53,6 +53,8 @@ private module Cached {
DataFlowPrivate::iterableUnpackingStoreStep(nodeFrom, _, nodeTo)
or
awaitStep(nodeFrom, nodeTo)
+ or
+ asyncWithStep(nodeFrom, nodeTo)
}
}
@@ -211,3 +213,24 @@ predicate copyStep(DataFlow::CfgNode nodeFrom, DataFlow::CfgNode nodeTo) {
predicate awaitStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
nodeTo.asExpr().(Await).getValue() = nodeFrom.asExpr()
}
+
+/**
+ * Holds if taint can flow from `nodeFrom` to `nodeTo` inside an `async with` statement.
+ *
+ * For example in
+ * ```python
+ * async with open("foo") as f:
+ * ```
+ * the variable `f` is tainted if the result of `open("foo")` is tainted.
+ */
+predicate asyncWithStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
+ exists(With with, ControlFlowNode contextManager, ControlFlowNode var |
+ nodeFrom.(DataFlow::CfgNode).getNode() = contextManager and
+ nodeTo.(DataFlow::EssaNode).getVar().getDefinition().(WithDefinition).getDefiningNode() = var and
+ // see `with_flow` in `python/ql/src/semmle/python/dataflow/Implementation.qll`
+ with.getContextExpr() = contextManager.getNode() and
+ with.getOptionalVars() = var.getNode() and
+ with.isAsync() and
+ contextManager.strictlyDominates(var)
+ )
+}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll
index 6ced6a8206e..272e87b4995 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll
@@ -52,6 +52,24 @@ private module Cached {
)
}
+ /** Gets the summary resulting from prepending `step` to this type-tracking summary. */
+ cached
+ TypeBackTracker prepend(TypeBackTracker tbt, StepSummary step) {
+ exists(Boolean hasReturn, string content | tbt = MkTypeBackTracker(hasReturn, content) |
+ step = LevelStep() and result = tbt
+ or
+ step = CallStep() and hasReturn = false and result = tbt
+ or
+ step = ReturnStep() and result = MkTypeBackTracker(true, content)
+ or
+ exists(string p |
+ step = LoadStep(p) and content = "" and result = MkTypeBackTracker(hasReturn, p)
+ )
+ or
+ step = StoreStep(content) and result = MkTypeBackTracker(hasReturn, "")
+ )
+ }
+
/**
* Gets the summary that corresponds to having taken a forwards
* heap and/or intra-procedural step from `nodeFrom` to `nodeTo`.
@@ -365,19 +383,7 @@ class TypeBackTracker extends TTypeBackTracker {
TypeBackTracker() { this = MkTypeBackTracker(hasReturn, content) }
/** Gets the summary resulting from prepending `step` to this type-tracking summary. */
- TypeBackTracker prepend(StepSummary step) {
- step = LevelStep() and result = this
- or
- step = CallStep() and hasReturn = false and result = this
- or
- step = ReturnStep() and result = MkTypeBackTracker(true, content)
- or
- exists(string p |
- step = LoadStep(p) and content = "" and result = MkTypeBackTracker(hasReturn, p)
- )
- or
- step = StoreStep(content) and result = MkTypeBackTracker(hasReturn, "")
- }
+ TypeBackTracker prepend(StepSummary step) { result = prepend(this, step) }
/** Gets a textual representation of this summary. */
string toString() {
@@ -459,6 +465,19 @@ class TypeBackTracker extends TTypeBackTracker {
simpleLocalFlowStep(nodeFrom, nodeTo) and
this = result
}
+
+ /**
+ * Gets a forwards summary that is compatible with this backwards summary.
+ * That is, if this summary describes the steps needed to back-track a value
+ * from `sink` to `mid`, and the result is a valid summary of the steps needed
+ * to track a value from `source` to `mid`, then the value from `source` may
+ * also flow to `sink`.
+ */
+ TypeTracker getACompatibleTypeTracker() {
+ exists(boolean hasCall | result = MkTypeTracker(hasCall, content) |
+ hasCall = false or this.hasReturn() = false
+ )
+ }
}
/** Provides predicates for implementing custom `TypeBackTracker`s. */
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/tainttracking1/TaintTrackingImpl.qll b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/tainttracking1/TaintTrackingImpl.qll
index f4f73b8247c..acb029c23d9 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/tainttracking1/TaintTrackingImpl.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/tainttracking1/TaintTrackingImpl.qll
@@ -75,24 +75,26 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isSanitizer(DataFlow::Node node) { none() }
final override predicate isBarrier(DataFlow::Node node) {
- isSanitizer(node) or
+ this.isSanitizer(node) or
defaultTaintSanitizer(node)
}
/** Holds if taint propagation into `node` is prohibited. */
predicate isSanitizerIn(DataFlow::Node node) { none() }
- final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) }
+ final override predicate isBarrierIn(DataFlow::Node node) { this.isSanitizerIn(node) }
/** Holds if taint propagation out of `node` is prohibited. */
predicate isSanitizerOut(DataFlow::Node node) { none() }
- final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) }
+ final override predicate isBarrierOut(DataFlow::Node node) { this.isSanitizerOut(node) }
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
- final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { isSanitizerGuard(guard) }
+ final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
+ this.isSanitizerGuard(guard)
+ }
/**
* Holds if the additional taint propagation step from `node1` to `node2`
@@ -101,7 +103,7 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
- isAdditionalTaintStep(node1, node2) or
+ this.isAdditionalTaintStep(node1, node2) or
defaultAdditionalTaintStep(node1, node2)
}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/tainttracking2/TaintTrackingImpl.qll b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/tainttracking2/TaintTrackingImpl.qll
index f4f73b8247c..acb029c23d9 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/tainttracking2/TaintTrackingImpl.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/tainttracking2/TaintTrackingImpl.qll
@@ -75,24 +75,26 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isSanitizer(DataFlow::Node node) { none() }
final override predicate isBarrier(DataFlow::Node node) {
- isSanitizer(node) or
+ this.isSanitizer(node) or
defaultTaintSanitizer(node)
}
/** Holds if taint propagation into `node` is prohibited. */
predicate isSanitizerIn(DataFlow::Node node) { none() }
- final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) }
+ final override predicate isBarrierIn(DataFlow::Node node) { this.isSanitizerIn(node) }
/** Holds if taint propagation out of `node` is prohibited. */
predicate isSanitizerOut(DataFlow::Node node) { none() }
- final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) }
+ final override predicate isBarrierOut(DataFlow::Node node) { this.isSanitizerOut(node) }
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
- final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { isSanitizerGuard(guard) }
+ final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
+ this.isSanitizerGuard(guard)
+ }
/**
* Holds if the additional taint propagation step from `node1` to `node2`
@@ -101,7 +103,7 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
- isAdditionalTaintStep(node1, node2) or
+ this.isAdditionalTaintStep(node1, node2) or
defaultAdditionalTaintStep(node1, node2)
}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/tainttracking3/TaintTrackingImpl.qll b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/tainttracking3/TaintTrackingImpl.qll
index f4f73b8247c..acb029c23d9 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/tainttracking3/TaintTrackingImpl.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/tainttracking3/TaintTrackingImpl.qll
@@ -75,24 +75,26 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isSanitizer(DataFlow::Node node) { none() }
final override predicate isBarrier(DataFlow::Node node) {
- isSanitizer(node) or
+ this.isSanitizer(node) or
defaultTaintSanitizer(node)
}
/** Holds if taint propagation into `node` is prohibited. */
predicate isSanitizerIn(DataFlow::Node node) { none() }
- final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) }
+ final override predicate isBarrierIn(DataFlow::Node node) { this.isSanitizerIn(node) }
/** Holds if taint propagation out of `node` is prohibited. */
predicate isSanitizerOut(DataFlow::Node node) { none() }
- final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) }
+ final override predicate isBarrierOut(DataFlow::Node node) { this.isSanitizerOut(node) }
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
- final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { isSanitizerGuard(guard) }
+ final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
+ this.isSanitizerGuard(guard)
+ }
/**
* Holds if the additional taint propagation step from `node1` to `node2`
@@ -101,7 +103,7 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
- isAdditionalTaintStep(node1, node2) or
+ this.isAdditionalTaintStep(node1, node2) or
defaultAdditionalTaintStep(node1, node2)
}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/tainttracking4/TaintTrackingImpl.qll b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/tainttracking4/TaintTrackingImpl.qll
index f4f73b8247c..acb029c23d9 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/tainttracking4/TaintTrackingImpl.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/new/internal/tainttracking4/TaintTrackingImpl.qll
@@ -75,24 +75,26 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isSanitizer(DataFlow::Node node) { none() }
final override predicate isBarrier(DataFlow::Node node) {
- isSanitizer(node) or
+ this.isSanitizer(node) or
defaultTaintSanitizer(node)
}
/** Holds if taint propagation into `node` is prohibited. */
predicate isSanitizerIn(DataFlow::Node node) { none() }
- final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) }
+ final override predicate isBarrierIn(DataFlow::Node node) { this.isSanitizerIn(node) }
/** Holds if taint propagation out of `node` is prohibited. */
predicate isSanitizerOut(DataFlow::Node node) { none() }
- final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) }
+ final override predicate isBarrierOut(DataFlow::Node node) { this.isSanitizerOut(node) }
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
- final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { isSanitizerGuard(guard) }
+ final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
+ this.isSanitizerGuard(guard)
+ }
/**
* Holds if the additional taint propagation step from `node1` to `node2`
@@ -101,7 +103,7 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
- isAdditionalTaintStep(node1, node2) or
+ this.isAdditionalTaintStep(node1, node2) or
defaultAdditionalTaintStep(node1, node2)
}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/old/TaintTracking.qll b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/old/TaintTracking.qll
index e7ee6a3ee08..8ed6711ba38 100755
--- a/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/old/TaintTracking.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/dataflow/old/TaintTracking.qll
@@ -639,16 +639,14 @@ module DataFlow {
}
}
- deprecated private class ConfigurationAdapter extends TaintTracking::Configuration {
- ConfigurationAdapter() { this instanceof Configuration }
-
+ deprecated private class ConfigurationAdapter extends TaintTracking::Configuration instanceof Configuration {
override predicate isSource(DataFlow::Node node, TaintKind kind) {
- this.(Configuration).isSource(node.asCfgNode()) and
+ Configuration.super.isSource(node.asCfgNode()) and
kind instanceof DataFlowType
}
override predicate isSink(DataFlow::Node node, TaintKind kind) {
- this.(Configuration).isSink(node.asCfgNode()) and
+ Configuration.super.isSink(node.asCfgNode()) and
kind instanceof DataFlowType
}
}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/dependencies/TechInventory.qll b/repo-tests/codeql/python/ql/lib/semmle/python/dependencies/TechInventory.qll
index 0c48b5ecfbb..20a938b86d0 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/dependencies/TechInventory.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/dependencies/TechInventory.qll
@@ -14,16 +14,14 @@ string munge(File sourceFile, ExternalPackage package) {
result = "/" + sourceFile.getRelativePath() + "<|>" + package.getName() + "<|>unknown"
}
-abstract class ExternalPackage extends Object {
- ExternalPackage() { this instanceof ModuleObject }
-
+abstract class ExternalPackage extends Object instanceof ModuleObject {
abstract string getName();
abstract string getVersion();
- Object getAttribute(string name) { result = this.(ModuleObject).attr(name) }
+ Object getAttribute(string name) { result = super.attr(name) }
- PackageObject getPackage() { result = this.(ModuleObject).getPackage() }
+ PackageObject getPackage() { result = super.getPackage() }
}
bindingset[text]
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/essa/Definitions.qll b/repo-tests/codeql/python/ql/lib/semmle/python/essa/Definitions.qll
index 752ff9da329..ed5e6995e53 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/essa/Definitions.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/essa/Definitions.qll
@@ -152,17 +152,17 @@ class NonLocalVariable extends SsaSourceVariable {
}
override ControlFlowNode getAnImplicitUse() {
- result.(CallNode).getScope().getScope*() = this.(LocalVariable).getScope()
+ result.(CallNode).getScope().getScope*() = this.scope_as_local_variable()
}
override ControlFlowNode getScopeEntryDefinition() {
exists(Function f |
- f.getScope+() = this.(LocalVariable).getScope() and
+ f.getScope+() = this.scope_as_local_variable() and
f.getEntryNode() = result
)
or
not this.(LocalVariable).isParameter() and
- this.(LocalVariable).getScope().getEntryNode() = result
+ this.scope_as_local_variable().getEntryNode() = result
}
pragma[noinline]
@@ -215,19 +215,22 @@ class ModuleVariable extends SsaSourceVariable {
)
}
+ pragma[nomagic]
+ private Scope scope_as_global_variable() { result = this.(GlobalVariable).getScope() }
+
pragma[noinline]
- CallNode global_variable_callnode() { result.getScope() = this.(GlobalVariable).getScope() }
+ CallNode global_variable_callnode() { result.getScope() = this.scope_as_global_variable() }
pragma[noinline]
ImportMemberNode global_variable_import() {
- result.getScope() = this.(GlobalVariable).getScope() and
- import_from_dot_in_init(result.(ImportMemberNode).getModule(this.getName()))
+ result.getScope() = this.scope_as_global_variable() and
+ import_from_dot_in_init(result.getModule(this.getName()))
}
override ControlFlowNode getAnImplicitUse() {
- result = global_variable_callnode()
+ result = this.global_variable_callnode()
or
- result = global_variable_import()
+ result = this.global_variable_import()
or
exists(ImportTimeScope scope | scope.entryEdge(result, _) |
this = scope.getOuterVariable(_) or
@@ -250,7 +253,7 @@ class ModuleVariable extends SsaSourceVariable {
override ControlFlowNode getScopeEntryDefinition() {
exists(Scope s | s.getEntryNode() = result |
/* Module entry point */
- this.(GlobalVariable).getScope() = s
+ this.scope_as_global_variable() = s
or
/* For implicit use of __metaclass__ when constructing class */
class_with_global_metaclass(s, this)
@@ -286,13 +289,13 @@ class EscapingGlobalVariable extends ModuleVariable {
override ControlFlowNode getAnImplicitUse() {
result = ModuleVariable.super.getAnImplicitUse()
or
- result.(CallNode).getScope().getScope+() = this.(GlobalVariable).getScope()
+ result.(CallNode).getScope().getScope+() = this.scope_as_global_variable()
or
result = this.innerScope().getANormalExit()
}
private Scope innerScope() {
- result.getScope+() = this.(GlobalVariable).getScope() and
+ result.getScope+() = this.scope_as_global_variable() and
not result instanceof ImportTimeScope
}
@@ -306,7 +309,7 @@ class EscapingGlobalVariable extends ModuleVariable {
Scope scope_as_global_variable() { result = this.(GlobalVariable).getScope() }
override CallNode redefinedAtCallSite() {
- result.(CallNode).getScope().getScope*() = this.scope_as_global_variable()
+ result.getScope().getScope*() = this.scope_as_global_variable()
}
}
@@ -332,7 +335,7 @@ class SpecialSsaSourceVariable extends SsaSourceVariable {
Scope scope_as_global_variable() { result = this.(GlobalVariable).getScope() }
override CallNode redefinedAtCallSite() {
- result.(CallNode).getScope().getScope*() = this.scope_as_global_variable()
+ result.getScope().getScope*() = this.scope_as_global_variable()
}
}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/essa/Essa.qll b/repo-tests/codeql/python/ql/lib/semmle/python/essa/Essa.qll
index d703b72242a..2d403070c4c 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/essa/Essa.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/essa/Essa.qll
@@ -41,7 +41,7 @@ class EssaVariable extends TEssaDefinition {
*/
ControlFlowNode getASourceUse() {
exists(SsaSourceVariable var |
- result = use_for_var(var) and
+ result = this.use_for_var(var) and
result = var.getASourceUse()
)
}
@@ -258,7 +258,7 @@ class PhiFunction extends EssaDefinition, TPhiFunction {
/** Gets another definition of the same source variable that reaches this definition. */
private EssaDefinition reachingDefinition(BasicBlock pred) {
result.getScope() = this.getScope() and
- result.getSourceVariable() = pred_var(pred) and
+ result.getSourceVariable() = this.pred_var(pred) and
result.reachesEndOfBlock(pred)
}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Aiohttp.qll b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Aiohttp.qll
index 46bcf3e554c..3ec70643986 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Aiohttp.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Aiohttp.qll
@@ -330,8 +330,8 @@ module AiohttpWebModel {
exists(Await await, DataFlow::CallCfgNode call, DataFlow::AttrRead read |
this.asExpr() = await
|
- read.(DataFlow::AttrRead).getObject() = Request::instance() and
- read.(DataFlow::AttrRead).getAttributeName() = "post" and
+ read.getObject() = Request::instance() and
+ read.getAttributeName() = "post" and
call.getFunction() = read and
await.getValue() = call.asExpr()
)
@@ -424,7 +424,7 @@ module AiohttpWebModel {
override string getAttributeName() { none() }
- override string getMethodName() { result in ["read_nowait"] }
+ override string getMethodName() { result = "read_nowait" }
override string getAsyncMethodName() {
result in [
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Aiomysql.qll b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Aiomysql.qll
new file mode 100644
index 00000000000..62c68d1b869
--- /dev/null
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Aiomysql.qll
@@ -0,0 +1,145 @@
+/**
+ * Provides classes modeling security-relevant aspects of the `aiomysql` PyPI package.
+ * See
+ * - https://aiomysql.readthedocs.io/en/stable/index.html
+ * - https://pypi.org/project/aiomysql/
+ */
+
+private import python
+private import semmle.python.dataflow.new.DataFlow
+private import semmle.python.Concepts
+private import semmle.python.ApiGraphs
+
+/** Provides models for the `aiomysql` PyPI package. */
+private module Aiomysql {
+ private import semmle.python.internal.Awaited
+
+ /**
+ * A `ConectionPool` is created when the result of `aiomysql.create_pool()` is awaited.
+ * See https://aiomysql.readthedocs.io/en/stable/pool.html
+ */
+ API::Node connectionPool() {
+ result = API::moduleImport("aiomysql").getMember("create_pool").getReturn().getAwaited()
+ }
+
+ /**
+ * A `Connection` is created when
+ * - the result of `aiomysql.connect()` is awaited.
+ * - the result of calling `aquire` on a `ConnectionPool` is awaited.
+ * See https://aiomysql.readthedocs.io/en/stable/connection.html#connection
+ */
+ API::Node connection() {
+ result = API::moduleImport("aiomysql").getMember("connect").getReturn().getAwaited()
+ or
+ result = connectionPool().getMember("acquire").getReturn().getAwaited()
+ }
+
+ /**
+ * A `Cursor` is created when
+ * - the result of calling `cursor` on a `ConnectionPool` is awaited.
+ * - the result of calling `cursor` on a `Connection` is awaited.
+ * See https://aiomysql.readthedocs.io/en/stable/cursors.html
+ */
+ API::Node cursor() {
+ result = connectionPool().getMember("cursor").getReturn().getAwaited()
+ or
+ result = connection().getMember("cursor").getReturn().getAwaited()
+ }
+
+ /**
+ * Calling `execute` on a `Cursor` constructs a query.
+ * See https://aiomysql.readthedocs.io/en/stable/cursors.html#Cursor.execute
+ */
+ class CursorExecuteCall extends SqlConstruction::Range, DataFlow::CallCfgNode {
+ CursorExecuteCall() { this = cursor().getMember("execute").getACall() }
+
+ override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("operation")] }
+ }
+
+ /**
+ * This is only needed to connect the argument to the execute call with the subsequnt awaiting.
+ * It should be obsolete once we have `API::CallNode` available.
+ */
+ private DataFlow::TypeTrackingNode cursorExecuteCall(DataFlow::TypeTracker t, DataFlow::Node sql) {
+ // cursor created from connection
+ t.start() and
+ sql = result.(CursorExecuteCall).getSql()
+ or
+ exists(DataFlow::TypeTracker t2 | result = cursorExecuteCall(t2, sql).track(t2, t))
+ }
+
+ DataFlow::Node cursorExecuteCall(DataFlow::Node sql) {
+ cursorExecuteCall(DataFlow::TypeTracker::end(), sql).flowsTo(result)
+ }
+
+ /**
+ * Awaiting the result of calling `execute` executes the query.
+ * See https://aiomysql.readthedocs.io/en/stable/cursors.html#Cursor.execute
+ */
+ class AwaitedCursorExecuteCall extends SqlExecution::Range {
+ DataFlow::Node sql;
+
+ AwaitedCursorExecuteCall() { this = awaited(cursorExecuteCall(sql)) }
+
+ override DataFlow::Node getSql() { result = sql }
+ }
+
+ /**
+ * An `Engine` is created when the result of calling `aiomysql.sa.create_engine` is awaited.
+ * See https://aiomysql.readthedocs.io/en/stable/sa.html#engine
+ */
+ API::Node engine() {
+ result =
+ API::moduleImport("aiomysql")
+ .getMember("sa")
+ .getMember("create_engine")
+ .getReturn()
+ .getAwaited()
+ }
+
+ /**
+ * A `SAConnection` is created when the result of calling `aquire` on an `Engine` is awaited.
+ * See https://aiomysql.readthedocs.io/en/stable/sa.html#connection
+ */
+ API::Node saConnection() { result = engine().getMember("acquire").getReturn().getAwaited() }
+
+ /**
+ * Calling `execute` on a `SAConnection` constructs a query.
+ * See https://aiomysql.readthedocs.io/en/stable/sa.html#aiomysql.sa.SAConnection.execute
+ */
+ class SAConnectionExecuteCall extends SqlConstruction::Range, DataFlow::CallCfgNode {
+ SAConnectionExecuteCall() { this = saConnection().getMember("execute").getACall() }
+
+ override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("query")] }
+ }
+
+ /**
+ * This is only needed to connect the argument to the execute call with the subsequnt awaiting.
+ * It should be obsolete once we have `API::CallNode` available.
+ */
+ private DataFlow::TypeTrackingNode saConnectionExecuteCall(
+ DataFlow::TypeTracker t, DataFlow::Node sql
+ ) {
+ // saConnection created from engine
+ t.start() and
+ sql = result.(SAConnectionExecuteCall).getSql()
+ or
+ exists(DataFlow::TypeTracker t2 | result = saConnectionExecuteCall(t2, sql).track(t2, t))
+ }
+
+ DataFlow::Node saConnectionExecuteCall(DataFlow::Node sql) {
+ saConnectionExecuteCall(DataFlow::TypeTracker::end(), sql).flowsTo(result)
+ }
+
+ /**
+ * Awaiting the result of calling `execute` executes the query.
+ * See https://aiomysql.readthedocs.io/en/stable/sa.html#aiomysql.sa.SAConnection.execute
+ */
+ class AwaitedSAConnectionExecuteCall extends SqlExecution::Range {
+ DataFlow::Node sql;
+
+ AwaitedSAConnectionExecuteCall() { this = awaited(saConnectionExecuteCall(sql)) }
+
+ override DataFlow::Node getSql() { result = sql }
+ }
+}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Aiopg.qll b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Aiopg.qll
new file mode 100644
index 00000000000..927ae90ed05
--- /dev/null
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Aiopg.qll
@@ -0,0 +1,141 @@
+/**
+ * Provides classes modeling security-relevant aspects of the `aiopg` PyPI package.
+ * See
+ * - https://aiopg.readthedocs.io/en/stable/index.html
+ * - https://pypi.org/project/aiopg/
+ */
+
+private import python
+private import semmle.python.dataflow.new.DataFlow
+private import semmle.python.Concepts
+private import semmle.python.ApiGraphs
+
+/** Provides models for the `aiopg` PyPI package. */
+private module Aiopg {
+ private import semmle.python.internal.Awaited
+
+ /**
+ * A `ConectionPool` is created when the result of `aiopg.create_pool()` is awaited.
+ * See https://aiopg.readthedocs.io/en/stable/core.html#pool
+ */
+ API::Node connectionPool() {
+ result = API::moduleImport("aiopg").getMember("create_pool").getReturn().getAwaited()
+ }
+
+ /**
+ * A `Connection` is created when
+ * - the result of `aiopg.connect()` is awaited.
+ * - the result of calling `aquire` on a `ConnectionPool` is awaited.
+ * See https://aiopg.readthedocs.io/en/stable/core.html#connection
+ */
+ API::Node connection() {
+ result = API::moduleImport("aiopg").getMember("connect").getReturn().getAwaited()
+ or
+ result = connectionPool().getMember("acquire").getReturn().getAwaited()
+ }
+
+ /**
+ * A `Cursor` is created when
+ * - the result of calling `cursor` on a `ConnectionPool` is awaited.
+ * - the result of calling `cursor` on a `Connection` is awaited.
+ * See https://aiopg.readthedocs.io/en/stable/core.html#cursor
+ */
+ API::Node cursor() {
+ result = connectionPool().getMember("cursor").getReturn().getAwaited()
+ or
+ result = connection().getMember("cursor").getReturn().getAwaited()
+ }
+
+ /**
+ * Calling `execute` on a `Cursor` constructs a query.
+ * See https://aiopg.readthedocs.io/en/stable/core.html#aiopg.Cursor.execute
+ */
+ class CursorExecuteCall extends SqlConstruction::Range, DataFlow::CallCfgNode {
+ CursorExecuteCall() { this = cursor().getMember("execute").getACall() }
+
+ override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("operation")] }
+ }
+
+ /**
+ * This is only needed to connect the argument to the execute call with the subsequnt awaiting.
+ * It should be obsolete once we have `API::CallNode` available.
+ */
+ private DataFlow::TypeTrackingNode cursorExecuteCall(DataFlow::TypeTracker t, DataFlow::Node sql) {
+ // cursor created from connection
+ t.start() and
+ sql = result.(CursorExecuteCall).getSql()
+ or
+ exists(DataFlow::TypeTracker t2 | result = cursorExecuteCall(t2, sql).track(t2, t))
+ }
+
+ DataFlow::Node cursorExecuteCall(DataFlow::Node sql) {
+ cursorExecuteCall(DataFlow::TypeTracker::end(), sql).flowsTo(result)
+ }
+
+ /**
+ * Awaiting the result of calling `execute` executes the query.
+ * See https://aiopg.readthedocs.io/en/stable/core.html#aiopg.Cursor.execute
+ */
+ class AwaitedCursorExecuteCall extends SqlExecution::Range {
+ DataFlow::Node sql;
+
+ AwaitedCursorExecuteCall() { this = awaited(cursorExecuteCall(sql)) }
+
+ override DataFlow::Node getSql() { result = sql }
+ }
+
+ /**
+ * An `Engine` is created when the result of calling `aiopg.sa.create_engine` is awaited.
+ * See https://aiopg.readthedocs.io/en/stable/sa.html#engine
+ */
+ API::Node engine() {
+ result =
+ API::moduleImport("aiopg").getMember("sa").getMember("create_engine").getReturn().getAwaited()
+ }
+
+ /**
+ * A `SAConnection` is created when the result of calling `aquire` on an `Engine` is awaited.
+ * See https://aiopg.readthedocs.io/en/stable/sa.html#connection
+ */
+ API::Node saConnection() { result = engine().getMember("acquire").getReturn().getAwaited() }
+
+ /**
+ * Calling `execute` on a `SAConnection` constructs a query.
+ * See https://aiopg.readthedocs.io/en/stable/sa.html#aiopg.sa.SAConnection.execute
+ */
+ class SAConnectionExecuteCall extends SqlConstruction::Range, DataFlow::CallCfgNode {
+ SAConnectionExecuteCall() { this = saConnection().getMember("execute").getACall() }
+
+ override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("query")] }
+ }
+
+ /**
+ * This is only needed to connect the argument to the execute call with the subsequnt awaiting.
+ * It should be obsolete once we have `API::CallNode` available.
+ */
+ private DataFlow::TypeTrackingNode saConnectionExecuteCall(
+ DataFlow::TypeTracker t, DataFlow::Node sql
+ ) {
+ // saConnection created from engine
+ t.start() and
+ sql = result.(SAConnectionExecuteCall).getSql()
+ or
+ exists(DataFlow::TypeTracker t2 | result = saConnectionExecuteCall(t2, sql).track(t2, t))
+ }
+
+ DataFlow::Node saConnectionExecuteCall(DataFlow::Node sql) {
+ saConnectionExecuteCall(DataFlow::TypeTracker::end(), sql).flowsTo(result)
+ }
+
+ /**
+ * Awaiting the result of calling `execute` executes the query.
+ * See https://aiopg.readthedocs.io/en/stable/sa.html#aiopg.sa.SAConnection.execute
+ */
+ class AwaitedSAConnectionExecuteCall extends SqlExecution::Range {
+ DataFlow::Node sql;
+
+ AwaitedSAConnectionExecuteCall() { this = awaited(saConnectionExecuteCall(sql)) }
+
+ override DataFlow::Node getSql() { result = sql }
+ }
+}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Asyncpg.qll b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Asyncpg.qll
new file mode 100644
index 00000000000..031342a4c23
--- /dev/null
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Asyncpg.qll
@@ -0,0 +1,162 @@
+/**
+ * Provides classes modeling security-relevant aspects of the `asyncpg` PyPI package.
+ * See https://magicstack.github.io/asyncpg/.
+ */
+
+private import python
+private import semmle.python.dataflow.new.DataFlow
+private import semmle.python.Concepts
+private import semmle.python.ApiGraphs
+
+/** Provides models for the `asyncpg` PyPI package. */
+private module Asyncpg {
+ private import semmle.python.internal.Awaited
+
+ /** A `ConectionPool` is created when the result of `asyncpg.create_pool()` is awaited. */
+ API::Node connectionPool() {
+ result = API::moduleImport("asyncpg").getMember("create_pool").getReturn().getAwaited()
+ }
+
+ /**
+ * A `Connection` is created when
+ * - the result of `asyncpg.connect()` is awaited.
+ * - the result of calling `aquire` on a `ConnectionPool` is awaited.
+ */
+ API::Node connection() {
+ result = API::moduleImport("asyncpg").getMember("connect").getReturn().getAwaited()
+ or
+ result = connectionPool().getMember("acquire").getReturn().getAwaited()
+ }
+
+ /** `Connection`s and `ConnectionPool`s provide some methods that execute SQL. */
+ class SqlExecutionOnConnection extends SqlExecution::Range, DataFlow::MethodCallNode {
+ string methodName;
+
+ SqlExecutionOnConnection() {
+ methodName in ["copy_from_query", "execute", "fetch", "fetchrow", "fetchval", "executemany"] and
+ this.calls([connectionPool().getAUse(), connection().getAUse()], methodName)
+ }
+
+ override DataFlow::Node getSql() {
+ methodName in ["copy_from_query", "execute", "fetch", "fetchrow", "fetchval"] and
+ result in [this.getArg(0), this.getArgByName("query")]
+ or
+ methodName = "executemany" and
+ result in [this.getArg(0), this.getArgByName("command")]
+ }
+ }
+
+ /** `Connection`s and `ConnectionPool`s provide some methods that access the file system. */
+ class FileAccessOnConnection extends FileSystemAccess::Range, DataFlow::MethodCallNode {
+ string methodName;
+
+ FileAccessOnConnection() {
+ methodName in ["copy_from_query", "copy_from_table", "copy_to_table"] and
+ this.calls([connectionPool().getAUse(), connection().getAUse()], methodName)
+ }
+
+ // The path argument is keyword only.
+ override DataFlow::Node getAPathArgument() {
+ methodName in ["copy_from_query", "copy_from_table"] and
+ result = this.getArgByName("output")
+ or
+ methodName = "copy_to_table" and
+ result = this.getArgByName("source")
+ }
+ }
+
+ /**
+ * Provides models of the `PreparedStatement` class in `asyncpg`.
+ * `PreparedStatement`s are created when the result of calling `prepare(query)` on a connection is awaited.
+ * The result of calling `prepare(query)` is a `PreparedStatementFactory` and the argument, `query` needs to
+ * be tracked to the place where a `PreparedStatement` is created and then futher to any executing methods.
+ * Hence the two type trackers.
+ *
+ * TODO: Rewrite this, once we have `API::CallNode` available.
+ */
+ module PreparedStatement {
+ class PreparedStatementConstruction extends SqlConstruction::Range, DataFlow::CallCfgNode {
+ PreparedStatementConstruction() { this = connection().getMember("prepare").getACall() }
+
+ override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("query")] }
+ }
+
+ private DataFlow::TypeTrackingNode preparedStatementFactory(
+ DataFlow::TypeTracker t, DataFlow::Node sql
+ ) {
+ t.start() and
+ sql = result.(PreparedStatementConstruction).getSql()
+ or
+ exists(DataFlow::TypeTracker t2 | result = preparedStatementFactory(t2, sql).track(t2, t))
+ }
+
+ DataFlow::Node preparedStatementFactory(DataFlow::Node sql) {
+ preparedStatementFactory(DataFlow::TypeTracker::end(), sql).flowsTo(result)
+ }
+
+ private DataFlow::TypeTrackingNode preparedStatement(DataFlow::TypeTracker t, DataFlow::Node sql) {
+ t.start() and
+ result = awaited(preparedStatementFactory(sql))
+ or
+ exists(DataFlow::TypeTracker t2 | result = preparedStatement(t2, sql).track(t2, t))
+ }
+
+ DataFlow::Node preparedStatement(DataFlow::Node sql) {
+ preparedStatement(DataFlow::TypeTracker::end(), sql).flowsTo(result)
+ }
+
+ class PreparedStatementExecution extends SqlExecution::Range, DataFlow::MethodCallNode {
+ DataFlow::Node sql;
+
+ PreparedStatementExecution() {
+ this.calls(preparedStatement(sql), ["executemany", "fetch", "fetchrow", "fetchval"])
+ }
+
+ override DataFlow::Node getSql() { result = sql }
+ }
+ }
+
+ /**
+ * Provides models of the `Cursor` class in `asyncpg`.
+ * `Cursor`s are created
+ * - when the result of calling `cursor(query)` on a connection is awaited.
+ * - when the result of calling `cursor()` on a prepared statement is awaited.
+ * The result of calling `cursor` in either case is a `CursorFactory` and the argument, `query` needs to
+ * be tracked to the place where a `Cursor` is created, hence the type tracker.
+ * The creation of the `Cursor` executes the query.
+ *
+ * TODO: Rewrite this, once we have `API::CallNode` available.
+ */
+ module Cursor {
+ class CursorConstruction extends SqlConstruction::Range, DataFlow::CallCfgNode {
+ CursorConstruction() { this = connection().getMember("cursor").getACall() }
+
+ override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("query")] }
+ }
+
+ private DataFlow::TypeTrackingNode cursorFactory(DataFlow::TypeTracker t, DataFlow::Node sql) {
+ // cursor created from connection
+ t.start() and
+ sql = result.(CursorConstruction).getSql()
+ or
+ // cursor created from prepared statement
+ t.start() and
+ result.(DataFlow::MethodCallNode).calls(PreparedStatement::preparedStatement(sql), "cursor")
+ or
+ exists(DataFlow::TypeTracker t2 | result = cursorFactory(t2, sql).track(t2, t))
+ }
+
+ DataFlow::Node cursorFactory(DataFlow::Node sql) {
+ cursorFactory(DataFlow::TypeTracker::end(), sql).flowsTo(result)
+ }
+
+ /** The creation of a `Cursor` executes the associated query. */
+ class CursorCreation extends SqlExecution::Range {
+ DataFlow::Node sql;
+
+ CursorCreation() { this = awaited(cursorFactory(sql)) }
+
+ override DataFlow::Node getSql() { result = sql }
+ }
+ }
+}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Cryptodome.qll b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Cryptodome.qll
index 4d108196148..54b5b9437a3 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Cryptodome.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Cryptodome.qll
@@ -116,7 +116,7 @@ private module CryptodomeModel {
] and
this =
API::moduleImport(["Crypto", "Cryptodome"])
- .getMember(["Cipher"])
+ .getMember("Cipher")
.getMember(cipherName)
.getMember("new")
.getReturn()
@@ -135,21 +135,21 @@ private module CryptodomeModel {
or
// for the following methods, method signatures can be found in
// https://pycryptodome.readthedocs.io/en/latest/src/cipher/modern.html
- methodName in ["update"] and
+ methodName = "update" and
result in [this.getArg(0), this.getArgByName("data")]
or
// although `mac_tag` is used as the parameter name in the spec above, some implementations use `received_mac_tag`, for an example, see
// https://github.com/Legrandin/pycryptodome/blob/5dace638b70ac35bb5d9b565f3e75f7869c9d851/lib/Crypto/Cipher/ChaCha20_Poly1305.py#L207
- methodName in ["verify"] and
+ methodName = "verify" and
result in [this.getArg(0), this.getArgByName(["mac_tag", "received_mac_tag"])]
or
- methodName in ["hexverify"] and
+ methodName = "hexverify" and
result in [this.getArg(0), this.getArgByName("mac_tag_hex")]
or
- methodName in ["encrypt_and_digest"] and
+ methodName = "encrypt_and_digest" and
result in [this.getArg(0), this.getArgByName("plaintext")]
or
- methodName in ["decrypt_and_verify"] and
+ methodName = "decrypt_and_verify" and
result in [
this.getArg(0), this.getArgByName("ciphertext"), this.getArg(1),
this.getArgByName("mac_tag")
@@ -169,7 +169,7 @@ private module CryptodomeModel {
methodName in ["sign", "verify"] and
this =
API::moduleImport(["Crypto", "Cryptodome"])
- .getMember(["Signature"])
+ .getMember("Signature")
.getMember(signatureName)
.getMember("new")
.getReturn()
@@ -185,11 +185,11 @@ private module CryptodomeModel {
methodName = "sign" and
result in [this.getArg(0), this.getArgByName("msg_hash")] // Cryptodome.Hash instance
or
- methodName in ["verify"] and
+ methodName = "verify" and
(
- result in [this.getArg(0), this.getArgByName(["msg_hash"])] // Cryptodome.Hash instance
+ result in [this.getArg(0), this.getArgByName("msg_hash")] // Cryptodome.Hash instance
or
- result in [this.getArg(1), this.getArgByName(["signature"])]
+ result in [this.getArg(1), this.getArgByName("signature")]
)
}
}
@@ -204,7 +204,7 @@ private module CryptodomeModel {
CryptodomeGenericHashOperation() {
exists(API::Node hashModule |
hashModule =
- API::moduleImport(["Crypto", "Cryptodome"]).getMember(["Hash"]).getMember(hashName)
+ API::moduleImport(["Crypto", "Cryptodome"]).getMember("Hash").getMember(hashName)
|
this = hashModule.getMember("new").getACall()
or
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Django.qll b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Django.qll
index 08afa55635a..9e66c728f6e 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Django.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Django.qll
@@ -17,10 +17,12 @@ private import semmle.python.frameworks.internal.SelfRefMixin
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
/**
+ * INTERNAL: Do not use.
+ *
* Provides models for the `django` PyPI package.
* See https://www.djangoproject.com/.
*/
-private module Django {
+module Django {
/** Provides models for the `django.views` module */
module Views {
/**
@@ -367,6 +369,52 @@ private module Django {
}
}
+ /**
+ * Provides models for the `django.contrib.auth.models.User` class
+ *
+ * See https://docs.djangoproject.com/en/3.2/ref/contrib/auth/#user-model.
+ */
+ module User {
+ /**
+ * A source of instances of `django.contrib.auth.models.User`, extend this class to model new instances.
+ *
+ * This can include instantiations of the class, return values from function
+ * calls, or a special parameter that will be set when functions are called by an external
+ * library.
+ *
+ * Use the predicate `User::instance()` to get references to instances of `django.contrib.auth.models.User`.
+ */
+ abstract class InstanceSource extends DataFlow::LocalSourceNode { }
+
+ /** Gets a reference to an instance of `django.contrib.auth.models.User`. */
+ private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
+ t.start() and
+ result instanceof InstanceSource
+ or
+ exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
+ }
+
+ /** Gets a reference to an instance of `django.contrib.auth.models.User`. */
+ DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
+
+ /**
+ * Taint propagation for `django.contrib.auth.models.User`.
+ */
+ private class InstanceTaintSteps extends InstanceTaintStepsHelper {
+ InstanceTaintSteps() { this = "django.contrib.auth.models.User" }
+
+ override DataFlow::Node getInstance() { result = instance() }
+
+ override string getAttributeName() {
+ result in ["username", "first_name", "last_name", "email"]
+ }
+
+ override string getMethodName() { none() }
+
+ override string getAsyncMethodName() { none() }
+ }
+ }
+
/**
* Provides models for the `django.core.files.uploadedfile.UploadedFile` class
*
@@ -466,10 +514,12 @@ private module Django {
}
/**
+ * INTERNAL: Do not use.
+ *
* Provides models for the `django` PyPI package (that we are not quite ready to publicly expose yet).
* See https://www.djangoproject.com/.
*/
-private module PrivateDjango {
+module PrivateDjango {
// ---------------------------------------------------------------------------
// django
// ---------------------------------------------------------------------------
@@ -496,6 +546,7 @@ private module PrivateDjango {
/** Gets a reference to the `django.db.connection` object. */
API::Node connection() { result = db().getMember("connection") }
+ /** A `django.db.connection` is a PEP249 compliant DB connection. */
class DjangoDbConnection extends PEP249::Connection::InstanceSource {
DjangoDbConnection() { this = connection().getAUse() }
}
@@ -692,6 +743,7 @@ private module PrivateDjango {
/** Provides models for the `django.conf` module */
module conf {
+ /** Provides models for the `django.conf.urls` module */
module conf_urls {
// -------------------------------------------------------------------------
// django.conf.urls
@@ -890,6 +942,7 @@ private module PrivateDjango {
* See https://docs.djangoproject.com/en/3.1/ref/request-response/#django.http.HttpResponse.
*/
module HttpResponse {
+ /** Gets a reference to the `django.http.response.HttpResponse` class. */
API::Node baseClassRef() {
result = response().getMember("HttpResponse")
or
@@ -897,7 +950,7 @@ private module PrivateDjango {
result = http().getMember("HttpResponse")
}
- /** Gets a reference to the `django.http.response.HttpResponse` class. */
+ /** Gets a reference to the `django.http.response.HttpResponse` class or any subclass. */
API::Node classRef() { result = baseClassRef().getASubclass*() }
/**
@@ -1844,11 +1897,13 @@ private module PrivateDjango {
t.start() and
result.asCfgNode().(CallNode).getFunction() = this.asViewRef().asCfgNode()
or
- exists(DataFlow::TypeTracker t2 | result = asViewResult(t2).track(t2, t))
+ exists(DataFlow::TypeTracker t2 | result = this.asViewResult(t2).track(t2, t))
}
/** Gets a reference to the result of calling the `as_view` classmethod of this class. */
- DataFlow::Node asViewResult() { asViewResult(DataFlow::TypeTracker::end()).flowsTo(result) }
+ DataFlow::Node asViewResult() {
+ this.asViewResult(DataFlow::TypeTracker::end()).flowsTo(result)
+ }
}
/** A class that we consider a django View class. */
@@ -1891,14 +1946,11 @@ private module PrivateDjango {
* with the django framework.
*
* Most functions take a django HttpRequest as a parameter (but not all).
+ *
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `DjangoRouteHandler::Range` instead.
*/
- private class DjangoRouteHandler extends Function {
- DjangoRouteHandler() {
- exists(DjangoRouteSetup route | route.getViewArg() = poorMansFunctionTracker(this))
- or
- any(DjangoViewClass vc).getARequestHandler() = this
- }
-
+ class DjangoRouteHandler extends Function instanceof DjangoRouteHandler::Range {
/**
* Gets the index of the parameter where the first routed parameter can be passed --
* that is, the one just after any possible `self` or HttpRequest parameters.
@@ -1918,6 +1970,24 @@ private module PrivateDjango {
Parameter getRequestParam() { result = this.getArg(this.getRequestParamIndex()) }
}
+ /** Provides a class for modeling new django route handlers. */
+ module DjangoRouteHandler {
+ /**
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `DjangoRouteHandler` instead.
+ */
+ abstract class Range extends Function { }
+
+ /** Route handlers from normal usage of django. */
+ private class StandardDjangoRouteHandlers extends Range {
+ StandardDjangoRouteHandlers() {
+ exists(DjangoRouteSetup route | route.getViewArg() = poorMansFunctionTracker(this))
+ or
+ any(DjangoViewClass vc).getARequestHandler() = this
+ }
+ }
+ }
+
/**
* A method named `get_redirect_url` on a django view class.
*
@@ -1939,15 +2009,15 @@ private module PrivateDjango {
}
/** A data-flow node that sets up a route on a server, using the django framework. */
- abstract private class DjangoRouteSetup extends HTTP::Server::RouteSetup::Range, DataFlow::CfgNode {
+ abstract class DjangoRouteSetup extends HTTP::Server::RouteSetup::Range, DataFlow::CfgNode {
/** Gets the data-flow node that is used as the argument for the view handler. */
abstract DataFlow::Node getViewArg();
final override DjangoRouteHandler getARequestHandler() {
- poorMansFunctionTracker(result) = getViewArg()
+ poorMansFunctionTracker(result) = this.getViewArg()
or
exists(DjangoViewClass vc |
- getViewArg() = vc.asViewResult() and
+ this.getViewArg() = vc.asViewResult() and
result = vc.getARequestHandler()
)
}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/FastApi.qll b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/FastApi.qll
new file mode 100644
index 00000000000..35ffdc43dd8
--- /dev/null
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/FastApi.qll
@@ -0,0 +1,352 @@
+/**
+ * Provides classes modeling security-relevant aspects of the `fastapi` PyPI package.
+ * See https://fastapi.tiangolo.com/.
+ */
+
+private import python
+private import semmle.python.dataflow.new.DataFlow
+private import semmle.python.dataflow.new.RemoteFlowSources
+private import semmle.python.dataflow.new.TaintTracking
+private import semmle.python.Concepts
+private import semmle.python.ApiGraphs
+private import semmle.python.frameworks.Pydantic
+private import semmle.python.frameworks.Starlette
+
+/**
+ * Provides models for the `fastapi` PyPI package.
+ * See https://fastapi.tiangolo.com/.
+ */
+private module FastApi {
+ /**
+ * Provides models for FastAPI applications (an instance of `fastapi.FastAPI`).
+ */
+ module App {
+ /** Gets a reference to a FastAPI application (an instance of `fastapi.FastAPI`). */
+ API::Node instance() { result = API::moduleImport("fastapi").getMember("FastAPI").getReturn() }
+ }
+
+ /**
+ * Provides models for the `fastapi.APIRouter` class
+ *
+ * See https://fastapi.tiangolo.com/tutorial/bigger-applications/.
+ */
+ module APIRouter {
+ /** Gets a reference to an instance of `fastapi.APIRouter`. */
+ API::Node instance() {
+ result = API::moduleImport("fastapi").getMember("APIRouter").getReturn()
+ }
+ }
+
+ // ---------------------------------------------------------------------------
+ // routing modeling
+ // ---------------------------------------------------------------------------
+ /**
+ * A call to a method like `get` or `post` on a FastAPI application.
+ *
+ * See https://fastapi.tiangolo.com/tutorial/first-steps/#define-a-path-operation-decorator
+ */
+ private class FastApiRouteSetup extends HTTP::Server::RouteSetup::Range, DataFlow::CallCfgNode {
+ FastApiRouteSetup() {
+ exists(string routeAddingMethod |
+ routeAddingMethod = HTTP::httpVerbLower()
+ or
+ routeAddingMethod in ["api_route", "websocket"]
+ |
+ this = App::instance().getMember(routeAddingMethod).getACall()
+ or
+ this = APIRouter::instance().getMember(routeAddingMethod).getACall()
+ )
+ }
+
+ override Parameter getARoutedParameter() {
+ // this will need to be refined a bit, since you can add special parameters to
+ // your request handler functions that are used to pass in the response. There
+ // might be other special cases as well, but as a start this is not too far off
+ // the mark.
+ result = this.getARequestHandler().getArgByName(_) and
+ // type-annotated with `Response`
+ not any(Response::RequestHandlerParam src).asExpr() = result
+ }
+
+ override DataFlow::Node getUrlPatternArg() {
+ result in [this.getArg(0), this.getArgByName("path")]
+ }
+
+ override Function getARequestHandler() { result.getADecorator().getAFlowNode() = node }
+
+ override string getFramework() { result = "FastAPI" }
+
+ /** Gets the argument specifying the response class to use, if any. */
+ DataFlow::Node getResponseClassArg() { result = this.getArgByName("response_class") }
+ }
+
+ /**
+ * A parameter to a request handler that has a type-annotation with a class that is a
+ * Pydantic model.
+ */
+ private class PydanticModelRequestHandlerParam extends Pydantic::BaseModel::InstanceSource,
+ DataFlow::ParameterNode {
+ PydanticModelRequestHandlerParam() {
+ this.getParameter().getAnnotation() = Pydantic::BaseModel::subclassRef().getAUse().asExpr() and
+ any(FastApiRouteSetup rs).getARequestHandler().getArgByName(_) = this.getParameter()
+ }
+ }
+
+ // ---------------------------------------------------------------------------
+ // Response modeling
+ // ---------------------------------------------------------------------------
+ /**
+ * A parameter to a request handler that has a WebSocket type-annotation.
+ */
+ private class WebSocketRequestHandlerParam extends Starlette::WebSocket::InstanceSource,
+ DataFlow::ParameterNode {
+ WebSocketRequestHandlerParam() {
+ this.getParameter().getAnnotation() = Starlette::WebSocket::classRef().getAUse().asExpr() and
+ any(FastApiRouteSetup rs).getARequestHandler().getArgByName(_) = this.getParameter()
+ }
+ }
+
+ /**
+ * Provides models for the `fastapi.Response` class and subclasses.
+ *
+ * See https://fastapi.tiangolo.com/advanced/custom-response/#response.
+ */
+ module Response {
+ /**
+ * Gets the `API::Node` for the manually modeled response classes called `name`.
+ */
+ private API::Node getModeledResponseClass(string name) {
+ name = "Response" and
+ result = API::moduleImport("fastapi").getMember(name)
+ or
+ // see https://github.com/tiangolo/fastapi/blob/master/fastapi/responses.py
+ name in [
+ "Response", "HTMLResponse", "PlainTextResponse", "JSONResponse", "UJSONResponse",
+ "ORJSONResponse", "RedirectResponse", "StreamingResponse", "FileResponse"
+ ] and
+ result = API::moduleImport("fastapi").getMember("responses").getMember(name)
+ }
+
+ /**
+ * Gets the default MIME type for a FastAPI response class (defined with the
+ * `media_type` class-attribute).
+ *
+ * Also models user-defined classes and tries to take inheritance into account.
+ *
+ * TODO: build easy way to solve problems like this, like we used to have the
+ * `ClassValue.lookup` predicate.
+ */
+ private string getDefaultMimeType(API::Node responseClass) {
+ exists(string name | responseClass = getModeledResponseClass(name) |
+ // no defaults for these.
+ name in ["Response", "RedirectResponse", "StreamingResponse"] and
+ none()
+ or
+ // For `FileResponse` the code will guess what mimetype
+ // to use, or fall back to "text/plain", but claiming that all responses will
+ // have "text/plain" per default is also highly inaccurate, so just going to not
+ // do anything about this.
+ name = "FileResponse" and
+ none()
+ or
+ name = "HTMLResponse" and
+ result = "text/html"
+ or
+ name = "PlainTextResponse" and
+ result = "text/plain"
+ or
+ name in ["JSONResponse", "UJSONResponse", "ORJSONResponse"] and
+ result = "application/json"
+ )
+ or
+ // user-defined subclasses
+ exists(Class cls, API::Node base |
+ base = getModeledResponseClass(_).getASubclass*() and
+ cls.getABase() = base.getAUse().asExpr() and
+ responseClass.getAnImmediateUse().asExpr().(ClassExpr) = cls.getParent()
+ |
+ exists(Assign assign | assign = cls.getAStmt() |
+ assign.getATarget().(Name).getId() = "media_type" and
+ result = assign.getValue().(StrConst).getText()
+ )
+ or
+ // TODO: this should use a proper MRO calculation instead
+ not exists(Assign assign | assign = cls.getAStmt() |
+ assign.getATarget().(Name).getId() = "media_type"
+ ) and
+ result = getDefaultMimeType(base)
+ )
+ }
+
+ /**
+ * A source of instances of `fastapi.Response` and its' subclasses, extend this class to model new instances.
+ *
+ * This can include instantiations of the class, return values from function
+ * calls, or a special parameter that will be set when functions are called by an external
+ * library.
+ *
+ * Use the predicate `Response::instance()` to get references to instances of `fastapi.Response`.
+ */
+ abstract class InstanceSource extends DataFlow::LocalSourceNode { }
+
+ /** A direct instantiation of a response class. */
+ private class ResponseInstantiation extends InstanceSource, HTTP::Server::HttpResponse::Range,
+ DataFlow::CallCfgNode {
+ API::Node baseApiNode;
+ API::Node responseClass;
+
+ ResponseInstantiation() {
+ baseApiNode = getModeledResponseClass(_) and
+ responseClass = baseApiNode.getASubclass*() and
+ this = responseClass.getACall()
+ }
+
+ override DataFlow::Node getBody() {
+ not baseApiNode = getModeledResponseClass(["RedirectResponse", "FileResponse"]) and
+ result in [this.getArg(0), this.getArgByName("content")]
+ }
+
+ override DataFlow::Node getMimetypeOrContentTypeArg() {
+ not baseApiNode = getModeledResponseClass("RedirectResponse") and
+ result in [this.getArg(3), this.getArgByName("media_type")]
+ }
+
+ override string getMimetypeDefault() { result = getDefaultMimeType(responseClass) }
+ }
+
+ /**
+ * A direct instantiation of a redirect response.
+ */
+ private class RedirectResponseInstantiation extends ResponseInstantiation,
+ HTTP::Server::HttpRedirectResponse::Range {
+ RedirectResponseInstantiation() { baseApiNode = getModeledResponseClass("RedirectResponse") }
+
+ override DataFlow::Node getRedirectLocation() {
+ result in [this.getArg(0), this.getArgByName("url")]
+ }
+ }
+
+ /**
+ * An implicit response from a return of FastAPI request handler.
+ */
+ private class FastApiRequestHandlerReturn extends HTTP::Server::HttpResponse::Range,
+ DataFlow::CfgNode {
+ FastApiRouteSetup routeSetup;
+
+ FastApiRequestHandlerReturn() {
+ node = routeSetup.getARequestHandler().getAReturnValueFlowNode()
+ }
+
+ override DataFlow::Node getBody() { result = this }
+
+ override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
+
+ override string getMimetypeDefault() {
+ exists(API::Node responseClass |
+ responseClass.getAUse() = routeSetup.getResponseClassArg() and
+ result = getDefaultMimeType(responseClass)
+ )
+ or
+ not exists(routeSetup.getResponseClassArg()) and
+ result = "application/json"
+ }
+ }
+
+ /**
+ * An implicit response from a return of FastAPI request handler, that has
+ * `response_class` set to a `FileResponse`.
+ */
+ private class FastApiRequestHandlerFileResponseReturn extends FastApiRequestHandlerReturn {
+ FastApiRequestHandlerFileResponseReturn() {
+ exists(API::Node responseClass |
+ responseClass.getAUse() = routeSetup.getResponseClassArg() and
+ responseClass = getModeledResponseClass("FileResponse").getASubclass*()
+ )
+ }
+
+ override DataFlow::Node getBody() { none() }
+ }
+
+ /**
+ * An implicit response from a return of FastAPI request handler, that has
+ * `response_class` set to a `RedirectResponse`.
+ */
+ private class FastApiRequestHandlerRedirectReturn extends FastApiRequestHandlerReturn,
+ HTTP::Server::HttpRedirectResponse::Range {
+ FastApiRequestHandlerRedirectReturn() {
+ exists(API::Node responseClass |
+ responseClass.getAUse() = routeSetup.getResponseClassArg() and
+ responseClass = getModeledResponseClass("RedirectResponse").getASubclass*()
+ )
+ }
+
+ override DataFlow::Node getBody() { none() }
+
+ override DataFlow::Node getRedirectLocation() { result = this }
+ }
+
+ /**
+ * INTERNAL: Do not use.
+ *
+ * A parameter to a FastAPI request-handler that has a `fastapi.Response`
+ * type-annotation.
+ */
+ class RequestHandlerParam extends InstanceSource, DataFlow::ParameterNode {
+ RequestHandlerParam() {
+ this.getParameter().getAnnotation() =
+ getModeledResponseClass(_).getASubclass*().getAUse().asExpr() and
+ any(FastApiRouteSetup rs).getARequestHandler().getArgByName(_) = this.getParameter()
+ }
+ }
+
+ /** Gets a reference to an instance of `fastapi.Response`. */
+ private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
+ t.start() and
+ result instanceof InstanceSource
+ or
+ exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
+ }
+
+ /** Gets a reference to an instance of `fastapi.Response`. */
+ DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
+
+ /**
+ * A call to `set_cookie` on a FastAPI Response.
+ */
+ private class SetCookieCall extends HTTP::Server::CookieWrite::Range, DataFlow::MethodCallNode {
+ SetCookieCall() { this.calls(instance(), "set_cookie") }
+
+ override DataFlow::Node getHeaderArg() { none() }
+
+ override DataFlow::Node getNameArg() { result in [this.getArg(0), this.getArgByName("key")] }
+
+ override DataFlow::Node getValueArg() {
+ result in [this.getArg(1), this.getArgByName("value")]
+ }
+ }
+
+ /**
+ * A call to `append` on a `headers` of a FastAPI Response, with the `Set-Cookie`
+ * header-key.
+ */
+ private class HeadersAppendCookie extends HTTP::Server::CookieWrite::Range,
+ DataFlow::MethodCallNode {
+ HeadersAppendCookie() {
+ exists(DataFlow::AttrRead headers, DataFlow::Node keyArg |
+ headers.accesses(instance(), "headers") and
+ this.calls(headers, "append") and
+ keyArg in [this.getArg(0), this.getArgByName("key")] and
+ keyArg.getALocalSource().asExpr().(StrConst).getText().toLowerCase() = "set-cookie"
+ )
+ }
+
+ override DataFlow::Node getHeaderArg() {
+ result in [this.getArg(1), this.getArgByName("value")]
+ }
+
+ override DataFlow::Node getNameArg() { none() }
+
+ override DataFlow::Node getValueArg() { none() }
+ }
+ }
+}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Flask.qll b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Flask.qll
index e854e07658b..88544ca8537 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Flask.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Flask.qll
@@ -11,6 +11,7 @@ private import semmle.python.Concepts
private import semmle.python.frameworks.Werkzeug
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
+private import semmle.python.security.dataflow.PathInjectionCustomizations
/**
* Provides models for the `flask` PyPI package.
@@ -73,7 +74,11 @@ module Flask {
*/
module Blueprint {
/** Gets a reference to the `flask.Blueprint` class. */
- API::Node classRef() { result = API::moduleImport("flask").getMember("Blueprint") }
+ API::Node classRef() {
+ result = API::moduleImport("flask").getMember("Blueprint")
+ or
+ result = API::moduleImport("flask").getMember("blueprints").getMember("Blueprint")
+ }
/** Gets a reference to an instance of `flask.Blueprint`. */
API::Node instance() { result = classRef().getReturn() }
@@ -233,7 +238,7 @@ module Flask {
}
/** A route setup made by flask (sharing handling of URL patterns). */
- abstract private class FlaskRouteSetup extends HTTP::Server::RouteSetup::Range {
+ abstract class FlaskRouteSetup extends HTTP::Server::RouteSetup::Range {
override Parameter getARoutedParameter() {
// If we don't know the URL pattern, we simply mark all parameters as a routed
// parameter. This should give us more RemoteFlowSources but could also lead to
@@ -292,12 +297,12 @@ module Flask {
override Function getARequestHandler() {
exists(DataFlow::LocalSourceNode func_src |
- func_src.flowsTo(getViewArg()) and
+ func_src.flowsTo(this.getViewArg()) and
func_src.asExpr().(CallableExpr) = result.getDefinition()
)
or
exists(FlaskViewClass vc |
- getViewArg() = vc.asViewResult().getAUse() and
+ this.getViewArg() = vc.asViewResult().getAUse() and
result = vc.getARequestHandler()
)
}
@@ -519,4 +524,49 @@ module Flask {
override DataFlow::Node getValueArg() { none() }
}
+
+ /**
+ * A call to `flask.send_from_directory`.
+ *
+ * See https://flask.palletsprojects.com/en/1.1.x/api/#flask.send_from_directory
+ */
+ private class FlaskSendFromDirectoryCall extends FileSystemAccess::Range, DataFlow::CallCfgNode {
+ FlaskSendFromDirectoryCall() {
+ this = API::moduleImport("flask").getMember("send_from_directory").getACall()
+ }
+
+ override DataFlow::Node getAPathArgument() {
+ result in [
+ this.getArg(0), this.getArgByName("directory"),
+ // as described in the docs, the `filename` argument is restrained to be within
+ // the provided directory, so is not exposed to path-injection. (but is still a
+ // path-argument).
+ this.getArg(1), this.getArgByName("filename")
+ ]
+ }
+ }
+
+ /**
+ * To exclude `filename` argument to `flask.send_from_directory` as a path-injection sink.
+ */
+ private class FlaskSendFromDirectoryCallFilenameSanitizer extends PathInjection::Sanitizer {
+ FlaskSendFromDirectoryCallFilenameSanitizer() {
+ this = any(FlaskSendFromDirectoryCall c).getArg(1)
+ or
+ this = any(FlaskSendFromDirectoryCall c).getArgByName("filename")
+ }
+ }
+
+ /**
+ * A call to `flask.send_file`.
+ *
+ * See https://flask.palletsprojects.com/en/1.1.x/api/#flask.send_file
+ */
+ private class FlaskSendFileCall extends FileSystemAccess::Range, DataFlow::CallCfgNode {
+ FlaskSendFileCall() { this = API::moduleImport("flask").getMember("send_file").getACall() }
+
+ override DataFlow::Node getAPathArgument() {
+ result in [this.getArg(0), this.getArgByName("filename_or_fp")]
+ }
+ }
}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/FlaskAdmin.qll b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/FlaskAdmin.qll
new file mode 100644
index 00000000000..a9b90f1d9c4
--- /dev/null
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/FlaskAdmin.qll
@@ -0,0 +1,79 @@
+/**
+ * Provides classes modeling security-relevant aspects of the `Flask-Admin` PyPI package
+ * (imported as `flask_admin`).
+ *
+ * See
+ * - https://flask-admin.readthedocs.io/en/latest/
+ * - https://pypi.org/project/Flask-Admin/
+ */
+
+private import python
+private import semmle.python.dataflow.new.DataFlow
+private import semmle.python.dataflow.new.RemoteFlowSources
+private import semmle.python.dataflow.new.TaintTracking
+private import semmle.python.Concepts
+private import semmle.python.frameworks.Flask
+private import semmle.python.ApiGraphs
+
+/**
+ * Provides models for the `Flask-Admin` PyPI package (imported as `flask_admin`).
+ *
+ * See
+ * - https://flask-admin.readthedocs.io/en/latest/
+ * - https://pypi.org/project/Flask-Admin/
+ */
+private module FlaskAdmin {
+ /**
+ * A call to `flask_admin.expose`, which is used as a decorator to make the
+ * function exposed in the admin interface (and make it a request handler)
+ *
+ * See https://flask-admin.readthedocs.io/en/latest/api/mod_base/#flask_admin.base.expose
+ */
+ private class FlaskAdminExposeCall extends Flask::FlaskRouteSetup, DataFlow::CallCfgNode {
+ FlaskAdminExposeCall() {
+ this = API::moduleImport("flask_admin").getMember("expose").getACall()
+ }
+
+ override DataFlow::Node getUrlPatternArg() {
+ result in [this.getArg(0), this.getArgByName("url")]
+ }
+
+ override Function getARequestHandler() { result.getADecorator().getAFlowNode() = node }
+ }
+
+ /**
+ * A call to `flask_admin.expose_plugview`, which is used as a decorator to make the
+ * class (which we expect to be a flask View class) exposed in the admin interface.
+ *
+ * See https://flask-admin.readthedocs.io/en/latest/api/mod_base/#flask_admin.base.expose_plugview
+ */
+ private class FlaskAdminExposePlugviewCall extends Flask::FlaskRouteSetup, DataFlow::CallCfgNode {
+ FlaskAdminExposePlugviewCall() {
+ this = API::moduleImport("flask_admin").getMember("expose_plugview").getACall()
+ }
+
+ override DataFlow::Node getUrlPatternArg() {
+ result in [this.getArg(0), this.getArgByName("url")]
+ }
+
+ override Parameter getARoutedParameter() {
+ result = super.getARoutedParameter() and
+ (
+ exists(this.getUrlPattern())
+ or
+ // the first argument is `self`, and the second argument `cls` will receive the
+ // containing flask_admin View class -- this is only relevant if the URL pattern
+ // is not known
+ not exists(this.getUrlPattern()) and
+ not result = this.getARequestHandler().getArg([0, 1])
+ )
+ }
+
+ override Function getARequestHandler() {
+ exists(Flask::FlaskViewClass cls |
+ cls.getADecorator().getAFlowNode() = node and
+ result = cls.getARequestHandler()
+ )
+ }
+ }
+}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Pydantic.qll b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Pydantic.qll
new file mode 100644
index 00000000000..f2d86cf381d
--- /dev/null
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Pydantic.qll
@@ -0,0 +1,108 @@
+/**
+ * Provides classes modeling security-relevant aspects of the `pydantic` PyPI package.
+ *
+ * See
+ * - https://pypi.org/project/pydantic/
+ * - https://pydantic-docs.helpmanual.io/
+ */
+
+private import python
+private import semmle.python.dataflow.new.DataFlow
+private import semmle.python.dataflow.new.TaintTracking
+private import semmle.python.Concepts
+private import semmle.python.ApiGraphs
+
+/**
+ * INTERNAL: Do not use.
+ *
+ * Provides models for `pydantic` PyPI package.
+ *
+ * See
+ * - https://pypi.org/project/pydantic/
+ * - https://pydantic-docs.helpmanual.io/
+ */
+module Pydantic {
+ /**
+ * Provides models for `pydantic.BaseModel` subclasses (a pydantic model).
+ *
+ * See https://pydantic-docs.helpmanual.io/usage/models/.
+ */
+ module BaseModel {
+ /** Gets a reference to a `pydantic.BaseModel` subclass (a pydantic model). */
+ API::Node subclassRef() {
+ result = API::moduleImport("pydantic").getMember("BaseModel").getASubclass+()
+ }
+
+ /**
+ * A source of instances of `pydantic.BaseModel` subclasses, extend this class to model new instances.
+ *
+ * This can include instantiations of the class, return values from function
+ * calls, or a special parameter that will be set when functions are called by an external
+ * library.
+ *
+ * Use the predicate `BaseModel::instance()` to get references to instances of `pydantic.BaseModel`.
+ */
+ abstract class InstanceSource extends DataFlow::LocalSourceNode { }
+
+ /** Gets a reference to an instance of a `pydantic.BaseModel` subclass. */
+ private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
+ t.start() and
+ result instanceof InstanceSource
+ or
+ t.start() and
+ instanceStepToPydanticModel(_, result)
+ or
+ exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
+ }
+
+ /** Gets a reference to an instance of a `pydantic.BaseModel` subclass. */
+ DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
+
+ /**
+ * A step from an instance of a `pydantic.BaseModel` subclass, that might result in
+ * an instance of a `pydantic.BaseModel` subclass.
+ *
+ * NOTE: We currently overapproximate, and treat all attributes as containing
+ * another pydantic model. For the code below, we _could_ limit this to `main_foo`
+ * and members of `other_foos`. IF THIS IS CHANGED, YOU MUST CHANGE THE ADDITIONAL
+ * TAINT STEPS BELOW, SUCH THAT SIMPLE ACCESS OF SOMETHIGN LIKE `str` IS STILL
+ * TAINTED.
+ *
+ *
+ * ```py
+ * class MyComplexModel(BaseModel):
+ * field: str
+ * main_foo: Foo
+ * other_foos: List[Foo]
+ * ```
+ */
+ private predicate instanceStepToPydanticModel(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
+ // attributes (such as `model.foo`)
+ nodeFrom = instance() and
+ nodeTo.(DataFlow::AttrRead).getObject() = nodeFrom
+ or
+ // subscripts on attributes (such as `model.foo[0]`). This needs to handle nested
+ // lists (such as `model.foo[0][0]`), and access being split into multiple
+ // statements (such as `xs = model.foo; xs[0]`).
+ //
+ // To handle this we overapproximate which things are a Pydantic model, by
+ // treating any subscript on anything that originates on a Pydantic model to also
+ // be a Pydantic model. So `model[0]` will be an overapproximation, but should not
+ // really cause problems (since we don't expect real code to contain such accesses)
+ nodeFrom = instance() and
+ nodeTo.asCfgNode().(SubscriptNode).getObject() = nodeFrom.asCfgNode()
+ }
+
+ /**
+ * Extra taint propagation for `pydantic.BaseModel` subclasses. (note that these could also be `pydantic.BaseModel` subclasses)
+ */
+ private class AdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
+ override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
+ // NOTE: if `instanceStepToPydanticModel` is changed to be more precise, these
+ // taint steps should be expanded, such that a field that has type `str` is
+ // still tainted.
+ instanceStepToPydanticModel(nodeFrom, nodeTo)
+ }
+ }
+ }
+}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/RestFramework.qll b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/RestFramework.qll
new file mode 100644
index 00000000000..a6373df7120
--- /dev/null
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/RestFramework.qll
@@ -0,0 +1,369 @@
+/**
+ * Provides classes modeling security-relevant aspects of the `djangorestframework` PyPI package
+ * (imported as `rest_framework`)
+ *
+ * See
+ * - https://www.django-rest-framework.org/
+ * - https://pypi.org/project/djangorestframework/
+ */
+
+private import python
+private import semmle.python.dataflow.new.DataFlow
+private import semmle.python.dataflow.new.RemoteFlowSources
+private import semmle.python.dataflow.new.TaintTracking
+private import semmle.python.Concepts
+private import semmle.python.ApiGraphs
+private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
+private import semmle.python.frameworks.Django
+private import semmle.python.frameworks.Stdlib
+
+/**
+ * INTERNAL: Do not use.
+ *
+ * Provides models for the `djangorestframework` PyPI package
+ * (imported as `rest_framework`)
+ *
+ * See
+ * - https://www.django-rest-framework.org/
+ * - https://pypi.org/project/djangorestframework/
+ */
+private module RestFramework {
+ // ---------------------------------------------------------------------------
+ // rest_framework.views.APIView handling
+ // ---------------------------------------------------------------------------
+ /**
+ * An `API::Node` representing the `rest_framework.views.APIView` class or any subclass
+ * that has explicitly been modeled in the CodeQL libraries.
+ */
+ private class ModeledApiViewClasses extends Django::Views::View::ModeledSubclass {
+ ModeledApiViewClasses() {
+ this = API::moduleImport("rest_framework").getMember("views").getMember("APIView")
+ or
+ // imports generated by python/frameworks/internal/SubclassFinder.qll
+ this =
+ API::moduleImport("rest_framework")
+ .getMember("authtoken")
+ .getMember("views")
+ .getMember("APIView")
+ or
+ this =
+ API::moduleImport("rest_framework")
+ .getMember("authtoken")
+ .getMember("views")
+ .getMember("ObtainAuthToken")
+ or
+ this = API::moduleImport("rest_framework").getMember("decorators").getMember("APIView")
+ or
+ this = API::moduleImport("rest_framework").getMember("generics").getMember("CreateAPIView")
+ or
+ this = API::moduleImport("rest_framework").getMember("generics").getMember("DestroyAPIView")
+ or
+ this = API::moduleImport("rest_framework").getMember("generics").getMember("GenericAPIView")
+ or
+ this = API::moduleImport("rest_framework").getMember("generics").getMember("ListAPIView")
+ or
+ this =
+ API::moduleImport("rest_framework").getMember("generics").getMember("ListCreateAPIView")
+ or
+ this = API::moduleImport("rest_framework").getMember("generics").getMember("RetrieveAPIView")
+ or
+ this =
+ API::moduleImport("rest_framework")
+ .getMember("generics")
+ .getMember("RetrieveDestroyAPIView")
+ or
+ this =
+ API::moduleImport("rest_framework").getMember("generics").getMember("RetrieveUpdateAPIView")
+ or
+ this =
+ API::moduleImport("rest_framework")
+ .getMember("generics")
+ .getMember("RetrieveUpdateDestroyAPIView")
+ or
+ this = API::moduleImport("rest_framework").getMember("generics").getMember("UpdateAPIView")
+ or
+ this = API::moduleImport("rest_framework").getMember("routers").getMember("APIRootView")
+ or
+ this = API::moduleImport("rest_framework").getMember("routers").getMember("SchemaView")
+ or
+ this =
+ API::moduleImport("rest_framework")
+ .getMember("schemas")
+ .getMember("views")
+ .getMember("APIView")
+ or
+ this =
+ API::moduleImport("rest_framework")
+ .getMember("schemas")
+ .getMember("views")
+ .getMember("SchemaView")
+ or
+ this = API::moduleImport("rest_framework").getMember("viewsets").getMember("GenericViewSet")
+ or
+ this = API::moduleImport("rest_framework").getMember("viewsets").getMember("ModelViewSet")
+ or
+ this =
+ API::moduleImport("rest_framework").getMember("viewsets").getMember("ReadOnlyModelViewSet")
+ or
+ this = API::moduleImport("rest_framework").getMember("viewsets").getMember("ViewSet")
+ }
+ }
+
+ /**
+ * A class that has a super-type which is a rest_framework APIView class, therefore also
+ * becoming a APIView class.
+ */
+ class RestFrameworkApiViewClass extends PrivateDjango::DjangoViewClassFromSuperClass {
+ RestFrameworkApiViewClass() {
+ this.getABase() = any(ModeledApiViewClasses c).getASubclass*().getAUse().asExpr()
+ }
+
+ override Function getARequestHandler() {
+ result = super.getARequestHandler()
+ or
+ // TODO: This doesn't handle attribute assignment. Should be OK, but analysis is not as complete as with
+ // points-to and `.lookup`, which would handle `post = my_post_handler` inside class def
+ result = this.getAMethod() and
+ result.getName() in [
+ // these method names where found by looking through the APIView
+ // implementation in
+ // https://github.com/encode/django-rest-framework/blob/master/rest_framework/views.py#L104
+ "initial", "http_method_not_allowed", "permission_denied", "throttled",
+ "get_authenticate_header", "perform_content_negotiation", "perform_authentication",
+ "check_permissions", "check_object_permissions", "check_throttles", "determine_version",
+ "initialize_request", "finalize_response", "dispatch", "options"
+ ]
+ }
+ }
+
+ // ---------------------------------------------------------------------------
+ // rest_framework.decorators.api_view handling
+ // ---------------------------------------------------------------------------
+ /**
+ * A function that is a request handler since it is decorated with `rest_framework.decorators.api_view`
+ */
+ class RestFrameworkFunctionBasedView extends PrivateDjango::DjangoRouteHandler::Range {
+ RestFrameworkFunctionBasedView() {
+ this.getADecorator() =
+ API::moduleImport("rest_framework")
+ .getMember("decorators")
+ .getMember("api_view")
+ .getACall()
+ .asExpr()
+ }
+ }
+
+ /**
+ * Ensuring that all `RestFrameworkFunctionBasedView` are also marked as a
+ * `HTTP::Server::RequestHandler`. We only need this for the ones that doesn't have a
+ * known route setup.
+ */
+ class RestFrameworkFunctionBasedViewWithoutKnownRoute extends HTTP::Server::RequestHandler::Range,
+ PrivateDjango::DjangoRouteHandler instanceof RestFrameworkFunctionBasedView {
+ RestFrameworkFunctionBasedViewWithoutKnownRoute() {
+ not exists(PrivateDjango::DjangoRouteSetup setup | setup.getARequestHandler() = this)
+ }
+
+ override Parameter getARoutedParameter() {
+ // Since we don't know the URL pattern, we simply mark all parameters as a routed
+ // parameter. This should give us more RemoteFlowSources but could also lead to
+ // more FPs. If this turns out to be the wrong tradeoff, we can always change our mind.
+ result in [this.getArg(_), this.getArgByName(_)] and
+ not result = any(int i | i < this.getFirstPossibleRoutedParamIndex() | this.getArg(i))
+ }
+
+ override string getFramework() { result = "Django (rest_framework)" }
+ }
+
+ // ---------------------------------------------------------------------------
+ // request modeling
+ // ---------------------------------------------------------------------------
+ /**
+ * A parameter that will receive a `rest_framework.request.Request` instance when a
+ * request handler is invoked.
+ */
+ private class RestFrameworkRequestHandlerRequestParam extends Request::InstanceSource,
+ RemoteFlowSource::Range, DataFlow::ParameterNode {
+ RestFrameworkRequestHandlerRequestParam() {
+ // rest_framework.views.APIView subclass
+ exists(RestFrameworkApiViewClass vc |
+ this.getParameter() =
+ vc.getARequestHandler().(PrivateDjango::DjangoRouteHandler).getRequestParam()
+ )
+ or
+ // annotated with @api_view decorator
+ exists(PrivateDjango::DjangoRouteHandler rh | rh instanceof RestFrameworkFunctionBasedView |
+ this.getParameter() = rh.getRequestParam()
+ )
+ }
+
+ override string getSourceType() { result = "rest_framework.request.HttpRequest" }
+ }
+
+ /**
+ * Provides models for the `rest_framework.request.Request` class
+ *
+ * See https://www.django-rest-framework.org/api-guide/requests/.
+ */
+ module Request {
+ /** Gets a reference to the `rest_framework.request.Request` class. */
+ private API::Node classRef() {
+ result = API::moduleImport("rest_framework").getMember("request").getMember("Request")
+ }
+
+ /**
+ * A source of instances of `rest_framework.request.Request`, extend this class to model new instances.
+ *
+ * This can include instantiations of the class, return values from function
+ * calls, or a special parameter that will be set when functions are called by an external
+ * library.
+ *
+ * Use the predicate `Request::instance()` to get references to instances of `rest_framework.request.Request`.
+ */
+ abstract class InstanceSource extends PrivateDjango::django::http::request::HttpRequest::InstanceSource {
+ }
+
+ /** A direct instantiation of `rest_framework.request.Request`. */
+ private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
+ ClassInstantiation() { this = classRef().getACall() }
+ }
+
+ /** Gets a reference to an instance of `rest_framework.request.Request`. */
+ private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
+ t.start() and
+ result instanceof InstanceSource
+ or
+ exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
+ }
+
+ /** Gets a reference to an instance of `rest_framework.request.Request`. */
+ DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
+
+ /**
+ * Taint propagation for `rest_framework.request.Request`.
+ */
+ private class InstanceTaintSteps extends InstanceTaintStepsHelper {
+ InstanceTaintSteps() { this = "rest_framework.request.Request" }
+
+ override DataFlow::Node getInstance() { result = instance() }
+
+ override string getAttributeName() {
+ result in ["data", "query_params", "user", "auth", "content_type", "stream"]
+ }
+
+ override string getMethodName() { none() }
+
+ override string getAsyncMethodName() { none() }
+ }
+
+ /** An attribute read that is a `MultiValueDict` instance. */
+ private class MultiValueDictInstances extends Django::MultiValueDict::InstanceSource {
+ MultiValueDictInstances() {
+ this.(DataFlow::AttrRead).getObject() = instance() and
+ this.(DataFlow::AttrRead).getAttributeName() = "query_params"
+ }
+ }
+
+ /** An attribute read that is a `User` instance. */
+ private class UserInstances extends Django::User::InstanceSource {
+ UserInstances() {
+ this.(DataFlow::AttrRead).getObject() = instance() and
+ this.(DataFlow::AttrRead).getAttributeName() = "user"
+ }
+ }
+
+ /** An attribute read that is a file-like instance. */
+ private class FileLikeInstances extends Stdlib::FileLikeObject::InstanceSource {
+ FileLikeInstances() {
+ this.(DataFlow::AttrRead).getObject() = instance() and
+ this.(DataFlow::AttrRead).getAttributeName() = "stream"
+ }
+ }
+ }
+
+ // ---------------------------------------------------------------------------
+ // response modeling
+ // ---------------------------------------------------------------------------
+ /**
+ * Provides models for the `rest_framework.response.Response` class
+ *
+ * See https://www.django-rest-framework.org/api-guide/responses/.
+ */
+ module Response {
+ /** Gets a reference to the `rest_framework.response.Response` class. */
+ private API::Node classRef() {
+ result = API::moduleImport("rest_framework").getMember("response").getMember("Response")
+ }
+
+ /**
+ * A source of instances of `rest_framework.response.Response`, extend this class to model new instances.
+ *
+ * This can include instantiations of the class, return values from function
+ * calls, or a special parameter that will be set when functions are called by an external
+ * library.
+ *
+ * Use the predicate `Response::instance()` to get references to instances of `rest_framework.response.Response`.
+ */
+ abstract class InstanceSource extends DataFlow::LocalSourceNode { }
+
+ /** A direct instantiation of `rest_framework.response.Response`. */
+ private class ClassInstantiation extends PrivateDjango::django::http::response::HttpResponse::InstanceSource,
+ DataFlow::CallCfgNode {
+ ClassInstantiation() { this = classRef().getACall() }
+
+ override DataFlow::Node getBody() { result in [this.getArg(0), this.getArgByName("data")] }
+
+ override DataFlow::Node getMimetypeOrContentTypeArg() {
+ result in [this.getArg(5), this.getArgByName("content_type")]
+ }
+
+ override string getMimetypeDefault() { none() }
+ }
+ }
+
+ // ---------------------------------------------------------------------------
+ // Exception response modeling
+ // ---------------------------------------------------------------------------
+ /**
+ * Provides models for the `rest_framework.exceptions.APIException` class and subclasses
+ *
+ * See https://www.django-rest-framework.org/api-guide/exceptions/#api-reference
+ */
+ module APIException {
+ /** A direct instantiation of `rest_framework.exceptions.APIException` or subclass. */
+ private class ClassInstantiation extends HTTP::Server::HttpResponse::Range,
+ DataFlow::CallCfgNode {
+ string className;
+
+ ClassInstantiation() {
+ className in [
+ "APIException", "ValidationError", "ParseError", "AuthenticationFailed",
+ "NotAuthenticated", "PermissionDenied", "NotFound", "MethodNotAllowed", "NotAcceptable",
+ "UnsupportedMediaType", "Throttled"
+ ] and
+ this =
+ API::moduleImport("rest_framework")
+ .getMember("exceptions")
+ .getMember(className)
+ .getACall()
+ }
+
+ override DataFlow::Node getBody() {
+ className in [
+ "APIException", "ValidationError", "ParseError", "AuthenticationFailed",
+ "NotAuthenticated", "PermissionDenied", "NotFound", "NotAcceptable"
+ ] and
+ result = this.getArg(0)
+ or
+ className in ["MethodNotAllowed", "UnsupportedMediaType", "Throttled"] and
+ result = this.getArg(1)
+ or
+ result = this.getArgByName("detail")
+ }
+
+ override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
+
+ override string getMimetypeDefault() { none() }
+ }
+ }
+}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/RuamelYaml.qll b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/RuamelYaml.qll
new file mode 100644
index 00000000000..2d553c409b0
--- /dev/null
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/RuamelYaml.qll
@@ -0,0 +1,57 @@
+/**
+ * Provides classes modeling security-relevant aspects of the `ruamel.yaml` PyPI package
+ *
+ * See
+ * - https://pypi.org/project/ruamel.yaml/
+ */
+
+private import python
+private import semmle.python.dataflow.new.DataFlow
+private import semmle.python.Concepts
+private import semmle.python.ApiGraphs
+
+/**
+ * Provides models for the `ruamel.yaml` PyPI package.
+ *
+ * See
+ * - https://pypi.org/project/ruamel.yaml/
+ */
+private module RuamelYaml {
+ // Note: `ruamel.yaml` is a fork of the `PyYAML` PyPI package, so that's why the
+ // interface is so similar.
+ /**
+ * A call to any of the loading functions in `yaml` (`load`, `load_all`, `safe_load`, `safe_load_all`)
+ *
+ * See https://pyyaml.org/wiki/PyYAMLDocumentation (you will have to scroll down).
+ */
+ private class RuamelYamlLoadCall extends Decoding::Range, DataFlow::CallCfgNode {
+ string func_name;
+
+ RuamelYamlLoadCall() {
+ func_name in ["load", "load_all", "safe_load", "safe_load_all"] and
+ this = API::moduleImport("ruamel").getMember("yaml").getMember(func_name).getACall()
+ }
+
+ override predicate mayExecuteInput() {
+ func_name in ["load", "load_all"] and
+ // If the `Loader` argument is not set, the default loader will be used, which is
+ // not safe. The only safe loaders are `SafeLoader` or `BaseLoader` (and their
+ // variants with C implementation).
+ not exists(DataFlow::Node loader_arg |
+ loader_arg in [this.getArg(1), this.getArgByName("Loader")]
+ |
+ loader_arg =
+ API::moduleImport("ruamel")
+ .getMember("yaml")
+ .getMember(["SafeLoader", "BaseLoader", "CSafeLoader", "CBaseLoader"])
+ .getAUse()
+ )
+ }
+
+ override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("stream")] }
+
+ override DataFlow::Node getOutput() { result = this }
+
+ override string getFormat() { result = "YAML" }
+ }
+}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/SqlAlchemy.qll b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/SqlAlchemy.qll
index a74d44573f7..a343e3f4283 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/SqlAlchemy.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/SqlAlchemy.qll
@@ -313,9 +313,9 @@ module SqlAlchemy {
* A construction of a `sqlalchemy.sql.expression.TextClause`, which represents a
* textual SQL string directly.
*/
- abstract class TextClauseConstruction extends DataFlow::CallCfgNode {
+ abstract class TextClauseConstruction extends SqlConstruction::Range, DataFlow::CallCfgNode {
/** Gets the argument that specifies the SQL text. */
- DataFlow::Node getTextArg() { result in [this.getArg(0), this.getArgByName("text")] }
+ override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("text")] }
}
/** `TextClause` constructions from the `sqlalchemy` package. */
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Starlette.qll b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Starlette.qll
new file mode 100644
index 00000000000..e22259d560c
--- /dev/null
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Starlette.qll
@@ -0,0 +1,162 @@
+/**
+ * Provides classes modeling security-relevant aspects of the `starlette` PyPI package.
+ *
+ * See
+ * - https://pypi.org/project/starlette/
+ * - https://www.starlette.io/
+ */
+
+private import python
+private import semmle.python.dataflow.new.DataFlow
+private import semmle.python.dataflow.new.TaintTracking
+private import semmle.python.Concepts
+private import semmle.python.ApiGraphs
+private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
+private import semmle.python.frameworks.Stdlib
+
+/**
+ * INTERNAL: Do not use.
+ *
+ * Provides models for `starlette` PyPI package.
+ *
+ * See
+ * - https://pypi.org/project/starlette/
+ * - https://www.starlette.io/
+ */
+module Starlette {
+ /**
+ * Provides models for the `starlette.websockets.WebSocket` class
+ *
+ * See https://www.starlette.io/websockets/.
+ */
+ module WebSocket {
+ /** Gets a reference to the `starlette.websockets.WebSocket` class. */
+ API::Node classRef() {
+ result = API::moduleImport("starlette").getMember("websockets").getMember("WebSocket")
+ or
+ result = API::moduleImport("fastapi").getMember("WebSocket")
+ }
+
+ /**
+ * A source of instances of `starlette.websockets.WebSocket`, extend this class to model new instances.
+ *
+ * This can include instantiations of the class, return values from function
+ * calls, or a special parameter that will be set when functions are called by an external
+ * library.
+ *
+ * Use the predicate `WebSocket::instance()` to get references to instances of `starlette.websockets.WebSocket`.
+ */
+ abstract class InstanceSource extends DataFlow::LocalSourceNode { }
+
+ /** A direct instantiation of `starlette.websockets.WebSocket`. */
+ private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
+ ClassInstantiation() { this = classRef().getACall() }
+ }
+
+ /** Gets a reference to an instance of `starlette.websockets.WebSocket`. */
+ private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
+ t.start() and
+ result instanceof InstanceSource
+ or
+ exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
+ }
+
+ /** Gets a reference to an instance of `starlette.websockets.WebSocket`. */
+ DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
+
+ /**
+ * Taint propagation for `starlette.websockets.WebSocket`.
+ */
+ private class InstanceTaintSteps extends InstanceTaintStepsHelper {
+ InstanceTaintSteps() { this = "starlette.websockets.WebSocket" }
+
+ override DataFlow::Node getInstance() { result = instance() }
+
+ override string getAttributeName() { result in ["url", "headers", "query_params", "cookies"] }
+
+ override string getMethodName() { none() }
+
+ override string getAsyncMethodName() {
+ result in [
+ "receive", "receive_bytes", "receive_text", "receive_json", "iter_bytes", "iter_text",
+ "iter_json"
+ ]
+ }
+ }
+
+ /** An attribute read on a `starlette.websockets.WebSocket` instance that is a `starlette.requests.URL` instance. */
+ private class UrlInstances extends URL::InstanceSource {
+ UrlInstances() {
+ this.(DataFlow::AttrRead).getObject() = instance() and
+ this.(DataFlow::AttrRead).getAttributeName() = "url"
+ }
+ }
+ }
+
+ /**
+ * Provides models for the `starlette.requests.URL` class
+ *
+ * See the URL part of https://www.starlette.io/websockets/.
+ */
+ module URL {
+ /** Gets a reference to the `starlette.requests.URL` class. */
+ private API::Node classRef() {
+ result = API::moduleImport("starlette").getMember("requests").getMember("URL")
+ }
+
+ /**
+ * A source of instances of `starlette.requests.URL`, extend this class to model new instances.
+ *
+ * This can include instantiations of the class, return values from function
+ * calls, or a special parameter that will be set when functions are called by an external
+ * library.
+ *
+ * Use the predicate `URL::instance()` to get references to instances of `starlette.requests.URL`.
+ */
+ abstract class InstanceSource extends DataFlow::LocalSourceNode { }
+
+ /** A direct instantiation of `starlette.requests.URL`. */
+ private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
+ ClassInstantiation() { this = classRef().getACall() }
+ }
+
+ /** Gets a reference to an instance of `starlette.requests.URL`. */
+ private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
+ t.start() and
+ result instanceof InstanceSource
+ or
+ exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
+ }
+
+ /** Gets a reference to an instance of `starlette.requests.URL`. */
+ DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
+
+ /**
+ * Taint propagation for `starlette.requests.URL`.
+ */
+ private class InstanceTaintSteps extends InstanceTaintStepsHelper {
+ InstanceTaintSteps() { this = "starlette.requests.URL" }
+
+ override DataFlow::Node getInstance() { result = instance() }
+
+ override string getAttributeName() {
+ result in [
+ "components", "netloc", "path", "query", "fragment", "username", "password", "hostname",
+ "port"
+ ]
+ }
+
+ override string getMethodName() { none() }
+
+ override string getAsyncMethodName() { none() }
+ }
+
+ /** An attribute read on a `starlette.requests.URL` instance that is a `urllib.parse.SplitResult` instance. */
+ private class UrlSplitInstances extends Stdlib::SplitResult::InstanceSource instanceof DataFlow::AttrRead {
+ UrlSplitInstances() {
+ super.getObject() = instance() and
+ super.getAttributeName() = "components"
+ }
+ }
+ }
+}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Stdlib.qll b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Stdlib.qll
index 86c2f0ff557..1b04c983f51 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Stdlib.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Stdlib.qll
@@ -167,6 +167,74 @@ module Stdlib {
override string getAsyncMethodName() { none() }
}
}
+
+ /**
+ * Provides models for the `urllib.parse.SplitResult` class
+ *
+ * See https://docs.python.org/3.9/library/urllib.parse.html#urllib.parse.SplitResult.
+ */
+ module SplitResult {
+ /** Gets a reference to the `urllib.parse.SplitResult` class. */
+ private API::Node classRef() {
+ result = API::moduleImport("urllib").getMember("parse").getMember("SplitResult")
+ }
+
+ /**
+ * A source of instances of `urllib.parse.SplitResult`, extend this class to model new instances.
+ *
+ * This can include instantiations of the class, return values from function
+ * calls, or a special parameter that will be set when functions are called by an external
+ * library.
+ *
+ * Use the predicate `SplitResult::instance()` to get references to instances of `urllib.parse.SplitResult`.
+ */
+ abstract class InstanceSource extends DataFlow::LocalSourceNode { }
+
+ /** A direct instantiation of `urllib.parse.SplitResult`. */
+ private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
+ ClassInstantiation() { this = classRef().getACall() }
+ }
+
+ /** Gets a reference to an instance of `urllib.parse.SplitResult`. */
+ private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
+ t.start() and
+ result instanceof InstanceSource
+ or
+ exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
+ }
+
+ /** Gets a reference to an instance of `urllib.parse.SplitResult`. */
+ DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
+
+ /**
+ * Taint propagation for `urllib.parse.SplitResult`.
+ */
+ private class InstanceTaintSteps extends InstanceTaintStepsHelper {
+ InstanceTaintSteps() { this = "urllib.parse.SplitResult" }
+
+ override DataFlow::Node getInstance() { result = instance() }
+
+ override string getAttributeName() {
+ result in [
+ "netloc", "path", "query", "fragment", "username", "password", "hostname", "port"
+ ]
+ }
+
+ override string getMethodName() { none() }
+
+ override string getAsyncMethodName() { none() }
+ }
+
+ /**
+ * Extra taint propagation for `urllib.parse.SplitResult`, not covered by `InstanceTaintSteps`.
+ */
+ private class AdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
+ override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
+ // TODO
+ none()
+ }
+ }
+ }
}
/**
@@ -462,8 +530,8 @@ private module StdlibPrivate {
result = this.get_executable_arg()
or
exists(DataFlow::Node arg_args, boolean shell |
- arg_args = get_args_arg() and
- shell = get_shell_arg_value()
+ arg_args = this.get_args_arg() and
+ shell = this.get_shell_arg_value()
|
// When "executable" argument is set, and "shell" argument is `False`, the
// "args" argument will only be used to set the program name and arguments to
@@ -784,7 +852,7 @@ private module StdlibPrivate {
Base64EncodeCall() {
name in [
"b64encode", "standard_b64encode", "urlsafe_b64encode", "b32encode", "b16encode",
- "encodestring", "a85encode", "b85encode", "encodebytes"
+ "encodestring", "a85encode", "b85encode", "encodebytes", "b32hexencode"
] and
this = base64().getMember(name).getACall()
}
@@ -799,7 +867,7 @@ private module StdlibPrivate {
] and
result = "Base64"
or
- name = "b32encode" and result = "Base32"
+ name in ["b32encode", "b32hexencode"] and result = "Base32"
or
name = "b16encode" and result = "Base16"
or
@@ -816,7 +884,7 @@ private module StdlibPrivate {
Base64DecodeCall() {
name in [
"b64decode", "standard_b64decode", "urlsafe_b64decode", "b32decode", "b16decode",
- "decodestring", "a85decode", "b85decode", "decodebytes"
+ "decodestring", "a85decode", "b85decode", "decodebytes", "b32hexdecode"
] and
this = base64().getMember(name).getACall()
}
@@ -833,7 +901,7 @@ private module StdlibPrivate {
] and
result = "Base64"
or
- name = "b32decode" and result = "Base32"
+ name in ["b32decode", "b32hexdecode"] and result = "Base32"
or
name = "b16decode" and result = "Base16"
or
@@ -1277,7 +1345,7 @@ private module StdlibPrivate {
/**
* Gets a name of an attribute of a `pathlib.Path` object that is also a `pathlib.Path` object.
*/
- private string pathlibPathAttribute() { result in ["parent"] }
+ private string pathlibPathAttribute() { result = "parent" }
/**
* Gets a name of a method of a `pathlib.Path` object that returns a `pathlib.Path` object.
@@ -1384,7 +1452,7 @@ private module StdlibPrivate {
"is_symlink", "is_socket", "is_fifo", "is_block_device", "is_char_device", "iter_dir",
"lchmod", "lstat", "mkdir", "open", "owner", "read_bytes", "read_text", "readlink",
"rename", "replace", "resolve", "rglob", "rmdir", "samefile", "symlink_to", "touch",
- "unlink", "link_to", "write_bytes", "write_text"
+ "unlink", "link_to", "write_bytes", "write_text", "hardlink_to"
] and
pathlibPath().flowsTo(fileAccess.getObject()) and
fileAccess.(DataFlow::LocalSourceNode).flowsTo(this.getFunction())
@@ -1466,15 +1534,36 @@ private module StdlibPrivate {
// ---------------------------------------------------------------------------
// hashlib
// ---------------------------------------------------------------------------
+ /** Gets a back-reference to the hashname argument `arg` that was used in a call to `hashlib.new`. */
+ private DataFlow::TypeTrackingNode hashlibNewCallNameBacktracker(
+ DataFlow::TypeBackTracker t, DataFlow::Node arg
+ ) {
+ t.start() and
+ hashlibNewCallImpl(_, arg) and
+ result = arg.getALocalSource()
+ or
+ exists(DataFlow::TypeBackTracker t2 |
+ result = hashlibNewCallNameBacktracker(t2, arg).backtrack(t2, t)
+ )
+ }
+
+ /** Gets a back-reference to the hashname argument `arg` that was used in a call to `hashlib.new`. */
+ private DataFlow::LocalSourceNode hashlibNewCallNameBacktracker(DataFlow::Node arg) {
+ result = hashlibNewCallNameBacktracker(DataFlow::TypeBackTracker::end(), arg)
+ }
+
+ /** Holds when `call` is a call to `hashlib.new` with `nameArg` as the first argument. */
+ private predicate hashlibNewCallImpl(DataFlow::CallCfgNode call, DataFlow::Node nameArg) {
+ call = API::moduleImport("hashlib").getMember("new").getACall() and
+ nameArg in [call.getArg(0), call.getArgByName("name")]
+ }
+
/** Gets a call to `hashlib.new` with `algorithmName` as the first argument. */
private DataFlow::CallCfgNode hashlibNewCall(string algorithmName) {
- exists(DataFlow::Node nameArg |
- result = API::moduleImport("hashlib").getMember("new").getACall() and
- nameArg in [result.getArg(0), result.getArgByName("name")] and
- exists(StrConst str |
- nameArg.getALocalSource() = DataFlow::exprNode(str) and
- algorithmName = str.getText()
- )
+ exists(DataFlow::Node origin, DataFlow::Node nameArg |
+ origin = hashlibNewCallNameBacktracker(nameArg) and
+ algorithmName = origin.asExpr().(StrConst).getText() and
+ hashlibNewCallImpl(result, nameArg)
)
}
@@ -1636,6 +1725,143 @@ private module StdlibPrivate {
result = this.getArg(any(int i | i >= msgIndex))
}
}
+
+ // ---------------------------------------------------------------------------
+ // re
+ // ---------------------------------------------------------------------------
+ /**
+ * List of methods in the `re` module immediately executing a regular expression.
+ *
+ * See https://docs.python.org/3/library/re.html#module-contents
+ */
+ private class RegexExecutionMethod extends string {
+ RegexExecutionMethod() {
+ this in ["match", "fullmatch", "search", "split", "findall", "finditer", "sub", "subn"]
+ }
+
+ /** Gets the index of the argument representing the string to be searched by a regex. */
+ int getStringArgIndex() {
+ this in ["match", "fullmatch", "search", "split", "findall", "finditer"] and
+ result = 1
+ or
+ this in ["sub", "subn"] and
+ result = 2
+ }
+ }
+
+ /**
+ * A a call to a method from the `re` module immediately executing a regular expression.
+ *
+ * See `RegexExecutionMethods`
+ */
+ private class DirectRegexExecution extends DataFlow::CallCfgNode, RegexExecution::Range {
+ RegexExecutionMethod method;
+
+ DirectRegexExecution() { this = API::moduleImport("re").getMember(method).getACall() }
+
+ override DataFlow::Node getRegex() { result in [this.getArg(0), this.getArgByName("pattern")] }
+
+ override DataFlow::Node getString() {
+ result in [this.getArg(method.getStringArgIndex()), this.getArgByName("string")]
+ }
+
+ override string getName() { result = "re." + method }
+ }
+
+ /** Helper module for tracking compiled regexes. */
+ private module CompiledRegexes {
+ private DataFlow::TypeTrackingNode compiledRegex(DataFlow::TypeTracker t, DataFlow::Node regex) {
+ t.start() and
+ result = API::moduleImport("re").getMember("compile").getACall() and
+ regex in [
+ result.(DataFlow::CallCfgNode).getArg(0),
+ result.(DataFlow::CallCfgNode).getArgByName("pattern")
+ ]
+ or
+ exists(DataFlow::TypeTracker t2 | result = compiledRegex(t2, regex).track(t2, t))
+ }
+
+ DataFlow::Node compiledRegex(DataFlow::Node regex) {
+ compiledRegex(DataFlow::TypeTracker::end(), regex).flowsTo(result)
+ }
+ }
+
+ private import CompiledRegexes
+
+ /**
+ * A call on compiled regular expression (obtained via `re.compile`) executing a
+ * regular expression.
+ *
+ * Given the following example:
+ *
+ * ```py
+ * pattern = re.compile(input)
+ * pattern.match(s)
+ * ```
+ *
+ * This class will identify that `re.compile` compiles `input` and afterwards
+ * executes `re`'s `match`. As a result, `this` will refer to `pattern.match(s)`
+ * and `this.getRegexNode()` will return the node for `input` (`re.compile`'s first argument).
+ *
+ *
+ * See `RegexExecutionMethods`
+ *
+ * See https://docs.python.org/3/library/re.html#regular-expression-objects
+ */
+ private class CompiledRegexExecution extends DataFlow::MethodCallNode, RegexExecution::Range {
+ DataFlow::Node regexNode;
+ RegexExecutionMethod method;
+
+ CompiledRegexExecution() { this.calls(compiledRegex(regexNode), method) }
+
+ override DataFlow::Node getRegex() { result = regexNode }
+
+ override DataFlow::Node getString() {
+ result in [this.getArg(method.getStringArgIndex() - 1), this.getArgByName("string")]
+ }
+
+ override string getName() { result = "re." + method }
+ }
+
+ /**
+ * A call to 're.escape'.
+ * See https://docs.python.org/3/library/re.html#re.escape
+ */
+ private class ReEscapeCall extends Escaping::Range, DataFlow::CallCfgNode {
+ ReEscapeCall() { this = API::moduleImport("re").getMember("escape").getACall() }
+
+ override DataFlow::Node getAnInput() {
+ result in [this.getArg(0), this.getArgByName("pattern")]
+ }
+
+ override DataFlow::Node getOutput() { result = this }
+
+ override string getKind() { result = Escaping::getRegexKind() }
+ }
+
+ // ---------------------------------------------------------------------------
+ // urllib
+ // ---------------------------------------------------------------------------
+ /**
+ * A call to `urllib.parse.urlsplit`
+ *
+ * See https://docs.python.org/3.9/library/urllib.parse.html#urllib.parse.urlsplit
+ */
+ class UrllibParseUrlsplitCall extends Stdlib::SplitResult::InstanceSource, DataFlow::CallCfgNode {
+ UrllibParseUrlsplitCall() {
+ this = API::moduleImport("urllib").getMember("parse").getMember("urlsplit").getACall()
+ }
+
+ /** Gets the argument that specifies the URL. */
+ DataFlow::Node getUrl() { result in [this.getArg(0), this.getArgByName("url")] }
+ }
+
+ /** Extra taint-step such that the result of `urllib.parse.urlsplit(tainted_string)` is tainted. */
+ private class UrllibParseUrlsplitCallAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
+ override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
+ nodeTo.(UrllibParseUrlsplitCall).getUrl() = nodeFrom
+ }
+ }
}
// ---------------------------------------------------------------------------
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Toml.qll b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Toml.qll
new file mode 100644
index 00000000000..ad52961faa2
--- /dev/null
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Toml.qll
@@ -0,0 +1,101 @@
+/**
+ * Provides classes modeling security-relevant aspects of the `toml` PyPI package.
+ *
+ * See
+ * - https://pypi.org/project/toml/
+ * - https://github.com/uiri/toml#api-reference
+ */
+
+private import python
+private import semmle.python.dataflow.new.DataFlow
+private import semmle.python.Concepts
+private import semmle.python.ApiGraphs
+
+/**
+ * Provides classes modeling security-relevant aspects of the `toml` PyPI package
+ *
+ * See
+ * - https://pypi.org/project/toml/
+ * - https://github.com/uiri/toml#api-reference
+ */
+private module Toml {
+ /**
+ * A call to `toml.loads`
+ *
+ * See https://github.com/uiri/toml#api-reference
+ */
+ private class TomlLoadsCall extends Decoding::Range, DataFlow::CallCfgNode {
+ TomlLoadsCall() {
+ this = API::moduleImport("toml").getMember("loads").getACall()
+ or
+ this = API::moduleImport("toml").getMember("decoder").getMember("loads").getACall()
+ }
+
+ override predicate mayExecuteInput() { none() }
+
+ override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("s")] }
+
+ override DataFlow::Node getOutput() { result = this }
+
+ override string getFormat() { result = "TOML" }
+ }
+
+ /**
+ * A call to `toml.load`
+ *
+ * See https://github.com/uiri/toml#api-reference
+ */
+ private class TomlLoadCall extends Decoding::Range, DataFlow::CallCfgNode {
+ TomlLoadCall() {
+ this = API::moduleImport("toml").getMember("load").getACall()
+ or
+ this = API::moduleImport("toml").getMember("decoder").getMember("load").getACall()
+ }
+
+ override predicate mayExecuteInput() { none() }
+
+ override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("f")] }
+
+ override DataFlow::Node getOutput() { result = this }
+
+ override string getFormat() { result = "TOML" }
+ }
+
+ /**
+ * A call to `toml.dumps`
+ *
+ * See https://github.com/uiri/toml#api-reference
+ */
+ private class TomlDumpsCall extends Encoding::Range, DataFlow::CallCfgNode {
+ TomlDumpsCall() {
+ this = API::moduleImport("toml").getMember("dumps").getACall()
+ or
+ this = API::moduleImport("toml").getMember("encoder").getMember("dumps").getACall()
+ }
+
+ override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("o")] }
+
+ override DataFlow::Node getOutput() { result = this }
+
+ override string getFormat() { result = "TOML" }
+ }
+
+ /**
+ * A call to `toml.dump`
+ *
+ * See https://github.com/uiri/toml#api-reference
+ */
+ private class TomlDumpCall extends Encoding::Range, DataFlow::CallCfgNode {
+ TomlDumpCall() {
+ this = API::moduleImport("toml").getMember("dump").getACall()
+ or
+ this = API::moduleImport("toml").getMember("encoder").getMember("dump").getACall()
+ }
+
+ override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("o")] }
+
+ override DataFlow::Node getOutput() { result in [this.getArg(1), this.getArgByName("f")] }
+
+ override string getFormat() { result = "TOML" }
+ }
+}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Tornado.qll b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Tornado.qll
index ba4898facc8..91ae3ac2575 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Tornado.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Tornado.qll
@@ -318,7 +318,7 @@ private module Tornado {
]
}
- override string getMethodName() { result in ["full_url"] }
+ override string getMethodName() { result = "full_url" }
override string getAsyncMethodName() { none() }
}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Werkzeug.qll b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Werkzeug.qll
index 039481f8522..e9e3f257871 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Werkzeug.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Werkzeug.qll
@@ -58,7 +58,7 @@ module Werkzeug {
override string getAttributeName() { none() }
- override string getMethodName() { result in ["getlist"] }
+ override string getMethodName() { result = "getlist" }
override string getAsyncMethodName() { none() }
}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Yaml.qll b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Yaml.qll
index e818b2e95ec..07a98ec2ba3 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Yaml.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Yaml.qll
@@ -9,7 +9,6 @@
private import python
private import semmle.python.dataflow.new.DataFlow
-private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
@@ -41,11 +40,17 @@ private module Yaml {
}
/**
- * This function was thought safe from the 5.1 release in 2017, when the default loader was changed to `FullLoader`.
- * In 2020 new exploits were found, meaning it's not safe. The Current plan is to change the default to `SafeLoader` in release 6.0
- * (as explained in https://github.com/yaml/pyyaml/issues/420#issuecomment-696752389).
- * Until 6.0 is released, we will mark `yaml.load` as possibly leading to arbitrary code execution.
- * See https://github.com/yaml/pyyaml/wiki/PyYAML-yaml.load(input)-Deprecation for more details.
+ * This function was thought safe from the 5.1 release in 2017, when the default
+ * loader was changed to `FullLoader` (see
+ * https://github.com/yaml/pyyaml/wiki/PyYAML-yaml.load(input)-Deprecation).
+ *
+ * In 2020 new exploits were found, meaning it's not safe. With the 6.0 release (see
+ * https://github.com/yaml/pyyaml/commit/8cdff2c80573b8be8e8ad28929264a913a63aa33),
+ * when using `load` and `load_all` you are now required to specify a Loader. But
+ * from what I (@RasmusWL) can gather, `FullLoader` is not to be considered safe,
+ * although known exploits have been mitigated (is at least my impression). Also see
+ * https://github.com/yaml/pyyaml/issues/420#issuecomment-696752389 for more
+ * details.
*/
override predicate mayExecuteInput() {
func_name in ["full_load", "full_load_all", "unsafe_load", "unsafe_load_all"]
@@ -63,7 +68,7 @@ private module Yaml {
)
}
- override DataFlow::Node getAnInput() { result = this.getArg(0) }
+ override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("stream")] }
override DataFlow::Node getOutput() { result = this }
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Yarl.qll b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Yarl.qll
index 00b0911471b..5ea78c1ac8e 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Yarl.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/Yarl.qll
@@ -68,7 +68,7 @@ module Yarl {
]
}
- override string getMethodName() { result in ["human_repr"] }
+ override string getMethodName() { result = "human_repr" }
override string getAsyncMethodName() { none() }
}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/internal/SubclassFinder.qll b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/internal/SubclassFinder.qll
new file mode 100644
index 00000000000..36f03cd1401
--- /dev/null
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/frameworks/internal/SubclassFinder.qll
@@ -0,0 +1,209 @@
+/**
+ * INTERNAL: Do not use.
+ *
+ * Has predicates to help find subclasses in library code. Should only be used to aid in
+ * the manual library modeling process,
+ */
+
+private import python
+private import semmle.python.dataflow.new.DataFlow
+private import semmle.python.ApiGraphs
+private import semmle.python.filters.Tests
+
+// very much inspired by the draft at https://github.com/github/codeql/pull/5632
+private module NotExposed {
+ // Instructions:
+ // This needs to be automated better, but for this prototype, here are some rough instructions:
+ // 0) get a database of the library you are about to model
+ // 1) fill out the `getAlreadyModeledClass` body below
+ // 2) quick-eval the `quickEvalMe` predicate below, and copy the output to your modeling predicate
+ class MySpec extends FindSubclassesSpec {
+ MySpec() { this = "MySpec" }
+
+ override API::Node getAlreadyModeledClass() {
+ // FILL ME OUT ! (but don't commit with any changes)
+ none()
+ // for example
+ // result = API::moduleImport("rest_framework").getMember("views").getMember("APIView")
+ }
+ }
+
+ predicate quickEvalMe(string newImport) {
+ newImport =
+ "// imports generated by python/frameworks/internal/SubclassFinder.qll\n" + "this = API::" +
+ concat(string newModelFullyQualified |
+ newModel(any(MySpec spec), newModelFullyQualified, _, _, _)
+ |
+ fullyQualifiedToAPIGraphPath(newModelFullyQualified), " or this = API::"
+ )
+ }
+
+ // ---------------------------------------------------------------------------
+ // Implementation below
+ // ---------------------------------------------------------------------------
+ //
+ // We are looking to find all subclassed of the already modelled classes, and ideally
+ // we would identify an `API::Node` for each (then `toString` would give the API
+ // path).
+ //
+ // An inherent problem with API graphs is that there doesn't need to exist a result
+ // for the API graph path that we want to add to our modeling (the path to the new
+ // subclass). As an example, the following query has no results when evaluated against
+ // a django/django DB.
+ //
+ // select API::moduleImport("django") .getMember("contrib") .getMember("admin")
+ // .getMember("views") .getMember("main") .getMember("ChangeListSearchForm")
+ //
+ //
+ // Since it is a Form subclass that we would want to capture for our Django modeling,
+ // we want to extend our modeling (that is written in a qll file) with exactly that
+ // piece of code, but since the API::Node doesn't exist, we can't select that from a
+ // predicate and print its path. We need a different approach, and for that we use
+ // fully qualified names to capture new classes/new aliases, and transform these into
+ // API paths (to be included in the modeling that is inserted into the `.qll` files),
+ // see `fullyQualifiedToAPIGraphPath`.
+ //
+ // NOTE: this implementation was originally created to help with automatically
+ // modeling packages in mind, and has been adjusted to help with manual library
+ // modeling. See https://github.com/github/codeql/pull/5632 for more discussion.
+ //
+ //
+ bindingset[fullyQaulified]
+ string fullyQualifiedToAPIGraphPath(string fullyQaulified) {
+ result = "moduleImport(\"" + fullyQaulified.replaceAll(".", "\").getMember(\"") + "\")"
+ }
+
+ bindingset[this]
+ abstract class FindSubclassesSpec extends string {
+ abstract API::Node getAlreadyModeledClass();
+ }
+
+ /**
+ * Holds if `newModelFullyQualified` describes either a new subclass, or a new alias, belonging to `spec` that we should include in our automated modeling.
+ * This new element is defined by `ast`, which is defined at `loc` in the module `mod`.
+ */
+ query predicate newModel(
+ FindSubclassesSpec spec, string newModelFullyQualified, AstNode ast, Module mod, Location loc
+ ) {
+ (
+ newSubclass(spec, newModelFullyQualified, ast, mod, loc)
+ or
+ newDirectAlias(spec, newModelFullyQualified, ast, mod, loc)
+ or
+ newImportStar(spec, newModelFullyQualified, ast, mod, _, _, loc)
+ )
+ }
+
+ API::Node newOrExistingModeling(FindSubclassesSpec spec) {
+ result = spec.getAlreadyModeledClass()
+ or
+ exists(string newSubclassName |
+ newModel(spec, newSubclassName, _, _, _) and
+ result.getPath() = fullyQualifiedToAPIGraphPath(newSubclassName)
+ )
+ }
+
+ bindingset[fullyQualifiedName]
+ predicate alreadyModeled(FindSubclassesSpec spec, string fullyQualifiedName) {
+ fullyQualifiedToAPIGraphPath(fullyQualifiedName) = spec.getAlreadyModeledClass().getPath()
+ }
+
+ predicate isNonTestProjectCode(AstNode ast) {
+ not ast.getScope*() instanceof TestScope and
+ not ast.getLocation().getFile().getRelativePath().matches("tests/%") and
+ exists(ast.getLocation().getFile().getRelativePath())
+ }
+
+ predicate hasAllStatement(Module mod) {
+ exists(AssignStmt a, GlobalVariable all |
+ a.defines(all) and
+ a.getScope() = mod and
+ all.getId() = "__all__"
+ )
+ }
+
+ /**
+ * Holds if `newAliasFullyQualified` describes new alias originating from the import
+ * `from import [as ]`, where `.` belongs to
+ * `spec`.
+ * So if this import happened in module `foo.bar`, `newAliasFullyQualified` would be
+ * `foo.bar.` (or `foo.bar.`).
+ *
+ * Note that this predicate currently respects `__all__` in sort of a backwards fashion.
+ * - if `__all__` is defined in module `foo.bar`, we only allow new aliases where the member name is also in `__all__`. (this doesn't map 100% to the semantics of imports though)
+ * - If `__all__` is not defined we don't impose any limitations.
+ *
+ * Also note that we don't currently consider deleting module-attributes at all, so in the code snippet below, we would consider that `my_module.foo` is a
+ * reference to `django.foo`, although `my_module.foo` isn't even available at runtime. (there currently also isn't any code to discover that `my_module.bar`
+ * is an alias to `django.foo`)
+ * ```py
+ * # module my_module
+ * from django import foo
+ * bar = foo
+ * del foo
+ * ```
+ */
+ predicate newDirectAlias(
+ FindSubclassesSpec spec, string newAliasFullyQualified, ImportMember importMember, Module mod,
+ Location loc
+ ) {
+ importMember = newOrExistingModeling(spec).getAUse().asExpr() and
+ importMember.getScope() = mod and
+ loc = importMember.getLocation() and
+ (
+ mod.isPackageInit() and
+ newAliasFullyQualified = mod.getPackageName() + "." + importMember.getName()
+ or
+ not mod.isPackageInit() and
+ newAliasFullyQualified = mod.getName() + "." + importMember.getName()
+ ) and
+ (
+ not hasAllStatement(mod)
+ or
+ mod.declaredInAll(importMember.getName())
+ ) and
+ not alreadyModeled(spec, newAliasFullyQualified) and
+ isNonTestProjectCode(importMember)
+ }
+
+ /** same as `newDirectAlias` predicate, but handling `from import *`, considering all ``, where `.` belongs to `spec`. */
+ predicate newImportStar(
+ FindSubclassesSpec spec, string newAliasFullyQualified, ImportStar importStar, Module mod,
+ API::Node relevantClass, string relevantName, Location loc
+ ) {
+ relevantClass = newOrExistingModeling(spec) and
+ loc = importStar.getLocation() and
+ importStar.getScope() = mod and
+ // WHAT A HACK :D :D
+ relevantClass.getPath() =
+ relevantClass.getAPredecessor().getPath() + ".getMember(\"" + relevantName + "\")" and
+ relevantClass.getAPredecessor().getAUse().asExpr() = importStar.getModule() and
+ (
+ mod.isPackageInit() and
+ newAliasFullyQualified = mod.getPackageName() + "." + relevantName
+ or
+ not mod.isPackageInit() and
+ newAliasFullyQualified = mod.getName() + "." + relevantName
+ ) and
+ (
+ not hasAllStatement(mod)
+ or
+ mod.declaredInAll(relevantName)
+ ) and
+ not alreadyModeled(spec, newAliasFullyQualified) and
+ isNonTestProjectCode(importStar)
+ }
+
+ /** Holds if `classExpr` defines a new subclass that belongs to `spec`, which has the fully qualified name `newSubclassQualified`. */
+ predicate newSubclass(
+ FindSubclassesSpec spec, string newSubclassQualified, ClassExpr classExpr, Module mod,
+ Location loc
+ ) {
+ classExpr = newOrExistingModeling(spec).getASubclass*().getAUse().asExpr() and
+ classExpr.getScope() = mod and
+ newSubclassQualified = mod.getName() + "." + classExpr.getName() and
+ loc = classExpr.getLocation() and
+ not alreadyModeled(spec, newSubclassQualified) and
+ isNonTestProjectCode(classExpr)
+ }
+}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/internal/Awaited.qll b/repo-tests/codeql/python/ql/lib/semmle/python/internal/Awaited.qll
new file mode 100644
index 00000000000..356464ef580
--- /dev/null
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/internal/Awaited.qll
@@ -0,0 +1,47 @@
+/**
+ * INTERNAL: Do not use.
+ *
+ * Provides helper class for defining additional API graph edges.
+ */
+
+private import python
+private import semmle.python.dataflow.new.DataFlow
+
+/**
+ * INTERNAL: Do not use.
+ *
+ * Holds if `result` is the result of awaiting `awaitedValue`.
+ */
+cached
+DataFlow::Node awaited(DataFlow::Node awaitedValue) {
+ // `await` x
+ // - `awaitedValue` is `x`
+ // - `result` is `await x`
+ exists(Await await |
+ await.getValue() = awaitedValue.asExpr() and
+ result.asExpr() = await
+ )
+ or
+ // `async for x in l`
+ // - `awaitedValue` is `l`
+ // - `result` is `l` (`x` is behind a read step)
+ exists(AsyncFor asyncFor |
+ // To consider `x` the result of awaiting, we would use asyncFor.getTarget() = awaitedValue.asExpr(),
+ // but that is behind a read step rather than a flow step.
+ asyncFor.getIter() = awaitedValue.asExpr() and
+ result.asExpr() = asyncFor.getIter()
+ )
+ or
+ // `async with x as y`
+ // - `awaitedValue` is `x`
+ // - `result` is `x` and `y` if it exists
+ exists(AsyncWith asyncWith |
+ awaitedValue.asExpr() = asyncWith.getContextExpr() and
+ result.asExpr() in [
+ // `x`
+ asyncWith.getContextExpr(),
+ // `y`, if it exists
+ asyncWith.getOptionalVars()
+ ]
+ )
+}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/objects/Constants.qll b/repo-tests/codeql/python/ql/lib/semmle/python/objects/Constants.qll
index 941f631b9b3..2f5c062e63a 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/objects/Constants.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/objects/Constants.qll
@@ -248,7 +248,7 @@ class UnicodeObjectInternal extends ConstantObjectInternal, TUnicode {
override ObjectInternal getClass() { result = TBuiltinClassObject(Builtin::special("unicode")) }
override Builtin getBuiltin() {
- result.(Builtin).strValue() = this.strValue() and
+ result.strValue() = this.strValue() and
result.getClass() = Builtin::special("unicode")
}
@@ -281,7 +281,7 @@ class BytesObjectInternal extends ConstantObjectInternal, TBytes {
override ObjectInternal getClass() { result = TBuiltinClassObject(Builtin::special("bytes")) }
override Builtin getBuiltin() {
- result.(Builtin).strValue() = this.strValue() and
+ result.strValue() = this.strValue() and
result.getClass() = Builtin::special("bytes")
}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/objects/ObjectAPI.qll b/repo-tests/codeql/python/ql/lib/semmle/python/objects/ObjectAPI.qll
index 946eb1de04d..d3082d26130 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/objects/ObjectAPI.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/objects/ObjectAPI.qll
@@ -147,9 +147,7 @@ class Value extends TObject {
* Class representing modules in the Python program
* Each `ModuleValue` represents a module object in the Python program.
*/
-class ModuleValue extends Value {
- ModuleValue() { this instanceof ModuleObjectInternal }
-
+class ModuleValue extends Value instanceof ModuleObjectInternal {
/**
* Holds if this module "exports" name.
* That is, does it define `name` in `__all__` or is
@@ -159,7 +157,7 @@ class ModuleValue extends Value {
predicate exports(string name) { PointsTo::moduleExports(this, name) }
/** Gets the scope for this module, provided that it is a Python module. */
- ModuleScope getScope() { result = this.(ModuleObjectInternal).getSourceModule() }
+ ModuleScope getScope() { result = super.getSourceModule() }
/**
* Gets the container path for this module. Will be the file for a Python module,
@@ -181,7 +179,7 @@ class ModuleValue extends Value {
predicate isPackage() { this instanceof PackageObjectInternal }
/** Whether the complete set of names "exported" by this module can be accurately determined */
- predicate hasCompleteExportInfo() { this.(ModuleObjectInternal).hasCompleteExportInfo() }
+ predicate hasCompleteExportInfo() { super.hasCompleteExportInfo() }
/** Get a module that this module imports */
ModuleValue getAnImportedModule() { result.importedAs(this.getScope().getAnImportedModuleName()) }
@@ -452,23 +450,21 @@ class CallableValue extends Value {
* Class representing bound-methods, such as `o.func`, where `o` is an instance
* of a class that has a callable attribute `func`.
*/
-class BoundMethodValue extends CallableValue {
- BoundMethodValue() { this instanceof BoundMethodObjectInternal }
-
+class BoundMethodValue extends CallableValue instanceof BoundMethodObjectInternal {
/**
* Gets the callable that will be used when `this` is called.
* The actual callable for `func` in `o.func`.
*/
- CallableValue getFunction() { result = this.(BoundMethodObjectInternal).getFunction() }
+ CallableValue getFunction() { result = super.getFunction() }
/**
* Gets the value that will be used for the `self` parameter when `this` is called.
* The value for `o` in `o.func`.
*/
- Value getSelf() { result = this.(BoundMethodObjectInternal).getSelf() }
+ Value getSelf() { result = super.getSelf() }
/** Gets the parameter node that will be used for `self`. */
- NameNode getSelfParameter() { result = this.(BoundMethodObjectInternal).getSelfParameter() }
+ NameNode getSelfParameter() { result = super.getSelfParameter() }
}
/**
@@ -831,12 +827,10 @@ class BuiltinMethodValue extends FunctionValue {
/**
* A class representing sequence objects with a length and tracked items.
*/
-class SequenceValue extends Value {
- SequenceValue() { this instanceof SequenceObjectInternal }
+class SequenceValue extends Value instanceof SequenceObjectInternal {
+ Value getItem(int n) { result = super.getItem(n) }
- Value getItem(int n) { result = this.(SequenceObjectInternal).getItem(n) }
-
- int length() { result = this.(SequenceObjectInternal).length() }
+ int length() { result = super.length() }
}
/** A class representing tuple objects */
@@ -887,14 +881,12 @@ class NumericValue extends Value {
* https://docs.python.org/3/howto/descriptor.html#properties
* https://docs.python.org/3/library/functions.html#property
*/
-class PropertyValue extends Value {
- PropertyValue() { this instanceof PropertyInternal }
+class PropertyValue extends Value instanceof PropertyInternal {
+ CallableValue getGetter() { result = super.getGetter() }
- CallableValue getGetter() { result = this.(PropertyInternal).getGetter() }
+ CallableValue getSetter() { result = super.getSetter() }
- CallableValue getSetter() { result = this.(PropertyInternal).getSetter() }
-
- CallableValue getDeleter() { result = this.(PropertyInternal).getDeleter() }
+ CallableValue getDeleter() { result = super.getDeleter() }
}
/** A method-resolution-order sequence of classes */
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/objects/TObject.qll b/repo-tests/codeql/python/ql/lib/semmle/python/objects/TObject.qll
index d83d47af1ee..99eb05aa795 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/objects/TObject.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/objects/TObject.qll
@@ -387,7 +387,7 @@ private predicate concrete_class(PythonClassObjectInternal cls) {
not exists(Raise r, Name ex |
r.getScope() = f and
(r.getException() = ex or r.getException().(Call).getFunc() = ex) and
- (ex.getId() = "NotImplementedError" or ex.getId() = "NotImplemented")
+ ex.getId() = ["NotImplementedError", "NotImplemented"]
)
)
)
@@ -437,11 +437,7 @@ predicate missing_imported_module(ControlFlowNode imp, Context ctx, string name)
* Helper for missing modules to determine if name `x.y` is a module `x.y` or
* an attribute `y` of module `x`. This list should be added to as required.
*/
-predicate common_module_name(string name) {
- name = "zope.interface"
- or
- name = "six.moves"
-}
+predicate common_module_name(string name) { name = ["zope.interface", "six.moves"] }
/**
* A declaration of a class, either a built-in class or a source definition
@@ -482,16 +478,11 @@ library class ClassDecl extends @py_object {
*/
predicate isSpecial() {
exists(string name | this = Builtin::special(name) |
- name = "type" or
- name = "super" or
- name = "bool" or
- name = "NoneType" or
- name = "tuple" or
- name = "property" or
- name = "ClassMethod" or
- name = "StaticMethod" or
- name = "MethodType" or
- name = "ModuleType"
+ name =
+ [
+ "type", "super", "bool", "NoneType", "tuple", "property", "ClassMethod", "StaticMethod",
+ "MethodType", "ModuleType"
+ ]
)
}
@@ -514,11 +505,7 @@ library class ClassDecl extends @py_object {
/** Holds if this class is the abstract base class */
predicate isAbstractBaseClass(string name) {
- exists(Module m |
- m.getName() = "_abcoll"
- or
- m.getName() = "_collections_abc"
- |
+ exists(Module m | m.getName() = ["_abcoll", "_collections_abc"] |
this.getClass().getScope() = m and
this.getName() = name
)
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/pointsto/PointsTo.qll b/repo-tests/codeql/python/ql/lib/semmle/python/pointsto/PointsTo.qll
index 48bbb283d07..a6510ea0ec0 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/pointsto/PointsTo.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/pointsto/PointsTo.qll
@@ -300,7 +300,7 @@ module PointsToInternal {
ssa_definition_points_to(var.getDefinition(), context, value, origin)
or
exists(EssaVariable prev |
- ssaShortCut+(prev, var) and
+ ssaShortCut(prev, var) and
variablePointsTo(prev, context, value, origin)
)
}
@@ -1195,16 +1195,22 @@ module InterProceduralPointsTo {
ControlFlowNode argument, PointsToContext caller, ParameterDefinition param,
PointsToContext callee
) {
+ PointsToInternal::pointsTo(argument, caller, _, _) and
exists(CallNode call, Function func, int offset |
callsite_calls_function(call, caller, func, callee, offset)
|
exists(string name |
argument = call.getArgByName(name) and
- param.getParameter() = func.getArgByName(name)
+ function_parameter_name(func, param, name)
)
)
}
+ pragma[nomagic]
+ private predicate function_parameter_name(Function func, ParameterDefinition param, string name) {
+ param.getParameter() = func.getArgByName(name)
+ }
+
/**
* Holds if the `call` with context `caller` calls the function `scope` in context `callee`
* and the offset from argument to parameter is `parameter_offset`
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/regex.qll b/repo-tests/codeql/python/ql/lib/semmle/python/regex.qll
index 883d2c3fbc4..b8a5584aa51 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/regex.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/regex.qll
@@ -102,15 +102,7 @@ string mode_from_node(DataFlow::Node node) { node = re_flag_tracker(result) }
* Gets a regular expression mode flag associated with the given value.
*/
deprecated string mode_from_mode_object(Value obj) {
- (
- result = "DEBUG" or
- result = "IGNORECASE" or
- result = "LOCALE" or
- result = "MULTILINE" or
- result = "DOTALL" or
- result = "UNICODE" or
- result = "VERBOSE"
- ) and
+ result in ["DEBUG", "IGNORECASE", "LOCALE", "MULTILINE", "DOTALL", "UNICODE", "VERBOSE"] and
exists(int flag |
flag = Value::named("sre_constants.SRE_FLAG_" + result).(OI::ObjectInternal).intValue() and
obj.(OI::ObjectInternal).intValue().bitAnd(flag) = flag
@@ -462,6 +454,7 @@ abstract class RegexString extends Expr {
/** Gets the number of the group in start,end */
int getGroupNumber(int start, int end) {
this.group(start, end) and
+ not this.non_capturing_group_start(start, _) and
result =
count(int i | this.group(i, _) and i < start and not this.non_capturing_group_start(i, _)) + 1
}
@@ -611,14 +604,7 @@ abstract class RegexString extends Expr {
this.getChar(start + 1) = "?" and
end = start + 3 and
c = this.getChar(start + 2) and
- (
- c = "i" or
- c = "L" or
- c = "m" or
- c = "s" or
- c = "u" or
- c = "x"
- )
+ c in ["i", "L", "m", "s", "u", "x"]
}
/**
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/security/BadTagFilterQuery.qll b/repo-tests/codeql/python/ql/lib/semmle/python/security/BadTagFilterQuery.qll
new file mode 100644
index 00000000000..1b94173ca49
--- /dev/null
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/security/BadTagFilterQuery.qll
@@ -0,0 +1,306 @@
+/**
+ * Provides precicates for reasoning about bad tag filter vulnerabilities.
+ */
+
+import performance.ReDoSUtil
+
+/**
+ * A module for determining if a regexp matches a given string,
+ * and reasoning about which capture groups are filled by a given string.
+ */
+private module RegexpMatching {
+ /**
+ * A class to test whether a regular expression matches a string.
+ * Override this class and extend `test`/`testWithGroups` to configure which strings should be tested for acceptance by this regular expression.
+ * The result can afterwards be read from the `matches` predicate.
+ *
+ * Strings in the `testWithGroups` predicate are also tested for which capture groups are filled by the given string.
+ * The result is available in the `fillCaptureGroup` predicate.
+ */
+ abstract class MatchedRegExp extends RegExpTerm {
+ MatchedRegExp() { this.isRootTerm() }
+
+ /**
+ * Holds if it should be tested whether this regular expression matches `str`.
+ *
+ * If `ignorePrefix` is true, then a regexp without a start anchor will be treated as if it had a start anchor.
+ * E.g. a regular expression `/foo$/` will match any string that ends with "foo",
+ * but if `ignorePrefix` is true, it will only match "foo".
+ */
+ predicate test(string str, boolean ignorePrefix) {
+ none() // maybe overriden in subclasses
+ }
+
+ /**
+ * Same as `test(..)`, but where the `fillsCaptureGroup` afterwards tells which capture groups were filled by the given string.
+ */
+ predicate testWithGroups(string str, boolean ignorePrefix) {
+ none() // maybe overriden in subclasses
+ }
+
+ /**
+ * Holds if this RegExp matches `str`, where `str` is either in the `test` or `testWithGroups` predicate.
+ */
+ final predicate matches(string str) {
+ exists(State state | state = getAState(this, str.length() - 1, str, _) |
+ epsilonSucc*(state) = Accept(_)
+ )
+ }
+
+ /**
+ * Holds if matching `str` may fill capture group number `g`.
+ * Only holds if `str` is in the `testWithGroups` predicate.
+ */
+ final predicate fillsCaptureGroup(string str, int g) {
+ exists(State s |
+ s = getAStateThatReachesAccept(this, _, str, _) and
+ g = group(s.getRepr())
+ )
+ }
+ }
+
+ /**
+ * Gets a state the regular expression `reg` can be in after matching the `i`th char in `str`.
+ * The regular expression is modelled as a non-determistic finite automaton,
+ * the regular expression can therefore be in multiple states after matching a character.
+ *
+ * It's a forward search to all possible states, and there is thus no guarantee that the state is on a path to an accepting state.
+ */
+ private State getAState(MatchedRegExp reg, int i, string str, boolean ignorePrefix) {
+ // start state, the -1 position before any chars have been matched
+ i = -1 and
+ (
+ reg.test(str, ignorePrefix)
+ or
+ reg.testWithGroups(str, ignorePrefix)
+ ) and
+ result.getRepr().getRootTerm() = reg and
+ isStartState(result)
+ or
+ // recursive case
+ result = getAStateAfterMatching(reg, _, str, i, _, ignorePrefix)
+ }
+
+ /**
+ * Gets the next state after the `prev` state from `reg`.
+ * `prev` is the state after matching `fromIndex` chars in `str`,
+ * and the result is the state after matching `toIndex` chars in `str`.
+ *
+ * This predicate is used as a step relation in the forwards search (`getAState`),
+ * and also as a step relation in the later backwards search (`getAStateThatReachesAccept`).
+ */
+ private State getAStateAfterMatching(
+ MatchedRegExp reg, State prev, string str, int toIndex, int fromIndex, boolean ignorePrefix
+ ) {
+ // the basic recursive case - outlined into a noopt helper to make performance work out.
+ result = getAStateAfterMatchingAux(reg, prev, str, toIndex, fromIndex, ignorePrefix)
+ or
+ // we can skip past word boundaries if the next char is a non-word char.
+ fromIndex = toIndex and
+ prev.getRepr() instanceof RegExpWordBoundary and
+ prev = getAState(reg, toIndex, str, ignorePrefix) and
+ after(prev.getRepr()) = result and
+ str.charAt(toIndex + 1).regexpMatch("\\W") // \W matches any non-word char.
+ }
+
+ pragma[noopt]
+ private State getAStateAfterMatchingAux(
+ MatchedRegExp reg, State prev, string str, int toIndex, int fromIndex, boolean ignorePrefix
+ ) {
+ prev = getAState(reg, fromIndex, str, ignorePrefix) and
+ fromIndex = toIndex - 1 and
+ exists(string char | char = str.charAt(toIndex) | specializedDeltaClosed(prev, char, result)) and
+ not discardedPrefixStep(prev, result, ignorePrefix)
+ }
+
+ /** Holds if a step from `prev` to `next` should be discarded when the `ignorePrefix` flag is set. */
+ private predicate discardedPrefixStep(State prev, State next, boolean ignorePrefix) {
+ prev = mkMatch(any(RegExpRoot r)) and
+ ignorePrefix = true and
+ next = prev
+ }
+
+ // The `deltaClosed` relation specialized to the chars that exists in strings tested by a `MatchedRegExp`.
+ private predicate specializedDeltaClosed(State prev, string char, State next) {
+ deltaClosed(prev, specializedGetAnInputSymbolMatching(char), next)
+ }
+
+ // The `getAnInputSymbolMatching` relation specialized to the chars that exists in strings tested by a `MatchedRegExp`.
+ pragma[noinline]
+ private InputSymbol specializedGetAnInputSymbolMatching(string char) {
+ exists(string s, MatchedRegExp r |
+ r.test(s, _)
+ or
+ r.testWithGroups(s, _)
+ |
+ char = s.charAt(_)
+ ) and
+ result = getAnInputSymbolMatching(char)
+ }
+
+ /**
+ * Gets the `i`th state on a path to the accepting state when `reg` matches `str`.
+ * Starts with an accepting state as found by `getAState` and searches backwards
+ * to the start state through the reachable states (as found by `getAState`).
+ *
+ * This predicate holds the invariant that the result state can be reached with `i` steps from a start state,
+ * and an accepting state can be found after (`str.length() - 1 - i`) steps from the result.
+ * The result state is therefore always on a valid path where `reg` accepts `str`.
+ *
+ * This predicate is only used to find which capture groups a regular expression has filled,
+ * and thus the search is only performed for the strings in the `testWithGroups(..)` predicate.
+ */
+ private State getAStateThatReachesAccept(
+ MatchedRegExp reg, int i, string str, boolean ignorePrefix
+ ) {
+ // base case, reaches an accepting state from the last state in `getAState(..)`
+ reg.testWithGroups(str, ignorePrefix) and
+ i = str.length() - 1 and
+ result = getAState(reg, i, str, ignorePrefix) and
+ epsilonSucc*(result) = Accept(_)
+ or
+ // recursive case. `next` is the next state to be matched after matching `prev`.
+ // this predicate is doing a backwards search, so `prev` is the result we are looking for.
+ exists(State next, State prev, int fromIndex, int toIndex |
+ next = getAStateThatReachesAccept(reg, toIndex, str, ignorePrefix) and
+ next = getAStateAfterMatching(reg, prev, str, toIndex, fromIndex, ignorePrefix) and
+ i = fromIndex and
+ result = prev
+ )
+ }
+
+ /** Gets the capture group number that `term` belongs to. */
+ private int group(RegExpTerm term) {
+ exists(RegExpGroup grp | grp.getNumber() = result | term.getParent*() = grp)
+ }
+}
+
+/** A class to test whether a regular expression matches certain HTML tags. */
+class HTMLMatchingRegExp extends RegexpMatching::MatchedRegExp {
+ HTMLMatchingRegExp() {
+ // the regexp must mention "<" and ">" explicitly.
+ forall(string angleBracket | angleBracket = ["<", ">"] |
+ any(RegExpConstant term | term.getValue().matches("%" + angleBracket + "%")).getRootTerm() =
+ this
+ )
+ }
+
+ override predicate testWithGroups(string str, boolean ignorePrefix) {
+ ignorePrefix = true and
+ str = ["", "", "", "", "",
+ "", "", "", "",
+ "", "",
+ "", "", "",
+ "", "", "",
+ "", ""
+ ]
+ }
+}
+
+/**
+ * Holds if `regexp` matches some HTML tags, but misses some HTML tags that it should match.
+ *
+ * When adding a new case to this predicate, make sure the test string used in `matches(..)` calls are present in `HTMLMatchingRegExp::test` / `HTMLMatchingRegExp::testWithGroups`.
+ */
+predicate isBadRegexpFilter(HTMLMatchingRegExp regexp, string msg) {
+ // CVE-2021-33829 - matching both "" and "", but in different capture groups
+ regexp.matches("") and
+ regexp.matches("") and
+ exists(int a, int b | a != b |
+ regexp.fillsCaptureGroup("", a) and
+ // might be ambigously parsed (matching both capture groups), and that is ok here.
+ regexp.fillsCaptureGroup("", b) and
+ not regexp.fillsCaptureGroup("", a) and
+ msg =
+ "Comments ending with --> are matched differently from comments ending with --!>. The first is matched with capture group "
+ + a + " and comments ending with --!> are matched with capture group " +
+ strictconcat(int i | regexp.fillsCaptureGroup("", i) | i.toString(), ", ") +
+ "."
+ )
+ or
+ // CVE-2020-17480 - matching "" and other tags, but not "".
+ exists(int group, int other |
+ group != other and
+ regexp.fillsCaptureGroup("", group) and
+ regexp.fillsCaptureGroup("", other) and
+ not regexp.matches("") and
+ not regexp.fillsCaptureGroup("", any(int i | i != group)) and
+ not regexp.fillsCaptureGroup("", group) and
+ not regexp.fillsCaptureGroup("", group) and
+ not regexp.fillsCaptureGroup("") and
+ regexp.matches("") and
+ not regexp.matches("") and
+ (
+ not regexp.matches("") and
+ msg = "This regular expression matches , but not "
+ or
+ not regexp.matches("") and
+ msg = "This regular expression matches , but not "
+ )
+ or
+ regexp.matches("") and
+ regexp.matches("") and
+ not regexp.matches("") and
+ not regexp.matches("") and
+ msg = "This regular expression does not match script tags where the attribute uses single-quotes."
+ or
+ regexp.matches("") and
+ regexp.matches("") and
+ not regexp.matches("") and
+ not regexp.matches("") and
+ msg = "This regular expression does not match script tags where the attribute uses double-quotes."
+ or
+ regexp.matches("") and
+ regexp.matches("") and
+ not regexp.matches("") and
+ not regexp.matches("") and
+ not regexp.matches("") and
+ msg = "This regular expression does not match script tags where tabs are used between attributes."
+ or
+ regexp.matches("") and
+ not RegExpFlags::isIgnoreCase(regexp) and
+ not regexp.matches("") and
+ not regexp.matches("") and
+ (
+ not regexp.matches("") and
+ msg = "This regular expression does not match upper case ") and
+ regexp.matches("") and
+ msg = "This regular expression does not match mixed case ") and
+ not regexp.matches("") and
+ not regexp.matches("") and
+ (
+ not regexp.matches("") and
+ msg = "This regular expression does not match script end tags like ."
+ or
+ not regexp.matches("") and
+ msg = "This regular expression does not match script end tags like ."
+ or
+ not regexp.matches("") and
+ msg = "This regular expression does not match script end tags like ."
+ )
+}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/security/ClearText.qll b/repo-tests/codeql/python/ql/lib/semmle/python/security/ClearText.qll
index 8e964d19386..9905040da18 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/security/ClearText.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/security/ClearText.qll
@@ -47,11 +47,7 @@ module ClearTextLogging {
meth.getObject(name).(NameNode).getId().matches("logg%") and
call.getAnArg() = this
|
- name = "error" or
- name = "warn" or
- name = "warning" or
- name = "debug" or
- name = "info"
+ name = ["error", "warn", "warning", "debug", "info"]
)
}
}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/security/Exceptions.qll b/repo-tests/codeql/python/ql/lib/semmle/python/security/Exceptions.qll
index 25b0592c931..7bc4374bd64 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/security/Exceptions.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/security/Exceptions.qll
@@ -74,13 +74,10 @@ class ExceptionInfoSequence extends SequenceKind {
class CallToTracebackFunction extends ErrorInfoSource {
CallToTracebackFunction() {
exists(string name |
- name = "extract_tb" or
- name = "extract_stack" or
- name = "format_list" or
- name = "format_exception_only" or
- name = "format_exception" or
- name = "format_tb" or
- name = "format_stack"
+ name in [
+ "extract_tb", "extract_stack", "format_list", "format_exception_only", "format_exception",
+ "format_tb", "format_stack"
+ ]
|
this = traceback_function(name).getACall()
)
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/security/dataflow/PathInjection.qll b/repo-tests/codeql/python/ql/lib/semmle/python/security/dataflow/PathInjection.qll
index 30570c32f58..827fe806d71 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/security/dataflow/PathInjection.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/security/dataflow/PathInjection.qll
@@ -26,7 +26,11 @@ class PathNotNormalizedConfiguration extends TaintTracking::Configuration {
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
- override predicate isSanitizer(DataFlow::Node node) { node instanceof Path::PathNormalization }
+ override predicate isSanitizer(DataFlow::Node node) {
+ node instanceof Sanitizer
+ or
+ node instanceof Path::PathNormalization
+ }
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
guard instanceof SanitizerGuard
@@ -52,6 +56,8 @@ class FirstNormalizationConfiguration extends TaintTracking::Configuration {
override predicate isSink(DataFlow::Node sink) { sink instanceof Path::PathNormalization }
+ override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
+
override predicate isSanitizerOut(DataFlow::Node node) { node instanceof Path::PathNormalization }
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
@@ -67,6 +73,8 @@ class NormalizedPathNotCheckedConfiguration extends TaintTracking2::Configuratio
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
+ override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
+
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
guard instanceof Path::SafeAccessCheck
or
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll b/repo-tests/codeql/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll
index 21a6a2093e5..410eee50b29 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll
@@ -32,6 +32,16 @@ module PathInjection {
*/
abstract class Sink extends DataFlow::Node { }
+ /**
+ * A sanitizer for "path injection" vulnerabilities.
+ *
+ * This should only be used for things like calls to library functions that perform their own
+ * (correct) normalization/escaping of untrusted paths.
+ *
+ * Please also see `Path::SafeAccessCheck` and `Path::PathNormalization` Concepts.
+ */
+ abstract class Sanitizer extends DataFlow::Node { }
+
/**
* A sanitizer guard for "path injection" vulnerabilities.
*/
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/security/dataflow/PolynomialReDoSCustomizations.qll b/repo-tests/codeql/python/ql/lib/semmle/python/security/dataflow/PolynomialReDoSCustomizations.qll
index cbaf3b982e9..b92ff341ec8 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/security/dataflow/PolynomialReDoSCustomizations.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/security/dataflow/PolynomialReDoSCustomizations.qll
@@ -60,8 +60,8 @@ module PolynomialReDoS {
RegExpTerm t;
RegexExecutionAsSink() {
- exists(CompiledRegexes::RegexExecution re |
- re.getRegexNode().asExpr() = t.getRegex() and
+ exists(RegexExecution re |
+ re.getRegex().asExpr() = t.getRegex() and
this = re.getString()
) and
t.isRootTerm()
@@ -76,137 +76,3 @@ module PolynomialReDoS {
*/
class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { }
}
-
-/** Helper module for tracking compiled regexes. */
-private module CompiledRegexes {
- // TODO: This module should be refactored and merged with the experimental work done on detecting
- // regex injections, such that this can be expressed from just using a concept.
- /** A configuration for finding uses of compiled regexes. */
- class RegexDefinitionConfiguration extends DataFlow2::Configuration {
- RegexDefinitionConfiguration() { this = "RegexDefinitionConfiguration" }
-
- override predicate isSource(DataFlow::Node source) { source instanceof RegexDefinitonSource }
-
- override predicate isSink(DataFlow::Node sink) { sink instanceof RegexDefinitionSink }
- }
-
- /** A regex compilation. */
- class RegexDefinitonSource extends DataFlow::CallCfgNode {
- DataFlow::Node regexNode;
-
- RegexDefinitonSource() {
- this = API::moduleImport("re").getMember("compile").getACall() and
- regexNode in [this.getArg(0), this.getArgByName("pattern")]
- }
-
- /** Gets the regex that is being compiled by this node. */
- RegExpTerm getRegExp() { result.getRegex() = regexNode.asExpr() and result.isRootTerm() }
-
- /** Gets the data flow node for the regex being compiled by this node. */
- DataFlow::Node getRegexNode() { result = regexNode }
- }
-
- /** A use of a compiled regex. */
- class RegexDefinitionSink extends DataFlow::Node {
- RegexExecutionMethod method;
- DataFlow::CallCfgNode executingCall;
-
- RegexDefinitionSink() {
- exists(DataFlow::AttrRead reMethod |
- executingCall.getFunction() = reMethod and
- reMethod.getAttributeName() = method and
- this = reMethod.getObject()
- )
- }
-
- /** Gets the method used to execute the regex. */
- RegexExecutionMethod getMethod() { result = method }
-
- /** Gets the data flow node for the executing call. */
- DataFlow::CallCfgNode getExecutingCall() { result = executingCall }
- }
-
- /** A data flow node executing a regex. */
- abstract class RegexExecution extends DataFlow::Node {
- /** Gets the data flow node for the regex being compiled by this node. */
- abstract DataFlow::Node getRegexNode();
-
- /** Gets a dataflow node for the string to be searched or matched against. */
- abstract DataFlow::Node getString();
- }
-
- private class RegexExecutionMethod extends string {
- RegexExecutionMethod() {
- this in ["match", "fullmatch", "search", "split", "findall", "finditer", "sub", "subn"]
- }
- }
-
- /** Gets the index of the argument representing the string to be searched by a regex. */
- int stringArg(RegexExecutionMethod method) {
- method in ["match", "fullmatch", "search", "split", "findall", "finditer"] and
- result = 1
- or
- method in ["sub", "subn"] and
- result = 2
- }
-
- /**
- * A class to find `re` methods immediately executing an expression.
- *
- * See `RegexExecutionMethods`
- */
- class DirectRegex extends DataFlow::CallCfgNode, RegexExecution {
- RegexExecutionMethod method;
-
- DirectRegex() { this = API::moduleImport("re").getMember(method).getACall() }
-
- override DataFlow::Node getRegexNode() {
- result in [this.getArg(0), this.getArgByName("pattern")]
- }
-
- override DataFlow::Node getString() {
- result in [this.getArg(stringArg(method)), this.getArgByName("string")]
- }
- }
-
- /**
- * A class to find `re` methods immediately executing a compiled expression by `re.compile`.
- *
- * Given the following example:
- *
- * ```py
- * pattern = re.compile(input)
- * pattern.match(s)
- * ```
- *
- * This class will identify that `re.compile` compiles `input` and afterwards
- * executes `re`'s `match`. As a result, `this` will refer to `pattern.match(s)`
- * and `this.getRegexNode()` will return the node for `input` (`re.compile`'s first argument)
- *
- *
- * See `RegexExecutionMethods`
- *
- * See https://docs.python.org/3/library/re.html#regular-expression-objects
- */
- private class CompiledRegex extends DataFlow::CallCfgNode, RegexExecution {
- DataFlow::Node regexNode;
- RegexExecutionMethod method;
-
- CompiledRegex() {
- exists(
- RegexDefinitionConfiguration conf, RegexDefinitonSource source, RegexDefinitionSink sink
- |
- conf.hasFlow(source, sink) and
- regexNode = source.getRegexNode() and
- method = sink.getMethod() and
- this = sink.getExecutingCall()
- )
- }
-
- override DataFlow::Node getRegexNode() { result = regexNode }
-
- override DataFlow::Node getString() {
- result in [this.getArg(stringArg(method) - 1), this.getArgByName("string")]
- }
- }
-}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/security/dataflow/ReflectedXSSCustomizations.qll b/repo-tests/codeql/python/ql/lib/semmle/python/security/dataflow/ReflectedXSSCustomizations.qll
index 0e5410a8be2..93363d3409a 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/security/dataflow/ReflectedXSSCustomizations.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/security/dataflow/ReflectedXSSCustomizations.qll
@@ -59,7 +59,7 @@ module ReflectedXSS {
class HtmlEscapingAsSanitizer extends Sanitizer {
HtmlEscapingAsSanitizer() {
// TODO: For now, since there is not an `isSanitizingStep` member-predicate part of a
- // `TaintTracking::Configuration`, we use treat the output is a taint-sanitizer. This
+ // `TaintTracking::Configuration`, we treat the output as a taint-sanitizer. This
// is slightly imprecise, which you can see in the `m_unsafe + SAFE` test-case in
// python/ql/test/library-tests/frameworks/markupsafe/taint_test.py
//
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/security/dataflow/SqlInjectionCustomizations.qll b/repo-tests/codeql/python/ql/lib/semmle/python/security/dataflow/SqlInjectionCustomizations.qll
index c132878951f..756a1f6b773 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/security/dataflow/SqlInjectionCustomizations.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/security/dataflow/SqlInjectionCustomizations.qll
@@ -42,6 +42,13 @@ module SqlInjection {
*/
class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
+ /**
+ * A SQL statement of a SQL construction, considered as a flow sink.
+ */
+ class SqlConstructionAsSink extends Sink {
+ SqlConstructionAsSink() { this = any(SqlConstruction c).getSql() }
+ }
+
/**
* A SQL statement of a SQL execution, considered as a flow sink.
*/
@@ -49,13 +56,6 @@ module SqlInjection {
SqlExecutionAsSink() { this = any(SqlExecution e).getSql() }
}
- /**
- * The text argument of a SQLAlchemy TextClause construction, considered as a flow sink.
- */
- class TextArgAsSink extends Sink {
- TextArgAsSink() { this = any(SqlAlchemy::TextClause::TextClauseConstruction tcc).getTextArg() }
- }
-
/**
* A comparison with a constant string, considered as a sanitizer-guard.
*/
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/security/injection/Command.qll b/repo-tests/codeql/python/ql/lib/semmle/python/security/injection/Command.qll
index 3ed453268ee..2bb4d275938 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/security/injection/Command.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/security/injection/Command.qll
@@ -13,18 +13,11 @@ import semmle.python.security.strings.Untrusted
/** Abstract taint sink that is potentially vulnerable to malicious shell commands. */
abstract class CommandSink extends TaintSink { }
-private ModuleObject osOrPopenModule() {
- result.getName() = "os" or
- result.getName() = "popen2"
-}
+private ModuleObject osOrPopenModule() { result.getName() = ["os", "popen2"] }
private Object makeOsCall() {
exists(string name | result = ModuleObject::named("subprocess").attr(name) |
- name = "Popen" or
- name = "call" or
- name = "check_call" or
- name = "check_output" or
- name = "run"
+ name = ["Popen", "call", "check_call", "check_output", "run"]
)
}
@@ -65,8 +58,7 @@ class ShellCommand extends CommandSink {
call.getAnArg() = this and
call.getFunction().refersTo(osOrPopenModule().attr(name))
|
- name = "system" or
- name = "popen" or
+ name = ["system", "popen"] or
name.matches("popen_")
)
or
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/security/injection/RegexInjection.qll b/repo-tests/codeql/python/ql/lib/semmle/python/security/injection/RegexInjection.qll
new file mode 100644
index 00000000000..80601bd638f
--- /dev/null
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/security/injection/RegexInjection.qll
@@ -0,0 +1,37 @@
+/**
+ * Provides a taint-tracking configuration for detecting regular expression injection
+ * vulnerabilities.
+ *
+ * Note, for performance reasons: only import this file if
+ * `RegexInjection::Configuration` is needed, otherwise
+ * `RegexInjectionCustomizations` should be imported instead.
+ */
+
+private import python
+import semmle.python.dataflow.new.DataFlow
+import semmle.python.dataflow.new.TaintTracking
+
+/**
+ * Provides a taint-tracking configuration for detecting regular expression injection
+ * vulnerabilities.
+ */
+module RegexInjection {
+ import RegexInjectionCustomizations::RegexInjection
+
+ /**
+ * A taint-tracking configuration for detecting "reflected server-side cross-site scripting" vulnerabilities.
+ */
+ class Configuration extends TaintTracking::Configuration {
+ Configuration() { this = "RegexInjection" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof Source }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
+
+ override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
+
+ override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
+ guard instanceof SanitizerGuard
+ }
+ }
+}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/security/injection/RegexInjectionCustomizations.qll b/repo-tests/codeql/python/ql/lib/semmle/python/security/injection/RegexInjectionCustomizations.qll
new file mode 100644
index 00000000000..c26bae1c1b4
--- /dev/null
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/security/injection/RegexInjectionCustomizations.qll
@@ -0,0 +1,62 @@
+/**
+ * Provides default sources, sinks and sanitizers for detecting
+ * "regular expression injection"
+ * vulnerabilities, as well as extension points for adding your own.
+ */
+
+private import python
+private import semmle.python.Concepts
+private import semmle.python.dataflow.new.DataFlow
+private import semmle.python.dataflow.new.TaintTracking
+private import semmle.python.dataflow.new.RemoteFlowSources
+
+/**
+ * Provides default sources, sinks and sanitizers for detecting
+ * "regular expression injection"
+ * vulnerabilities, as well as extension points for adding your own.
+ */
+module RegexInjection {
+ /**
+ * A data flow source for "regular expression injection" vulnerabilities.
+ */
+ abstract class Source extends DataFlow::Node { }
+
+ /**
+ * A sink for "regular expression injection" vulnerabilities is the execution of a regular expression.
+ * If you have a custom way to execute regular expressions, you can extend `RegexExecution::Range`.
+ */
+ class Sink extends DataFlow::Node {
+ RegexExecution regexExecution;
+
+ Sink() { this = regexExecution.getRegex() }
+
+ /** Gets the call that executes the regular expression marked by this sink. */
+ RegexExecution getRegexExecution() { result = regexExecution }
+ }
+
+ /**
+ * A sanitizer for "regular expression injection" vulnerabilities.
+ */
+ abstract class Sanitizer extends DataFlow::Node { }
+
+ /**
+ * A sanitizer guard for "regular expression injection" vulnerabilities.
+ */
+ abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
+
+ /**
+ * A source of remote user input, considered as a flow source.
+ */
+ class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
+
+ /**
+ * A regex escaping, considered as a sanitizer.
+ */
+ class RegexEscapingAsSanitizer extends Sanitizer {
+ RegexEscapingAsSanitizer() {
+ // Due to use-use flow, we want the output rather than an input
+ // (so the input can still flow to other sinks).
+ this = any(RegexEscaping esc).getOutput()
+ }
+ }
+}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/security/internal/SensitiveDataHeuristics.qll b/repo-tests/codeql/python/ql/lib/semmle/python/security/internal/SensitiveDataHeuristics.qll
index 589c37120b9..2adce57db11 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/security/internal/SensitiveDataHeuristics.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/security/internal/SensitiveDataHeuristics.qll
@@ -58,7 +58,7 @@ module HeuristicNames {
*/
string maybeAccountInfo() {
result = "(?is).*acc(ou)?nt.*" or
- result = "(?is).*(puid|username|userid).*" or
+ result = "(?is).*(puid|username|userid|session(id|key)).*" or
result = "(?s).*([uU]|^|_|[a-z](?=U))([uU][iI][dD]).*"
}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/security/performance/ReDoSUtil.qll b/repo-tests/codeql/python/ql/lib/semmle/python/security/performance/ReDoSUtil.qll
index 12b7559615d..8b443992c0b 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/security/performance/ReDoSUtil.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/security/performance/ReDoSUtil.qll
@@ -477,7 +477,7 @@ private module CharacterClasses {
result = ["0", "9"]
or
cc.getValue() = "s" and
- result = [" "]
+ result = " "
or
cc.getValue() = "w" and
result = ["a", "Z", "_", "0", "9"]
@@ -490,7 +490,7 @@ private module CharacterClasses {
result = "9"
or
cc.getValue() = "s" and
- result = [" "]
+ result = " "
or
cc.getValue() = "w" and
result = "a"
@@ -542,7 +542,7 @@ private State before(RegExpTerm t) { result = Match(t, 0) }
/**
* Gets a state the NFA may be in after matching `t`.
*/
-private State after(RegExpTerm t) {
+State after(RegExpTerm t) {
exists(RegExpAlt alt | t = alt.getAChild() | result = after(alt))
or
exists(RegExpSequence seq, int i | t = seq.getChild(i) |
@@ -671,7 +671,7 @@ RegExpRoot getRoot(RegExpTerm term) {
/**
* A state in the NFA.
*/
-private newtype TState =
+newtype TState =
/**
* A state representing that the NFA is about to match a term.
* `i` is used to index into multi-char literals.
@@ -801,29 +801,26 @@ InputSymbol getAnInputSymbolMatching(string char) {
result = Any()
}
+/**
+ * Holds if `state` is a start state.
+ */
+predicate isStartState(State state) {
+ state = mkMatch(any(RegExpRoot r))
+ or
+ exists(RegExpCaret car | state = after(car))
+}
+
/**
* Predicates for constructing a prefix string that leads to a given state.
*/
private module PrefixConstruction {
- /**
- * Holds if `state` starts the string matched by the regular expression.
- */
- private predicate isStartState(State state) {
- state instanceof StateInPumpableRegexp and
- (
- state = Match(any(RegExpRoot r), _)
- or
- exists(RegExpCaret car | state = after(car))
- )
- }
-
/**
* Holds if `state` is the textually last start state for the regular expression.
*/
private predicate lastStartState(State state) {
exists(RegExpRoot root |
state =
- max(State s, Location l |
+ max(StateInPumpableRegexp s, Location l |
isStartState(s) and getRoot(s.getRepr()) = root and l = s.getRepr().getLocation()
|
s
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/templates/PyxlTags.qll b/repo-tests/codeql/python/ql/lib/semmle/python/templates/PyxlTags.qll
index f0e663cdad0..abfef070d78 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/templates/PyxlTags.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/templates/PyxlTags.qll
@@ -29,7 +29,7 @@ private predicate pyxl_tag(Call c, string name) {
}
class PyxlHtmlTag extends PyxlTag {
- PyxlHtmlTag() { this.getPyxlTagName().prefix(2) = "x_" }
+ PyxlHtmlTag() { this.getPyxlTagName().matches("x\\_%") }
string getTagName() { result = this.getPyxlTagName().suffix(2) }
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/types/Extensions.qll b/repo-tests/codeql/python/ql/lib/semmle/python/types/Extensions.qll
index 9c067ed7e4a..f0be03e7b23 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/types/Extensions.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/types/Extensions.qll
@@ -112,14 +112,7 @@ class BottleRoutePointToExtension extends PointsToExtension {
/* Python 3.6+ regex module constants */
string short_flag(string flag) {
- (
- flag = "ASCII" or
- flag = "IGNORECASE" or
- flag = "LOCALE" or
- flag = "UNICODE" or
- flag = "MULTILINE" or
- flag = "TEMPLATE"
- ) and
+ flag in ["ASCII", "IGNORECASE", "LOCALE", "UNICODE", "MULTILINE", "TEMPLATE"] and
result = flag.prefix(1)
or
flag = "DOTALL" and result = "S"
@@ -151,12 +144,10 @@ class ReModulePointToExtension extends PointsToExtension {
private predicate pointsTo_helper(Context context) { context.appliesTo(this) }
}
-deprecated private class BackwardCompatiblePointToExtension extends PointsToExtension {
- BackwardCompatiblePointToExtension() { this instanceof CustomPointsToFact }
-
+deprecated private class BackwardCompatiblePointToExtension extends PointsToExtension instanceof CustomPointsToFact {
override predicate pointsTo(Context context, ObjectInternal value, ControlFlowNode origin) {
exists(Object obj, ClassObject cls |
- this.(CustomPointsToFact).pointsTo(context, obj, cls, origin)
+ CustomPointsToFact.super.pointsTo(context, obj, cls, origin)
|
value.getBuiltin() = obj
or
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/types/FunctionObject.qll b/repo-tests/codeql/python/ql/lib/semmle/python/types/FunctionObject.qll
index c293a43d675..eb2f1e07c8b 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/types/FunctionObject.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/types/FunctionObject.qll
@@ -183,6 +183,7 @@ class PyFunctionObject extends FunctionObject {
}
/** Factored out to help join ordering */
+ pragma[noinline]
private predicate implicitlyReturns(Object none_, ClassObject noneType) {
noneType = theNoneType() and
not this.getFunction().isGenerator() and
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/types/Properties.qll b/repo-tests/codeql/python/ql/lib/semmle/python/types/Properties.qll
index 09bd08b6c15..a0efe6ca5eb 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/types/Properties.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/types/Properties.qll
@@ -84,7 +84,7 @@ private predicate property_getter(CallNode decorated, FunctionObject getter) {
private predicate property_setter(CallNode decorated, FunctionObject setter) {
property_getter(decorated, _) and
exists(CallNode setter_call, AttrNode prop_setter |
- prop_setter.getObject("setter").refersTo(decorated.(Object))
+ prop_setter.getObject("setter").refersTo(decorated)
|
setter_call.getArg(0).refersTo(setter) and
setter_call.getFunction() = prop_setter
@@ -97,7 +97,7 @@ private predicate property_setter(CallNode decorated, FunctionObject setter) {
private predicate property_deleter(CallNode decorated, FunctionObject deleter) {
property_getter(decorated, _) and
exists(CallNode deleter_call, AttrNode prop_deleter |
- prop_deleter.getObject("deleter").refersTo(decorated.(Object))
+ prop_deleter.getObject("deleter").refersTo(decorated)
|
deleter_call.getArg(0).refersTo(deleter) and
deleter_call.getFunction() = prop_deleter
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/values/StringAttributes.qll b/repo-tests/codeql/python/ql/lib/semmle/python/values/StringAttributes.qll
index a7a8ef00f00..792ee923227 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/values/StringAttributes.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/values/StringAttributes.qll
@@ -78,5 +78,5 @@ private predicate tracking_step(ControlFlowNode src, ControlFlowNode dest) {
or
tracked_call_step(src, dest)
or
- dest.refersTo(src.(Object))
+ dest.refersTo(src)
}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/web/Http.qll b/repo-tests/codeql/python/ql/lib/semmle/python/web/Http.qll
index 527a050d814..fc1b1bc5756 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/web/Http.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/web/Http.qll
@@ -33,7 +33,7 @@ class WsgiEnvironment extends TaintKind {
(
text = "QUERY_STRING" or
text = "PATH_INFO" or
- text.prefix(5) = "HTTP_"
+ text.matches("HTTP\\_%")
)
)
}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/web/HttpConstants.qll b/repo-tests/codeql/python/ql/lib/semmle/python/web/HttpConstants.qll
index d3e8b0412a4..f2ed3c92f97 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/web/HttpConstants.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/web/HttpConstants.qll
@@ -1,13 +1,5 @@
/** Gets an HTTP verb, in upper case */
-string httpVerb() {
- result = "GET" or
- result = "POST" or
- result = "PUT" or
- result = "PATCH" or
- result = "DELETE" or
- result = "OPTIONS" or
- result = "HEAD"
-}
+string httpVerb() { result in ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"] }
/** Gets an HTTP verb, in lower case */
string httpVerbLower() { result = httpVerb().toLowerCase() }
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/web/django/Model.qll b/repo-tests/codeql/python/ql/lib/semmle/python/web/django/Model.qll
index d48fe6e04f9..0b04340091a 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/web/django/Model.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/web/django/Model.qll
@@ -15,31 +15,11 @@ class DjangoDbTableObjects extends TaintKind {
override TaintKind getTaintOfMethodResult(string name) {
result = this and
- (
- name = "filter" or
- name = "exclude" or
- name = "annotate" or
- name = "order_by" or
- name = "reverse" or
- name = "distinct" or
- name = "values" or
- name = "values_list" or
- name = "dates" or
- name = "datetimes" or
- name = "none" or
- name = "all" or
- name = "union" or
- name = "intersection" or
- name = "difference" or
- name = "select_related" or
- name = "prefetch_related" or
- name = "extra" or
- name = "defer" or
- name = "only" or
- name = "using" or
- name = "select_for_update" or
- name = "raw"
- )
+ name in [
+ "filter", "exclude", "none", "all", "union", "intersection", "difference", "select_related",
+ "prefetch_related", "extra", "defer", "only", "annotate", "using", "select_for_update",
+ "raw", "order_by", "reverse", "distinct", "values", "values_list", "dates", "datetimes"
+ ]
}
}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/web/falcon/Request.qll b/repo-tests/codeql/python/ql/lib/semmle/python/web/falcon/Request.qll
index 4b6ceb93fb6..3951d80d29d 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/web/falcon/Request.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/web/falcon/Request.qll
@@ -12,13 +12,7 @@ class FalconRequest extends TaintKind {
name = "env" and result instanceof WsgiEnvironment
or
result instanceof ExternalStringKind and
- (
- name = "uri" or
- name = "url" or
- name = "forwarded_uri" or
- name = "relative_uri" or
- name = "query_string"
- )
+ name in ["uri", "url", "forwarded_uri", "relative_uri", "query_string"]
or
result instanceof ExternalStringDictKind and
(name = "cookies" or name = "params")
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/web/flask/Request.qll b/repo-tests/codeql/python/ql/lib/semmle/python/web/flask/Request.qll
index ceae5e7a2c6..67bf77a5ac5 100644
--- a/repo-tests/codeql/python/ql/lib/semmle/python/web/flask/Request.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/web/flask/Request.qll
@@ -32,12 +32,7 @@ class FlaskRequestData extends HttpRequestTaintSource {
class FlaskRequestArgs extends HttpRequestTaintSource {
FlaskRequestArgs() {
exists(string attr | flask_request_attr(this, attr) |
- attr = "args" or
- attr = "form" or
- attr = "values" or
- attr = "files" or
- attr = "headers" or
- attr = "json"
+ attr in ["args", "form", "values", "files", "headers", "json"]
)
}
diff --git a/repo-tests/codeql/python/ql/lib/semmle/python/xml/XML.qll b/repo-tests/codeql/python/ql/lib/semmle/python/xml/XML.qll
index 4c762f4bf65..76f3b3cb022 100755
--- a/repo-tests/codeql/python/ql/lib/semmle/python/xml/XML.qll
+++ b/repo-tests/codeql/python/ql/lib/semmle/python/xml/XML.qll
@@ -108,7 +108,7 @@ class XMLParent extends @xmlparent {
}
/** Gets the text value contained in this XML parent. */
- string getTextValue() { result = allCharactersString() }
+ string getTextValue() { result = this.allCharactersString() }
/** Gets a printable representation of this XML parent. */
string toString() { result = this.getName() }
@@ -119,7 +119,7 @@ class XMLFile extends XMLParent, File {
XMLFile() { xmlEncoding(this, _) }
/** Gets a printable representation of this XML file. */
- override string toString() { result = getName() }
+ override string toString() { result = this.getName() }
/** Gets the name of this XML file. */
override string getName() { result = File.super.getAbsolutePath() }
@@ -129,14 +129,14 @@ class XMLFile extends XMLParent, File {
*
* Gets the path of this XML file.
*/
- deprecated string getPath() { result = getAbsolutePath() }
+ deprecated string getPath() { result = this.getAbsolutePath() }
/**
* DEPRECATED: Use `getParentContainer().getAbsolutePath()` instead.
*
* Gets the path of the folder that contains this XML file.
*/
- deprecated string getFolder() { result = getParentContainer().getAbsolutePath() }
+ deprecated string getFolder() { result = this.getParentContainer().getAbsolutePath() }
/** Gets the encoding of this XML file. */
string getEncoding() { xmlEncoding(this, result) }
@@ -200,7 +200,7 @@ class XMLDTD extends XMLLocatable, @xmldtd {
*/
class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
/** Holds if this XML element has the given `name`. */
- predicate hasName(string name) { name = getName() }
+ predicate hasName(string name) { name = this.getName() }
/** Gets the name of this XML element. */
override string getName() { xmlElements(this, result, _, _, _) }
@@ -239,7 +239,7 @@ class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
string getAttributeValue(string name) { result = this.getAttribute(name).getValue() }
/** Gets a printable representation of this XML element. */
- override string toString() { result = getName() }
+ override string toString() { result = this.getName() }
}
/**
diff --git a/repo-tests/codeql/python/ql/src/Classes/UselessClass.ql b/repo-tests/codeql/python/ql/src/Classes/UselessClass.ql
index 2695e9a7a1d..19d21c7e7ca 100644
--- a/repo-tests/codeql/python/ql/src/Classes/UselessClass.ql
+++ b/repo-tests/codeql/python/ql/src/Classes/UselessClass.ql
@@ -52,11 +52,7 @@ predicate is_stateful(Class c) {
call.getFunc() = a and
a.getName() = name
|
- name = "pop" or
- name = "remove" or
- name = "discard" or
- name = "extend" or
- name = "append"
+ name in ["pop", "remove", "discard", "extend", "append"]
)
}
diff --git a/repo-tests/codeql/python/ql/src/Diagnostics/ExtractionErrors.ql b/repo-tests/codeql/python/ql/src/Diagnostics/ExtractionErrors.ql
deleted file mode 100644
index 5d9ddee6eb7..00000000000
--- a/repo-tests/codeql/python/ql/src/Diagnostics/ExtractionErrors.ql
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * @name Python extraction errors
- * @description List all extraction errors for Python files in the source code directory.
- * @kind diagnostic
- * @id py/diagnostics/extraction-errors
- */
-
-import python
-
-/**
- * Gets the SARIF severity for errors.
- *
- * See point 3.27.10 in https://docs.oasis-open.org/sarif/sarif/v2.0/sarif-v2.0.html for
- * what error means.
- */
-int getErrorSeverity() { result = 2 }
-
-from SyntaxError error, File file
-where
- file = error.getFile() and
- exists(file.getRelativePath())
-select error, "Extraction failed in " + file + " with error " + error.getMessage(),
- getErrorSeverity()
diff --git a/repo-tests/codeql/python/ql/src/Diagnostics/ExtractionWarnings.ql b/repo-tests/codeql/python/ql/src/Diagnostics/ExtractionWarnings.ql
new file mode 100644
index 00000000000..553798a546e
--- /dev/null
+++ b/repo-tests/codeql/python/ql/src/Diagnostics/ExtractionWarnings.ql
@@ -0,0 +1,36 @@
+/**
+ * @name Python extraction warnings
+ * @description List all extraction warnings for Python files in the source code directory.
+ * @kind diagnostic
+ * @id py/diagnostics/extraction-warnings
+ */
+
+import python
+
+/**
+ * Gets the SARIF severity for warnings.
+ *
+ * See https://docs.oasis-open.org/sarif/sarif/v2.1.0/csprd01/sarif-v2.1.0-csprd01.html#_Toc10541338
+ */
+int getWarningSeverity() { result = 1 }
+
+// The spec
+// https://docs.oasis-open.org/sarif/sarif/v2.1.0/csprd01/sarif-v2.1.0-csprd01.html#_Toc10541338
+// defines error and warning as:
+//
+// "error": A serious problem was found. The condition encountered by the tool resulted
+// in the analysis being halted or caused the results to be incorrect or incomplete.
+//
+// "warning": A problem that is not considered serious was found. The condition
+// encountered by the tool is such that it is uncertain whether a problem occurred, or
+// is such that the analysis might be incomplete but the results that were generated are
+// probably valid.
+//
+// So SyntaxErrors are reported at the warning level, since analysis might be incomplete
+// but the results that were generated are probably valid.
+from SyntaxError error, File file
+where
+ file = error.getFile() and
+ exists(file.getRelativePath())
+select error, "Extraction failed in " + file + " with error " + error.getMessage(),
+ getWarningSeverity()
diff --git a/repo-tests/codeql/python/ql/src/Expressions/CallArgs.qll b/repo-tests/codeql/python/ql/src/Expressions/CallArgs.qll
index 5e7e56d99d8..d2f94ca7cf0 100644
--- a/repo-tests/codeql/python/ql/src/Expressions/CallArgs.qll
+++ b/repo-tests/codeql/python/ql/src/Expressions/CallArgs.qll
@@ -99,14 +99,14 @@ private ControlFlowNode get_a_call(Value callable) {
/** Gets the function object corresponding to the given class or function. */
FunctionObject get_function_or_initializer_objectapi(Object func_or_cls) {
- result = func_or_cls.(FunctionObject)
+ result = func_or_cls
or
result = func_or_cls.(ClassObject).declaredAttribute("__init__")
}
/** Gets the function object corresponding to the given class or function. */
FunctionValue get_function_or_initializer(Value func_or_cls) {
- result = func_or_cls.(FunctionValue)
+ result = func_or_cls
or
result = func_or_cls.(ClassValue).declaredAttribute("__init__")
}
diff --git a/repo-tests/codeql/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.ql b/repo-tests/codeql/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.ql
index b3c97e967f6..a99a66bca3b 100644
--- a/repo-tests/codeql/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.ql
+++ b/repo-tests/codeql/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.ql
@@ -22,49 +22,14 @@ private predicate indexing_method(string name) {
}
private predicate arithmetic_method(string name) {
- name = "__add__" or
- name = "__sub__" or
- name = "__div__" or
- name = "__pos__" or
- name = "__abs__" or
- name = "__floordiv__" or
- name = "__div__" or
- name = "__divmod__" or
- name = "__lshift__" or
- name = "__and__" or
- name = "__or__" or
- name = "__xor__" or
- name = "__rshift__" or
- name = "__pow__" or
- name = "__mul__" or
- name = "__neg__" or
- name = "__radd__" or
- name = "__rsub__" or
- name = "__rdiv__" or
- name = "__rfloordiv__" or
- name = "__rdiv__" or
- name = "__rlshift__" or
- name = "__rand__" or
- name = "__ror__" or
- name = "__rxor__" or
- name = "__rrshift__" or
- name = "__rpow__" or
- name = "__rmul__" or
- name = "__truediv__" or
- name = "__rtruediv__" or
- name = "__iadd__" or
- name = "__isub__" or
- name = "__idiv__" or
- name = "__ifloordiv__" or
- name = "__idiv__" or
- name = "__ilshift__" or
- name = "__iand__" or
- name = "__ior__" or
- name = "__ixor__" or
- name = "__irshift__" or
- name = "__ipow__" or
- name = "__imul__" or
- name = "__itruediv__"
+ name in [
+ "__add__", "__sub__", "__or__", "__xor__", "__rshift__", "__pow__", "__mul__", "__neg__",
+ "__radd__", "__rsub__", "__rdiv__", "__rfloordiv__", "__div__", "__rdiv__", "__rlshift__",
+ "__rand__", "__ror__", "__rxor__", "__rrshift__", "__rpow__", "__rmul__", "__truediv__",
+ "__rtruediv__", "__pos__", "__iadd__", "__isub__", "__idiv__", "__ifloordiv__", "__idiv__",
+ "__ilshift__", "__iand__", "__ior__", "__ixor__", "__irshift__", "__abs__", "__ipow__",
+ "__imul__", "__itruediv__", "__floordiv__", "__div__", "__divmod__", "__lshift__", "__and__"
+ ]
}
private predicate ordering_method(string name) {
diff --git a/repo-tests/codeql/python/ql/src/Functions/SignatureOverriddenMethod.ql b/repo-tests/codeql/python/ql/src/Functions/SignatureOverriddenMethod.ql
index e695f2385ea..85f1f0c2eb1 100644
--- a/repo-tests/codeql/python/ql/src/Functions/SignatureOverriddenMethod.ql
+++ b/repo-tests/codeql/python/ql/src/Functions/SignatureOverriddenMethod.ql
@@ -24,7 +24,6 @@ where
not derived.getScope().isSpecialMethod() and
derived.getName() != "__init__" and
derived.isNormalMethod() and
- not derived.getScope().isSpecialMethod() and
// call to overrides distributed for efficiency
(
derived.overrides(base) and derived.minParameters() > base.maxParameters()
diff --git a/repo-tests/codeql/python/ql/src/Functions/SignatureSpecialMethods.ql b/repo-tests/codeql/python/ql/src/Functions/SignatureSpecialMethods.ql
index f240f0e5087..feedb6c94b6 100644
--- a/repo-tests/codeql/python/ql/src/Functions/SignatureSpecialMethods.ql
+++ b/repo-tests/codeql/python/ql/src/Functions/SignatureSpecialMethods.ql
@@ -13,98 +13,29 @@
import python
predicate is_unary_op(string name) {
- name = "__del__" or
- name = "__repr__" or
- name = "__str__" or
- name = "__hash__" or
- name = "__bool__" or
- name = "__nonzero__" or
- name = "__unicode__" or
- name = "__len__" or
- name = "__iter__" or
- name = "__reversed__" or
- name = "__neg__" or
- name = "__pos__" or
- name = "__abs__" or
- name = "__invert__" or
- name = "__complex__" or
- name = "__int__" or
- name = "__float__" or
- name = "__long__" or
- name = "__oct__" or
- name = "__hex__" or
- name = "__index__" or
- name = "__enter__"
+ name in [
+ "__del__", "__repr__", "__neg__", "__pos__", "__abs__", "__invert__", "__complex__",
+ "__int__", "__float__", "__long__", "__oct__", "__hex__", "__str__", "__index__", "__enter__",
+ "__hash__", "__bool__", "__nonzero__", "__unicode__", "__len__", "__iter__", "__reversed__"
+ ]
}
predicate is_binary_op(string name) {
- name = "__lt__" or
- name = "__le__" or
- name = "__eq__" or
- name = "__ne__" or
- name = "__gt__" or
- name = "__ge__" or
- name = "__cmp__" or
- name = "__rcmp__" or
- name = "__getattr___" or
- name = "__getattribute___" or
- name = "__delattr__" or
- name = "__delete__" or
- name = "__instancecheck__" or
- name = "__subclasscheck__" or
- name = "__getitem__" or
- name = "__delitem__" or
- name = "__contains__" or
- name = "__add__" or
- name = "__sub__" or
- name = "__mul__" or
- name = "__floordiv__" or
- name = "__div__" or
- name = "__truediv__" or
- name = "__mod__" or
- name = "__divmod__" or
- name = "__lshift__" or
- name = "__rshift__" or
- name = "__and__" or
- name = "__xor__" or
- name = "__or__" or
- name = "__radd__" or
- name = "__rsub__" or
- name = "__rmul__" or
- name = "__rfloordiv__" or
- name = "__rdiv__" or
- name = "__rtruediv__" or
- name = "__rmod__" or
- name = "__rdivmod__" or
- name = "__rpow__" or
- name = "__rlshift__" or
- name = "__rrshift__" or
- name = "__rand__" or
- name = "__rxor__" or
- name = "__ror__" or
- name = "__iadd__" or
- name = "__isub__" or
- name = "__imul__" or
- name = "__ifloordiv__" or
- name = "__idiv__" or
- name = "__itruediv__" or
- name = "__imod__" or
- name = "__idivmod__" or
- name = "__ipow__" or
- name = "__ilshift__" or
- name = "__irshift__" or
- name = "__iand__" or
- name = "__ixor__" or
- name = "__ior__" or
- name = "__coerce__"
+ name in [
+ "__lt__", "__le__", "__delattr__", "__delete__", "__instancecheck__", "__subclasscheck__",
+ "__getitem__", "__delitem__", "__contains__", "__add__", "__sub__", "__mul__", "__eq__",
+ "__floordiv__", "__div__", "__truediv__", "__mod__", "__divmod__", "__lshift__", "__rshift__",
+ "__and__", "__xor__", "__or__", "__ne__", "__radd__", "__rsub__", "__rmul__", "__rfloordiv__",
+ "__rdiv__", "__rtruediv__", "__rmod__", "__rdivmod__", "__rpow__", "__rlshift__", "__gt__",
+ "__rrshift__", "__rand__", "__rxor__", "__ror__", "__iadd__", "__isub__", "__imul__",
+ "__ifloordiv__", "__idiv__", "__itruediv__", "__ge__", "__imod__", "__idivmod__", "__ipow__",
+ "__ilshift__", "__irshift__", "__iand__", "__ixor__", "__ior__", "__coerce__", "__cmp__",
+ "__rcmp__", "__getattr___", "__getattribute___"
+ ]
}
predicate is_ternary_op(string name) {
- name = "__setattr__" or
- name = "__set__" or
- name = "__setitem__" or
- name = "__getslice__" or
- name = "__delslice__"
+ name in ["__setattr__", "__set__", "__setitem__", "__getslice__", "__delslice__"]
}
predicate is_quad_op(string name) { name = "__setslice__" or name = "__exit__" }
diff --git a/repo-tests/codeql/python/ql/src/Lexical/CommentedOutCode.qll b/repo-tests/codeql/python/ql/src/Lexical/CommentedOutCode.qll
index f0db1180231..afe72e927a6 100644
--- a/repo-tests/codeql/python/ql/src/Lexical/CommentedOutCode.qll
+++ b/repo-tests/codeql/python/ql/src/Lexical/CommentedOutCode.qll
@@ -210,9 +210,9 @@ class CommentedOutCodeBlock extends @py_comment {
/** Whether this commented-out code block is likely to be example code embedded in a larger comment. */
predicate maybeExampleCode() {
- exists(CommentBlock block | block.contains(this.(Comment)) |
+ exists(CommentBlock block | block.contains(this) |
exists(int all_code |
- all_code = sum(CommentedOutCodeBlock code | block.contains(code.(Comment)) | code.length()) and
+ all_code = sum(CommentedOutCodeBlock code | block.contains(code) | code.length()) and
/* This ratio may need fine tuning */
block.length() > all_code * 2
)
@@ -297,41 +297,17 @@ private predicate file_or_url(Comment c) {
c.getText().regexpMatch("#[^'\"]+(\\[a-zA-Z]\\w*)+\\.[a-zA-Z]+.*")
}
-private string operator_keyword() {
- result = "import" or
- result = "and" or
- result = "is" or
- result = "or" or
- result = "in" or
- result = "not" or
- result = "as"
-}
+private string operator_keyword() { result in ["import", "and", "is", "or", "in", "not", "as"] }
private string keyword_requiring_colon() {
- result = "try" or
- result = "while" or
- result = "elif" or
- result = "else" or
- result = "if" or
- result = "except" or
- result = "def" or
- result = "class"
+ result in ["try", "while", "elif", "else", "if", "except", "def", "class"]
}
private string other_keyword() {
- result = "del" or
- result = "lambda" or
- result = "from" or
- result = "global" or
- result = "with" or
- result = "assert" or
- result = "yield" or
- result = "finally" or
- result = "print" or
- result = "exec" or
- result = "raise" or
- result = "return" or
- result = "for"
+ result in [
+ "del", "lambda", "raise", "return", "for", "from", "global", "with", "assert", "yield",
+ "finally", "print", "exec"
+ ]
}
private string a_keyword() {
diff --git a/repo-tests/codeql/python/ql/src/Security/CWE-116/BadTagFilter.ql b/repo-tests/codeql/python/ql/src/Security/CWE-116/BadTagFilter.ql
new file mode 100644
index 00000000000..56990590b22
--- /dev/null
+++ b/repo-tests/codeql/python/ql/src/Security/CWE-116/BadTagFilter.ql
@@ -0,0 +1,19 @@
+/**
+ * @name Bad HTML filtering regexp
+ * @description Matching HTML tags using regular expressions is hard to do right, and can easily lead to security issues.
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity 7.8
+ * @precision high
+ * @id py/bad-tag-filter
+ * @tags correctness
+ * security
+ * external/cwe/cwe-116
+ * external/cwe/cwe-020
+ */
+
+import semmle.python.security.BadTagFilterQuery
+
+from HTMLMatchingRegExp regexp, string msg
+where msg = min(string m | isBadRegexpFilter(regexp, m) | m order by m.length(), m) // there might be multiple, we arbitrarily pick the shortest one
+select regexp, msg
diff --git a/repo-tests/codeql/python/ql/src/Security/CWE-295/RequestWithoutValidation.ql b/repo-tests/codeql/python/ql/src/Security/CWE-295/RequestWithoutValidation.ql
index 5a3819f498e..6eeac1e133a 100644
--- a/repo-tests/codeql/python/ql/src/Security/CWE-295/RequestWithoutValidation.ql
+++ b/repo-tests/codeql/python/ql/src/Security/CWE-295/RequestWithoutValidation.ql
@@ -11,17 +11,46 @@
*/
import python
-import semmle.python.web.Http
+private import semmle.python.dataflow.new.DataFlow
+private import semmle.python.Concepts
+private import semmle.python.ApiGraphs
-FunctionValue requestFunction() { result = Module::named("requests").attr(httpVerbLower()) }
+/**
+ * Gets a call to a method that makes an outgoing request using the `requests` module,
+ * such as `requests.get` or `requests.put`, with the specified HTTP verb `verb`
+ */
+DataFlow::CallCfgNode outgoingRequestCall(string verb) {
+ verb = HTTP::httpVerbLower() and
+ result = API::moduleImport("requests").getMember(verb).getACall()
+}
-/** requests treats None as the default and all other "falsey" values as False */
-predicate falseNotNone(Value v) { v.getDefiniteBooleanValue() = false and not v = Value::none_() }
+/** Gets the "verfiy" argument to a outgoingRequestCall. */
+DataFlow::Node verifyArg(DataFlow::CallCfgNode call) {
+ call = outgoingRequestCall(_) and
+ result = call.getArgByName("verify")
+}
-from CallNode call, FunctionValue func, Value falsey, ControlFlowNode origin
+/** Gets a back-reference to the verify argument `arg`. */
+private DataFlow::TypeTrackingNode verifyArgBacktracker(
+ DataFlow::TypeBackTracker t, DataFlow::Node arg
+) {
+ t.start() and
+ arg = verifyArg(_) and
+ result = arg.getALocalSource()
+ or
+ exists(DataFlow::TypeBackTracker t2 | result = verifyArgBacktracker(t2, arg).backtrack(t2, t))
+}
+
+/** Gets a back-reference to the verify argument `arg`. */
+DataFlow::LocalSourceNode verifyArgBacktracker(DataFlow::Node arg) {
+ result = verifyArgBacktracker(DataFlow::TypeBackTracker::end(), arg)
+}
+
+from DataFlow::CallCfgNode call, DataFlow::Node falseyOrigin, string verb
where
- func = requestFunction() and
- func.getACall() = call and
- falseNotNone(falsey) and
- call.getArgByName("verify").pointsTo(falsey, origin)
-select call, "Call to $@ with verify=$@", func, "requests." + func.getName(), origin, "False"
+ call = outgoingRequestCall(verb) and
+ falseyOrigin = verifyArgBacktracker(verifyArg(call)) and
+ // requests treats `None` as the default and all other "falsey" values as `False`.
+ falseyOrigin.asExpr().(ImmutableLiteral).booleanValue() = false and
+ not falseyOrigin.asExpr() instanceof None
+select call, "Call to requests." + verb + " with verify=$@", falseyOrigin, "False"
diff --git a/repo-tests/codeql/python/ql/src/experimental/Security/CWE-730/RegexInjection.ql b/repo-tests/codeql/python/ql/src/Security/CWE-730/RegexInjection.ql
similarity index 58%
rename from repo-tests/codeql/python/ql/src/experimental/Security/CWE-730/RegexInjection.ql
rename to repo-tests/codeql/python/ql/src/Security/CWE-730/RegexInjection.ql
index 7725f636eb0..0dfb5b00d52 100644
--- a/repo-tests/codeql/python/ql/src/experimental/Security/CWE-730/RegexInjection.ql
+++ b/repo-tests/codeql/python/ql/src/Security/CWE-730/RegexInjection.ql
@@ -5,25 +5,24 @@
* exponential time on certain inputs.
* @kind path-problem
* @problem.severity error
+ * @precision high
* @id py/regex-injection
* @tags security
* external/cwe/cwe-730
* external/cwe/cwe-400
*/
-// determine precision above
import python
-import experimental.semmle.python.security.injection.RegexInjection
+private import semmle.python.Concepts
+import semmle.python.security.injection.RegexInjection
import DataFlow::PathGraph
from
- RegexInjectionFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink,
- RegexInjectionSink regexInjectionSink, Attribute methodAttribute
+ RegexInjection::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink,
+ RegexExecution regexExecution
where
config.hasFlowPath(source, sink) and
- regexInjectionSink = sink.getNode() and
- methodAttribute = regexInjectionSink.getRegexMethod()
+ regexExecution = sink.getNode().(RegexInjection::Sink).getRegexExecution()
select sink.getNode(), source, sink,
"$@ regular expression is constructed from a $@ and executed by $@.", sink.getNode(), "This",
- source.getNode(), "user-provided value", methodAttribute,
- regexInjectionSink.getRegexModule() + "." + methodAttribute.getName()
+ source.getNode(), "user-provided value", regexExecution, regexExecution.getName()
diff --git a/repo-tests/codeql/python/ql/src/Security/CWE-798/HardcodedCredentials.ql b/repo-tests/codeql/python/ql/src/Security/CWE-798/HardcodedCredentials.ql
index cd00908fe05..895352be75c 100644
--- a/repo-tests/codeql/python/ql/src/Security/CWE-798/HardcodedCredentials.ql
+++ b/repo-tests/codeql/python/ql/src/Security/CWE-798/HardcodedCredentials.ql
@@ -88,7 +88,7 @@ class CredentialSink extends TaintSink {
CredentialSink() {
exists(string name |
name.regexpMatch(getACredentialRegex()) and
- not name.suffix(name.length() - 4) = "file"
+ not name.matches("%file")
|
any(FunctionValue func).getNamedArgumentForCall(_, name) = this
or
diff --git a/repo-tests/codeql/python/ql/src/Statements/RedundantAssignment.ql b/repo-tests/codeql/python/ql/src/Statements/RedundantAssignment.ql
index b56dea3eecb..097e4d0052f 100644
--- a/repo-tests/codeql/python/ql/src/Statements/RedundantAssignment.ql
+++ b/repo-tests/codeql/python/ql/src/Statements/RedundantAssignment.ql
@@ -70,10 +70,11 @@ predicate same_attribute(Attribute a1, Attribute a2) {
not is_property_access(a1)
}
+pragma[nomagic]
+Comment pyflakes_comment() { result.getText().toLowerCase().matches("%pyflakes%") }
+
int pyflakes_commented_line(File file) {
- exists(Comment c | c.getText().toLowerCase().matches("%pyflakes%") |
- c.getLocation().hasLocationInfo(file.getAbsolutePath(), result, _, _, _)
- )
+ pyflakes_comment().getLocation().hasLocationInfo(file.getAbsolutePath(), result, _, _, _)
}
predicate pyflakes_commented(AssignStmt assignment) {
diff --git a/repo-tests/codeql/python/ql/src/Statements/SideEffectInAssert.ql b/repo-tests/codeql/python/ql/src/Statements/SideEffectInAssert.ql
index f96e04243af..21aff6ca646 100644
--- a/repo-tests/codeql/python/ql/src/Statements/SideEffectInAssert.ql
+++ b/repo-tests/codeql/python/ql/src/Statements/SideEffectInAssert.ql
@@ -15,16 +15,9 @@ import python
predicate func_with_side_effects(Expr e) {
exists(string name | name = e.(Attribute).getName() or name = e.(Name).getId() |
- name = "print" or
- name = "write" or
- name = "append" or
- name = "pop" or
- name = "remove" or
- name = "discard" or
- name = "delete" or
- name = "close" or
- name = "open" or
- name = "exit"
+ name in [
+ "print", "write", "append", "pop", "remove", "discard", "delete", "close", "open", "exit"
+ ]
)
}
diff --git a/repo-tests/codeql/python/ql/src/analysis/Consistency.ql b/repo-tests/codeql/python/ql/src/analysis/Consistency.ql
index a504216a252..ae48f126235 100644
--- a/repo-tests/codeql/python/ql/src/analysis/Consistency.ql
+++ b/repo-tests/codeql/python/ql/src/analysis/Consistency.ql
@@ -8,15 +8,10 @@ import python
import DefinitionTracking
predicate uniqueness_error(int number, string what, string problem) {
- (
- what = "toString" or
- what = "getLocation" or
- what = "getNode" or
- what = "getDefinition" or
- what = "getEntryNode" or
- what = "getOrigin" or
- what = "getAnInferredType"
- ) and
+ what in [
+ "toString", "getLocation", "getNode", "getDefinition", "getEntryNode", "getOrigin",
+ "getAnInferredType"
+ ] and
(
number = 0 and problem = "no results for " + what + "()"
or
@@ -141,7 +136,7 @@ predicate builtin_object_consistency(string clsname, string problem, string what
or
not exists(o.toString()) and
problem = "no toString" and
- not exists(string name | name.prefix(7) = "_semmle" | py_special_objects(o, name)) and
+ not exists(string name | name.matches("\\_semmle%") | py_special_objects(o, name)) and
not o = unknownValue()
)
}
diff --git a/repo-tests/codeql/python/ql/src/experimental/Security/CWE-113/HeaderInjection.ql b/repo-tests/codeql/python/ql/src/experimental/Security/CWE-113/HeaderInjection.ql
new file mode 100644
index 00000000000..3cb4a20d5de
--- /dev/null
+++ b/repo-tests/codeql/python/ql/src/experimental/Security/CWE-113/HeaderInjection.ql
@@ -0,0 +1,21 @@
+/**
+ * @name HTTP Header Injection
+ * @description User input should not be used in HTTP headers, otherwise a malicious user
+ * may be able to inject a value that could manipulate the response.
+ * @kind path-problem
+ * @problem.severity error
+ * @id py/header-injection
+ * @tags security
+ * external/cwe/cwe-113
+ * external/cwe/cwe-079
+ */
+
+// determine precision above
+import python
+import experimental.semmle.python.security.injection.HTTPHeaders
+import DataFlow::PathGraph
+
+from HeaderInjectionFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
+where config.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "$@ HTTP header is constructed from a $@.", sink.getNode(),
+ "This", source.getNode(), "user-provided value"
diff --git a/repo-tests/codeql/python/ql/src/experimental/Security/CWE-347/JWTEmptyKeyOrAlgorithm.ql b/repo-tests/codeql/python/ql/src/experimental/Security/CWE-347/JWTEmptyKeyOrAlgorithm.ql
new file mode 100644
index 00000000000..2f071a60536
--- /dev/null
+++ b/repo-tests/codeql/python/ql/src/experimental/Security/CWE-347/JWTEmptyKeyOrAlgorithm.ql
@@ -0,0 +1,22 @@
+/**
+ * @name JWT encoding using empty key or algorithm
+ * @description The application uses an empty secret or algorithm while encoding a JWT Token.
+ * @kind problem
+ * @problem.severity warning
+ * @id py/jwt-empty-secret-or-algorithm
+ * @tags security
+ */
+
+// determine precision above
+import python
+import experimental.semmle.python.Concepts
+import experimental.semmle.python.frameworks.JWT
+
+from JWTEncoding jwtEncoding, string affectedComponent
+where
+ affectedComponent = "algorithm" and
+ isEmptyOrNone(jwtEncoding.getAlgorithm())
+ or
+ affectedComponent = "key" and
+ isEmptyOrNone(jwtEncoding.getKey())
+select jwtEncoding, "This JWT encoding has an empty " + affectedComponent + "."
diff --git a/repo-tests/codeql/python/ql/src/experimental/Security/CWE-347/JWTMissingSecretOrPublicKeyVerification.ql b/repo-tests/codeql/python/ql/src/experimental/Security/CWE-347/JWTMissingSecretOrPublicKeyVerification.ql
new file mode 100644
index 00000000000..e1f1cbb95ba
--- /dev/null
+++ b/repo-tests/codeql/python/ql/src/experimental/Security/CWE-347/JWTMissingSecretOrPublicKeyVerification.ql
@@ -0,0 +1,17 @@
+/**
+ * @name JWT missing secret or public key verification
+ * @description The application does not verify the JWT payload with a cryptographic secret or public key.
+ * @kind problem
+ * @problem.severity warning
+ * @id py/jwt-missing-verification
+ * @tags security
+ * external/cwe/cwe-347
+ */
+
+// determine precision above
+import python
+import experimental.semmle.python.Concepts
+
+from JWTDecoding jwtDecoding
+where not jwtDecoding.verifiesSignature()
+select jwtDecoding.getPayload(), "is not verified with a cryptographic secret or public key."
diff --git a/repo-tests/codeql/python/ql/src/experimental/semmle/python/Concepts.qll b/repo-tests/codeql/python/ql/src/experimental/semmle/python/Concepts.qll
index d0f021ee5fd..3b1f6072f0c 100644
--- a/repo-tests/codeql/python/ql/src/experimental/semmle/python/Concepts.qll
+++ b/repo-tests/codeql/python/ql/src/experimental/semmle/python/Concepts.qll
@@ -44,73 +44,6 @@ class LogOutput extends DataFlow::Node {
DataFlow::Node getAnInput() { result = range.getAnInput() }
}
-/** Provides classes for modeling Regular Expression-related APIs. */
-module RegexExecution {
- /**
- * A data-flow node that executes a regular expression.
- *
- * Extend this class to model new APIs. If you want to refine existing API models,
- * extend `RegexExecution` instead.
- */
- abstract class Range extends DataFlow::Node {
- /**
- * Gets the argument containing the executed expression.
- */
- abstract DataFlow::Node getRegexNode();
-
- /**
- * Gets the library used to execute the regular expression.
- */
- abstract string getRegexModule();
- }
-}
-
-/**
- * A data-flow node that executes a regular expression.
- *
- * Extend this class to refine existing API models. If you want to model new APIs,
- * extend `RegexExecution::Range` instead.
- */
-class RegexExecution extends DataFlow::Node {
- RegexExecution::Range range;
-
- RegexExecution() { this = range }
-
- DataFlow::Node getRegexNode() { result = range.getRegexNode() }
-
- string getRegexModule() { result = range.getRegexModule() }
-}
-
-/** Provides classes for modeling Regular Expression escape-related APIs. */
-module RegexEscape {
- /**
- * A data-flow node that escapes a regular expression.
- *
- * Extend this class to model new APIs. If you want to refine existing API models,
- * extend `RegexEscape` instead.
- */
- abstract class Range extends DataFlow::Node {
- /**
- * Gets the argument containing the escaped expression.
- */
- abstract DataFlow::Node getRegexNode();
- }
-}
-
-/**
- * A data-flow node that escapes a regular expression.
- *
- * Extend this class to refine existing API models. If you want to model new APIs,
- * extend `RegexEscape::Range` instead.
- */
-class RegexEscape extends DataFlow::Node {
- RegexEscape::Range range;
-
- RegexEscape() { this = range }
-
- DataFlow::Node getRegexNode() { result = range.getRegexNode() }
-}
-
/** Provides classes for modeling LDAP query execution-related APIs. */
module LDAPQuery {
/**
@@ -320,3 +253,184 @@ class NoSQLSanitizer extends DataFlow::Node {
/** Gets the argument that specifies the NoSQL query to be sanitized. */
DataFlow::Node getAnInput() { result = range.getAnInput() }
}
+
+/** Provides classes for modeling HTTP Header APIs. */
+module HeaderDeclaration {
+ /**
+ * A data-flow node that collects functions setting HTTP Headers.
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `HeaderDeclaration` instead.
+ */
+ abstract class Range extends DataFlow::Node {
+ /**
+ * Gets the argument containing the header name.
+ */
+ abstract DataFlow::Node getNameArg();
+
+ /**
+ * Gets the argument containing the header value.
+ */
+ abstract DataFlow::Node getValueArg();
+ }
+}
+
+/**
+ * A data-flow node that collects functions setting HTTP Headers.
+ *
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `HeaderDeclaration::Range` instead.
+ */
+class HeaderDeclaration extends DataFlow::Node {
+ HeaderDeclaration::Range range;
+
+ HeaderDeclaration() { this = range }
+
+ /**
+ * Gets the argument containing the header name.
+ */
+ DataFlow::Node getNameArg() { result = range.getNameArg() }
+
+ /**
+ * Gets the argument containing the header value.
+ */
+ DataFlow::Node getValueArg() { result = range.getValueArg() }
+}
+
+/** Provides classes for modeling JWT encoding-related APIs. */
+module JWTEncoding {
+ /**
+ * A data-flow node that collects methods encoding a JWT token.
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `JWTEncoding` instead.
+ */
+ abstract class Range extends DataFlow::Node {
+ /**
+ * Gets the argument containing the encoding payload.
+ */
+ abstract DataFlow::Node getPayload();
+
+ /**
+ * Gets the argument containing the encoding key.
+ */
+ abstract DataFlow::Node getKey();
+
+ /**
+ * Gets the argument for the algorithm used in the encoding.
+ */
+ abstract DataFlow::Node getAlgorithm();
+
+ /**
+ * Gets a string representation of the algorithm used in the encoding.
+ */
+ abstract string getAlgorithmString();
+ }
+}
+
+/**
+ * A data-flow node that collects methods encoding a JWT token.
+ *
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `JWTEncoding::Range` instead.
+ */
+class JWTEncoding extends DataFlow::Node instanceof JWTEncoding::Range {
+ /**
+ * Gets the argument containing the payload.
+ */
+ DataFlow::Node getPayload() { result = super.getPayload() }
+
+ /**
+ * Gets the argument containing the encoding key.
+ */
+ DataFlow::Node getKey() { result = super.getKey() }
+
+ /**
+ * Gets the argument for the algorithm used in the encoding.
+ */
+ DataFlow::Node getAlgorithm() { result = super.getAlgorithm() }
+
+ /**
+ * Gets a string representation of the algorithm used in the encoding.
+ */
+ string getAlgorithmString() { result = super.getAlgorithmString() }
+}
+
+/** Provides classes for modeling JWT decoding-related APIs. */
+module JWTDecoding {
+ /**
+ * A data-flow node that collects methods decoding a JWT token.
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `JWTDecoding` instead.
+ */
+ abstract class Range extends DataFlow::Node {
+ /**
+ * Gets the argument containing the encoding payload.
+ */
+ abstract DataFlow::Node getPayload();
+
+ /**
+ * Gets the argument containing the encoding key.
+ */
+ abstract DataFlow::Node getKey();
+
+ /**
+ * Gets the argument for the algorithm used in the encoding.
+ */
+ abstract DataFlow::Node getAlgorithm();
+
+ /**
+ * Gets a string representation of the algorithm used in the encoding.
+ */
+ abstract string getAlgorithmString();
+
+ /**
+ * Gets the options Node used in the encoding.
+ */
+ abstract DataFlow::Node getOptions();
+
+ /**
+ * Checks if the signature gets verified while decoding.
+ */
+ abstract predicate verifiesSignature();
+ }
+}
+
+/**
+ * A data-flow node that collects methods encoding a JWT token.
+ *
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `JWTDecoding::Range` instead.
+ */
+class JWTDecoding extends DataFlow::Node instanceof JWTDecoding::Range {
+ /**
+ * Gets the argument containing the payload.
+ */
+ DataFlow::Node getPayload() { result = super.getPayload() }
+
+ /**
+ * Gets the argument containing the encoding key.
+ */
+ DataFlow::Node getKey() { result = super.getKey() }
+
+ /**
+ * Gets the argument for the algorithm used in the encoding.
+ */
+ DataFlow::Node getAlgorithm() { result = super.getAlgorithm() }
+
+ /**
+ * Gets a string representation of the algorithm used in the encoding.
+ */
+ string getAlgorithmString() { result = super.getAlgorithmString() }
+
+ /**
+ * Gets the options Node used in the encoding.
+ */
+ DataFlow::Node getOptions() { result = super.getOptions() }
+
+ /**
+ * Checks if the signature gets verified while decoding.
+ */
+ predicate verifiesSignature() { super.verifiesSignature() }
+}
diff --git a/repo-tests/codeql/python/ql/src/experimental/semmle/python/Frameworks.qll b/repo-tests/codeql/python/ql/src/experimental/semmle/python/Frameworks.qll
index 2ad560cfc1e..90fe21cc933 100644
--- a/repo-tests/codeql/python/ql/src/experimental/semmle/python/Frameworks.qll
+++ b/repo-tests/codeql/python/ql/src/experimental/semmle/python/Frameworks.qll
@@ -3,6 +3,13 @@
*/
private import experimental.semmle.python.frameworks.Stdlib
+private import experimental.semmle.python.frameworks.Flask
+private import experimental.semmle.python.frameworks.Django
+private import experimental.semmle.python.frameworks.Werkzeug
private import experimental.semmle.python.frameworks.LDAP
private import experimental.semmle.python.frameworks.NoSQL
private import experimental.semmle.python.frameworks.Log
+private import experimental.semmle.python.frameworks.JWT
+private import experimental.semmle.python.libraries.PyJWT
+private import experimental.semmle.python.libraries.Authlib
+private import experimental.semmle.python.libraries.PythonJose
diff --git a/repo-tests/codeql/python/ql/src/experimental/semmle/python/frameworks/Django.qll b/repo-tests/codeql/python/ql/src/experimental/semmle/python/frameworks/Django.qll
new file mode 100644
index 00000000000..55d3b4af35c
--- /dev/null
+++ b/repo-tests/codeql/python/ql/src/experimental/semmle/python/frameworks/Django.qll
@@ -0,0 +1,93 @@
+/**
+ * Provides classes modeling security-relevant aspects of the `django` PyPI package.
+ * See https://www.djangoproject.com/.
+ */
+
+private import python
+private import semmle.python.frameworks.Django
+private import semmle.python.dataflow.new.DataFlow
+private import experimental.semmle.python.Concepts
+private import semmle.python.ApiGraphs
+import semmle.python.dataflow.new.RemoteFlowSources
+
+private module ExperimentalPrivateDjango {
+ private module django {
+ API::Node http() { result = API::moduleImport("django").getMember("http") }
+
+ module http {
+ API::Node response() { result = http().getMember("response") }
+
+ API::Node request() { result = http().getMember("request") }
+
+ module request {
+ module HttpRequest {
+ class DjangoGETParameter extends DataFlow::Node, RemoteFlowSource::Range {
+ DjangoGETParameter() { this = request().getMember("GET").getMember("get").getACall() }
+
+ override string getSourceType() { result = "django.http.request.GET.get" }
+ }
+ }
+ }
+
+ module response {
+ module HttpResponse {
+ API::Node baseClassRef() {
+ result = response().getMember("HttpResponse").getReturn()
+ or
+ // Handle `django.http.HttpResponse` alias
+ result = http().getMember("HttpResponse").getReturn()
+ }
+
+ /** Gets a reference to a header instance. */
+ private DataFlow::LocalSourceNode headerInstance(DataFlow::TypeTracker t) {
+ t.start() and
+ (
+ exists(SubscriptNode subscript |
+ subscript.getObject() = baseClassRef().getAUse().asCfgNode() and
+ result.asCfgNode() = subscript
+ )
+ or
+ result.(DataFlow::AttrRead).getObject() = baseClassRef().getAUse()
+ )
+ or
+ exists(DataFlow::TypeTracker t2 | result = headerInstance(t2).track(t2, t))
+ }
+
+ /** Gets a reference to a header instance use. */
+ private DataFlow::Node headerInstance() {
+ headerInstance(DataFlow::TypeTracker::end()).flowsTo(result)
+ }
+
+ /** Gets a reference to a header instance call with `__setitem__`. */
+ private DataFlow::Node headerSetItemCall() {
+ result = headerInstance() and
+ result.(DataFlow::AttrRead).getAttributeName() = "__setitem__"
+ }
+
+ class DjangoResponseSetItemCall extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
+ DjangoResponseSetItemCall() { this.getFunction() = headerSetItemCall() }
+
+ override DataFlow::Node getNameArg() { result = this.getArg(0) }
+
+ override DataFlow::Node getValueArg() { result = this.getArg(1) }
+ }
+
+ class DjangoResponseDefinition extends DataFlow::Node, HeaderDeclaration::Range {
+ DataFlow::Node headerInput;
+
+ DjangoResponseDefinition() {
+ this.asCfgNode().(DefinitionNode) = headerInstance().asCfgNode() and
+ headerInput.asCfgNode() = this.asCfgNode().(DefinitionNode).getValue()
+ }
+
+ override DataFlow::Node getNameArg() {
+ result.asExpr() = this.asExpr().(Subscript).getIndex()
+ }
+
+ override DataFlow::Node getValueArg() { result = headerInput }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/repo-tests/codeql/python/ql/src/experimental/semmle/python/frameworks/Flask.qll b/repo-tests/codeql/python/ql/src/experimental/semmle/python/frameworks/Flask.qll
new file mode 100644
index 00000000000..9c66d9a4601
--- /dev/null
+++ b/repo-tests/codeql/python/ql/src/experimental/semmle/python/frameworks/Flask.qll
@@ -0,0 +1,84 @@
+/**
+ * Provides classes modeling security-relevant aspects of the `flask` PyPI package.
+ * See https://flask.palletsprojects.com/en/1.1.x/.
+ */
+
+private import python
+private import semmle.python.frameworks.Flask
+private import semmle.python.dataflow.new.DataFlow
+private import experimental.semmle.python.Concepts
+private import semmle.python.ApiGraphs
+
+module ExperimentalFlask {
+ /**
+ * A reference to either `flask.make_response` function, or the `make_response` method on
+ * an instance of `flask.Flask`. This creates an instance of the `flask_response`
+ * class (class-attribute on a flask application), which by default is
+ * `flask.Response`.
+ *
+ * See
+ * - https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.make_response
+ * - https://flask.palletsprojects.com/en/1.1.x/api/#flask.make_response
+ */
+ private API::Node flaskMakeResponse() {
+ result =
+ [API::moduleImport("flask"), Flask::FlaskApp::instance()]
+ .getMember(["make_response", "jsonify", "make_default_options_response"])
+ }
+
+ /** Gets a reference to a header instance. */
+ private DataFlow::LocalSourceNode headerInstance(DataFlow::TypeTracker t) {
+ t.start() and
+ result.(DataFlow::AttrRead).getObject().getALocalSource() =
+ [Flask::Response::classRef(), flaskMakeResponse()].getReturn().getAUse()
+ or
+ exists(DataFlow::TypeTracker t2 | result = headerInstance(t2).track(t2, t))
+ }
+
+ /** Gets a reference to a header instance use. */
+ private DataFlow::Node headerInstance() {
+ headerInstance(DataFlow::TypeTracker::end()).flowsTo(result)
+ }
+
+ /** Gets a reference to a header instance call/subscript */
+ private DataFlow::Node headerInstanceCall() {
+ headerInstance() in [result.(DataFlow::AttrRead), result.(DataFlow::AttrRead).getObject()] or
+ headerInstance().asExpr() = result.asExpr().(Subscript).getObject()
+ }
+
+ class FlaskHeaderDefinition extends DataFlow::Node, HeaderDeclaration::Range {
+ DataFlow::Node headerInput;
+
+ FlaskHeaderDefinition() {
+ this.asCfgNode().(DefinitionNode) = headerInstanceCall().asCfgNode() and
+ headerInput.asCfgNode() = this.asCfgNode().(DefinitionNode).getValue()
+ }
+
+ override DataFlow::Node getNameArg() { result.asExpr() = this.asExpr().(Subscript).getIndex() }
+
+ override DataFlow::Node getValueArg() { result = headerInput }
+ }
+
+ private class FlaskMakeResponseExtend extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
+ KeyValuePair item;
+
+ FlaskMakeResponseExtend() {
+ this.getFunction() = headerInstanceCall() and
+ item = this.getArg(_).asExpr().(Dict).getAnItem()
+ }
+
+ override DataFlow::Node getNameArg() { result.asExpr() = item.getKey() }
+
+ override DataFlow::Node getValueArg() { result.asExpr() = item.getValue() }
+ }
+
+ private class FlaskResponse extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
+ KeyValuePair item;
+
+ FlaskResponse() { this = Flask::Response::classRef().getACall() }
+
+ override DataFlow::Node getNameArg() { result.asExpr() = item.getKey() }
+
+ override DataFlow::Node getValueArg() { result.asExpr() = item.getValue() }
+ }
+}
diff --git a/repo-tests/codeql/python/ql/src/experimental/semmle/python/frameworks/JWT.qll b/repo-tests/codeql/python/ql/src/experimental/semmle/python/frameworks/JWT.qll
new file mode 100644
index 00000000000..f5098014f2b
--- /dev/null
+++ b/repo-tests/codeql/python/ql/src/experimental/semmle/python/frameworks/JWT.qll
@@ -0,0 +1,23 @@
+private import python
+private import semmle.python.ApiGraphs
+
+/** Checks if the argument is empty or none. */
+predicate isEmptyOrNone(DataFlow::Node arg) { isEmpty(arg) or isNone(arg) }
+
+/** Checks if an empty string `""` flows to `arg` */
+predicate isEmpty(DataFlow::Node arg) {
+ exists(StrConst emptyString |
+ emptyString.getText() = "" and
+ DataFlow::exprNode(emptyString).(DataFlow::LocalSourceNode).flowsTo(arg)
+ )
+}
+
+/** Checks if `None` flows to `arg` */
+predicate isNone(DataFlow::Node arg) {
+ DataFlow::exprNode(any(None no)).(DataFlow::LocalSourceNode).flowsTo(arg)
+}
+
+/** Checks if `False` flows to `arg` */
+predicate isFalse(DataFlow::Node arg) {
+ DataFlow::exprNode(any(False falseExpr)).(DataFlow::LocalSourceNode).flowsTo(arg)
+}
diff --git a/repo-tests/codeql/python/ql/src/experimental/semmle/python/frameworks/Stdlib.qll b/repo-tests/codeql/python/ql/src/experimental/semmle/python/frameworks/Stdlib.qll
index b3b70f43394..420caf0d73b 100644
--- a/repo-tests/codeql/python/ql/src/experimental/semmle/python/frameworks/Stdlib.qll
+++ b/repo-tests/codeql/python/ql/src/experimental/semmle/python/frameworks/Stdlib.qll
@@ -9,91 +9,3 @@ private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.dataflow.new.RemoteFlowSources
private import experimental.semmle.python.Concepts
private import semmle.python.ApiGraphs
-
-/**
- * Provides models for Python's `re` library.
- *
- * See https://docs.python.org/3/library/re.html
- */
-private module Re {
- /**
- * List of `re` methods immediately executing an expression.
- *
- * See https://docs.python.org/3/library/re.html#module-contents
- */
- private class RegexExecutionMethods extends string {
- RegexExecutionMethods() {
- this in ["match", "fullmatch", "search", "split", "findall", "finditer", "sub", "subn"]
- }
- }
-
- /**
- * A class to find `re` methods immediately executing an expression.
- *
- * See `RegexExecutionMethods`
- */
- private class DirectRegex extends DataFlow::CallCfgNode, RegexExecution::Range {
- DataFlow::Node regexNode;
-
- DirectRegex() {
- this = API::moduleImport("re").getMember(any(RegexExecutionMethods m)).getACall() and
- regexNode = this.getArg(0)
- }
-
- override DataFlow::Node getRegexNode() { result = regexNode }
-
- override string getRegexModule() { result = "re" }
- }
-
- /**
- * A class to find `re` methods immediately executing a compiled expression by `re.compile`.
- *
- * Given the following example:
- *
- * ```py
- * pattern = re.compile(input)
- * pattern.match(s)
- * ```
- *
- * This class will identify that `re.compile` compiles `input` and afterwards
- * executes `re`'s `match`. As a result, `this` will refer to `pattern.match(s)`
- * and `this.getRegexNode()` will return the node for `input` (`re.compile`'s first argument)
- *
- *
- * See `RegexExecutionMethods`
- *
- * See https://docs.python.org/3/library/re.html#regular-expression-objects
- */
- private class CompiledRegex extends DataFlow::MethodCallNode, RegexExecution::Range {
- DataFlow::Node regexNode;
-
- CompiledRegex() {
- exists(DataFlow::MethodCallNode patternCall |
- patternCall = API::moduleImport("re").getMember("compile").getACall() and
- patternCall.flowsTo(this.getObject()) and
- this.getMethodName() instanceof RegexExecutionMethods and
- regexNode = patternCall.getArg(0)
- )
- }
-
- override DataFlow::Node getRegexNode() { result = regexNode }
-
- override string getRegexModule() { result = "re" }
- }
-
- /**
- * A class to find `re` methods escaping an expression.
- *
- * See https://docs.python.org/3/library/re.html#re.escape
- */
- class ReEscape extends DataFlow::CallCfgNode, RegexEscape::Range {
- DataFlow::Node regexNode;
-
- ReEscape() {
- this = API::moduleImport("re").getMember("escape").getACall() and
- regexNode = this.getArg(0)
- }
-
- override DataFlow::Node getRegexNode() { result = regexNode }
- }
-}
diff --git a/repo-tests/codeql/python/ql/src/experimental/semmle/python/frameworks/Werkzeug.qll b/repo-tests/codeql/python/ql/src/experimental/semmle/python/frameworks/Werkzeug.qll
new file mode 100644
index 00000000000..dce67c258fb
--- /dev/null
+++ b/repo-tests/codeql/python/ql/src/experimental/semmle/python/frameworks/Werkzeug.qll
@@ -0,0 +1,33 @@
+/**
+ * Provides classes modeling security-relevant aspects of the `Werkzeug` PyPI package.
+ * See
+ * - https://pypi.org/project/Werkzeug/
+ * - https://werkzeug.palletsprojects.com/en/1.0.x/#werkzeug
+ */
+
+private import python
+private import semmle.python.frameworks.Flask
+private import semmle.python.dataflow.new.DataFlow
+private import experimental.semmle.python.Concepts
+private import semmle.python.ApiGraphs
+
+private module Werkzeug {
+ module datastructures {
+ module Headers {
+ class WerkzeugHeaderAddCall extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
+ WerkzeugHeaderAddCall() {
+ this.getFunction().(DataFlow::AttrRead).getObject().getALocalSource() =
+ API::moduleImport("werkzeug")
+ .getMember("datastructures")
+ .getMember("Headers")
+ .getACall() and
+ this.getFunction().(DataFlow::AttrRead).getAttributeName() = "add"
+ }
+
+ override DataFlow::Node getNameArg() { result = this.getArg(0) }
+
+ override DataFlow::Node getValueArg() { result = this.getArg(1) }
+ }
+ }
+ }
+}
diff --git a/repo-tests/codeql/python/ql/src/experimental/semmle/python/libraries/Authlib.qll b/repo-tests/codeql/python/ql/src/experimental/semmle/python/libraries/Authlib.qll
new file mode 100644
index 00000000000..23afbf333e1
--- /dev/null
+++ b/repo-tests/codeql/python/ql/src/experimental/semmle/python/libraries/Authlib.qll
@@ -0,0 +1,87 @@
+private import python
+private import experimental.semmle.python.Concepts
+private import semmle.python.ApiGraphs
+private import experimental.semmle.python.frameworks.JWT
+
+private module Authlib {
+ /** Gets a reference to `authlib.jose.(jwt|JsonWebToken)` */
+ private API::Node authlibJWT() {
+ result in [
+ API::moduleImport("authlib").getMember("jose").getMember("jwt"),
+ API::moduleImport("authlib").getMember("jose").getMember("JsonWebToken").getReturn()
+ ]
+ }
+
+ /** Gets a reference to `jwt.encode` */
+ private API::Node authlibJWTEncode() { result = authlibJWT().getMember("encode") }
+
+ /** Gets a reference to `jwt.decode` */
+ private API::Node authlibJWTDecode() { result = authlibJWT().getMember("decode") }
+
+ /**
+ * Gets a call to `authlib.jose.(jwt|JsonWebToken).encode`.
+ *
+ * Given the following example:
+ *
+ * ```py
+ * jwt.encode({"alg": "HS256"}, token, "key")
+ * ```
+ *
+ * * `this` would be `jwt.encode({"alg": "HS256"}, token, "key")`.
+ * * `getPayload()`'s result would be `token`.
+ * * `getKey()`'s result would be `"key"`.
+ * * `getAlgorithm()`'s result would be `"HS256"`.
+ * * `getAlgorithmstring()`'s result would be `HS256`.
+ */
+ private class AuthlibJWTEncodeCall extends DataFlow::CallCfgNode, JWTEncoding::Range {
+ AuthlibJWTEncodeCall() { this = authlibJWTEncode().getACall() }
+
+ override DataFlow::Node getPayload() { result = this.getArg(1) }
+
+ override DataFlow::Node getKey() { result = this.getArg(2) }
+
+ override DataFlow::Node getAlgorithm() {
+ exists(KeyValuePair headerDict |
+ headerDict = this.getArg(0).asExpr().(Dict).getItem(_) and
+ headerDict.getKey().(Str_).getS().matches("alg") and
+ result.asExpr() = headerDict.getValue()
+ )
+ }
+
+ override string getAlgorithmString() {
+ exists(StrConst str |
+ DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(getAlgorithm()) and
+ result = str.getText()
+ )
+ }
+ }
+
+ /**
+ * Gets a call to `authlib.jose.(jwt|JsonWebToken).decode`
+ *
+ * Given the following example:
+ *
+ * ```py
+ * jwt.decode(token, key)
+ * ```
+ *
+ * * `this` would be `jwt.decode(token, key)`.
+ * * `getPayload()`'s result would be `token`.
+ * * `getKey()`'s result would be `key`.
+ */
+ private class AuthlibJWTDecodeCall extends DataFlow::CallCfgNode, JWTDecoding::Range {
+ AuthlibJWTDecodeCall() { this = authlibJWTDecode().getACall() }
+
+ override DataFlow::Node getPayload() { result = this.getArg(0) }
+
+ override DataFlow::Node getKey() { result = this.getArg(1) }
+
+ override DataFlow::Node getAlgorithm() { none() }
+
+ override string getAlgorithmString() { none() }
+
+ override DataFlow::Node getOptions() { none() }
+
+ override predicate verifiesSignature() { any() }
+ }
+}
diff --git a/repo-tests/codeql/python/ql/src/experimental/semmle/python/libraries/PyJWT.qll b/repo-tests/codeql/python/ql/src/experimental/semmle/python/libraries/PyJWT.qll
new file mode 100644
index 00000000000..ed506914afe
--- /dev/null
+++ b/repo-tests/codeql/python/ql/src/experimental/semmle/python/libraries/PyJWT.qll
@@ -0,0 +1,108 @@
+private import python
+private import experimental.semmle.python.Concepts
+private import semmle.python.ApiGraphs
+private import experimental.semmle.python.frameworks.JWT
+
+private module PyJWT {
+ /** Gets a reference to `jwt.encode` */
+ private API::Node pyjwtEncode() { result = API::moduleImport("jwt").getMember("encode") }
+
+ /** Gets a reference to `jwt.decode` */
+ private API::Node pyjwtDecode() { result = API::moduleImport("jwt").getMember("decode") }
+
+ /**
+ * Gets a call to `jwt.encode`.
+ *
+ * Given the following example:
+ *
+ * ```py
+ * jwt.encode(token, "key", "HS256")
+ * ```
+ *
+ * * `this` would be `jwt.encode(token, "key", "HS256")`.
+ * * `getPayload()`'s result would be `token`.
+ * * `getKey()`'s result would be `"key"`.
+ * * `getAlgorithm()`'s result would be `"HS256"`.
+ * * `getAlgorithmstring()`'s result would be `HS256`.
+ */
+ private class PyJWTEncodeCall extends DataFlow::CallCfgNode, JWTEncoding::Range {
+ PyJWTEncodeCall() { this = pyjwtEncode().getACall() }
+
+ override DataFlow::Node getPayload() {
+ result in [this.getArg(0), this.getArgByName("payload")]
+ }
+
+ override DataFlow::Node getKey() { result in [this.getArg(1), this.getArgByName("key")] }
+
+ override DataFlow::Node getAlgorithm() {
+ result in [this.getArg(2), this.getArgByName("algorithm")]
+ }
+
+ override string getAlgorithmString() {
+ exists(StrConst str |
+ DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(getAlgorithm()) and
+ result = str.getText()
+ )
+ }
+ }
+
+ /**
+ * Gets a call to `jwt.decode`.
+ *
+ * Given the following example:
+ *
+ * ```py
+ * jwt.decode(token, key, "HS256", options={"verify_signature": True})
+ * ```
+ *
+ * * `this` would be `jwt.decode(token, key, options={"verify_signature": True})`.
+ * * `getPayload()`'s result would be `token`.
+ * * `getKey()`'s result would be `key`.
+ * * `getAlgorithm()`'s result would be `"HS256"`.
+ * * `getAlgorithmstring()`'s result would be `HS256`.
+ * * `getOptions()`'s result would be `{"verify_signature": True}`.
+ * * `verifiesSignature()` predicate would succeed.
+ */
+ private class PyJWTDecodeCall extends DataFlow::CallCfgNode, JWTDecoding::Range {
+ PyJWTDecodeCall() { this = pyjwtDecode().getACall() }
+
+ override DataFlow::Node getPayload() { result in [this.getArg(0), this.getArgByName("jwt")] }
+
+ override DataFlow::Node getKey() { result in [this.getArg(1), this.getArgByName("key")] }
+
+ override DataFlow::Node getAlgorithm() {
+ result in [this.getArg(2), this.getArgByName("algorithms")]
+ }
+
+ override string getAlgorithmString() {
+ exists(StrConst str |
+ DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(getAlgorithm()) and
+ result = str.getText()
+ )
+ }
+
+ override DataFlow::Node getOptions() {
+ result in [this.getArg(3), this.getArgByName("options")]
+ }
+
+ override predicate verifiesSignature() {
+ not this.hasVerifySetToFalse() and
+ not this.hasVerifySignatureSetToFalse()
+ }
+
+ predicate hasNoVerifyArgumentOrOptions() {
+ not exists(this.getArgByName("verify")) and not exists(this.getOptions())
+ }
+
+ predicate hasVerifySetToFalse() { isFalse(this.getArgByName("verify")) }
+
+ predicate hasVerifySignatureSetToFalse() {
+ exists(KeyValuePair optionsDict, NameConstant falseName |
+ falseName.getId() = "False" and
+ optionsDict = this.getOptions().asExpr().(Dict).getItem(_) and
+ optionsDict.getKey().(Str_).getS().matches("%verify%") and
+ falseName = optionsDict.getValue()
+ )
+ }
+ }
+}
diff --git a/repo-tests/codeql/python/ql/src/experimental/semmle/python/libraries/PythonJose.qll b/repo-tests/codeql/python/ql/src/experimental/semmle/python/libraries/PythonJose.qll
new file mode 100644
index 00000000000..b74c56fd28f
--- /dev/null
+++ b/repo-tests/codeql/python/ql/src/experimental/semmle/python/libraries/PythonJose.qll
@@ -0,0 +1,105 @@
+private import python
+private import experimental.semmle.python.Concepts
+private import semmle.python.ApiGraphs
+private import experimental.semmle.python.frameworks.JWT
+
+private module PythonJose {
+ /** Gets a reference to `jwt` */
+ private API::Node joseJWT() { result = API::moduleImport("jose").getMember("jwt") }
+
+ /** Gets a reference to `jwt.encode` */
+ private API::Node joseJWTEncode() { result = joseJWT().getMember("encode") }
+
+ /** Gets a reference to `jwt.decode` */
+ private API::Node joseJWTDecode() { result = joseJWT().getMember("decode") }
+
+ /**
+ * Gets a call to `jwt.encode`.
+ *
+ * Given the following example:
+ *
+ * ```py
+ * jwt.encode(token, key="key", algorithm="HS256")
+ * ```
+ *
+ * * `this` would be `jwt.encode(token, key="key", algorithm="HS256")`.
+ * * `getPayload()`'s result would be `token`.
+ * * `getKey()`'s result would be `"key"`.
+ * * `getAlgorithm()`'s result would be `"HS256"`.
+ * * `getAlgorithmstring()`'s result would be `HS256`.
+ */
+ private class JoseJWTEncodeCall extends DataFlow::CallCfgNode, JWTEncoding::Range {
+ JoseJWTEncodeCall() { this = joseJWTEncode().getACall() }
+
+ override DataFlow::Node getPayload() { result = this.getArg(0) }
+
+ override DataFlow::Node getKey() { result in [this.getArg(1), this.getArgByName("key")] }
+
+ override DataFlow::Node getAlgorithm() {
+ result in [this.getArg(2), this.getArgByName("algorithm")]
+ }
+
+ override string getAlgorithmString() {
+ exists(StrConst str |
+ DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(getAlgorithm()) and
+ result = str.getText()
+ )
+ }
+ }
+
+ /**
+ * Gets a call to `jwt.decode`.
+ *
+ * Given the following example:
+ *
+ * ```py
+ * jwt.decode(token, "key", "HS256")
+ * ```
+ *
+ * * `this` would be `jwt.decode(token, "key", "HS256")`.
+ * * `getPayload()`'s result would be `token`.
+ * * `getKey()`'s result would be `"key"`.
+ * * `getAlgorithm()`'s result would be `"HS256"`.
+ * * `getAlgorithmstring()`'s result would be `HS256`.
+ * * `getOptions()`'s result would be none.
+ * * `verifiesSignature()` predicate would succeed.
+ */
+ private class JoseJWTDecodeCall extends DataFlow::CallCfgNode, JWTDecoding::Range {
+ JoseJWTDecodeCall() { this = joseJWTDecode().getACall() }
+
+ override DataFlow::Node getPayload() { result = this.getArg(0) }
+
+ override DataFlow::Node getKey() { result in [this.getArg(1), this.getArgByName("key")] }
+
+ override DataFlow::Node getAlgorithm() {
+ result in [this.getArg(2), this.getArgByName("algorithms")]
+ }
+
+ override string getAlgorithmString() {
+ exists(StrConst str |
+ DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(getAlgorithm()) and
+ result = str.getText()
+ )
+ }
+
+ override DataFlow::Node getOptions() {
+ result in [this.getArg(3), this.getArgByName("options")]
+ }
+
+ override predicate verifiesSignature() {
+ // jwt.decode(token, key, options={"verify_signature": False})
+ not this.hasVerifySignatureSetToFalse()
+ }
+
+ predicate hasNoOptions() { not exists(this.getOptions()) }
+
+ predicate hasVerifySignatureSetToFalse() {
+ exists(KeyValuePair optionsDict, NameConstant falseName |
+ falseName.getId() = "False" and
+ optionsDict = this.getOptions().asExpr().(Dict).getItem(_) and
+ optionsDict.getKey().(Str_).getS().matches("%verify%") and
+ falseName = optionsDict.getValue()
+ )
+ }
+ }
+}
diff --git a/repo-tests/codeql/python/ql/src/experimental/semmle/python/security/injection/HTTPHeaders.qll b/repo-tests/codeql/python/ql/src/experimental/semmle/python/security/injection/HTTPHeaders.qll
new file mode 100644
index 00000000000..4ba70cd37a2
--- /dev/null
+++ b/repo-tests/codeql/python/ql/src/experimental/semmle/python/security/injection/HTTPHeaders.qll
@@ -0,0 +1,20 @@
+import python
+import experimental.semmle.python.Concepts
+import semmle.python.dataflow.new.DataFlow
+import semmle.python.dataflow.new.TaintTracking
+import semmle.python.dataflow.new.RemoteFlowSources
+
+/**
+ * A taint-tracking configuration for detecting HTTP Header injections.
+ */
+class HeaderInjectionFlowConfig extends TaintTracking::Configuration {
+ HeaderInjectionFlowConfig() { this = "HeaderInjectionFlowConfig" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(HeaderDeclaration headerDeclaration |
+ sink in [headerDeclaration.getNameArg(), headerDeclaration.getValueArg()]
+ )
+ }
+}
diff --git a/repo-tests/codeql/python/ql/src/experimental/semmle/python/security/injection/RegexInjection.qll b/repo-tests/codeql/python/ql/src/experimental/semmle/python/security/injection/RegexInjection.qll
deleted file mode 100644
index 7b7b08cacab..00000000000
--- a/repo-tests/codeql/python/ql/src/experimental/semmle/python/security/injection/RegexInjection.qll
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * Provides a taint-tracking configuration for detecting regular expression injection
- * vulnerabilities.
- */
-
-import python
-import experimental.semmle.python.Concepts
-import semmle.python.dataflow.new.DataFlow
-import semmle.python.dataflow.new.TaintTracking
-import semmle.python.dataflow.new.RemoteFlowSources
-
-/**
- * A class to find methods executing regular expressions.
- *
- * See `RegexExecution`
- */
-class RegexInjectionSink extends DataFlow::Node {
- string regexModule;
- Attribute regexMethod;
-
- RegexInjectionSink() {
- exists(RegexExecution reExec |
- this = reExec.getRegexNode() and
- regexModule = reExec.getRegexModule() and
- regexMethod = reExec.(DataFlow::CallCfgNode).getFunction().asExpr().(Attribute)
- )
- }
-
- /**
- * Gets the argument containing the executed expression.
- */
- string getRegexModule() { result = regexModule }
-
- /**
- * Gets the method used to execute the regular expression.
- */
- Attribute getRegexMethod() { result = regexMethod }
-}
-
-/**
- * A taint-tracking configuration for detecting regular expression injections.
- */
-class RegexInjectionFlowConfig extends TaintTracking::Configuration {
- RegexInjectionFlowConfig() { this = "RegexInjectionFlowConfig" }
-
- override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
-
- override predicate isSink(DataFlow::Node sink) { sink instanceof RegexInjectionSink }
-
- override predicate isSanitizer(DataFlow::Node sanitizer) {
- sanitizer = any(RegexEscape reEscape).getRegexNode()
- }
-}
diff --git a/repo-tests/codeql/python/ql/src/meta/alerts/RequestHandlers.ql b/repo-tests/codeql/python/ql/src/meta/alerts/RequestHandlers.ql
new file mode 100644
index 00000000000..2f79a3f1b35
--- /dev/null
+++ b/repo-tests/codeql/python/ql/src/meta/alerts/RequestHandlers.ql
@@ -0,0 +1,23 @@
+/**
+ * @name Request Handlers
+ * @description HTTP Server Request Handlers
+ * @kind problem
+ * @problem.severity recommendation
+ * @id py/meta/alerts/request-handlers
+ * @tags meta
+ * @precision very-low
+ */
+
+private import python
+private import semmle.python.dataflow.new.DataFlow
+private import semmle.python.Concepts
+private import meta.MetaMetrics
+
+from HTTP::Server::RequestHandler requestHandler, string title
+where
+ not requestHandler.getLocation().getFile() instanceof IgnoredFile and
+ if requestHandler.isMethod()
+ then
+ title = "Method " + requestHandler.getScope().(Class).getName() + "." + requestHandler.getName()
+ else title = requestHandler.toString()
+select requestHandler, "RequestHandler: " + title
diff --git a/repo-tests/codeql/ruby/ql/consistency-queries/AstConsistency.ql b/repo-tests/codeql/ruby/ql/consistency-queries/AstConsistency.ql
new file mode 100644
index 00000000000..8a5ebcdcda7
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/consistency-queries/AstConsistency.ql
@@ -0,0 +1,25 @@
+import codeql.ruby.AST
+import codeql.ruby.ast.internal.Synthesis
+
+query predicate missingParent(AstNode node, string cls) {
+ not exists(node.getParent()) and
+ node.getLocation().getFile().getExtension() != "erb" and
+ not node instanceof Toplevel and
+ cls = node.getPrimaryQlClasses()
+}
+
+pragma[noinline]
+private AstNode parent(AstNode child, int desugarLevel) {
+ result = child.getParent() and
+ desugarLevel = desugarLevel(result)
+}
+
+query predicate multipleParents(AstNode node, AstNode parent, string cls) {
+ parent = node.getParent() and
+ cls = parent.getPrimaryQlClasses() and
+ exists(AstNode one, AstNode two, int desugarLevel |
+ one = parent(node, desugarLevel) and
+ two = parent(node, desugarLevel) and
+ one != two
+ )
+}
diff --git a/repo-tests/codeql/ruby/ql/consistency-queries/CfgConsistency.ql b/repo-tests/codeql/ruby/ql/consistency-queries/CfgConsistency.ql
new file mode 100644
index 00000000000..c2aaaad0ac1
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/consistency-queries/CfgConsistency.ql
@@ -0,0 +1 @@
+import codeql.ruby.controlflow.internal.ControlFlowGraphImplShared::Consistency
diff --git a/repo-tests/codeql/ruby/ql/consistency-queries/DataFlowConsistency.ql b/repo-tests/codeql/ruby/ql/consistency-queries/DataFlowConsistency.ql
new file mode 100644
index 00000000000..f5bc9552ab6
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/consistency-queries/DataFlowConsistency.ql
@@ -0,0 +1 @@
+import codeql.ruby.dataflow.internal.DataFlowImplConsistency::Consistency
diff --git a/repo-tests/codeql/ruby/ql/consistency-queries/SsaConsistency.ql b/repo-tests/codeql/ruby/ql/consistency-queries/SsaConsistency.ql
new file mode 100644
index 00000000000..79289273f95
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/consistency-queries/SsaConsistency.ql
@@ -0,0 +1,22 @@
+import ruby
+import codeql.ruby.dataflow.SSA
+import codeql.ruby.controlflow.ControlFlowGraph
+
+query predicate nonUniqueDef(CfgNode read, Ssa::Definition def) {
+ read = def.getARead() and
+ exists(Ssa::Definition other | read = other.getARead() and other != def)
+}
+
+query predicate readWithoutDef(LocalVariableReadAccess read) {
+ exists(CfgNode node |
+ node = read.getAControlFlowNode() and
+ not node = any(Ssa::Definition def).getARead()
+ )
+}
+
+query predicate deadDef(Ssa::Definition def, LocalVariable v) {
+ v = def.getSourceVariable() and
+ not v.isCaptured() and
+ not exists(def.getARead()) and
+ not def = any(Ssa::PhiNode phi).getAnInput()
+}
diff --git a/repo-tests/codeql/ruby/ql/consistency-queries/VariablesConsistency.ql b/repo-tests/codeql/ruby/ql/consistency-queries/VariablesConsistency.ql
new file mode 100644
index 00000000000..ed2183340d9
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/consistency-queries/VariablesConsistency.ql
@@ -0,0 +1,6 @@
+import codeql.ruby.ast.Variable
+
+query predicate ambiguousVariable(VariableAccess access, Variable variable) {
+ access.getVariable() = variable and
+ count(access.getVariable()) > 1
+}
diff --git a/repo-tests/codeql/ruby/ql/consistency-queries/qlpack.yml b/repo-tests/codeql/ruby/ql/consistency-queries/qlpack.yml
new file mode 100644
index 00000000000..fa76023b646
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/consistency-queries/qlpack.yml
@@ -0,0 +1,5 @@
+name: codeql/ruby-consistency-queries
+version: 0.0.1
+dependencies:
+ codeql/ruby-all: 0.0.1
+
diff --git a/repo-tests/codeql/ruby/ql/examples/qlpack.yml b/repo-tests/codeql/ruby/ql/examples/qlpack.yml
new file mode 100644
index 00000000000..87a6ffae9c1
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/examples/qlpack.yml
@@ -0,0 +1,4 @@
+name: codeql/ruby-examples
+version: 0.0.2
+dependencies:
+ codeql/ruby-all: ^0.0.2
diff --git a/repo-tests/codeql/ruby/ql/examples/snippets/emptythen.ql b/repo-tests/codeql/ruby/ql/examples/snippets/emptythen.ql
new file mode 100644
index 00000000000..b6e9bb87f13
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/examples/snippets/emptythen.ql
@@ -0,0 +1,18 @@
+/**
+ * @name If statements with empty then branch
+ * @description Finds 'if' statements where the 'then' branch is
+ * an empty block statement
+ * @id rb/examples/emptythen
+ * @tags if
+ * then
+ * empty
+ * conditional
+ * branch
+ * statement
+ */
+
+import ruby
+
+from IfExpr i
+where not exists(i.getThen().getAChild())
+select i
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/IDEContextual.qll b/repo-tests/codeql/ruby/ql/lib/codeql/IDEContextual.qll
new file mode 100644
index 00000000000..0e58b1d878b
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/IDEContextual.qll
@@ -0,0 +1,19 @@
+private import codeql.files.FileSystem
+
+/**
+ * Returns an appropriately encoded version of a filename `name`
+ * passed by the VS Code extension in order to coincide with the
+ * output of `.getFile()` on locatable entities.
+ */
+cached
+File getFileBySourceArchiveName(string name) {
+ // The name provided for a file in the source archive by the VS Code extension
+ // has some differences from the absolute path in the database:
+ // 1. colons are replaced by underscores
+ // 2. there's a leading slash, even for Windows paths: "C:/foo/bar" ->
+ // "/C_/foo/bar"
+ // 3. double slashes in UNC prefixes are replaced with a single slash
+ // We can handle 2 and 3 together by unconditionally adding a leading slash
+ // before replacing double slashes.
+ name = ("/" + result.getAbsolutePath().replaceAll(":", "_")).replaceAll("//", "/")
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/Locations.qll b/repo-tests/codeql/ruby/ql/lib/codeql/Locations.qll
new file mode 100644
index 00000000000..8bed8d904f1
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/Locations.qll
@@ -0,0 +1,66 @@
+/** Provides classes for working with locations. */
+
+import files.FileSystem
+
+/**
+ * A location as given by a file, a start line, a start column,
+ * an end line, and an end column.
+ *
+ * For more information about locations see [LGTM locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
+ */
+class Location extends @location {
+ /** Gets the file for this location. */
+ File getFile() { locations_default(this, result, _, _, _, _) }
+
+ /** Gets the 1-based line number (inclusive) where this location starts. */
+ int getStartLine() { locations_default(this, _, result, _, _, _) }
+
+ /** Gets the 1-based column number (inclusive) where this location starts. */
+ int getStartColumn() { locations_default(this, _, _, result, _, _) }
+
+ /** Gets the 1-based line number (inclusive) where this location ends. */
+ int getEndLine() { locations_default(this, _, _, _, result, _) }
+
+ /** Gets the 1-based column number (inclusive) where this location ends. */
+ int getEndColumn() { locations_default(this, _, _, _, _, result) }
+
+ /** Gets the number of lines covered by this location. */
+ int getNumLines() { result = this.getEndLine() - this.getStartLine() + 1 }
+
+ /** Gets a textual representation of this element. */
+ string toString() {
+ exists(string filepath, int startline, int startcolumn, int endline, int endcolumn |
+ this.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) and
+ result = filepath + "@" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
+ )
+ }
+
+ /**
+ * Holds if this element is at the specified location.
+ * The location spans column `startcolumn` of line `startline` to
+ * column `endcolumn` of line `endline` in file `filepath`.
+ * For more information, see
+ * [LGTM locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
+ */
+ predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
+ exists(File f |
+ locations_default(this, f, startline, startcolumn, endline, endcolumn) and
+ filepath = f.getAbsolutePath()
+ )
+ }
+
+ /** Holds if this location starts strictly before the specified location. */
+ pragma[inline]
+ predicate strictlyBefore(Location other) {
+ this.getStartLine() < other.getStartLine()
+ or
+ this.getStartLine() = other.getStartLine() and this.getStartColumn() < other.getStartColumn()
+ }
+}
+
+/** An entity representing an empty location. */
+class EmptyLocation extends Location {
+ EmptyLocation() { this.hasLocationInfo("", 0, 0, 0, 0) }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/files/FileSystem.qll b/repo-tests/codeql/ruby/ql/lib/codeql/files/FileSystem.qll
new file mode 100644
index 00000000000..552b85a4673
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/files/FileSystem.qll
@@ -0,0 +1,177 @@
+/** Provides classes for working with files and folders. */
+
+private import codeql.Locations
+
+/** A file or folder. */
+abstract class Container extends @container {
+ /** Gets a file or sub-folder in this container. */
+ Container getAChildContainer() { this = result.getParentContainer() }
+
+ /** Gets a file in this container. */
+ File getAFile() { result = this.getAChildContainer() }
+
+ /** Gets a sub-folder in this container. */
+ Folder getAFolder() { result = this.getAChildContainer() }
+
+ /**
+ * Gets the absolute, canonical path of this container, using forward slashes
+ * as path separator.
+ *
+ * The path starts with a _root prefix_ followed by zero or more _path
+ * segments_ separated by forward slashes.
+ *
+ * The root prefix is of one of the following forms:
+ *
+ * 1. A single forward slash `/` (Unix-style)
+ * 2. An upper-case drive letter followed by a colon and a forward slash,
+ * such as `C:/` (Windows-style)
+ * 3. Two forward slashes, a computer name, and then another forward slash,
+ * such as `//FileServer/` (UNC-style)
+ *
+ * Path segments are never empty (that is, absolute paths never contain two
+ * contiguous slashes, except as part of a UNC-style root prefix). Also, path
+ * segments never contain forward slashes, and no path segment is of the
+ * form `.` (one dot) or `..` (two dots).
+ *
+ * Note that an absolute path never ends with a forward slash, except if it is
+ * a bare root prefix, that is, the path has no path segments. A container
+ * whose absolute path has no segments is always a `Folder`, not a `File`.
+ */
+ abstract string getAbsolutePath();
+
+ /**
+ * Gets the base name of this container including extension, that is, the last
+ * segment of its absolute path, or the empty string if it has no segments.
+ *
+ * Here are some examples of absolute paths and the corresponding base names
+ * (surrounded with quotes to avoid ambiguity):
+ *
+ *
+ * | Absolute path | Base name |
+ * | "/tmp/tst.go" | "tst.go" |
+ * | "C:/Program Files (x86)" | "Program Files (x86)" |
+ * | "/" | "" |
+ * | "C:/" | "" |
+ * | "D:/" | "" |
+ * | "//FileServer/" | "" |
+ *
+ */
+ string getBaseName() {
+ result = this.getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1)
+ }
+
+ /**
+ * Gets the extension of this container, that is, the suffix of its base name
+ * after the last dot character, if any.
+ *
+ * In particular,
+ *
+ * - if the name does not include a dot, there is no extension, so this
+ * predicate has no result;
+ * - if the name ends in a dot, the extension is the empty string;
+ * - if the name contains multiple dots, the extension follows the last dot.
+ *
+ * Here are some examples of absolute paths and the corresponding extensions
+ * (surrounded with quotes to avoid ambiguity):
+ *
+ *
+ * | Absolute path | Extension |
+ * | "/tmp/tst.go" | "go" |
+ * | "/tmp/.classpath" | "classpath" |
+ * | "/bin/bash" | not defined |
+ * | "/tmp/tst2." | "" |
+ * | "/tmp/x.tar.gz" | "gz" |
+ *
+ */
+ string getExtension() {
+ result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3)
+ }
+
+ /** Gets the file in this container that has the given `baseName`, if any. */
+ File getFile(string baseName) {
+ result = this.getAFile() and
+ result.getBaseName() = baseName
+ }
+
+ /** Gets the sub-folder in this container that has the given `baseName`, if any. */
+ Folder getFolder(string baseName) {
+ result = this.getAFolder() and
+ result.getBaseName() = baseName
+ }
+
+ /** Gets the parent container of this file or folder, if any. */
+ Container getParentContainer() { containerparent(result, this) }
+
+ /**
+ * Gets the relative path of this file or folder from the root folder of the
+ * analyzed source location. The relative path of the root folder itself is
+ * the empty string.
+ *
+ * This has no result if the container is outside the source root, that is,
+ * if the root folder is not a reflexive, transitive parent of this container.
+ */
+ string getRelativePath() {
+ exists(string absPath, string pref |
+ absPath = this.getAbsolutePath() and sourceLocationPrefix(pref)
+ |
+ absPath = pref and result = ""
+ or
+ absPath = pref.regexpReplaceAll("/$", "") + "/" + result and
+ not result.matches("/%")
+ )
+ }
+
+ /**
+ * Gets the stem of this container, that is, the prefix of its base name up to
+ * (but not including) the last dot character if there is one, or the entire
+ * base name if there is not.
+ *
+ * Here are some examples of absolute paths and the corresponding stems
+ * (surrounded with quotes to avoid ambiguity):
+ *
+ *
+ * | Absolute path | Stem |
+ * | "/tmp/tst.go" | "tst" |
+ * | "/tmp/.classpath" | "" |
+ * | "/bin/bash" | "bash" |
+ * | "/tmp/tst2." | "tst2" |
+ * | "/tmp/x.tar.gz" | "x.tar" |
+ *
+ */
+ string getStem() {
+ result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1)
+ }
+
+ /**
+ * Gets a URL representing the location of this container.
+ *
+ * For more information see https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/#providing-urls.
+ */
+ abstract string getURL();
+
+ /**
+ * Gets a textual representation of the path of this container.
+ *
+ * This is the absolute path of the container.
+ */
+ string toString() { result = this.getAbsolutePath() }
+}
+
+/** A folder. */
+class Folder extends Container, @folder {
+ override string getAbsolutePath() { folders(this, result) }
+
+ /** Gets the URL of this folder. */
+ override string getURL() { result = "folder://" + this.getAbsolutePath() }
+}
+
+/** A file. */
+class File extends Container, @file {
+ override string getAbsolutePath() { files(this, result) }
+
+ /** Gets the URL of this file. */
+ override string getURL() { result = "file://" + this.getAbsolutePath() + ":0:0:0:0" }
+
+ /** Holds if this file was extracted from ordinary source code. */
+ predicate fromSource() { any() }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/AST.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/AST.qll
new file mode 100644
index 00000000000..2d006b6312a
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/AST.qll
@@ -0,0 +1,141 @@
+import codeql.Locations
+import ast.Call
+import ast.Control
+import ast.Constant
+import ast.Erb
+import ast.Expr
+import ast.Literal
+import ast.Method
+import ast.Module
+import ast.Parameter
+import ast.Operation
+import ast.Pattern
+import ast.Scope
+import ast.Statement
+import ast.Variable
+private import ast.internal.AST
+private import ast.internal.Scope
+private import ast.internal.Synthesis
+private import ast.internal.TreeSitter
+
+/**
+ * A node in the abstract syntax tree. This class is the base class for all Ruby
+ * program elements.
+ */
+class AstNode extends TAstNode {
+ /**
+ * Gets the name of a primary CodeQL class to which this node belongs.
+ *
+ * This predicate always has a result. If no primary class can be
+ * determined, the result is `"???"`. If multiple primary classes match,
+ * this predicate can have multiple results.
+ */
+ string getAPrimaryQlClass() { result = "???" }
+
+ /**
+ * Gets a comma-separated list of the names of the primary CodeQL classes to
+ * which this element belongs.
+ */
+ final string getPrimaryQlClasses() { result = concat(this.getAPrimaryQlClass(), ",") }
+
+ /** Gets the enclosing module, if any. */
+ ModuleBase getEnclosingModule() {
+ exists(Scope::Range s |
+ s = scopeOf(toGeneratedInclSynth(this)) and
+ toGeneratedInclSynth(result) = s.getEnclosingModule()
+ )
+ }
+
+ /** Gets the enclosing method, if any. */
+ MethodBase getEnclosingMethod() {
+ exists(Scope::Range s |
+ s = scopeOf(toGeneratedInclSynth(this)) and
+ toGeneratedInclSynth(result) = s.getEnclosingMethod()
+ )
+ }
+
+ /** Gets a textual representation of this node. */
+ cached
+ string toString() { none() }
+
+ /** Gets the location of this node. */
+ Location getLocation() { result = getLocation(this) }
+
+ /** Gets the file of this node. */
+ final File getFile() { result = this.getLocation().getFile() }
+
+ /** Gets a child node of this `AstNode`. */
+ final AstNode getAChild() { result = this.getAChild(_) }
+
+ /** Gets the parent of this `AstNode`, if this node is not a root node. */
+ final AstNode getParent() { result.getAChild() = this }
+
+ /**
+ * Gets a child of this node, which can also be retrieved using a predicate
+ * named `pred`.
+ */
+ cached
+ AstNode getAChild(string pred) {
+ pred = "getDesugared" and
+ result = this.getDesugared()
+ }
+
+ /**
+ * Holds if this node was synthesized to represent an implicit AST node not
+ * present in the source code. In the following example method call, the
+ * receiver is an implicit `self` reference, for which there is a synthesized
+ * `Self` node.
+ *
+ * ```rb
+ * foo(123)
+ * ```
+ */
+ final predicate isSynthesized() { this = getSynthChild(_, _) }
+
+ /**
+ * Gets the desugared version of this AST node, if any.
+ *
+ * For example, the desugared version of
+ *
+ * ```rb
+ * x += y
+ * ```
+ *
+ * is
+ *
+ * ```rb
+ * x = x + y
+ * ```
+ *
+ * when `x` is a variable. Whenever an AST node can be desugared,
+ * then the desugared version is used in the control-flow graph.
+ */
+ final AstNode getDesugared() { result = getSynthChild(this, -1) }
+}
+
+/** A Ruby source file */
+class RubyFile extends File {
+ RubyFile() { ruby_ast_node_parent(_, this, _) }
+
+ /** Gets a token in this file. */
+ private Ruby::Token getAToken() { result.getLocation().getFile() = this }
+
+ /** Holds if `line` contains a token. */
+ private predicate line(int line, boolean comment) {
+ exists(Ruby::Token token, Location l |
+ token = this.getAToken() and
+ l = token.getLocation() and
+ line in [l.getStartLine() .. l.getEndLine()] and
+ if token instanceof @ruby_token_comment then comment = true else comment = false
+ )
+ }
+
+ /** Gets the number of lines in this file. */
+ int getNumberOfLines() { result = max([0, this.getAToken().getLocation().getEndLine()]) }
+
+ /** Gets the number of lines of code in this file. */
+ int getNumberOfLinesOfCode() { result = count(int line | this.line(line, false)) }
+
+ /** Gets the number of lines of comments in this file. */
+ int getNumberOfLinesOfComments() { result = count(int line | this.line(line, true)) }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ApiGraphs.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ApiGraphs.qll
new file mode 100644
index 00000000000..8e5acfb7119
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ApiGraphs.qll
@@ -0,0 +1,423 @@
+/**
+ * Provides an implementation of _API graphs_, which are an abstract representation of the API
+ * surface used and/or defined by a code base.
+ *
+ * The nodes of the API graph represent definitions and uses of API components. The edges are
+ * directed and labeled; they specify how the components represented by nodes relate to each other.
+ */
+
+private import ruby
+import codeql.ruby.DataFlow
+import codeql.ruby.typetracking.TypeTracker
+import codeql.ruby.ast.internal.Module
+private import codeql.ruby.controlflow.CfgNodes
+
+/**
+ * Provides classes and predicates for working with APIs used in a database.
+ */
+module API {
+ /**
+ * An abstract representation of a definition or use of an API component such as a Ruby module,
+ * or the result of a method call.
+ */
+ class Node extends Impl::TApiNode {
+ /**
+ * Gets a data-flow node corresponding to a use of the API component represented by this node.
+ *
+ * For example, `Kernel.format "%s world!", "Hello"` is a use of the return of the `format` function of
+ * the `Kernel` module.
+ *
+ * This includes indirect uses found via data flow.
+ */
+ DataFlow::Node getAUse() {
+ exists(DataFlow::LocalSourceNode src | Impl::use(this, src) |
+ Impl::trackUseNode(src).flowsTo(result)
+ )
+ }
+
+ /**
+ * Gets an immediate use of the API component represented by this node.
+ *
+ * Unlike `getAUse()`, this predicate only gets the immediate references, not the indirect uses
+ * found via data flow.
+ */
+ DataFlow::LocalSourceNode getAnImmediateUse() { Impl::use(this, result) }
+
+ /**
+ * Gets a call to a method on the receiver represented by this API component.
+ */
+ DataFlow::CallNode getAMethodCall(string method) {
+ result = this.getReturn(method).getAnImmediateUse()
+ }
+
+ /**
+ * Gets a node representing member `m` of this API component.
+ *
+ * For example, a member can be:
+ *
+ * - A submodule of a module
+ * - An attribute of an object
+ */
+ bindingset[m]
+ bindingset[result]
+ Node getMember(string m) { result = this.getASuccessor(Label::member(m)) }
+
+ /**
+ * Gets a node representing a member of this API component where the name of the member is
+ * not known statically.
+ */
+ Node getUnknownMember() { result = this.getASuccessor(Label::unknownMember()) }
+
+ /**
+ * Gets a node representing a member of this API component where the name of the member may
+ * or may not be known statically.
+ */
+ Node getAMember() {
+ result = this.getASuccessor(Label::member(_)) or
+ result = this.getUnknownMember()
+ }
+
+ /**
+ * Gets a node representing an instance of this API component, that is, an object whose
+ * constructor is the function represented by this node.
+ *
+ * For example, if this node represents a use of some class `A`, then there might be a node
+ * representing instances of `A`, typically corresponding to expressions `new A()` at the
+ * source level.
+ *
+ * This predicate may have multiple results when there are multiple constructor calls invoking this API component.
+ * Consider using `getAnInstantiation()` if there is a need to distinguish between individual constructor calls.
+ */
+ Node getInstance() { result = this.getASuccessor(Label::instance()) }
+
+ /**
+ * Gets a node representing the result of calling a method on the receiver represented by this node.
+ */
+ Node getReturn(string method) { result = this.getASuccessor(Label::return(method)) }
+
+ /**
+ * Gets a `new` call to the function represented by this API component.
+ */
+ DataFlow::Node getAnInstantiation() { result = this.getInstance().getAnImmediateUse() }
+
+ /**
+ * Gets a node representing a subclass of the class represented by this node.
+ */
+ Node getASubclass() { result = this.getASuccessor(Label::subclass()) }
+
+ /**
+ * Gets a string representation of the lexicographically least among all shortest access paths
+ * from the root to this node.
+ */
+ string getPath() {
+ result = min(string p | p = this.getAPath(Impl::distanceFromRoot(this)) | p)
+ }
+
+ /**
+ * Gets a node such that there is an edge in the API graph between this node and the other
+ * one, and that edge is labeled with `lbl`.
+ */
+ Node getASuccessor(string lbl) { Impl::edge(this, lbl, result) }
+
+ /**
+ * Gets a node such that there is an edge in the API graph between that other node and
+ * this one, and that edge is labeled with `lbl`
+ */
+ Node getAPredecessor(string lbl) { this = result.getASuccessor(lbl) }
+
+ /**
+ * Gets a node such that there is an edge in the API graph between this node and the other
+ * one.
+ */
+ Node getAPredecessor() { result = this.getAPredecessor(_) }
+
+ /**
+ * Gets a node such that there is an edge in the API graph between that other node and
+ * this one.
+ */
+ Node getASuccessor() { result = this.getASuccessor(_) }
+
+ /**
+ * Gets the data-flow node that gives rise to this node, if any.
+ */
+ DataFlow::Node getInducingNode() { this = Impl::MkUse(result) }
+
+ /** Gets the location of this node. */
+ Location getLocation() {
+ result = this.getInducingNode().getLocation()
+ or
+ // For nodes that do not have a meaningful location, `path` is the empty string and all other
+ // parameters are zero.
+ not exists(this.getInducingNode()) and
+ result instanceof EmptyLocation
+ }
+
+ /**
+ * Gets a textual representation of this element.
+ */
+ abstract string toString();
+
+ /**
+ * Gets a path of the given `length` from the root to this node.
+ */
+ private string getAPath(int length) {
+ this instanceof Impl::MkRoot and
+ length = 0 and
+ result = ""
+ or
+ exists(Node pred, string lbl, string predpath |
+ Impl::edge(pred, lbl, this) and
+ lbl != "" and
+ predpath = pred.getAPath(length - 1) and
+ exists(string dot | if length = 1 then dot = "" else dot = "." |
+ result = predpath + dot + lbl and
+ // avoid producing strings longer than 1MB
+ result.length() < 1000 * 1000
+ )
+ ) and
+ length in [1 .. Impl::distanceFromRoot(this)]
+ }
+
+ /** Gets the shortest distance from the root to this node in the API graph. */
+ int getDepth() { result = Impl::distanceFromRoot(this) }
+ }
+
+ /** The root node of an API graph. */
+ class Root extends Node, Impl::MkRoot {
+ override string toString() { result = "root" }
+ }
+
+ /** A node corresponding to the use of an API component. */
+ class Use extends Node, Impl::MkUse {
+ override string toString() {
+ exists(string type | type = "Use " |
+ result = type + this.getPath()
+ or
+ not exists(this.getPath()) and result = type + "with no path"
+ )
+ }
+ }
+
+ /** Gets the root node. */
+ Root root() { any() }
+
+ /**
+ * Gets a node corresponding to a top-level member `m` (typically a module).
+ *
+ * This is equivalent to `root().getAMember("m")`.
+ *
+ * Note: You should only use this predicate for top level modules or classes. If you want nodes corresponding to a nested module or class,
+ * you should use `.getMember` on the parent module/class. For example, for nodes corresponding to the class `Gem::Version`,
+ * use `getTopLevelMember("Gem").getMember("Version")`.
+ */
+ Node getTopLevelMember(string m) { result = root().getMember(m) }
+
+ /**
+ * Provides the actual implementation of API graphs, cached for performance.
+ *
+ * Ideally, we'd like nodes to correspond to (global) access paths, with edge labels
+ * corresponding to extending the access path by one element. We also want to be able to map
+ * nodes to their definitions and uses in the data-flow graph, and this should happen modulo
+ * (inter-procedural) data flow.
+ *
+ * This, however, is not easy to implement, since access paths can have unbounded length
+ * and we need some way of recognizing cycles to avoid non-termination. Unfortunately, expressing
+ * a condition like "this node hasn't been involved in constructing any predecessor of
+ * this node in the API graph" without negative recursion is tricky.
+ *
+ * So instead most nodes are directly associated with a data-flow node, representing
+ * either a use or a definition of an API component. This ensures that we only have a finite
+ * number of nodes. However, we can now have multiple nodes with the same access
+ * path, which are essentially indistinguishable for a client of the API.
+ *
+ * On the other hand, a single node can have multiple access paths (which is, of
+ * course, unavoidable). We pick as canonical the alphabetically least access path with
+ * shortest length.
+ */
+ cached
+ private module Impl {
+ cached
+ newtype TApiNode =
+ /** The root of the API graph. */
+ MkRoot() or
+ /** A use of an API member at the node `nd`. */
+ MkUse(DataFlow::Node nd) { isUse(nd) }
+
+ private string resolveTopLevel(ConstantReadAccess read) {
+ TResolved(result) = resolveScopeExpr(read) and
+ not result.matches("%::%")
+ }
+
+ /**
+ * Holds if `ref` is a use of a node that should have an incoming edge from the root
+ * node labeled `lbl` in the API graph.
+ */
+ pragma[nomagic]
+ private predicate useRoot(string lbl, DataFlow::Node ref) {
+ exists(string name, ExprNodes::ConstantAccessCfgNode access, ConstantReadAccess read |
+ access = ref.asExpr() and
+ lbl = Label::member(read.getName()) and
+ read = access.getExpr()
+ |
+ name = resolveTopLevel(read)
+ or
+ name = read.getName() and
+ not exists(resolveTopLevel(read)) and
+ not exists(read.getScopeExpr())
+ )
+ }
+
+ /**
+ * Holds if `ref` is a use of a node that should have an incoming edge labeled `lbl`,
+ * from a use node that flows to `node`.
+ */
+ private predicate useStep(string lbl, ExprCfgNode node, DataFlow::Node ref) {
+ // // Referring to an attribute on a node that is a use of `base`:
+ // pred = `Rails` part of `Rails::Whatever`
+ // lbl = `Whatever`
+ // ref = `Rails::Whatever`
+ exists(ExprNodes::ConstantAccessCfgNode c, ConstantReadAccess read |
+ not exists(resolveTopLevel(read)) and
+ node = c.getScopeExpr() and
+ lbl = Label::member(read.getName()) and
+ ref.asExpr() = c and
+ read = c.getExpr()
+ )
+ or
+ // Calling a method on a node that is a use of `base`
+ exists(ExprNodes::MethodCallCfgNode call, string name |
+ node = call.getReceiver() and
+ name = call.getExpr().getMethodName() and
+ lbl = Label::return(name) and
+ name != "new" and
+ ref.asExpr() = call
+ )
+ or
+ // Calling the `new` method on a node that is a use of `base`, which creates a new instance
+ exists(ExprNodes::MethodCallCfgNode call |
+ node = call.getReceiver() and
+ lbl = Label::instance() and
+ call.getExpr().getMethodName() = "new" and
+ ref.asExpr() = call
+ )
+ }
+
+ pragma[nomagic]
+ private predicate isUse(DataFlow::Node nd) {
+ useRoot(_, nd)
+ or
+ exists(ExprCfgNode node, DataFlow::LocalSourceNode pred |
+ pred = useCandFwd() and
+ pred.flowsTo(any(DataFlow::ExprNode n | n.getExprNode() = node)) and
+ useStep(_, node, nd)
+ )
+ }
+
+ /**
+ * Holds if `ref` is a use of node `nd`.
+ */
+ cached
+ predicate use(TApiNode nd, DataFlow::Node ref) { nd = MkUse(ref) }
+
+ private DataFlow::LocalSourceNode useCandFwd(TypeTracker t) {
+ t.start() and
+ isUse(result)
+ or
+ exists(TypeTracker t2 | result = useCandFwd(t2).track(t2, t))
+ }
+
+ private DataFlow::LocalSourceNode useCandFwd() { result = useCandFwd(TypeTracker::end()) }
+
+ private DataFlow::Node useCandRev(TypeBackTracker tb) {
+ result = useCandFwd() and
+ tb.start()
+ or
+ exists(TypeBackTracker tb2, DataFlow::LocalSourceNode mid, TypeTracker t |
+ mid = useCandRev(tb2) and
+ result = mid.backtrack(tb2, tb) and
+ pragma[only_bind_out](result) = useCandFwd(t) and
+ pragma[only_bind_out](t) = pragma[only_bind_out](tb).getACompatibleTypeTracker()
+ )
+ }
+
+ private DataFlow::LocalSourceNode useCandRev() {
+ result = useCandRev(TypeBackTracker::end()) and
+ isUse(result)
+ }
+
+ /**
+ * Gets a data-flow node to which `src`, which is a use of an API-graph node, flows.
+ *
+ * The flow from `src` to the returned node may be inter-procedural.
+ */
+ private DataFlow::Node trackUseNode(DataFlow::LocalSourceNode src, TypeTracker t) {
+ result = src and
+ result = useCandRev() and
+ t.start()
+ or
+ exists(TypeTracker t2, DataFlow::LocalSourceNode mid, TypeBackTracker tb |
+ mid = trackUseNode(src, t2) and
+ result = mid.track(t2, t) and
+ pragma[only_bind_out](result) = useCandRev(tb) and
+ pragma[only_bind_out](t) = pragma[only_bind_out](tb).getACompatibleTypeTracker()
+ )
+ }
+
+ /**
+ * Gets a data-flow node to which `src`, which is a use of an API-graph node, flows.
+ *
+ * The flow from `src` to the returned node may be inter-procedural.
+ */
+ cached
+ DataFlow::LocalSourceNode trackUseNode(DataFlow::LocalSourceNode src) {
+ result = trackUseNode(src, TypeTracker::end())
+ }
+
+ /**
+ * Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`.
+ */
+ cached
+ predicate edge(TApiNode pred, string lbl, TApiNode succ) {
+ /* Every node that is a use of an API component is itself added to the API graph. */
+ exists(DataFlow::LocalSourceNode ref | succ = MkUse(ref) |
+ pred = MkRoot() and
+ useRoot(lbl, ref)
+ or
+ exists(ExprCfgNode node, DataFlow::Node src |
+ pred = MkUse(src) and
+ trackUseNode(src).flowsTo(any(DataFlow::ExprNode n | n.getExprNode() = node)) and
+ useStep(lbl, node, ref)
+ )
+ )
+ }
+
+ /**
+ * Holds if there is an edge from `pred` to `succ` in the API graph.
+ */
+ private predicate edge(TApiNode pred, TApiNode succ) { edge(pred, _, succ) }
+
+ /** Gets the shortest distance from the root to `nd` in the API graph. */
+ cached
+ int distanceFromRoot(TApiNode nd) = shortestDistances(MkRoot/0, edge/2)(_, nd, result)
+ }
+}
+
+private module Label {
+ /** Gets the `member` edge label for member `m`. */
+ bindingset[m]
+ bindingset[result]
+ string member(string m) { result = "getMember(\"" + m + "\")" }
+
+ /** Gets the `member` edge label for the unknown member. */
+ string unknownMember() { result = "getUnknownMember()" }
+
+ /** Gets the `instance` edge label. */
+ string instance() { result = "instance" }
+
+ /** Gets the `return` edge label. */
+ bindingset[m]
+ bindingset[result]
+ string return(string m) { result = "getReturn(\"" + m + "\")" }
+
+ string subclass() { result = "getASubclass()" }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/CFG.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/CFG.qll
new file mode 100644
index 00000000000..77507b05a7f
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/CFG.qll
@@ -0,0 +1,5 @@
+/** Provides classes representing the control flow graph. */
+
+import controlflow.ControlFlowGraph
+import controlflow.CfgNodes as CfgNodes
+import controlflow.BasicBlocks
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/Concepts.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/Concepts.qll
new file mode 100644
index 00000000000..9308c1ed1a1
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/Concepts.qll
@@ -0,0 +1,603 @@
+/**
+ * Provides abstract classes representing generic concepts such as file system
+ * access or system command execution, for which individual framework libraries
+ * provide concrete subclasses.
+ */
+
+private import codeql.ruby.AST
+private import codeql.ruby.CFG
+private import codeql.ruby.DataFlow
+private import codeql.ruby.Frameworks
+private import codeql.ruby.dataflow.RemoteFlowSources
+private import codeql.ruby.ApiGraphs
+
+/**
+ * A data-flow node that executes SQL statements.
+ *
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `SqlExecution::Range` instead.
+ */
+class SqlExecution extends DataFlow::Node instanceof SqlExecution::Range {
+ /** Gets the argument that specifies the SQL statements to be executed. */
+ DataFlow::Node getSql() { result = super.getSql() }
+}
+
+/** Provides a class for modeling new SQL execution APIs. */
+module SqlExecution {
+ /**
+ * A data-flow node that executes SQL statements.
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `SqlExecution` instead.
+ */
+ abstract class Range extends DataFlow::Node {
+ /** Gets the argument that specifies the SQL statements to be executed. */
+ abstract DataFlow::Node getSql();
+ }
+}
+
+/**
+ * A data flow node that performs a file system access, including reading and writing data,
+ * creating and deleting files and folders, checking and updating permissions, and so on.
+ *
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `FileSystemAccess::Range` instead.
+ */
+class FileSystemAccess extends DataFlow::Node instanceof FileSystemAccess::Range {
+ /** Gets an argument to this file system access that is interpreted as a path. */
+ DataFlow::Node getAPathArgument() { result = super.getAPathArgument() }
+}
+
+/** Provides a class for modeling new file system access APIs. */
+module FileSystemAccess {
+ /**
+ * A data-flow node that performs a file system access, including reading and writing data,
+ * creating and deleting files and folders, checking and updating permissions, and so on.
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `FileSystemAccess` instead.
+ */
+ abstract class Range extends DataFlow::Node {
+ /** Gets an argument to this file system access that is interpreted as a path. */
+ abstract DataFlow::Node getAPathArgument();
+ }
+}
+
+/**
+ * A data flow node that reads data from the file system.
+ *
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `FileSystemReadAccess::Range` instead.
+ */
+class FileSystemReadAccess extends FileSystemAccess instanceof FileSystemReadAccess::Range {
+ /**
+ * Gets a node that represents data read from the file system access.
+ */
+ DataFlow::Node getADataNode() { result = FileSystemReadAccess::Range.super.getADataNode() }
+}
+
+/** Provides a class for modeling new file system reads. */
+module FileSystemReadAccess {
+ /**
+ * A data flow node that reads data from the file system.
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `FileSystemReadAccess` instead.
+ */
+ abstract class Range extends FileSystemAccess::Range {
+ /**
+ * Gets a node that represents data read from the file system.
+ */
+ abstract DataFlow::Node getADataNode();
+ }
+}
+
+/**
+ * A data flow node that sets the permissions for one or more files.
+ *
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `FileSystemPermissionModification::Range` instead.
+ */
+class FileSystemPermissionModification extends DataFlow::Node instanceof FileSystemPermissionModification::Range {
+ /**
+ * Gets an argument to this permission modification that is interpreted as a
+ * set of permissions.
+ */
+ DataFlow::Node getAPermissionNode() { result = super.getAPermissionNode() }
+}
+
+/** Provides a class for modeling new file system permission modifications. */
+module FileSystemPermissionModification {
+ /**
+ * A data-flow node that sets permissions for a one or more files.
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `FileSystemPermissionModification` instead.
+ */
+ abstract class Range extends DataFlow::Node {
+ /**
+ * Gets an argument to this permission modification that is interpreted as a
+ * set of permissions.
+ */
+ abstract DataFlow::Node getAPermissionNode();
+ }
+}
+
+/**
+ * A data flow node that contains a file name or an array of file names from the local file system.
+ */
+abstract class FileNameSource extends DataFlow::Node { }
+
+/**
+ * A data-flow node that escapes meta-characters, which could be used to prevent
+ * injection attacks.
+ *
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `Escaping::Range` instead.
+ */
+class Escaping extends DataFlow::Node instanceof Escaping::Range {
+ Escaping() {
+ // escapes that don't have _both_ input/output defined are not valid
+ exists(super.getAnInput()) and
+ exists(super.getOutput())
+ }
+
+ /** Gets an input that will be escaped. */
+ DataFlow::Node getAnInput() { result = super.getAnInput() }
+
+ /** Gets the output that contains the escaped data. */
+ DataFlow::Node getOutput() { result = super.getOutput() }
+
+ /**
+ * Gets the context that this function escapes for, such as `html`, or `url`.
+ */
+ string getKind() { result = super.getKind() }
+}
+
+/** Provides a class for modeling new escaping APIs. */
+module Escaping {
+ /**
+ * A data-flow node that escapes meta-characters, which could be used to prevent
+ * injection attacks.
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `Escaping` instead.
+ */
+ abstract class Range extends DataFlow::Node {
+ /** Gets an input that will be escaped. */
+ abstract DataFlow::Node getAnInput();
+
+ /** Gets the output that contains the escaped data. */
+ abstract DataFlow::Node getOutput();
+
+ /**
+ * Gets the context that this function escapes for.
+ *
+ * While kinds are represented as strings, this should not be relied upon. Use the
+ * predicates in the `Escaping` module, such as `getHtmlKind`.
+ */
+ abstract string getKind();
+ }
+
+ /** Gets the escape-kind for escaping a string so it can safely be included in HTML. */
+ string getHtmlKind() { result = "html" }
+}
+
+/**
+ * An escape of a string so it can be safely included in
+ * the body of an HTML element, for example, replacing `{}` in
+ * `{}
`.
+ */
+class HtmlEscaping extends Escaping {
+ HtmlEscaping() { super.getKind() = Escaping::getHtmlKind() }
+}
+
+/** Provides classes for modeling HTTP-related APIs. */
+module HTTP {
+ /** Provides classes for modeling HTTP servers. */
+ module Server {
+ /**
+ * A data-flow node that sets up a route on a server.
+ *
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `RouteSetup::Range` instead.
+ */
+ class RouteSetup extends DataFlow::Node instanceof RouteSetup::Range {
+ /** Gets the URL pattern for this route, if it can be statically determined. */
+ string getUrlPattern() { result = super.getUrlPattern() }
+
+ /**
+ * Gets a function that will handle incoming requests for this route, if any.
+ *
+ * NOTE: This will be modified in the near future to have a `RequestHandler` result, instead of a `Method`.
+ */
+ Method getARequestHandler() { result = super.getARequestHandler() }
+
+ /**
+ * Gets a parameter that will receive parts of the url when handling incoming
+ * requests for this route, if any. These automatically become a `RemoteFlowSource`.
+ */
+ Parameter getARoutedParameter() { result = super.getARoutedParameter() }
+
+ /** Gets a string that identifies the framework used for this route setup. */
+ string getFramework() { result = super.getFramework() }
+ }
+
+ /** Provides a class for modeling new HTTP routing APIs. */
+ module RouteSetup {
+ /**
+ * A data-flow node that sets up a route on a server.
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `RouteSetup` instead.
+ */
+ abstract class Range extends DataFlow::Node {
+ /** Gets the argument used to set the URL pattern. */
+ abstract DataFlow::Node getUrlPatternArg();
+
+ /** Gets the URL pattern for this route, if it can be statically determined. */
+ string getUrlPattern() {
+ exists(CfgNodes::ExprNodes::StringlikeLiteralCfgNode strNode |
+ this.getUrlPatternArg().getALocalSource() = DataFlow::exprNode(strNode) and
+ result = strNode.getExpr().getValueText()
+ )
+ }
+
+ /**
+ * Gets a function that will handle incoming requests for this route, if any.
+ *
+ * NOTE: This will be modified in the near future to have a `RequestHandler` result, instead of a `Method`.
+ */
+ abstract Method getARequestHandler();
+
+ /**
+ * Gets a parameter that will receive parts of the url when handling incoming
+ * requests for this route, if any. These automatically become a `RemoteFlowSource`.
+ */
+ abstract Parameter getARoutedParameter();
+
+ /** Gets a string that identifies the framework used for this route setup. */
+ abstract string getFramework();
+ }
+ }
+
+ /**
+ * A function that will handle incoming HTTP requests.
+ *
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `RequestHandler::Range` instead.
+ */
+ class RequestHandler extends Method instanceof RequestHandler::Range {
+ /**
+ * Gets a parameter that could receive parts of the url when handling incoming
+ * requests, if any. These automatically become a `RemoteFlowSource`.
+ */
+ Parameter getARoutedParameter() { result = super.getARoutedParameter() }
+
+ /** Gets a string that identifies the framework used for this route setup. */
+ string getFramework() { result = super.getFramework() }
+ }
+
+ /** Provides a class for modeling new HTTP request handlers. */
+ module RequestHandler {
+ /**
+ * A function that will handle incoming HTTP requests.
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `RequestHandler` instead.
+ *
+ * Only extend this class if you can't provide a `RouteSetup`, since we handle that case automatically.
+ */
+ abstract class Range extends Method {
+ /**
+ * Gets a parameter that could receive parts of the url when handling incoming
+ * requests, if any. These automatically become a `RemoteFlowSource`.
+ */
+ abstract Parameter getARoutedParameter();
+
+ /** Gets a string that identifies the framework used for this request handler. */
+ abstract string getFramework();
+ }
+ }
+
+ private class RequestHandlerFromRouteSetup extends RequestHandler::Range {
+ RouteSetup rs;
+
+ RequestHandlerFromRouteSetup() { this = rs.getARequestHandler() }
+
+ override Parameter getARoutedParameter() {
+ result = rs.getARoutedParameter() and
+ result = this.getAParameter()
+ }
+
+ override string getFramework() { result = rs.getFramework() }
+ }
+
+ /** A parameter that will receive parts of the url when handling an incoming request. */
+ private class RoutedParameter extends RemoteFlowSource::Range, DataFlow::ParameterNode {
+ RequestHandler handler;
+
+ RoutedParameter() { this.getParameter() = handler.getARoutedParameter() }
+
+ override string getSourceType() { result = handler.getFramework() + " RoutedParameter" }
+ }
+
+ /**
+ * A data-flow node that creates a HTTP response on a server.
+ *
+ * Note: we don't require that this response must be sent to a client (a kind of
+ * "if a tree falls in a forest and nobody hears it" situation).
+ *
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `HttpResponse::Range` instead.
+ */
+ class HttpResponse extends DataFlow::Node instanceof HttpResponse::Range {
+ /** Gets the data-flow node that specifies the body of this HTTP response. */
+ DataFlow::Node getBody() { result = super.getBody() }
+
+ /** Gets the mimetype of this HTTP response, if it can be statically determined. */
+ string getMimetype() { result = super.getMimetype() }
+ }
+
+ /** Provides a class for modeling new HTTP response APIs. */
+ module HttpResponse {
+ /**
+ * A data-flow node that creates a HTTP response on a server.
+ *
+ * Note: we don't require that this response must be sent to a client (a kind of
+ * "if a tree falls in a forest and nobody hears it" situation).
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `HttpResponse` instead.
+ */
+ abstract class Range extends DataFlow::Node {
+ /** Gets the data-flow node that specifies the body of this HTTP response. */
+ abstract DataFlow::Node getBody();
+
+ /** Gets the data-flow node that specifies the content-type/mimetype of this HTTP response, if any. */
+ abstract DataFlow::Node getMimetypeOrContentTypeArg();
+
+ /** Gets the default mimetype that should be used if `getMimetypeOrContentTypeArg` has no results. */
+ abstract string getMimetypeDefault();
+
+ /** Gets the mimetype of this HTTP response, if it can be statically determined. */
+ string getMimetype() {
+ exists(CfgNodes::ExprNodes::StringlikeLiteralCfgNode strNode |
+ this.getMimetypeOrContentTypeArg().getALocalSource() = DataFlow::exprNode(strNode) and
+ result = strNode.getExpr().getValueText().splitAt(";", 0)
+ )
+ or
+ not exists(this.getMimetypeOrContentTypeArg()) and
+ result = this.getMimetypeDefault()
+ }
+ }
+ }
+
+ /**
+ * A data-flow node that creates a HTTP redirect response on a server.
+ *
+ * Note: we don't require that this redirect must be sent to a client (a kind of
+ * "if a tree falls in a forest and nobody hears it" situation).
+ *
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `HttpRedirectResponse::Range` instead.
+ */
+ class HttpRedirectResponse extends HttpResponse instanceof HttpRedirectResponse::Range {
+ /** Gets the data-flow node that specifies the location of this HTTP redirect response. */
+ DataFlow::Node getRedirectLocation() { result = super.getRedirectLocation() }
+ }
+
+ /** Provides a class for modeling new HTTP redirect response APIs. */
+ module HttpRedirectResponse {
+ /**
+ * A data-flow node that creates a HTTP redirect response on a server.
+ *
+ * Note: we don't require that this redirect must be sent to a client (a kind of
+ * "if a tree falls in a forest and nobody hears it" situation).
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `HttpResponse` instead.
+ */
+ abstract class Range extends HTTP::Server::HttpResponse::Range {
+ /** Gets the data-flow node that specifies the location of this HTTP redirect response. */
+ abstract DataFlow::Node getRedirectLocation();
+ }
+ }
+ }
+
+ /** Provides classes for modeling HTTP clients. */
+ module Client {
+ /**
+ * A method call that makes an outgoing HTTP request.
+ *
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `Request::Range` instead.
+ */
+ class Request extends MethodCall instanceof Request::Range {
+ /** Gets a node which returns the body of the response */
+ DataFlow::Node getResponseBody() { result = super.getResponseBody() }
+
+ /** Gets a string that identifies the framework used for this request. */
+ string getFramework() { result = super.getFramework() }
+
+ /**
+ * Holds if this request is made using a mode that disables SSL/TLS
+ * certificate validation, where `disablingNode` represents the point at
+ * which the validation was disabled.
+ */
+ predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
+ super.disablesCertificateValidation(disablingNode)
+ }
+ }
+
+ /** Provides a class for modeling new HTTP requests. */
+ module Request {
+ /**
+ * A method call that makes an outgoing HTTP request.
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `Request` instead.
+ */
+ abstract class Range extends MethodCall {
+ /** Gets a node which returns the body of the response */
+ abstract DataFlow::Node getResponseBody();
+
+ /** Gets a string that identifies the framework used for this request. */
+ abstract string getFramework();
+
+ /**
+ * Holds if this request is made using a mode that disables SSL/TLS
+ * certificate validation, where `disablingNode` represents the point at
+ * which the validation was disabled.
+ */
+ abstract predicate disablesCertificateValidation(DataFlow::Node disablingNode);
+ }
+ }
+
+ /** The response body from an outgoing HTTP request, considered as a remote flow source */
+ private class RequestResponseBody extends RemoteFlowSource::Range, DataFlow::Node {
+ Request request;
+
+ RequestResponseBody() { this = request.getResponseBody() }
+
+ override string getSourceType() { result = request.getFramework() }
+ }
+ }
+}
+
+/**
+ * A data flow node that executes an operating system command,
+ * for instance by spawning a new process.
+ */
+class SystemCommandExecution extends DataFlow::Node instanceof SystemCommandExecution::Range {
+ /** Holds if a shell interprets `arg`. */
+ predicate isShellInterpreted(DataFlow::Node arg) { super.isShellInterpreted(arg) }
+
+ /** Gets an argument to this execution that specifies the command or an argument to it. */
+ DataFlow::Node getAnArgument() { result = super.getAnArgument() }
+}
+
+/** Provides a class for modeling new operating system command APIs. */
+module SystemCommandExecution {
+ /**
+ * A data flow node that executes an operating system command, for instance by spawning a new
+ * process.
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `SystemCommandExecution` instead.
+ */
+ abstract class Range extends DataFlow::Node {
+ /** Gets an argument to this execution that specifies the command or an argument to it. */
+ abstract DataFlow::Node getAnArgument();
+
+ /** Holds if a shell interprets `arg`. */
+ predicate isShellInterpreted(DataFlow::Node arg) { none() }
+ }
+}
+
+/**
+ * A data-flow node that dynamically executes Ruby code.
+ *
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `CodeExecution::Range` instead.
+ */
+class CodeExecution extends DataFlow::Node instanceof CodeExecution::Range {
+ /** Gets the argument that specifies the code to be executed. */
+ DataFlow::Node getCode() { result = super.getCode() }
+}
+
+/** Provides a class for modeling new dynamic code execution APIs. */
+module CodeExecution {
+ /**
+ * A data-flow node that dynamically executes Ruby code.
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `CodeExecution` instead.
+ */
+ abstract class Range extends DataFlow::Node {
+ /** Gets the argument that specifies the code to be executed. */
+ abstract DataFlow::Node getCode();
+ }
+}
+
+/**
+ * A data-flow node that parses XML content.
+ *
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `XmlParserCall::Range` instead.
+ */
+class XmlParserCall extends DataFlow::Node {
+ XmlParserCall::Range range;
+
+ XmlParserCall() { this = range }
+
+ /** Gets the argument that specifies the XML content to be parsed. */
+ DataFlow::Node getInput() { result = range.getInput() }
+
+ /** Holds if this XML parser call is configured to process external entities */
+ predicate externalEntitiesEnabled() { range.externalEntitiesEnabled() }
+}
+
+/** Provides a class for modeling new XML parsing APIs. */
+module XmlParserCall {
+ /**
+ * A data-flow node that parses XML content.
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `class XmlParserCall` instead.
+ */
+ abstract class Range extends DataFlow::Node {
+ /** Gets the argument that specifies the XML content to be parsed. */
+ abstract DataFlow::Node getInput();
+
+ /** Holds if this XML parser call is configured to process external entities */
+ abstract predicate externalEntitiesEnabled();
+ }
+}
+
+/**
+ * A data-flow node that may represent a database object in an ORM system.
+ *
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `OrmInstantiation::Range` instead.
+ */
+class OrmInstantiation extends DataFlow::Node instanceof OrmInstantiation::Range {
+ /** Holds if a call to `methodName` on this instance may return a field of this ORM object. */
+ bindingset[methodName]
+ predicate methodCallMayAccessField(string methodName) {
+ super.methodCallMayAccessField(methodName)
+ }
+}
+
+/** Provides a class for modeling new ORM object instantiation APIs. */
+module OrmInstantiation {
+ /**
+ * A data-flow node that may represent a database object in an ORM system.
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `OrmInstantiation` instead.
+ */
+ abstract class Range extends DataFlow::Node {
+ /** Holds if a call to `methodName` on this instance may return a field of this ORM object. */
+ bindingset[methodName]
+ abstract predicate methodCallMayAccessField(string methodName);
+ }
+}
+
+/** Provides classes for modeling path-related APIs. */
+module Path {
+ /**
+ * A data-flow node that performs path sanitization. This is often needed in order
+ * to safely access paths.
+ */
+ class PathSanitization extends DataFlow::Node instanceof PathSanitization::Range { }
+
+ /** Provides a class for modeling new path sanitization APIs. */
+ module PathSanitization {
+ /**
+ * A data-flow node that performs path sanitization. This is often needed in order
+ * to safely access paths.
+ */
+ abstract class Range extends DataFlow::Node { }
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/DataFlow.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/DataFlow.qll
new file mode 100644
index 00000000000..e7645ce0c10
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/DataFlow.qll
@@ -0,0 +1,7 @@
+/**
+ * Provides classes for performing local (intra-procedural) and
+ * global (inter-procedural) data flow analyses.
+ */
+module DataFlow {
+ import codeql.ruby.dataflow.internal.DataFlowImpl
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/DataFlow2.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/DataFlow2.qll
new file mode 100644
index 00000000000..7486f52052d
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/DataFlow2.qll
@@ -0,0 +1,7 @@
+/**
+ * Provides classes for performing local (intra-procedural) and
+ * global (inter-procedural) data flow analyses.
+ */
+module DataFlow2 {
+ import codeql.ruby.dataflow.internal.DataFlowImpl2
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/Diagnostics.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/Diagnostics.qll
new file mode 100644
index 00000000000..b8995c01bc2
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/Diagnostics.qll
@@ -0,0 +1,52 @@
+private import codeql.Locations
+
+/** A diagnostic emitted during extraction, such as a parse error */
+class Diagnostic extends @diagnostic {
+ int severity;
+ string tag;
+ string message;
+ string fullMessage;
+ Location location;
+
+ Diagnostic() { diagnostics(this, severity, tag, message, fullMessage, location) }
+
+ /**
+ * Gets the numerical severity level associated with this diagnostic.
+ */
+ int getSeverity() { result = severity }
+
+ /** Gets a string representation of the severity of this diagnostic. */
+ string getSeverityText() {
+ severity = 10 and result = "Debug"
+ or
+ severity = 20 and result = "Info"
+ or
+ severity = 30 and result = "Warning"
+ or
+ severity = 40 and result = "Error"
+ }
+
+ /** Gets the error code associated with this diagnostic, e.g. parse_error. */
+ string getTag() { result = tag }
+
+ /**
+ * Gets the error message text associated with this diagnostic.
+ */
+ string getMessage() { result = message }
+
+ /**
+ * Gets the full error message text associated with this diagnostic.
+ */
+ string getFullMessage() { result = fullMessage }
+
+ /** Gets the source location of this diagnostic. */
+ Location getLocation() { result = location }
+
+ /** Gets a textual representation of this diagnostic. */
+ string toString() { result = this.getMessage() }
+}
+
+/** A diagnostic relating to a particular error in extracting a file. */
+class ExtractionError extends Diagnostic, @diagnostic_error {
+ ExtractionError() { this.getTag() = "parse_error" }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/Frameworks.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/Frameworks.qll
new file mode 100644
index 00000000000..36f7c0de25b
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/Frameworks.qll
@@ -0,0 +1,12 @@
+/**
+ * Helper file that imports all framework modeling.
+ */
+
+private import codeql.ruby.frameworks.ActionController
+private import codeql.ruby.frameworks.ActiveRecord
+private import codeql.ruby.frameworks.ActiveStorage
+private import codeql.ruby.frameworks.ActionView
+private import codeql.ruby.frameworks.StandardLibrary
+private import codeql.ruby.frameworks.Files
+private import codeql.ruby.frameworks.HttpClients
+private import codeql.ruby.frameworks.XmlParsing
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/TaintTracking.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/TaintTracking.qll
new file mode 100755
index 00000000000..e443b294273
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/TaintTracking.qll
@@ -0,0 +1,7 @@
+/**
+ * Provides classes for performing local (intra-procedural) and
+ * global (inter-procedural) taint-tracking analyses.
+ */
+module TaintTracking {
+ import codeql.ruby.dataflow.internal.tainttracking1.TaintTrackingImpl
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Call.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Call.qll
new file mode 100644
index 00000000000..d34034f14cd
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Call.qll
@@ -0,0 +1,215 @@
+private import codeql.ruby.AST
+private import internal.AST
+private import internal.Call
+private import internal.TreeSitter
+private import codeql.ruby.dataflow.internal.DataFlowDispatch
+private import codeql.ruby.dataflow.internal.DataFlowImplCommon
+
+/**
+ * A call.
+ */
+class Call extends Expr instanceof CallImpl {
+ override string getAPrimaryQlClass() { result = "Call" }
+
+ /**
+ * Gets the `n`th argument of this method call. In the following example, the
+ * result for n=0 is the `IntegerLiteral` 0, while for n=1 the result is a
+ * `Pair` (whose `getKey` returns the `SymbolLiteral` for `bar`, and
+ * `getValue` returns the `IntegerLiteral` 1). Keyword arguments like this
+ * can be accessed more naturally using the
+ * `getKeywordArgument(string keyword)` predicate.
+ * ```rb
+ * foo(0, bar: 1)
+ * yield 0, bar: 1
+ * ```
+ */
+ final Expr getArgument(int n) { result = super.getArgumentImpl(n) }
+
+ /**
+ * Gets an argument of this method call.
+ */
+ final Expr getAnArgument() { result = this.getArgument(_) }
+
+ /**
+ * Gets the value of the keyword argument whose key is `keyword`, if any. For
+ * example, the result for `getKeywordArgument("qux")` in the following
+ * example is the `IntegerLiteral` 123.
+ * ```rb
+ * foo :bar "baz", qux: 123
+ * ```
+ */
+ final Expr getKeywordArgument(string keyword) {
+ exists(Pair p |
+ p = this.getAnArgument() and
+ p.getKey().(SymbolLiteral).getValueText() = keyword and
+ result = p.getValue()
+ )
+ }
+
+ /**
+ * Gets the number of arguments of this method call.
+ */
+ final int getNumberOfArguments() { result = super.getNumberOfArgumentsImpl() }
+
+ /** Gets a potential target of this call, if any. */
+ final Callable getATarget() {
+ exists(DataFlowCall c | this = c.asCall().getExpr() |
+ TCfgScope(result) = [viableCallable(c), viableCallableLambda(c, _)]
+ )
+ }
+
+ override AstNode getAChild(string pred) {
+ result = Expr.super.getAChild(pred)
+ or
+ pred = "getArgument" and result = this.getArgument(_)
+ }
+}
+
+/**
+ * A method call.
+ */
+class MethodCall extends Call instanceof MethodCallImpl {
+ override string getAPrimaryQlClass() { result = "MethodCall" }
+
+ /**
+ * Gets the receiver of this call, if any. For example:
+ *
+ * ```rb
+ * foo.bar
+ * Baz::qux
+ * corge()
+ * ```
+ *
+ * The result for the call to `bar` is the `Expr` for `foo`; the result for
+ * the call to `qux` is the `Expr` for `Baz`; for the call to `corge` there
+ * is no result.
+ */
+ final Expr getReceiver() { result = super.getReceiverImpl() }
+
+ /**
+ * Gets the name of the method being called. For example, in:
+ *
+ * ```rb
+ * foo.bar x, y
+ * ```
+ *
+ * the result is `"bar"`.
+ */
+ final string getMethodName() { result = super.getMethodNameImpl() }
+
+ /**
+ * Gets the block of this method call, if any.
+ * ```rb
+ * foo.each { |x| puts x }
+ * ```
+ */
+ final Block getBlock() { result = super.getBlockImpl() }
+
+ override string toString() { result = "call to " + this.getMethodName() }
+
+ override AstNode getAChild(string pred) {
+ result = Call.super.getAChild(pred)
+ or
+ pred = "getReceiver" and result = this.getReceiver()
+ or
+ pred = "getBlock" and result = this.getBlock()
+ }
+}
+
+/**
+ * A call to a setter method.
+ * ```rb
+ * self.foo = 10
+ * a[0] = 10
+ * ```
+ */
+class SetterMethodCall extends MethodCall, TMethodCallSynth {
+ SetterMethodCall() { this = TMethodCallSynth(_, _, _, true, _) }
+
+ final override string getAPrimaryQlClass() { result = "SetterMethodCall" }
+}
+
+/**
+ * An element reference; a call to the `[]` method.
+ * ```rb
+ * a[0]
+ * ```
+ */
+class ElementReference extends MethodCall instanceof ElementReferenceImpl {
+ final override string getAPrimaryQlClass() { result = "ElementReference" }
+
+ final override string toString() { result = "...[...]" }
+}
+
+/**
+ * A call to `yield`.
+ * ```rb
+ * yield x, y
+ * ```
+ */
+class YieldCall extends Call instanceof YieldCallImpl {
+ final override string getAPrimaryQlClass() { result = "YieldCall" }
+
+ final override string toString() { result = "yield ..." }
+}
+
+/**
+ * A call to `super`.
+ * ```rb
+ * class Foo < Bar
+ * def baz
+ * super
+ * end
+ * end
+ * ```
+ */
+class SuperCall extends MethodCall instanceof SuperCallImpl {
+ final override string getAPrimaryQlClass() { result = "SuperCall" }
+}
+
+/**
+ * A block argument in a method call.
+ * ```rb
+ * foo(&block)
+ * ```
+ */
+class BlockArgument extends Expr, TBlockArgument {
+ private Ruby::BlockArgument g;
+
+ BlockArgument() { this = TBlockArgument(g) }
+
+ final override string getAPrimaryQlClass() { result = "BlockArgument" }
+
+ /**
+ * Gets the underlying expression representing the block. In the following
+ * example, the result is the `Expr` for `bar`:
+ * ```rb
+ * foo(&bar)
+ * ```
+ */
+ final Expr getValue() { toGenerated(result) = g.getChild() }
+
+ final override string toString() { result = "&..." }
+
+ final override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getValue" and result = this.getValue()
+ }
+}
+
+/**
+ * A `...` expression that contains forwarded arguments.
+ * ```rb
+ * foo(...)
+ * ```
+ */
+class ForwardedArguments extends Expr, TForwardArgument {
+ private Ruby::ForwardArgument g;
+
+ ForwardedArguments() { this = TForwardArgument(g) }
+
+ final override string getAPrimaryQlClass() { result = "ForwardedArguments" }
+
+ final override string toString() { result = "..." }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Constant.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Constant.qll
new file mode 100644
index 00000000000..11683d694b7
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Constant.qll
@@ -0,0 +1,210 @@
+private import codeql.ruby.AST
+private import internal.AST
+private import internal.Module
+private import internal.Variable
+private import internal.TreeSitter
+
+/** An access to a constant. */
+class ConstantAccess extends Expr, TConstantAccess {
+ /** Gets the name of the constant being accessed. */
+ string getName() { none() }
+
+ /** Holds if the name of the constant being accessed is `name`. */
+ final predicate hasName(string name) { this.getName() = name }
+
+ /**
+ * Gets the expression used in the access's scope resolution operation, if
+ * any. In the following example, the result is the `Call` expression for
+ * `foo()`.
+ *
+ * ```rb
+ * foo()::MESSAGE
+ * ```
+ *
+ * However, there is no result for the following example, since there is no
+ * scope resolution operation.
+ *
+ * ```rb
+ * MESSAGE
+ * ```
+ */
+ Expr getScopeExpr() { none() }
+
+ /**
+ * Holds if the access uses the scope resolution operator to refer to the
+ * global scope, as in this example:
+ *
+ * ```rb
+ * ::MESSAGE
+ * ```
+ */
+ predicate hasGlobalScope() { none() }
+
+ override string toString() { result = this.getName() }
+
+ override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getScopeExpr" and result = this.getScopeExpr()
+ }
+}
+
+private class TokenConstantAccess extends ConstantAccess, TTokenConstantAccess {
+ private Ruby::Constant g;
+
+ TokenConstantAccess() { this = TTokenConstantAccess(g) }
+
+ final override string getName() { result = g.getValue() }
+}
+
+private class ScopeResolutionConstantAccess extends ConstantAccess, TScopeResolutionConstantAccess {
+ private Ruby::ScopeResolution g;
+ private Ruby::Constant constant;
+
+ ScopeResolutionConstantAccess() { this = TScopeResolutionConstantAccess(g, constant) }
+
+ final override string getName() { result = constant.getValue() }
+
+ final override Expr getScopeExpr() { toGenerated(result) = g.getScope() }
+
+ final override predicate hasGlobalScope() { not exists(g.getScope()) }
+}
+
+private class ConstantReadAccessSynth extends ConstantAccess, TConstantReadAccessSynth {
+ private string value;
+
+ ConstantReadAccessSynth() { this = TConstantReadAccessSynth(_, _, value) }
+
+ final override string getName() {
+ if this.hasGlobalScope() then result = value.suffix(2) else result = value
+ }
+
+ final override Expr getScopeExpr() { synthChild(this, 0, result) }
+
+ final override predicate hasGlobalScope() { value.matches("::%") }
+}
+
+/**
+ * A use (read) of a constant.
+ *
+ * For example, the right-hand side of the assignment in:
+ *
+ * ```rb
+ * x = Foo
+ * ```
+ *
+ * Or the superclass `Bar` in this example:
+ *
+ * ```rb
+ * class Foo < Bar
+ * end
+ * ```
+ */
+class ConstantReadAccess extends ConstantAccess {
+ ConstantReadAccess() {
+ not this instanceof ConstantWriteAccess
+ or
+ // `X` in `X ||= 10` is considered both a read and a write
+ this = any(AssignOperation a).getLeftOperand()
+ or
+ this instanceof TConstantReadAccessSynth
+ }
+
+ /**
+ * Gets the value being read, if any. For example, in
+ *
+ * ```rb
+ * module M
+ * CONST = "const"
+ * end
+ *
+ * puts M::CONST
+ * ```
+ *
+ * the value being read at `M::CONST` is `"const"`.
+ */
+ Expr getValue() {
+ not exists(this.getScopeExpr()) and
+ result = lookupConst(this.getEnclosingModule+().getModule(), this.getName()) and
+ // For now, we restrict the scope of top-level declarations to their file.
+ // This may remove some plausible targets, but also removes a lot of
+ // implausible targets
+ if result.getEnclosingModule() instanceof Toplevel
+ then result.getFile() = this.getFile()
+ else any()
+ or
+ this.hasGlobalScope() and
+ result = lookupConst(TResolved("Object"), this.getName())
+ or
+ result = lookupConst(resolveScopeExpr(this.getScopeExpr()), this.getName())
+ }
+
+ override string getValueText() { result = this.getValue().getValueText() }
+
+ final override string getAPrimaryQlClass() { result = "ConstantReadAccess" }
+}
+
+/**
+ * A definition of a constant.
+ *
+ * Examples:
+ *
+ * ```rb
+ * Foo = 1 # defines constant Foo as an integer
+ * M::Foo = 1 # defines constant Foo as an integer in module M
+ *
+ * class Bar; end # defines constant Bar as a class
+ * class M::Bar; end # defines constant Bar as a class in module M
+ *
+ * module Baz; end # defines constant Baz as a module
+ * module M::Baz; end # defines constant Baz as a module in module M
+ * ```
+ */
+class ConstantWriteAccess extends ConstantAccess {
+ ConstantWriteAccess() {
+ explicitAssignmentNode(toGenerated(this), _) or this instanceof TNamespace
+ }
+
+ override string getAPrimaryQlClass() { result = "ConstantWriteAccess" }
+
+ /**
+ * Gets the fully qualified name for this constant, based on the context in
+ * which it is defined.
+ *
+ * For example, given
+ * ```rb
+ * module Foo
+ * module Bar
+ * class Baz
+ * end
+ * end
+ * CONST_A = "a"
+ * end
+ * ```
+ *
+ * the constant `Baz` has the fully qualified name `Foo::Bar::Baz`, and
+ * `CONST_A` has the fully qualified name `Foo::CONST_A`.
+ */
+ string getQualifiedName() {
+ /* get the qualified name for the parent module, then append w */
+ exists(ConstantWriteAccess parent | parent = this.getEnclosingModule() |
+ result = parent.getQualifiedName() + "::" + this.getName()
+ )
+ or
+ /* base case - there's no parent module */
+ not exists(ConstantWriteAccess parent | parent = this.getEnclosingModule()) and
+ result = this.getName()
+ }
+}
+
+/**
+ * A definition of a constant via assignment. For example, the left-hand
+ * operand in the following example:
+ *
+ * ```rb
+ * MAX_SIZE = 100
+ * ```
+ */
+class ConstantAssignment extends ConstantWriteAccess, LhsExpr {
+ override string getAPrimaryQlClass() { result = "ConstantAssignment" }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Control.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Control.qll
new file mode 100644
index 00000000000..d209e2861c0
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Control.qll
@@ -0,0 +1,611 @@
+private import codeql.ruby.AST
+private import internal.AST
+private import internal.TreeSitter
+
+/**
+ * A control expression that can be any of the following:
+ * - `case`
+ * - `if`/`unless` (including expression-modifier variants)
+ * - ternary-if (`?:`)
+ * - `while`/`until` (including expression-modifier variants)
+ * - `for`
+ */
+class ControlExpr extends Expr, TControlExpr { }
+
+/**
+ * A conditional expression: `if`/`unless` (including expression-modifier
+ * variants), and ternary-if (`?:`) expressions.
+ */
+class ConditionalExpr extends ControlExpr, TConditionalExpr {
+ /**
+ * Gets the condition expression. For example, the result is `foo` in the
+ * following:
+ * ```rb
+ * if foo
+ * bar = 1
+ * end
+ * ```
+ */
+ Expr getCondition() { none() }
+
+ /**
+ * Gets the branch of this conditional expression that is taken when the
+ * condition evaluates to `cond`, if any.
+ */
+ Stmt getBranch(boolean cond) { none() }
+
+ override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getCondition" and result = this.getCondition()
+ or
+ pred = "getBranch" and result = this.getBranch(_)
+ }
+}
+
+/**
+ * An `if` or `elsif` expression.
+ * ```rb
+ * if x
+ * a += 1
+ * elsif y
+ * a += 2
+ * end
+ * ```
+ */
+class IfExpr extends ConditionalExpr, TIfExpr {
+ final override string getAPrimaryQlClass() { result = "IfExpr" }
+
+ /** Holds if this is an `elsif` expression. */
+ predicate isElsif() { none() }
+
+ /** Gets the 'then' branch of this `if`/`elsif` expression. */
+ Stmt getThen() { none() }
+
+ /**
+ * Gets the `elsif`/`else` branch of this `if`/`elsif` expression, if any. In
+ * the following example, the result is a `StmtSequence` containing `b`.
+ * ```rb
+ * if foo
+ * a
+ * else
+ * b
+ * end
+ * ```
+ * But there is no result for the following:
+ * ```rb
+ * if foo
+ * a
+ * end
+ * ```
+ * There can be at most one result, since `elsif` branches nest. In the
+ * following example, `ifExpr.getElse()` returns an `ElsifExpr`, and the
+ * `else` branch is nested inside that. To get the `StmtSequence` for the
+ * `else` branch, i.e. the one containing `c`, use
+ * `getElse().(ElsifExpr).getElse()`.
+ * ```rb
+ * if foo
+ * a
+ * elsif bar
+ * b
+ * else
+ * c
+ * end
+ * ```
+ */
+ Stmt getElse() { none() }
+
+ final override Stmt getBranch(boolean cond) {
+ cond = true and result = this.getThen()
+ or
+ cond = false and result = this.getElse()
+ }
+
+ override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getThen" and result = this.getThen()
+ or
+ pred = "getElse" and result = this.getElse()
+ }
+}
+
+private class If extends IfExpr, TIf {
+ private Ruby::If g;
+
+ If() { this = TIf(g) }
+
+ final override Expr getCondition() { toGenerated(result) = g.getCondition() }
+
+ final override Stmt getThen() { toGenerated(result) = g.getConsequence() }
+
+ final override Stmt getElse() { toGenerated(result) = g.getAlternative() }
+
+ final override string toString() { result = "if ..." }
+}
+
+private class Elsif extends IfExpr, TElsif {
+ private Ruby::Elsif g;
+
+ Elsif() { this = TElsif(g) }
+
+ final override predicate isElsif() { any() }
+
+ final override Expr getCondition() { toGenerated(result) = g.getCondition() }
+
+ final override Stmt getThen() { toGenerated(result) = g.getConsequence() }
+
+ final override Stmt getElse() { toGenerated(result) = g.getAlternative() }
+
+ final override string toString() { result = "elsif ..." }
+}
+
+/**
+ * An `unless` expression.
+ * ```rb
+ * unless x == 0
+ * y /= x
+ * end
+ * ```
+ */
+class UnlessExpr extends ConditionalExpr, TUnlessExpr {
+ private Ruby::Unless g;
+
+ UnlessExpr() { this = TUnlessExpr(g) }
+
+ final override string getAPrimaryQlClass() { result = "UnlessExpr" }
+
+ final override Expr getCondition() { toGenerated(result) = g.getCondition() }
+
+ /**
+ * Gets the 'then' branch of this `unless` expression. In the following
+ * example, the result is the `StmtSequence` containing `foo`.
+ * ```rb
+ * unless a == b then
+ * foo
+ * else
+ * bar
+ * end
+ * ```
+ */
+ final Stmt getThen() { toGenerated(result) = g.getConsequence() }
+
+ /**
+ * Gets the 'else' branch of this `unless` expression. In the following
+ * example, the result is the `StmtSequence` containing `bar`.
+ * ```rb
+ * unless a == b then
+ * foo
+ * else
+ * bar
+ * end
+ * ```
+ */
+ final Stmt getElse() { toGenerated(result) = g.getAlternative() }
+
+ final override Expr getBranch(boolean cond) {
+ cond = false and result = this.getThen()
+ or
+ cond = true and result = this.getElse()
+ }
+
+ final override string toString() { result = "unless ..." }
+
+ override AstNode getAChild(string pred) {
+ result = ConditionalExpr.super.getAChild(pred)
+ or
+ pred = "getThen" and result = this.getThen()
+ or
+ pred = "getElse" and result = this.getElse()
+ }
+}
+
+/**
+ * An expression modified using `if`.
+ * ```rb
+ * foo if bar
+ * ```
+ */
+class IfModifierExpr extends ConditionalExpr, TIfModifierExpr {
+ private Ruby::IfModifier g;
+
+ IfModifierExpr() { this = TIfModifierExpr(g) }
+
+ final override string getAPrimaryQlClass() { result = "IfModifierExpr" }
+
+ final override Expr getCondition() { toGenerated(result) = g.getCondition() }
+
+ final override Stmt getBranch(boolean cond) { cond = true and result = this.getBody() }
+
+ /**
+ * Gets the statement that is conditionally evaluated. In the following
+ * example, the result is the `Expr` for `foo`.
+ * ```rb
+ * foo if bar
+ * ```
+ */
+ final Stmt getBody() { toGenerated(result) = g.getBody() }
+
+ final override string toString() { result = "... if ..." }
+
+ override AstNode getAChild(string pred) {
+ result = ConditionalExpr.super.getAChild(pred)
+ or
+ pred = "getBody" and result = this.getBody()
+ }
+}
+
+/**
+ * An expression modified using `unless`.
+ * ```rb
+ * y /= x unless x == 0
+ * ```
+ */
+class UnlessModifierExpr extends ConditionalExpr, TUnlessModifierExpr {
+ private Ruby::UnlessModifier g;
+
+ UnlessModifierExpr() { this = TUnlessModifierExpr(g) }
+
+ final override string getAPrimaryQlClass() { result = "UnlessModifierExpr" }
+
+ final override Expr getCondition() { toGenerated(result) = g.getCondition() }
+
+ final override Stmt getBranch(boolean cond) { cond = false and result = this.getBody() }
+
+ /**
+ * Gets the statement that is conditionally evaluated. In the following
+ * example, the result is the `Expr` for `foo`.
+ * ```rb
+ * foo unless bar
+ * ```
+ */
+ final Stmt getBody() { toGenerated(result) = g.getBody() }
+
+ final override string toString() { result = "... unless ..." }
+
+ override AstNode getAChild(string pred) {
+ result = ConditionalExpr.super.getAChild(pred)
+ or
+ pred = "getBody" and result = this.getBody()
+ }
+}
+
+/**
+ * A conditional expression using the ternary (`?:`) operator.
+ * ```rb
+ * (a > b) ? a : b
+ * ```
+ */
+class TernaryIfExpr extends ConditionalExpr, TTernaryIfExpr {
+ private Ruby::Conditional g;
+
+ TernaryIfExpr() { this = TTernaryIfExpr(g) }
+
+ final override string getAPrimaryQlClass() { result = "TernaryIfExpr" }
+
+ final override Expr getCondition() { toGenerated(result) = g.getCondition() }
+
+ /** Gets the 'then' branch of this ternary if expression. */
+ final Stmt getThen() { toGenerated(result) = g.getConsequence() }
+
+ /** Gets the 'else' branch of this ternary if expression. */
+ final Stmt getElse() { toGenerated(result) = g.getAlternative() }
+
+ final override Stmt getBranch(boolean cond) {
+ cond = true and result = this.getThen()
+ or
+ cond = false and result = this.getElse()
+ }
+
+ final override string toString() { result = "... ? ... : ..." }
+
+ override AstNode getAChild(string pred) {
+ result = ConditionalExpr.super.getAChild(pred)
+ or
+ pred = "getThen" and result = this.getThen()
+ or
+ pred = "getElse" and result = this.getElse()
+ }
+}
+
+class CaseExpr extends ControlExpr, TCaseExpr {
+ private Ruby::Case g;
+
+ CaseExpr() { this = TCaseExpr(g) }
+
+ final override string getAPrimaryQlClass() { result = "CaseExpr" }
+
+ /**
+ * Gets the expression being compared, if any. For example, `foo` in the following example.
+ * ```rb
+ * case foo
+ * when 0
+ * puts 'zero'
+ * when 1
+ * puts 'one'
+ * end
+ * ```
+ * There is no result for the following example:
+ * ```rb
+ * case
+ * when a then 0
+ * when b then 1
+ * else 2
+ * end
+ * ```
+ */
+ final Expr getValue() { toGenerated(result) = g.getValue() }
+
+ /**
+ * Gets the `n`th branch of this case expression, either a `WhenExpr` or a
+ * `StmtSequence`.
+ */
+ final Expr getBranch(int n) { toGenerated(result) = g.getChild(n) }
+
+ /**
+ * Gets a branch of this case expression, either a `WhenExpr` or an
+ * `ElseExpr`.
+ */
+ final Expr getABranch() { result = this.getBranch(_) }
+
+ /** Gets a `when` branch of this case expression. */
+ final WhenExpr getAWhenBranch() { result = this.getABranch() }
+
+ /** Gets the `else` branch of this case expression, if any. */
+ final StmtSequence getElseBranch() { result = this.getABranch() }
+
+ /**
+ * Gets the number of branches of this case expression.
+ */
+ final int getNumberOfBranches() { result = count(this.getBranch(_)) }
+
+ final override string toString() { result = "case ..." }
+
+ override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getValue" and result = this.getValue()
+ or
+ pred = "getBranch" and result = this.getBranch(_)
+ }
+}
+
+/**
+ * A `when` branch of a `case` expression.
+ * ```rb
+ * case
+ * when a > b then x
+ * end
+ * ```
+ */
+class WhenExpr extends Expr, TWhenExpr {
+ private Ruby::When g;
+
+ WhenExpr() { this = TWhenExpr(g) }
+
+ final override string getAPrimaryQlClass() { result = "WhenExpr" }
+
+ /** Gets the body of this case-when expression. */
+ final Stmt getBody() { toGenerated(result) = g.getBody() }
+
+ /**
+ * Gets the `n`th pattern (or condition) in this case-when expression. In the
+ * following example, the 0th pattern is `x`, the 1st pattern is `y`, and the
+ * 2nd pattern is `z`.
+ * ```rb
+ * case foo
+ * when x, y, z
+ * puts 'x/y/z'
+ * end
+ * ```
+ */
+ final Expr getPattern(int n) { toGenerated(result) = g.getPattern(n).getChild() }
+
+ /**
+ * Gets a pattern (or condition) in this case-when expression.
+ */
+ final Expr getAPattern() { result = this.getPattern(_) }
+
+ /**
+ * Gets the number of patterns in this case-when expression.
+ */
+ final int getNumberOfPatterns() { result = count(this.getPattern(_)) }
+
+ final override string toString() { result = "when ..." }
+
+ override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getBody" and result = this.getBody()
+ or
+ pred = "getPattern" and result = this.getPattern(_)
+ }
+}
+
+/**
+ * A loop. That is, a `for` loop, a `while` or `until` loop, or their
+ * expression-modifier variants.
+ */
+class Loop extends ControlExpr, TLoop {
+ /** Gets the body of this loop. */
+ Stmt getBody() { none() }
+
+ override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getBody" and result = this.getBody()
+ }
+}
+
+/**
+ * A loop using a condition expression. That is, a `while` or `until` loop, or
+ * their expression-modifier variants.
+ */
+class ConditionalLoop extends Loop, TConditionalLoop {
+ /** Gets the condition expression of this loop. */
+ Expr getCondition() { none() }
+
+ override AstNode getAChild(string pred) {
+ result = Loop.super.getAChild(pred)
+ or
+ pred = "getCondition" and result = this.getCondition()
+ }
+
+ /** Holds if the loop body is entered when the condition is `condValue`. */
+ predicate entersLoopWhenConditionIs(boolean condValue) { none() }
+}
+
+/**
+ * A `while` loop.
+ * ```rb
+ * while a < b
+ * p a
+ * a += 2
+ * end
+ * ```
+ */
+class WhileExpr extends ConditionalLoop, TWhileExpr {
+ private Ruby::While g;
+
+ WhileExpr() { this = TWhileExpr(g) }
+
+ final override string getAPrimaryQlClass() { result = "WhileExpr" }
+
+ /** Gets the body of this `while` loop. */
+ final override Stmt getBody() { toGenerated(result) = g.getBody() }
+
+ final override Expr getCondition() { toGenerated(result) = g.getCondition() }
+
+ /**
+ * Holds if the loop body is entered when the condition is `condValue`. For
+ * `while` loops, this holds when `condValue` is true.
+ */
+ final override predicate entersLoopWhenConditionIs(boolean condValue) { condValue = true }
+
+ final override string toString() { result = "while ..." }
+}
+
+/**
+ * An `until` loop.
+ * ```rb
+ * until a >= b
+ * p a
+ * a += 1
+ * end
+ * ```
+ */
+class UntilExpr extends ConditionalLoop, TUntilExpr {
+ private Ruby::Until g;
+
+ UntilExpr() { this = TUntilExpr(g) }
+
+ final override string getAPrimaryQlClass() { result = "UntilExpr" }
+
+ /** Gets the body of this `until` loop. */
+ final override Stmt getBody() { toGenerated(result) = g.getBody() }
+
+ final override Expr getCondition() { toGenerated(result) = g.getCondition() }
+
+ /**
+ * Holds if the loop body is entered when the condition is `condValue`. For
+ * `until` loops, this holds when `condValue` is false.
+ */
+ final override predicate entersLoopWhenConditionIs(boolean condValue) { condValue = false }
+
+ final override string toString() { result = "until ..." }
+}
+
+/**
+ * An expression looped using the `while` modifier.
+ * ```rb
+ * foo while bar
+ * ```
+ */
+class WhileModifierExpr extends ConditionalLoop, TWhileModifierExpr {
+ private Ruby::WhileModifier g;
+
+ WhileModifierExpr() { this = TWhileModifierExpr(g) }
+
+ final override Stmt getBody() { toGenerated(result) = g.getBody() }
+
+ final override Expr getCondition() { toGenerated(result) = g.getCondition() }
+
+ /**
+ * Holds if the loop body is entered when the condition is `condValue`. For
+ * `while`-modifier loops, this holds when `condValue` is true.
+ */
+ final override predicate entersLoopWhenConditionIs(boolean condValue) { condValue = true }
+
+ final override string getAPrimaryQlClass() { result = "WhileModifierExpr" }
+
+ final override string toString() { result = "... while ..." }
+}
+
+/**
+ * An expression looped using the `until` modifier.
+ * ```rb
+ * foo until bar
+ * ```
+ */
+class UntilModifierExpr extends ConditionalLoop, TUntilModifierExpr {
+ private Ruby::UntilModifier g;
+
+ UntilModifierExpr() { this = TUntilModifierExpr(g) }
+
+ final override Stmt getBody() { toGenerated(result) = g.getBody() }
+
+ final override Expr getCondition() { toGenerated(result) = g.getCondition() }
+
+ /**
+ * Holds if the loop body is entered when the condition is `condValue`. For
+ * `until`-modifier loops, this holds when `condValue` is false.
+ */
+ final override predicate entersLoopWhenConditionIs(boolean condValue) { condValue = false }
+
+ final override string getAPrimaryQlClass() { result = "UntilModifierExpr" }
+
+ final override string toString() { result = "... until ..." }
+}
+
+/**
+ * A `for` loop.
+ * ```rb
+ * for val in 1..n
+ * sum += val
+ * end
+ * ```
+ */
+class ForExpr extends Loop, TForExpr {
+ private Ruby::For g;
+
+ ForExpr() { this = TForExpr(g) }
+
+ final override string getAPrimaryQlClass() { result = "ForExpr" }
+
+ /** Gets the body of this `for` loop. */
+ final override Stmt getBody() { toGenerated(result) = g.getBody() }
+
+ /** Gets the pattern representing the iteration argument. */
+ final Pattern getPattern() { toGenerated(result) = g.getPattern() }
+
+ /**
+ * Gets the value being iterated over. In the following example, the result
+ * is the expression `1..10`:
+ * ```rb
+ * for n in 1..10 do
+ * puts n
+ * end
+ * ```
+ */
+ final Expr getValue() { toGenerated(result) = g.getValue().getChild() }
+
+ final override string toString() { result = "for ... in ..." }
+
+ override AstNode getAChild(string pred) {
+ result = Loop.super.getAChild(pred)
+ or
+ pred = "getPattern" and result = this.getPattern()
+ or
+ pred = "getValue" and result = this.getValue()
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Erb.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Erb.qll
new file mode 100644
index 00000000000..52b14b70aa6
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Erb.qll
@@ -0,0 +1,313 @@
+private import codeql.Locations
+private import codeql.ruby.AST
+private import internal.Erb
+private import internal.TreeSitter
+
+/**
+ * A node in the ERB abstract syntax tree. This class is the base class for all
+ * ERB elements.
+ */
+class ErbAstNode extends TAstNode {
+ /** Gets a textual representation of this node. */
+ cached
+ string toString() { none() }
+
+ /** Gets the location of this node. */
+ Location getLocation() { result = getLocation(this) }
+
+ /**
+ * Gets the name of a primary CodeQL class to which this node belongs.
+ *
+ * This predicate always has a result. If no primary class can be
+ * determined, the result is `"???"`. If multiple primary classes match,
+ * this predicate can have multiple results.
+ */
+ string getAPrimaryQlClass() { result = "???" }
+}
+
+/**
+ * An ERB template. This can contain multiple directives to be executed when
+ * the template is compiled.
+ */
+class ErbTemplate extends TTemplate, ErbAstNode {
+ private Erb::Template g;
+
+ ErbTemplate() { this = TTemplate(g) }
+
+ override string toString() { result = "erb template" }
+
+ final override string getAPrimaryQlClass() { result = "ErbTemplate" }
+
+ ErbAstNode getAChildNode() { toGenerated(result) = g.getChild(_) }
+}
+
+// Truncate the token string value to 32 char max
+bindingset[val]
+private string displayToken(string val) {
+ val.length() <= 32 and result = val
+ or
+ val.length() > 32 and result = val.prefix(29) + "..."
+}
+
+/**
+ * An ERB token. This could be embedded code, a comment, or arbitrary text.
+ */
+class ErbToken extends TTokenNode, ErbAstNode {
+ override string toString() { result = displayToken(this.getValue()) }
+
+ /** Gets the string value of this token. */
+ string getValue() { exists(Erb::Token g | this = fromGenerated(g) | result = g.getValue()) }
+
+ override string getAPrimaryQlClass() { result = "ErbToken" }
+}
+
+/**
+ * An ERB token appearing within a comment directive.
+ */
+class ErbComment extends ErbToken {
+ private Erb::Comment g;
+
+ ErbComment() { this = TComment(g) }
+
+ override string getValue() { result = g.getValue() }
+
+ final override string getAPrimaryQlClass() { result = "ErbComment" }
+}
+
+/**
+ * An ERB token appearing within a code directive. This will typically be
+ * interpreted as Ruby code or a GraphQL query, depending on context.
+ */
+class ErbCode extends ErbToken {
+ private Erb::Code g;
+
+ ErbCode() { this = TCode(g) }
+
+ override string getValue() { result = g.getValue() }
+
+ final override string getAPrimaryQlClass() { result = "ErbCode" }
+}
+
+bindingset[line, col]
+private predicate locationIncludesPosition(Location loc, int line, int col) {
+ // position between start and end line, exclusive
+ line > loc.getStartLine() and
+ line < loc.getEndLine()
+ or
+ // position on start line, multi line location
+ line = loc.getStartLine() and
+ not loc.getStartLine() = loc.getEndLine() and
+ col >= loc.getStartColumn()
+ or
+ // position on end line, multi line location
+ line = loc.getEndLine() and
+ not loc.getStartLine() = loc.getEndLine() and
+ col <= loc.getEndColumn()
+ or
+ // single line location, position between start and end column
+ line = loc.getStartLine() and
+ loc.getStartLine() = loc.getEndLine() and
+ col >= loc.getStartColumn() and
+ col <= loc.getEndColumn()
+}
+
+/** A file containing an ERB directive. */
+private class ErbDirectiveFile extends File {
+ pragma[nomagic]
+ ErbDirectiveFile() { this = any(ErbDirective dir).getLocation().getFile() }
+
+ /** Gets a statement in this file. */
+ pragma[nomagic]
+ Stmt getAStmt(int startLine, int startColumn) {
+ exists(Location loc |
+ result.getLocation() = loc and
+ loc.getFile() = this and
+ loc.getStartLine() = startLine and
+ loc.getStartColumn() = startColumn
+ )
+ }
+}
+
+/**
+ * A directive in an ERB template.
+ */
+class ErbDirective extends TDirectiveNode, ErbAstNode {
+ /** Holds if this directive spans line `line` in the file `file`. */
+ pragma[nomagic]
+ private predicate spans(ErbDirectiveFile file, int line) {
+ exists(Location loc |
+ loc = this.getLocation() and
+ file = loc.getFile() and
+ line in [loc.getStartLine() .. loc.getEndLine()]
+ )
+ }
+
+ private predicate containsStmtStart(Stmt s) {
+ // `Toplevel` statements are not contained within individual directives,
+ // though their start location may appear within a directive location
+ not s instanceof Toplevel and
+ exists(ErbDirectiveFile file, int startLine, int startColumn |
+ this.spans(file, startLine) and
+ s = file.getAStmt(startLine, startColumn) and
+ locationIncludesPosition(this.getLocation(), startLine, startColumn)
+ )
+ }
+
+ /**
+ * Gets a statement that starts in directive that is not a child of any other
+ * statement starting in this directive.
+ */
+ Stmt getAChildStmt() {
+ this.containsStmtStart(result) and
+ not this.containsStmtStart(result.getParent())
+ }
+
+ /**
+ * Gets the last child statement in this directive.
+ * See `getAChildStmt` for more details.
+ */
+ Stmt getTerminalStmt() {
+ result = this.getAChildStmt() and
+ forall(Stmt s | s = this.getAChildStmt() and not s = result |
+ s.getLocation().strictlyBefore(result.getLocation())
+ )
+ }
+
+ /** Gets the child token of this directive. */
+ ErbToken getToken() {
+ exists(Erb::Directive g | this = fromGenerated(g) | toGenerated(result) = g.getChild())
+ }
+
+ override string toString() { result = "erb directive" }
+
+ override string getAPrimaryQlClass() { result = "ErbDirective" }
+}
+
+/**
+ * A comment directive in an ERB template.
+ * ```erb
+ * <%#= 2 + 2 %>
+ * <%# for x in xs do %>
+ * ```
+ */
+class ErbCommentDirective extends ErbDirective {
+ private Erb::CommentDirective g;
+
+ ErbCommentDirective() { this = TCommentDirective(g) }
+
+ override ErbComment getToken() { toGenerated(result) = g.getChild() }
+
+ final override string toString() { result = "<%#" + this.getToken().toString() + "%>" }
+
+ final override string getAPrimaryQlClass() { result = "ErbCommentDirective" }
+}
+
+/**
+ * A GraphQL directive in an ERB template.
+ * ```erb
+ * <%graphql
+ * fragment Foo on Bar {
+ * some {
+ * queryText
+ * moreProperties
+ * }
+ * }
+ * %>
+ * ```
+ */
+class ErbGraphqlDirective extends ErbDirective {
+ private Erb::GraphqlDirective g;
+
+ ErbGraphqlDirective() { this = TGraphqlDirective(g) }
+
+ override ErbCode getToken() { toGenerated(result) = g.getChild() }
+
+ final override string toString() { result = "<%graphql" + this.getToken().toString() + "%>" }
+
+ final override string getAPrimaryQlClass() { result = "ErbGraphqlDirective" }
+}
+
+/**
+ * An output directive in an ERB template.
+ * ```erb
+ * <%=
+ * fragment Foo on Bar {
+ * some {
+ * queryText
+ * moreProperties
+ * }
+ * }
+ * %>
+ * ```
+ */
+class ErbOutputDirective extends ErbDirective {
+ private Erb::OutputDirective g;
+
+ ErbOutputDirective() { this = TOutputDirective(g) }
+
+ override ErbCode getToken() { toGenerated(result) = g.getChild() }
+
+ final override string toString() { result = "<%=" + this.getToken().toString() + "%>" }
+
+ final override string getAPrimaryQlClass() { result = "ErbOutputDirective" }
+}
+
+/**
+ * An execution directive in an ERB template.
+ * This code will be executed as Ruby, but not rendered.
+ * ```erb
+ * <% books = author.books
+ * for book in books do %>
+ * ```
+ */
+class ErbExecutionDirective extends ErbDirective {
+ private Erb::Directive g;
+
+ ErbExecutionDirective() { this = TDirective(g) }
+
+ final override string toString() { result = "<%" + this.getToken().toString() + "%>" }
+
+ final override string getAPrimaryQlClass() { result = "ErbExecutionDirective" }
+}
+
+/**
+ * A `File` containing an Embedded Ruby template.
+ * This is typically a file containing snippets of Ruby code that can be
+ * evaluated to create a compiled version of the file.
+ */
+class ErbFile extends File {
+ private ErbTemplate template;
+
+ ErbFile() { this = template.getLocation().getFile() }
+
+ /**
+ * Holds if the file represents a partial to be rendered in the context of
+ * another template.
+ */
+ predicate isPartial() { this.getStem().charAt(0) = "_" }
+
+ /**
+ * Gets the base template name associated with this ERB file.
+ * For instance, a file named `foo.html.erb` has a template name of `foo`.
+ * A partial template file named `_item.html.erb` has a template name of `item`.
+ */
+ string getTemplateName() { none() }
+
+ /**
+ * Gets the erb template contained within this file.
+ */
+ ErbTemplate getTemplate() { result = template }
+}
+
+private class PartialErbFile extends ErbFile {
+ PartialErbFile() { this.isPartial() }
+
+ // Drop the leading underscore
+ override string getTemplateName() { result = this.getStem().splitAt(".", 0).suffix(1) }
+}
+
+private class FullErbFile extends ErbFile {
+ FullErbFile() { not this.isPartial() }
+
+ override string getTemplateName() { result = this.getStem().splitAt(".", 0) }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Expr.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Expr.qll
new file mode 100644
index 00000000000..35bdb1d8911
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Expr.qll
@@ -0,0 +1,456 @@
+private import codeql.ruby.AST
+private import codeql.ruby.CFG
+private import internal.AST
+private import internal.TreeSitter
+
+/**
+ * An expression.
+ *
+ * This is the root QL class for all expressions.
+ */
+class Expr extends Stmt, TExpr {
+ /** Gets the textual (constant) value of this expression, if any. */
+ string getValueText() {
+ forex(CfgNodes::ExprCfgNode n | n = this.getAControlFlowNode() | result = n.getValueText())
+ }
+}
+
+/**
+ * A reference to the current object. For example:
+ * - `self == other`
+ * - `self.method_name`
+ * - `def self.method_name ... end`
+ *
+ * This also includes implicit references to the current object in method
+ * calls. For example, the method call `foo(123)` has an implicit `self`
+ * receiver, and is equivalent to the explicit `self.foo(123)`.
+ */
+class Self extends Expr, TSelf {
+ final override string getAPrimaryQlClass() { result = "Self" }
+
+ final override string toString() { result = "self" }
+}
+
+/**
+ * A sequence of expressions in the right-hand side of an assignment or
+ * a `return`, `break` or `next` statement.
+ * ```rb
+ * x = 1, *items, 3, *more
+ * return 1, 2
+ * next *list
+ * break **map
+ * return 1, 2, *items, k: 5, **map
+ * ```
+ */
+class ArgumentList extends Expr, TArgumentList {
+ private Ruby::AstNode g;
+
+ ArgumentList() { this = TArgumentList(g) }
+
+ /** Gets the `i`th element in this argument list. */
+ Expr getElement(int i) {
+ toGenerated(result) in [
+ g.(Ruby::ArgumentList).getChild(i), g.(Ruby::RightAssignmentList).getChild(i)
+ ]
+ }
+
+ final override string getAPrimaryQlClass() { result = "ArgumentList" }
+
+ final override string toString() { result = "..., ..." }
+
+ final override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getElement" and result = this.getElement(_)
+ }
+}
+
+/** A sequence of expressions. */
+class StmtSequence extends Expr, TStmtSequence {
+ override string getAPrimaryQlClass() { result = "StmtSequence" }
+
+ /** Gets the `n`th statement in this sequence. */
+ Stmt getStmt(int n) { none() }
+
+ /** Gets a statement in this sequence. */
+ final Stmt getAStmt() { result = this.getStmt(_) }
+
+ /** Gets the last statement in this sequence, if any. */
+ final Stmt getLastStmt() { result = this.getStmt(this.getNumberOfStatements() - 1) }
+
+ /** Gets the number of statements in this sequence. */
+ final int getNumberOfStatements() { result = count(this.getAStmt()) }
+
+ /** Holds if this sequence has no statements. */
+ final predicate isEmpty() { this.getNumberOfStatements() = 0 }
+
+ override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getStmt" and result = this.getStmt(_)
+ }
+}
+
+private class StmtSequenceSynth extends StmtSequence, TStmtSequenceSynth {
+ final override Stmt getStmt(int n) { synthChild(this, n, result) }
+
+ final override string toString() { result = "..." }
+}
+
+private class Then extends StmtSequence, TThen {
+ private Ruby::Then g;
+
+ Then() { this = TThen(g) }
+
+ override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
+
+ final override string toString() { result = "then ..." }
+}
+
+private class Else extends StmtSequence, TElse {
+ private Ruby::Else g;
+
+ Else() { this = TElse(g) }
+
+ override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
+
+ final override string toString() { result = "else ..." }
+}
+
+private class Do extends StmtSequence, TDo {
+ private Ruby::Do g;
+
+ Do() { this = TDo(g) }
+
+ override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
+
+ final override string toString() { result = "do ..." }
+}
+
+private class Ensure extends StmtSequence, TEnsure {
+ private Ruby::Ensure g;
+
+ Ensure() { this = TEnsure(g) }
+
+ override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
+
+ final override string toString() { result = "ensure ..." }
+}
+
+/**
+ * A sequence of statements representing the body of a method, class, module,
+ * or do-block. That is, any body that may also include rescue/ensure/else
+ * statements.
+ */
+class BodyStmt extends StmtSequence, TBodyStmt {
+ // Not defined by dispatch, as it should not be exposed
+ private Ruby::AstNode getChild(int i) {
+ result = any(Ruby::Method g | this = TMethod(g)).getChild(i)
+ or
+ result = any(Ruby::SingletonMethod g | this = TSingletonMethod(g)).getChild(i)
+ or
+ exists(Ruby::Lambda g | this = TLambda(g) |
+ result = g.getBody().(Ruby::DoBlock).getChild(i) or
+ result = g.getBody().(Ruby::Block).getChild(i)
+ )
+ or
+ result = any(Ruby::DoBlock g | this = TDoBlock(g)).getChild(i)
+ or
+ result = any(Ruby::Program g | this = TToplevel(g)).getChild(i) and
+ not result instanceof Ruby::BeginBlock
+ or
+ result = any(Ruby::Class g | this = TClassDeclaration(g)).getChild(i)
+ or
+ result = any(Ruby::SingletonClass g | this = TSingletonClass(g)).getChild(i)
+ or
+ result = any(Ruby::Module g | this = TModuleDeclaration(g)).getChild(i)
+ or
+ result = any(Ruby::Begin g | this = TBeginExpr(g)).getChild(i)
+ }
+
+ final override Stmt getStmt(int n) {
+ result =
+ rank[n + 1](AstNode node, int i |
+ toGenerated(node) = this.getChild(i) and
+ not node instanceof Else and
+ not node instanceof RescueClause and
+ not node instanceof Ensure
+ |
+ node order by i
+ )
+ }
+
+ /** Gets the `n`th rescue clause in this block. */
+ final RescueClause getRescue(int n) {
+ result =
+ rank[n + 1](RescueClause node, int i | toGenerated(node) = this.getChild(i) | node order by i)
+ }
+
+ /** Gets a rescue clause in this block. */
+ final RescueClause getARescue() { result = this.getRescue(_) }
+
+ /** Gets the `else` clause in this block, if any. */
+ final StmtSequence getElse() { result = unique(Else s | toGenerated(s) = getChild(_)) }
+
+ /** Gets the `ensure` clause in this block, if any. */
+ final StmtSequence getEnsure() { result = unique(Ensure s | toGenerated(s) = getChild(_)) }
+
+ final predicate hasEnsure() { exists(this.getEnsure()) }
+
+ override AstNode getAChild(string pred) {
+ result = StmtSequence.super.getAChild(pred)
+ or
+ pred = "getRescue" and result = this.getRescue(_)
+ or
+ pred = "getElse" and result = this.getElse()
+ or
+ pred = "getEnsure" and result = this.getEnsure()
+ }
+}
+
+/**
+ * A parenthesized expression sequence, typically containing a single expression:
+ * ```rb
+ * (x + 1)
+ * ```
+ * However, they can also contain multiple expressions (the value of the parenthesized
+ * expression is the last expression):
+ * ```rb
+ * (foo; bar)
+ * ```
+ * or even an empty sequence (value is `nil`):
+ * ```rb
+ * ()
+ * ```
+ */
+class ParenthesizedExpr extends StmtSequence, TParenthesizedExpr {
+ private Ruby::ParenthesizedStatements g;
+
+ ParenthesizedExpr() { this = TParenthesizedExpr(g) }
+
+ final override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
+
+ final override string getAPrimaryQlClass() { result = "ParenthesizedExpr" }
+
+ final override string toString() { result = "( ... )" }
+}
+
+/**
+ * A pair expression. For example, in a hash:
+ * ```rb
+ * { foo: bar }
+ * ```
+ * Or a keyword argument:
+ * ```rb
+ * baz(qux: 1)
+ * ```
+ */
+class Pair extends Expr, TPair {
+ private Ruby::Pair g;
+
+ Pair() { this = TPair(g) }
+
+ final override string getAPrimaryQlClass() { result = "Pair" }
+
+ /**
+ * Gets the key expression of this pair. For example, the `SymbolLiteral`
+ * representing the keyword `foo` in the following example:
+ * ```rb
+ * bar(foo: 123)
+ * ```
+ * Or the `StringLiteral` for `'foo'` in the following hash pair:
+ * ```rb
+ * { 'foo' => 123 }
+ * ```
+ */
+ final Expr getKey() { toGenerated(result) = g.getKey() }
+
+ /**
+ * Gets the value expression of this pair. For example, the `InteralLiteral`
+ * 123 in the following hash pair:
+ * ```rb
+ * { 'foo' => 123 }
+ * ```
+ */
+ final Expr getValue() { toGenerated(result) = g.getValue() }
+
+ final override string toString() { result = "Pair" }
+
+ override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getKey" and result = this.getKey()
+ or
+ pred = "getValue" and result = this.getValue()
+ }
+}
+
+/**
+ * A rescue clause. For example:
+ * ```rb
+ * begin
+ * write_file
+ * rescue StandardError => msg
+ * puts msg
+ * end
+ */
+class RescueClause extends Expr, TRescueClause {
+ private Ruby::Rescue g;
+
+ RescueClause() { this = TRescueClause(g) }
+
+ final override string getAPrimaryQlClass() { result = "RescueClause" }
+
+ /**
+ * Gets the `n`th exception to match, if any. For example `FirstError` or `SecondError` in:
+ * ```rb
+ * begin
+ * do_something
+ * rescue FirstError, SecondError => e
+ * handle_error(e)
+ * end
+ * ```
+ */
+ final Expr getException(int n) { toGenerated(result) = g.getExceptions().getChild(n) }
+
+ /**
+ * Gets an exception to match, if any. For example `FirstError` or `SecondError` in:
+ * ```rb
+ * begin
+ * do_something
+ * rescue FirstError, SecondError => e
+ * handle_error(e)
+ * end
+ * ```
+ */
+ final Expr getAnException() { result = this.getException(_) }
+
+ /**
+ * Gets the variable to which to assign the matched exception, if any.
+ * For example `err` in:
+ * ```rb
+ * begin
+ * do_something
+ * rescue StandardError => err
+ * handle_error(err)
+ * end
+ * ```
+ */
+ final LhsExpr getVariableExpr() { toGenerated(result) = g.getVariable().getChild() }
+
+ /**
+ * Gets the exception handler body.
+ */
+ final StmtSequence getBody() { toGenerated(result) = g.getBody() }
+
+ final override string toString() { result = "rescue ..." }
+
+ override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getException" and result = this.getException(_)
+ or
+ pred = "getVariableExpr" and result = this.getVariableExpr()
+ or
+ pred = "getBody" and result = this.getBody()
+ }
+}
+
+/**
+ * An expression with a `rescue` modifier. For example:
+ * ```rb
+ * contents = read_file rescue ""
+ * ```
+ */
+class RescueModifierExpr extends Expr, TRescueModifierExpr {
+ private Ruby::RescueModifier g;
+
+ RescueModifierExpr() { this = TRescueModifierExpr(g) }
+
+ final override string getAPrimaryQlClass() { result = "RescueModifierExpr" }
+
+ /**
+ * Gets the body of this `RescueModifierExpr`.
+ * ```rb
+ * body rescue handler
+ * ```
+ */
+ final Stmt getBody() { toGenerated(result) = g.getBody() }
+
+ /**
+ * Gets the exception handler of this `RescueModifierExpr`.
+ * ```rb
+ * body rescue handler
+ * ```
+ */
+ final Stmt getHandler() { toGenerated(result) = g.getHandler() }
+
+ final override string toString() { result = "... rescue ..." }
+
+ override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getBody" and result = this.getBody()
+ or
+ pred = "getHandler" and result = this.getHandler()
+ }
+}
+
+/**
+ * A concatenation of string literals.
+ *
+ * ```rb
+ * "foo" "bar" "baz"
+ * ```
+ */
+class StringConcatenation extends Expr, TStringConcatenation {
+ private Ruby::ChainedString g;
+
+ StringConcatenation() { this = TStringConcatenation(g) }
+
+ final override string getAPrimaryQlClass() { result = "StringConcatenation" }
+
+ /** Gets the `n`th string literal in this concatenation. */
+ final StringLiteral getString(int n) { toGenerated(result) = g.getChild(n) }
+
+ /** Gets a string literal in this concatenation. */
+ final StringLiteral getAString() { result = this.getString(_) }
+
+ /** Gets the number of string literals in this concatenation. */
+ final int getNumberOfStrings() { result = count(this.getString(_)) }
+
+ /**
+ * Gets the result of concatenating all the string literals, if and only if
+ * they do not contain any interpolations.
+ *
+ * For the following example, the result is `"foobar"`:
+ *
+ * ```rb
+ * "foo" 'bar'
+ * ```
+ *
+ * And for the following example, where one of the string literals includes
+ * an interpolation, there is no result:
+ *
+ * ```rb
+ * "foo" "bar#{ n }"
+ * ```
+ */
+ final string getConcatenatedValueText() {
+ forall(StringLiteral c | c = this.getString(_) | exists(c.getValueText())) and
+ result =
+ concat(string valueText, int i |
+ valueText = this.getString(i).getValueText()
+ |
+ valueText order by i
+ )
+ }
+
+ final override string toString() { result = "\"...\" \"...\"" }
+
+ override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getString" and result = this.getString(_)
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Literal.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Literal.qll
new file mode 100644
index 00000000000..f95da578baf
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Literal.qll
@@ -0,0 +1,892 @@
+private import codeql.ruby.AST
+private import codeql.ruby.security.performance.RegExpTreeView as RETV
+private import internal.AST
+private import internal.Scope
+private import internal.TreeSitter
+
+/**
+ * A literal.
+ *
+ * This is the QL root class for all literals.
+ */
+class Literal extends Expr, TLiteral {
+ /**
+ * Gets the source text for this literal, if this is a simple literal.
+ *
+ * For complex literals, such as arrays, hashes, and strings with
+ * interpolations, this predicate has no result.
+ */
+ override string getValueText() { none() }
+}
+
+/**
+ * A numeric literal, i.e. an integer, floating-point, rational, or complex
+ * value.
+ *
+ * ```rb
+ * 123
+ * 0xff
+ * 3.14159
+ * 1.0E2
+ * 7r
+ * 1i
+ * ```
+ */
+class NumericLiteral extends Literal, TNumericLiteral { }
+
+/**
+ * An integer literal.
+ *
+ * ```rb
+ * 123
+ * 0xff
+ * ```
+ */
+class IntegerLiteral extends NumericLiteral, TIntegerLiteral {
+ /** Gets the numerical value of this integer literal. */
+ int getValue() { none() }
+
+ final override string toString() { result = this.getValueText() }
+
+ final override string getAPrimaryQlClass() { result = "IntegerLiteral" }
+}
+
+private class IntegerLiteralReal extends IntegerLiteral, TIntegerLiteralReal {
+ private Ruby::Integer g;
+
+ IntegerLiteralReal() { this = TIntegerLiteralReal(g) }
+
+ final override string getValueText() { result = g.getValue() }
+
+ final override int getValue() {
+ exists(string s, string values, string str |
+ s = this.getValueText().toLowerCase() and
+ (
+ s.matches("0b%") and
+ values = "01" and
+ str = s.suffix(2)
+ or
+ s.matches("0x%") and
+ values = "0123456789abcdef" and
+ str = s.suffix(2)
+ or
+ s.charAt(0) = "0" and
+ not s.charAt(1) = ["b", "x", "o"] and
+ values = "01234567" and
+ str = s.suffix(1)
+ or
+ s.matches("0o%") and
+ values = "01234567" and
+ str = s.suffix(2)
+ or
+ s.charAt(0) != "0" and values = "0123456789" and str = s
+ )
+ |
+ result =
+ sum(int index, string c, int v, int exp |
+ c = str.replaceAll("_", "").charAt(index) and
+ v = values.indexOf(c.toLowerCase()) and
+ exp = str.replaceAll("_", "").length() - index - 1
+ |
+ v * values.length().pow(exp)
+ )
+ )
+ }
+}
+
+private class IntegerLiteralSynth extends IntegerLiteral, TIntegerLiteralSynth {
+ private int value;
+
+ IntegerLiteralSynth() { this = TIntegerLiteralSynth(_, _, value) }
+
+ final override string getValueText() { result = value.toString() }
+
+ final override int getValue() { result = value }
+}
+
+/**
+ * A floating-point literal.
+ *
+ * ```rb
+ * 1.3
+ * 2.7e+5
+ * ```
+ */
+class FloatLiteral extends NumericLiteral, TFloatLiteral {
+ private Ruby::Float g;
+
+ FloatLiteral() { this = TFloatLiteral(g) }
+
+ final override string getValueText() { result = g.getValue() }
+
+ final override string toString() { result = this.getValueText() }
+
+ final override string getAPrimaryQlClass() { result = "FloatLiteral" }
+}
+
+/**
+ * A rational literal.
+ *
+ * ```rb
+ * 123r
+ * ```
+ */
+class RationalLiteral extends NumericLiteral, TRationalLiteral {
+ private Ruby::Rational g;
+
+ RationalLiteral() { this = TRationalLiteral(g) }
+
+ final override string getValueText() { result = g.getChild().(Ruby::Token).getValue() + "r" }
+
+ final override string toString() { result = this.getValueText() }
+
+ final override string getAPrimaryQlClass() { result = "RationalLiteral" }
+}
+
+/**
+ * A complex literal.
+ *
+ * ```rb
+ * 1i
+ * ```
+ */
+class ComplexLiteral extends NumericLiteral, TComplexLiteral {
+ private Ruby::Complex g;
+
+ ComplexLiteral() { this = TComplexLiteral(g) }
+
+ final override string getValueText() { result = g.getValue() }
+
+ final override string toString() { result = this.getValueText() }
+
+ final override string getAPrimaryQlClass() { result = "ComplexLiteral" }
+}
+
+/** A `nil` literal. */
+class NilLiteral extends Literal, TNilLiteral {
+ private Ruby::Nil g;
+
+ NilLiteral() { this = TNilLiteral(g) }
+
+ final override string getValueText() { result = g.getValue() }
+
+ final override string toString() { result = this.getValueText() }
+
+ final override string getAPrimaryQlClass() { result = "NilLiteral" }
+}
+
+/**
+ * A Boolean literal.
+ * ```rb
+ * true
+ * false
+ * TRUE
+ * FALSE
+ * ```
+ */
+class BooleanLiteral extends Literal, TBooleanLiteral {
+ final override string getAPrimaryQlClass() { result = "BooleanLiteral" }
+
+ final override string toString() { result = this.getValueText() }
+
+ /** Holds if the Boolean literal is `true` or `TRUE`. */
+ predicate isTrue() { none() }
+
+ /** Holds if the Boolean literal is `false` or `FALSE`. */
+ predicate isFalse() { none() }
+
+ /** Gets the value of this Boolean literal. */
+ boolean getValue() {
+ this.isTrue() and result = true
+ or
+ this.isFalse() and result = false
+ }
+}
+
+private class TrueLiteral extends BooleanLiteral, TTrueLiteral {
+ private Ruby::True g;
+
+ TrueLiteral() { this = TTrueLiteral(g) }
+
+ final override string getValueText() { result = g.getValue() }
+
+ final override predicate isTrue() { any() }
+}
+
+private class FalseLiteral extends BooleanLiteral, TFalseLiteral {
+ private Ruby::False g;
+
+ FalseLiteral() { this = TFalseLiteral(g) }
+
+ final override string getValueText() { result = g.getValue() }
+
+ final override predicate isFalse() { any() }
+}
+
+/**
+ * The base class for a component of a string: `StringTextComponent`,
+ * `StringEscapeSequenceComponent`, or `StringInterpolationComponent`.
+ */
+class StringComponent extends AstNode, TStringComponent {
+ /**
+ * Gets the source text for this string component. Has no result if this is
+ * a `StringInterpolationComponent`.
+ */
+ string getValueText() { none() }
+}
+
+/**
+ * A component of a string (or string-like) literal that is simply text.
+ *
+ * For example, the following string literals all contain `StringTextComponent`
+ * components whose `getValueText()` returns `"foo"`:
+ *
+ * ```rb
+ * 'foo'
+ * "#{ bar() }foo"
+ * "foo#{ bar() } baz"
+ * ```
+ */
+class StringTextComponent extends StringComponent, TStringTextComponent {
+ private Ruby::Token g;
+
+ StringTextComponent() { this = TStringTextComponent(g) }
+
+ final override string toString() { result = g.getValue() }
+
+ final override string getValueText() { result = g.getValue() }
+
+ final override string getAPrimaryQlClass() { result = "StringTextComponent" }
+}
+
+/**
+ * An escape sequence component of a string or string-like literal.
+ */
+class StringEscapeSequenceComponent extends StringComponent, TStringEscapeSequenceComponent {
+ private Ruby::EscapeSequence g;
+
+ StringEscapeSequenceComponent() { this = TStringEscapeSequenceComponent(g) }
+
+ final override string toString() { result = g.getValue() }
+
+ final override string getValueText() { result = g.getValue() }
+
+ final override string getAPrimaryQlClass() { result = "StringEscapeSequenceComponent" }
+}
+
+/**
+ * An interpolation expression component of a string or string-like literal.
+ */
+class StringInterpolationComponent extends StringComponent, StmtSequence,
+ TStringInterpolationComponent {
+ private Ruby::Interpolation g;
+
+ StringInterpolationComponent() { this = TStringInterpolationComponent(g) }
+
+ final override string toString() { result = "#{...}" }
+
+ final override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
+
+ final override string getValueText() { none() }
+
+ final override string getAPrimaryQlClass() { result = "StringInterpolationComponent" }
+}
+
+/**
+ * A string, symbol, regexp, or subshell literal.
+ */
+class StringlikeLiteral extends Literal, TStringlikeLiteral {
+ /**
+ * Gets the `n`th component of this string or string-like literal. The result
+ * will be one of `StringTextComponent`, `StringInterpolationComponent`, and
+ * `StringEscapeSequenceComponent`.
+ *
+ * In the following example, the result for `n = 0` is the
+ * `StringTextComponent` for `foo_`, and the result for `n = 1` is the
+ * `StringInterpolationComponent` for `Time.now`.
+ *
+ * ```rb
+ * "foo_#{ Time.now }"
+ * ```
+ */
+ StringComponent getComponent(int n) { none() }
+
+ /**
+ * Gets the number of components in this string or string-like literal.
+ *
+ * For the empty string `""`, the result is 0.
+ *
+ * For the string `"foo"`, the result is 1: there is a single
+ * `StringTextComponent`.
+ *
+ * For the following example, the result is 3: there is a
+ * `StringTextComponent` for the substring `"foo_"`; a
+ * `StringEscapeSequenceComponent` for the escaped quote; and a
+ * `StringInterpolationComponent` for the interpolation.
+ *
+ * ```rb
+ * "foo\"#{bar}"
+ * ```
+ */
+ final int getNumberOfComponents() { result = count(this.getComponent(_)) }
+
+ private string getStartDelimiter() {
+ this instanceof TStringLiteral and
+ result = "\""
+ or
+ this instanceof TRegExpLiteral and
+ result = "/"
+ or
+ this instanceof TSimpleSymbolLiteral and
+ result = ":"
+ or
+ this instanceof TComplexSymbolLiteral and
+ result = ":\""
+ or
+ this instanceof THashKeySymbolLiteral and
+ result = ""
+ or
+ this instanceof TSubshellLiteral and
+ result = "`"
+ or
+ this instanceof THereDoc and
+ result = ""
+ }
+
+ private string getEndDelimiter() {
+ this instanceof TStringLiteral and
+ result = "\""
+ or
+ this instanceof TRegExpLiteral and
+ result = "/"
+ or
+ this instanceof TSimpleSymbolLiteral and
+ result = ""
+ or
+ this instanceof TComplexSymbolLiteral and
+ result = "\""
+ or
+ this instanceof THashKeySymbolLiteral and
+ result = ""
+ or
+ this instanceof TSubshellLiteral and
+ result = "`"
+ or
+ this instanceof THereDoc and
+ result = ""
+ }
+
+ override string getValueText() {
+ // 0 components should result in the empty string
+ // if there are any interpolations, there should be no result
+ // otherwise, concatenate all the components
+ forall(StringComponent c | c = this.getComponent(_) |
+ not c instanceof StringInterpolationComponent
+ ) and
+ result =
+ concat(StringComponent c, int i | c = this.getComponent(i) | c.getValueText() order by i)
+ }
+
+ override string toString() {
+ exists(string full, string summary |
+ full =
+ concat(StringComponent c, int i, string s |
+ c = this.getComponent(i) and
+ (
+ s = toGenerated(c).(Ruby::Token).getValue()
+ or
+ not toGenerated(c) instanceof Ruby::Token and
+ s = "#{...}"
+ )
+ |
+ s order by i
+ ) and
+ (
+ // summary should be 32 chars max (incl. ellipsis)
+ full.length() > 32 and summary = full.substring(0, 29) + "..."
+ or
+ full.length() <= 32 and summary = full
+ ) and
+ result = this.getStartDelimiter() + summary + this.getEndDelimiter()
+ )
+ }
+
+ final override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getComponent" and result = this.getComponent(_)
+ }
+}
+
+/**
+ * A string literal.
+ *
+ * ```rb
+ * 'hello'
+ * "hello, #{name}"
+ * ```
+ */
+class StringLiteral extends StringlikeLiteral, TStringLiteral {
+ final override string getAPrimaryQlClass() { result = "StringLiteral" }
+}
+
+private class RegularStringLiteral extends StringLiteral, TRegularStringLiteral {
+ private Ruby::String g;
+
+ RegularStringLiteral() { this = TRegularStringLiteral(g) }
+
+ final override StringComponent getComponent(int n) { toGenerated(result) = g.getChild(n) }
+}
+
+private class BareStringLiteral extends StringLiteral, TBareStringLiteral {
+ private Ruby::BareString g;
+
+ BareStringLiteral() { this = TBareStringLiteral(g) }
+
+ final override StringComponent getComponent(int n) { toGenerated(result) = g.getChild(n) }
+}
+
+/**
+ * A regular expression literal.
+ *
+ * ```rb
+ * /[a-z]+/
+ * ```
+ */
+class RegExpLiteral extends StringlikeLiteral, TRegExpLiteral {
+ private Ruby::Regex g;
+
+ RegExpLiteral() { this = TRegExpLiteral(g) }
+
+ final override string getAPrimaryQlClass() { result = "RegExpLiteral" }
+
+ final override StringComponent getComponent(int i) { toGenerated(result) = g.getChild(i) }
+
+ /**
+ * Gets the regexp flags as a string.
+ *
+ * ```rb
+ * /foo/ # => ""
+ * /foo/i # => "i"
+ * /foo/imxo # => "imxo"
+ */
+ final string getFlagString() {
+ // For `/foo/i`, there should be an `/i` token in the database with `this`
+ // as its parents. Strip the delimiter, which can vary.
+ result =
+ max(Ruby::Token t | t.getParent() = g | t.getValue().suffix(1) order by t.getParentIndex())
+ }
+
+ /**
+ * Holds if the regexp was specified using the `i` flag to indicate case
+ * insensitivity, as in the following example:
+ *
+ * ```rb
+ * /foo/i
+ * ```
+ */
+ final predicate hasCaseInsensitiveFlag() { this.getFlagString().charAt(_) = "i" }
+
+ /**
+ * Holds if the regex was specified using the `m` flag to indicate multiline
+ * mode. For example:
+ *
+ * ```rb
+ * /foo/m
+ * ```
+ */
+ final predicate hasMultilineFlag() { this.getFlagString().charAt(_) = "m" }
+
+ /**
+ * Holds if the regex was specified using the `x` flag to indicate
+ * 'free-spacing' mode (also known as 'extended' mode), meaning that
+ * whitespace and comments in the pattern are ignored. For example:
+ *
+ * ```rb
+ * %r{
+ * [a-zA-Z_] # starts with a letter or underscore
+ * \w* # and then zero or more letters/digits/underscores
+ * }/x
+ * ```
+ */
+ final predicate hasFreeSpacingFlag() { this.getFlagString().charAt(_) = "x" }
+
+ /** Returns the root node of the parse tree of this regular expression. */
+ final RETV::RegExpTerm getParsed() { result = RETV::getParsedRegExp(this) }
+}
+
+/**
+ * A symbol literal.
+ *
+ * ```rb
+ * :foo
+ * :"foo bar"
+ * :"foo bar #{baz}"
+ * ```
+ */
+class SymbolLiteral extends StringlikeLiteral, TSymbolLiteral {
+ final override string getAPrimaryQlClass() {
+ not this instanceof MethodName and result = "SymbolLiteral"
+ }
+}
+
+private class SimpleSymbolLiteral extends SymbolLiteral, TSimpleSymbolLiteral {
+ private Ruby::SimpleSymbol g;
+
+ SimpleSymbolLiteral() { this = TSimpleSymbolLiteral(g) }
+
+ // Tree-sitter gives us value text including the colon, which we skip.
+ final override string getValueText() { result = g.getValue().suffix(1) }
+
+ final override string toString() { result = g.getValue() }
+}
+
+private class ComplexSymbolLiteral extends SymbolLiteral, TComplexSymbolLiteral { }
+
+private class DelimitedSymbolLiteral extends ComplexSymbolLiteral, TDelimitedSymbolLiteral {
+ private Ruby::DelimitedSymbol g;
+
+ DelimitedSymbolLiteral() { this = TDelimitedSymbolLiteral(g) }
+
+ final override StringComponent getComponent(int i) { toGenerated(result) = g.getChild(i) }
+}
+
+private class BareSymbolLiteral extends ComplexSymbolLiteral, TBareSymbolLiteral {
+ private Ruby::BareSymbol g;
+
+ BareSymbolLiteral() { this = TBareSymbolLiteral(g) }
+
+ final override StringComponent getComponent(int i) { toGenerated(result) = g.getChild(i) }
+}
+
+private class HashKeySymbolLiteral extends SymbolLiteral, THashKeySymbolLiteral {
+ private Ruby::HashKeySymbol g;
+
+ HashKeySymbolLiteral() { this = THashKeySymbolLiteral(g) }
+
+ final override string getValueText() { result = g.getValue() }
+
+ final override string toString() { result = ":" + this.getValueText() }
+}
+
+/**
+ * A subshell literal.
+ *
+ * ```rb
+ * `ls -l`
+ * %x(/bin/sh foo.sh)
+ * ```
+ */
+class SubshellLiteral extends StringlikeLiteral, TSubshellLiteral {
+ private Ruby::Subshell g;
+
+ SubshellLiteral() { this = TSubshellLiteral(g) }
+
+ final override string getAPrimaryQlClass() { result = "SubshellLiteral" }
+
+ final override StringComponent getComponent(int i) { toGenerated(result) = g.getChild(i) }
+}
+
+/**
+ * A character literal.
+ *
+ * ```rb
+ * ?a
+ * ?\u{61}
+ * ```
+ */
+class CharacterLiteral extends Literal, TCharacterLiteral {
+ private Ruby::Character g;
+
+ CharacterLiteral() { this = TCharacterLiteral(g) }
+
+ final override string getValueText() { result = g.getValue() }
+
+ final override string toString() { result = g.getValue() }
+
+ final override string getAPrimaryQlClass() { result = "CharacterLiteral" }
+}
+
+/**
+ * A "here document". For example:
+ * ```rb
+ * query = < 21
+ * SQL
+ * ```
+ */
+class HereDoc extends StringlikeLiteral, THereDoc {
+ private Ruby::HeredocBeginning g;
+
+ HereDoc() { this = THereDoc(g) }
+
+ final override string getAPrimaryQlClass() { result = "HereDoc" }
+
+ /**
+ * Holds if this here document is executed in a subshell.
+ * ```rb
+ * <<`COMMAND`
+ * echo "Hello world!"
+ * COMMAND
+ * ```
+ */
+ final predicate isSubShell() { this.getQuoteStyle() = "`" }
+
+ /**
+ * Gets the quotation mark (`"`, `'` or `` ` ``) that surrounds the here document identifier, if any.
+ * ```rb
+ * <<"IDENTIFIER"
+ * <<'IDENTIFIER'
+ * <<`IDENTIFIER`
+ * ```
+ */
+ final string getQuoteStyle() {
+ exists(string s |
+ s = g.getValue() and
+ s.charAt(s.length() - 1) = result and
+ result = ["'", "`", "\""]
+ )
+ }
+
+ /**
+ * Gets the indentation modifier (`-` or `~`) of the here document identifier, if any.
+ * ```rb
+ * <<~IDENTIFIER
+ * <<-IDENTIFIER
+ * < i
+ )
+ or
+ // Top-level methods are private members of the Object class
+ this.getEnclosingModule() instanceof Toplevel
+ }
+
+ final override Parameter getParameter(int n) {
+ toGenerated(result) = g.getParameters().getChild(n)
+ }
+
+ final override string toString() { result = this.getName() }
+}
+
+/** A singleton method. */
+class SingletonMethod extends MethodBase, TSingletonMethod {
+ private Ruby::SingletonMethod g;
+
+ SingletonMethod() { this = TSingletonMethod(g) }
+
+ final override string getAPrimaryQlClass() { result = "SingletonMethod" }
+
+ /** Gets the object of this singleton method. */
+ final Expr getObject() { toGenerated(result) = g.getObject() }
+
+ final override string getName() {
+ result = g.getName().(Ruby::Token).getValue()
+ or
+ result = g.getName().(Ruby::Setter).getName().getValue() + "="
+ }
+
+ final override Parameter getParameter(int n) {
+ toGenerated(result) = g.getParameters().getChild(n)
+ }
+
+ final override string toString() { result = this.getName() }
+
+ final override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getObject" and result = this.getObject()
+ }
+}
+
+/**
+ * A lambda (anonymous method). For example:
+ * ```rb
+ * -> (x) { x + 1 }
+ * ```
+ */
+class Lambda extends Callable, BodyStmt, TLambda {
+ private Ruby::Lambda g;
+
+ Lambda() { this = TLambda(g) }
+
+ final override string getAPrimaryQlClass() { result = "Lambda" }
+
+ final override Parameter getParameter(int n) {
+ toGenerated(result) = g.getParameters().getChild(n)
+ }
+
+ final override string toString() { result = "-> { ... }" }
+
+ final override AstNode getAChild(string pred) {
+ result = Callable.super.getAChild(pred)
+ or
+ result = BodyStmt.super.getAChild(pred)
+ }
+}
+
+/** A block. */
+class Block extends Callable, StmtSequence, Scope, TBlock {
+ override AstNode getAChild(string pred) {
+ result = Callable.super.getAChild(pred)
+ or
+ result = StmtSequence.super.getAChild(pred)
+ }
+}
+
+/** A block enclosed within `do` and `end`. */
+class DoBlock extends Block, BodyStmt, TDoBlock {
+ private Ruby::DoBlock g;
+
+ DoBlock() { this = TDoBlock(g) }
+
+ final override Parameter getParameter(int n) {
+ toGenerated(result) = g.getParameters().getChild(n)
+ }
+
+ final override string toString() { result = "do ... end" }
+
+ final override AstNode getAChild(string pred) {
+ result = Block.super.getAChild(pred)
+ or
+ result = BodyStmt.super.getAChild(pred)
+ }
+
+ final override string getAPrimaryQlClass() { result = "DoBlock" }
+}
+
+/**
+ * A block defined using curly braces, e.g. in the following code:
+ * ```rb
+ * names.each { |name| puts name }
+ * ```
+ */
+class BraceBlock extends Block, TBraceBlock {
+ private Ruby::Block g;
+
+ BraceBlock() { this = TBraceBlock(g) }
+
+ final override Parameter getParameter(int n) {
+ toGenerated(result) = g.getParameters().getChild(n)
+ }
+
+ final override Stmt getStmt(int i) { toGenerated(result) = g.getChild(i) }
+
+ final override string toString() { result = "{ ... }" }
+
+ final override string getAPrimaryQlClass() { result = "BraceBlock" }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Module.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Module.qll
new file mode 100644
index 00000000000..8ac85668481
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Module.qll
@@ -0,0 +1,365 @@
+private import codeql.ruby.AST
+private import codeql.ruby.ast.Constant
+private import internal.AST
+private import internal.Module
+private import internal.TreeSitter
+
+/**
+ * A representation of a run-time `module` or `class` value.
+ */
+class Module extends TModule {
+ /** Gets a declaration of this module, if any. */
+ ModuleBase getADeclaration() { result.getModule() = this }
+
+ /** Gets the super class of this module, if any. */
+ Module getSuperClass() { result = getSuperClass(this) }
+
+ /** Gets a `prepend`ed module. */
+ Module getAPrependedModule() { result = getAPrependedModule(this) }
+
+ /** Gets an `include`d module. */
+ Module getAnIncludedModule() { result = getAnIncludedModule(this) }
+
+ /** Holds if this module is a class. */
+ pragma[noinline]
+ predicate isClass() { this.getADeclaration() instanceof ClassDeclaration }
+
+ /** Gets a textual representation of this module. */
+ string toString() {
+ this = TResolved(result)
+ or
+ exists(Namespace n | this = TUnresolved(n) and result = "...::" + n.toString())
+ }
+
+ /** Gets the location of this module. */
+ Location getLocation() {
+ exists(Namespace n | this = TUnresolved(n) and result = n.getLocation())
+ or
+ result =
+ min(Namespace n, string qName, Location loc, int weight |
+ this = TResolved(qName) and
+ qName = namespaceDeclaration(n) and
+ loc = n.getLocation() and
+ if exists(loc.getFile().getRelativePath()) then weight = 0 else weight = 1
+ |
+ loc
+ order by
+ weight, count(n.getAStmt()) desc, loc.getFile().getAbsolutePath(), loc.getStartLine(),
+ loc.getStartColumn()
+ )
+ }
+}
+
+/**
+ * The base class for classes, singleton classes, and modules.
+ */
+class ModuleBase extends BodyStmt, Scope, TModuleBase {
+ /** Gets a method defined in this module/class. */
+ MethodBase getAMethod() { result = this.getAStmt() }
+
+ /** Gets the method named `name` in this module/class, if any. */
+ MethodBase getMethod(string name) { result = this.getAMethod() and result.getName() = name }
+
+ /** Gets a class defined in this module/class. */
+ ClassDeclaration getAClass() { result = this.getAStmt() }
+
+ /** Gets the class named `name` in this module/class, if any. */
+ ClassDeclaration getClass(string name) { result = this.getAClass() and result.getName() = name }
+
+ /** Gets a module defined in this module/class. */
+ ModuleDeclaration getAModule() { result = this.getAStmt() }
+
+ /** Gets the module named `name` in this module/class, if any. */
+ ModuleDeclaration getModule(string name) {
+ result = this.getAModule() and result.getName() = name
+ }
+
+ /**
+ * Gets the value of the constant named `name`, if any.
+ *
+ * For example, the value of `CONST` is `"const"` in
+ * ```rb
+ * module M
+ * CONST = "const"
+ * end
+ * ```
+ */
+ Expr getConstant(string name) {
+ exists(AssignExpr ae, ConstantWriteAccess w |
+ ae = this.getAStmt() and
+ w = ae.getLeftOperand() and
+ w.getName() = name and
+ not exists(w.getScopeExpr()) and
+ result = ae.getRightOperand()
+ )
+ }
+
+ /** Gets the representation of the run-time value of this module or class. */
+ Module getModule() { none() }
+}
+
+/**
+ * A Ruby source file.
+ *
+ * ```rb
+ * def main
+ * puts "hello world!"
+ * end
+ * main
+ * ```
+ */
+class Toplevel extends ModuleBase, TToplevel {
+ private Ruby::Program g;
+
+ Toplevel() { this = TToplevel(g) }
+
+ final override string getAPrimaryQlClass() { result = "Toplevel" }
+
+ /**
+ * Gets the `n`th `BEGIN` block.
+ */
+ final BeginBlock getBeginBlock(int n) {
+ toGenerated(result) = rank[n + 1](int i, Ruby::BeginBlock b | b = g.getChild(i) | b order by i)
+ }
+
+ /**
+ * Gets a `BEGIN` block.
+ */
+ final BeginBlock getABeginBlock() { result = this.getBeginBlock(_) }
+
+ final override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getBeginBlock" and result = this.getBeginBlock(_)
+ }
+
+ final override Module getModule() { result = TResolved("Object") }
+
+ final override string toString() { result = g.getLocation().getFile().getBaseName() }
+}
+
+/**
+ * A class or module definition.
+ *
+ * ```rb
+ * class Foo
+ * def bar
+ * end
+ * end
+ * module Bar
+ * class Baz
+ * end
+ * end
+ * ```
+ */
+class Namespace extends ModuleBase, ConstantWriteAccess, TNamespace {
+ override string getAPrimaryQlClass() { result = "Namespace" }
+
+ /**
+ * Gets the name of the module/class. In the following example, the result is
+ * `"Foo"`.
+ * ```rb
+ * class Foo
+ * end
+ * ```
+ *
+ * N.B. in the following example, where the module/class name uses the scope
+ * resolution operator, the result is the name being resolved, i.e. `"Bar"`.
+ * Use `getScopeExpr` to get the `Foo` for `Foo`.
+ * ```rb
+ * module Foo::Bar
+ * end
+ * ```
+ */
+ override string getName() { none() }
+
+ /**
+ * Gets the scope expression used in the module/class name's scope resolution
+ * operation, if any.
+ *
+ * In the following example, the result is the `Expr` for `Foo`.
+ *
+ * ```rb
+ * module Foo::Bar
+ * end
+ * ```
+ *
+ * However, there is no result for the following example, since there is no
+ * scope resolution operation.
+ *
+ * ```rb
+ * module Baz
+ * end
+ * ```
+ */
+ override Expr getScopeExpr() { none() }
+
+ /**
+ * Holds if the module/class name uses the scope resolution operator to access the
+ * global scope, as in this example:
+ *
+ * ```rb
+ * class ::Foo
+ * end
+ * ```
+ */
+ override predicate hasGlobalScope() { none() }
+
+ final override Module getModule() {
+ result = any(string qName | qName = namespaceDeclaration(this) | TResolved(qName))
+ or
+ result = TUnresolved(this)
+ }
+
+ override AstNode getAChild(string pred) {
+ result = ModuleBase.super.getAChild(pred) or
+ result = ConstantWriteAccess.super.getAChild(pred)
+ }
+
+ final override string toString() { result = ConstantWriteAccess.super.toString() }
+}
+
+/**
+ * A class definition.
+ *
+ * ```rb
+ * class Foo
+ * def bar
+ * end
+ * end
+ * ```
+ */
+class ClassDeclaration extends Namespace, TClassDeclaration {
+ private Ruby::Class g;
+
+ ClassDeclaration() { this = TClassDeclaration(g) }
+
+ final override string getAPrimaryQlClass() { result = "ClassDeclaration" }
+
+ /**
+ * Gets the `Expr` used as the superclass in the class definition, if any.
+ *
+ * In the following example, the result is a `ConstantReadAccess`.
+ * ```rb
+ * class Foo < Bar
+ * end
+ * ```
+ *
+ * In the following example, where the superclass is a call expression, the
+ * result is a `Call`.
+ * ```rb
+ * class C < foo()
+ * end
+ * ```
+ */
+ final Expr getSuperclassExpr() { toGenerated(result) = g.getSuperclass().getChild() }
+
+ final override string getName() {
+ result = g.getName().(Ruby::Token).getValue() or
+ result = g.getName().(Ruby::ScopeResolution).getName().(Ruby::Token).getValue()
+ }
+
+ final override Expr getScopeExpr() {
+ toGenerated(result) = g.getName().(Ruby::ScopeResolution).getScope()
+ }
+
+ final override predicate hasGlobalScope() {
+ exists(Ruby::ScopeResolution sr |
+ sr = g.getName() and
+ not exists(sr.getScope())
+ )
+ }
+
+ final override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getSuperclassExpr" and result = this.getSuperclassExpr()
+ }
+}
+
+/**
+ * A definition of a singleton class on an object.
+ *
+ * ```rb
+ * class << foo
+ * def bar
+ * p 'bar'
+ * end
+ * end
+ * ```
+ */
+class SingletonClass extends ModuleBase, TSingletonClass {
+ private Ruby::SingletonClass g;
+
+ SingletonClass() { this = TSingletonClass(g) }
+
+ final override string getAPrimaryQlClass() { result = "SingletonClass" }
+
+ /**
+ * Gets the expression resulting in the object on which the singleton class
+ * is defined. In the following example, the result is the `Expr` for `foo`:
+ *
+ * ```rb
+ * class << foo
+ * end
+ * ```
+ */
+ final Expr getValue() { toGenerated(result) = g.getValue() }
+
+ final override string toString() { result = "class << ..." }
+
+ final override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getValue" and result = this.getValue()
+ }
+}
+
+/**
+ * A module definition.
+ *
+ * ```rb
+ * module Foo
+ * class Bar
+ * end
+ * end
+ * ```
+ *
+ * N.B. this class represents a single instance of a module definition. In the
+ * following example, classes `Bar` and `Baz` are both defined in the module
+ * `Foo`, but in two syntactically distinct definitions, meaning that there
+ * will be two instances of `ModuleDeclaration` in the database.
+ *
+ * ```rb
+ * module Foo
+ * class Bar; end
+ * end
+ *
+ * module Foo
+ * class Baz; end
+ * end
+ * ```
+ */
+class ModuleDeclaration extends Namespace, TModuleDeclaration {
+ private Ruby::Module g;
+
+ ModuleDeclaration() { this = TModuleDeclaration(g) }
+
+ final override string getAPrimaryQlClass() { result = "ModuleDeclaration" }
+
+ final override string getName() {
+ result = g.getName().(Ruby::Token).getValue() or
+ result = g.getName().(Ruby::ScopeResolution).getName().(Ruby::Token).getValue()
+ }
+
+ final override Expr getScopeExpr() {
+ toGenerated(result) = g.getName().(Ruby::ScopeResolution).getScope()
+ }
+
+ final override predicate hasGlobalScope() {
+ exists(Ruby::ScopeResolution sr |
+ sr = g.getName() and
+ not exists(sr.getScope())
+ )
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Operation.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Operation.qll
new file mode 100644
index 00000000000..6c30224b3b1
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Operation.qll
@@ -0,0 +1,620 @@
+private import codeql.ruby.AST
+private import internal.AST
+private import internal.TreeSitter
+private import internal.Operation
+
+/**
+ * An operation.
+ *
+ * This is the QL root class for all operations.
+ */
+class Operation extends Expr instanceof OperationImpl {
+ /** Gets the operator of this operation. */
+ final string getOperator() { result = super.getOperatorImpl() }
+
+ /** Gets an operand of this operation. */
+ final Expr getAnOperand() { result = super.getAnOperandImpl() }
+
+ override AstNode getAChild(string pred) {
+ result = Expr.super.getAChild(pred)
+ or
+ pred = "getAnOperand" and result = this.getAnOperand()
+ }
+}
+
+/** A unary operation. */
+class UnaryOperation extends Operation, MethodCall instanceof UnaryOperationImpl {
+ /** Gets the operand of this unary operation. */
+ final Expr getOperand() { result = super.getOperandImpl() }
+
+ final override AstNode getAChild(string pred) {
+ result = Operation.super.getAChild(pred)
+ or
+ result = MethodCall.super.getAChild(pred)
+ or
+ pred = "getOperand" and result = this.getOperand()
+ }
+
+ final override string toString() { result = this.getOperator() + " ..." }
+}
+
+/** A unary logical operation. */
+class UnaryLogicalOperation extends UnaryOperation, TUnaryLogicalOperation { }
+
+/**
+ * A logical NOT operation, using either `!` or `not`.
+ * ```rb
+ * !x.nil?
+ * not params.empty?
+ * ```
+ */
+class NotExpr extends UnaryLogicalOperation, TNotExpr {
+ final override string getAPrimaryQlClass() { result = "NotExpr" }
+}
+
+/** A unary arithmetic operation. */
+class UnaryArithmeticOperation extends UnaryOperation, TUnaryArithmeticOperation { }
+
+/**
+ * A unary plus expression.
+ * ```rb
+ * + a
+ * ```
+ */
+class UnaryPlusExpr extends UnaryArithmeticOperation, TUnaryPlusExpr {
+ final override string getAPrimaryQlClass() { result = "UnaryPlusExpr" }
+}
+
+/**
+ * A unary minus expression.
+ * ```rb
+ * - a
+ * ```
+ */
+class UnaryMinusExpr extends UnaryArithmeticOperation, TUnaryMinusExpr {
+ final override string getAPrimaryQlClass() { result = "UnaryMinusExpr" }
+}
+
+/**
+ * A splat expression.
+ * ```rb
+ * foo(*args)
+ * ```
+ */
+class SplatExpr extends UnaryOperation, TSplatExpr {
+ final override string getAPrimaryQlClass() { result = "SplatExpr" }
+}
+
+/**
+ * A hash-splat (or 'double-splat') expression.
+ * ```rb
+ * foo(**options)
+ * ```
+ */
+class HashSplatExpr extends UnaryOperation, THashSplatExpr {
+ private Ruby::HashSplatArgument g;
+
+ HashSplatExpr() { this = THashSplatExpr(g) }
+
+ final override string getAPrimaryQlClass() { result = "HashSplatExpr" }
+}
+
+/** A unary bitwise operation. */
+class UnaryBitwiseOperation extends UnaryOperation, TUnaryBitwiseOperation { }
+
+/**
+ * A complement (bitwise NOT) expression.
+ * ```rb
+ * ~x
+ * ```
+ */
+class ComplementExpr extends UnaryBitwiseOperation, TComplementExpr {
+ final override string getAPrimaryQlClass() { result = "ComplementExpr" }
+}
+
+/**
+ * A call to the special `defined?` operator.
+ * ```rb
+ * defined? some_method
+ * ```
+ */
+class DefinedExpr extends UnaryOperation, TDefinedExpr {
+ final override string getAPrimaryQlClass() { result = "DefinedExpr" }
+}
+
+/** A binary operation. */
+class BinaryOperation extends Operation, MethodCall instanceof BinaryOperationImpl {
+ final override string toString() { result = "... " + this.getOperator() + " ..." }
+
+ override AstNode getAChild(string pred) {
+ result = Operation.super.getAChild(pred)
+ or
+ result = MethodCall.super.getAChild(pred)
+ or
+ pred = "getLeftOperand" and result = this.getLeftOperand()
+ or
+ pred = "getRightOperand" and result = this.getRightOperand()
+ }
+
+ /** Gets the left operand of this binary operation. */
+ final Stmt getLeftOperand() { result = super.getLeftOperandImpl() }
+
+ /** Gets the right operand of this binary operation. */
+ final Stmt getRightOperand() { result = super.getRightOperandImpl() }
+}
+
+/**
+ * A binary arithmetic operation.
+ */
+class BinaryArithmeticOperation extends BinaryOperation, TBinaryArithmeticOperation { }
+
+/**
+ * An add expression.
+ * ```rb
+ * x + 1
+ * ```
+ */
+class AddExpr extends BinaryArithmeticOperation, TAddExpr {
+ final override string getAPrimaryQlClass() { result = "AddExpr" }
+}
+
+/**
+ * A subtract expression.
+ * ```rb
+ * x - 3
+ * ```
+ */
+class SubExpr extends BinaryArithmeticOperation, TSubExpr {
+ final override string getAPrimaryQlClass() { result = "SubExpr" }
+}
+
+/**
+ * A multiply expression.
+ * ```rb
+ * x * 10
+ * ```
+ */
+class MulExpr extends BinaryArithmeticOperation, TMulExpr {
+ final override string getAPrimaryQlClass() { result = "MulExpr" }
+}
+
+/**
+ * A divide expression.
+ * ```rb
+ * x / y
+ * ```
+ */
+class DivExpr extends BinaryArithmeticOperation, TDivExpr {
+ final override string getAPrimaryQlClass() { result = "DivExpr" }
+}
+
+/**
+ * A modulo expression.
+ * ```rb
+ * x % 2
+ * ```
+ */
+class ModuloExpr extends BinaryArithmeticOperation, TModuloExpr {
+ final override string getAPrimaryQlClass() { result = "ModuloExpr" }
+}
+
+/**
+ * An exponent expression.
+ * ```rb
+ * x ** 2
+ * ```
+ */
+class ExponentExpr extends BinaryArithmeticOperation, TExponentExpr {
+ final override string getAPrimaryQlClass() { result = "ExponentExpr" }
+}
+
+/**
+ * A binary logical operation.
+ */
+class BinaryLogicalOperation extends BinaryOperation, TBinaryLogicalOperation { }
+
+/**
+ * A logical AND operation, using either `and` or `&&`.
+ * ```rb
+ * x and y
+ * a && b
+ * ```
+ */
+class LogicalAndExpr extends BinaryLogicalOperation, TLogicalAndExpr {
+ final override string getAPrimaryQlClass() { result = "LogicalAndExpr" }
+}
+
+/**
+ * A logical OR operation, using either `or` or `||`.
+ * ```rb
+ * x or y
+ * a || b
+ * ```
+ */
+class LogicalOrExpr extends BinaryLogicalOperation, TLogicalOrExpr {
+ final override string getAPrimaryQlClass() { result = "LogicalOrExpr" }
+}
+
+/**
+ * A binary bitwise operation.
+ */
+class BinaryBitwiseOperation extends BinaryOperation, TBinaryBitwiseOperation { }
+
+/**
+ * A left-shift operation.
+ * ```rb
+ * x << n
+ * ```
+ */
+class LShiftExpr extends BinaryBitwiseOperation, TLShiftExpr {
+ final override string getAPrimaryQlClass() { result = "LShiftExpr" }
+}
+
+/**
+ * A right-shift operation.
+ * ```rb
+ * x >> n
+ * ```
+ */
+class RShiftExpr extends BinaryBitwiseOperation, TRShiftExpr {
+ final override string getAPrimaryQlClass() { result = "RShiftExpr" }
+}
+
+/**
+ * A bitwise AND operation.
+ * ```rb
+ * x & 0xff
+ * ```
+ */
+class BitwiseAndExpr extends BinaryBitwiseOperation, TBitwiseAndExpr {
+ final override string getAPrimaryQlClass() { result = "BitwiseAndExpr" }
+}
+
+/**
+ * A bitwise OR operation.
+ * ```rb
+ * x | 0x01
+ * ```
+ */
+class BitwiseOrExpr extends BinaryBitwiseOperation, TBitwiseOrExpr {
+ final override string getAPrimaryQlClass() { result = "BitwiseOrExpr" }
+}
+
+/**
+ * An XOR (exclusive OR) operation.
+ * ```rb
+ * x ^ y
+ * ```
+ */
+class BitwiseXorExpr extends BinaryBitwiseOperation, TBitwiseXorExpr {
+ final override string getAPrimaryQlClass() { result = "BitwiseXorExpr" }
+}
+
+/**
+ * A comparison operation. That is, either an equality operation or a
+ * relational operation.
+ */
+class ComparisonOperation extends BinaryOperation, TComparisonOperation { }
+
+/**
+ * An equality operation.
+ */
+class EqualityOperation extends ComparisonOperation, TEqualityOperation { }
+
+/**
+ * An equals expression.
+ * ```rb
+ * x == y
+ * ```
+ */
+class EqExpr extends EqualityOperation, TEqExpr {
+ final override string getAPrimaryQlClass() { result = "EqExpr" }
+}
+
+/**
+ * A not-equals expression.
+ * ```rb
+ * x != y
+ * ```
+ */
+class NEExpr extends EqualityOperation, TNEExpr {
+ final override string getAPrimaryQlClass() { result = "NEExpr" }
+}
+
+/**
+ * A case-equality (or 'threequals') expression.
+ * ```rb
+ * String === "foo"
+ * ```
+ */
+class CaseEqExpr extends EqualityOperation, TCaseEqExpr {
+ final override string getAPrimaryQlClass() { result = "CaseEqExpr" }
+}
+
+/**
+ * A relational operation, that is, one of `<=`, `<`, `>`, or `>=`.
+ */
+class RelationalOperation extends ComparisonOperation, TRelationalOperation {
+ /** Gets the greater operand. */
+ Expr getGreaterOperand() { none() }
+
+ /** Gets the lesser operand. */
+ Expr getLesserOperand() { none() }
+
+ final override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getGreaterOperand" and result = this.getGreaterOperand()
+ or
+ pred = "getLesserOperand" and result = this.getLesserOperand()
+ }
+}
+
+/**
+ * A greater-than expression.
+ * ```rb
+ * x > 0
+ * ```
+ */
+class GTExpr extends RelationalOperation, TGTExpr {
+ final override string getAPrimaryQlClass() { result = "GTExpr" }
+
+ final override Expr getGreaterOperand() { result = this.getLeftOperand() }
+
+ final override Expr getLesserOperand() { result = this.getRightOperand() }
+}
+
+/**
+ * A greater-than-or-equal expression.
+ * ```rb
+ * x >= 0
+ * ```
+ */
+class GEExpr extends RelationalOperation, TGEExpr {
+ final override string getAPrimaryQlClass() { result = "GEExpr" }
+
+ final override Expr getGreaterOperand() { result = this.getLeftOperand() }
+
+ final override Expr getLesserOperand() { result = this.getRightOperand() }
+}
+
+/**
+ * A less-than expression.
+ * ```rb
+ * x < 10
+ * ```
+ */
+class LTExpr extends RelationalOperation, TLTExpr {
+ final override string getAPrimaryQlClass() { result = "LTExpr" }
+
+ final override Expr getGreaterOperand() { result = this.getRightOperand() }
+
+ final override Expr getLesserOperand() { result = this.getLeftOperand() }
+}
+
+/**
+ * A less-than-or-equal expression.
+ * ```rb
+ * x <= 10
+ * ```
+ */
+class LEExpr extends RelationalOperation, TLEExpr {
+ final override string getAPrimaryQlClass() { result = "LEExpr" }
+
+ final override Expr getGreaterOperand() { result = this.getRightOperand() }
+
+ final override Expr getLesserOperand() { result = this.getLeftOperand() }
+}
+
+/**
+ * A three-way comparison ('spaceship') expression.
+ * ```rb
+ * a <=> b
+ * ```
+ */
+class SpaceshipExpr extends BinaryOperation, TSpaceshipExpr {
+ final override string getAPrimaryQlClass() { result = "SpaceshipExpr" }
+}
+
+/**
+ * A regexp match expression.
+ * ```rb
+ * input =~ /\d/
+ * ```
+ */
+class RegExpMatchExpr extends BinaryOperation, TRegExpMatchExpr {
+ final override string getAPrimaryQlClass() { result = "RegExpMatchExpr" }
+}
+
+/**
+ * A regexp-doesn't-match expression.
+ * ```rb
+ * input !~ /\d/
+ * ```
+ */
+class NoRegExpMatchExpr extends BinaryOperation, TNoRegExpMatchExpr {
+ final override string getAPrimaryQlClass() { result = "NoRegExpMatchExpr" }
+}
+
+/**
+ * A binary assignment operation, including `=`, `+=`, `&=`, etc.
+ *
+ * This is a QL base class for all assignments.
+ */
+class Assignment extends Operation instanceof AssignmentImpl {
+ /** Gets the left hand side of this assignment. */
+ final Pattern getLeftOperand() { result = super.getLeftOperandImpl() }
+
+ /** Gets the right hand side of this assignment. */
+ final Expr getRightOperand() { result = super.getRightOperandImpl() }
+
+ final override string toString() { result = "... " + this.getOperator() + " ..." }
+
+ override AstNode getAChild(string pred) {
+ result = Operation.super.getAChild(pred)
+ or
+ pred = "getLeftOperand" and result = this.getLeftOperand()
+ or
+ pred = "getRightOperand" and result = this.getRightOperand()
+ }
+}
+
+/**
+ * An assignment operation with the operator `=`.
+ * ```rb
+ * x = 123
+ * ```
+ */
+class AssignExpr extends Assignment, TAssignExpr {
+ final override string getAPrimaryQlClass() { result = "AssignExpr" }
+}
+
+/**
+ * A binary assignment operation other than `=`.
+ */
+class AssignOperation extends Assignment instanceof AssignOperationImpl { }
+
+/**
+ * An arithmetic assignment operation: `+=`, `-=`, `*=`, `/=`, `**=`, and `%=`.
+ */
+class AssignArithmeticOperation extends AssignOperation, TAssignArithmeticOperation { }
+
+/**
+ * A `+=` assignment expression.
+ * ```rb
+ * x += 1
+ * ```
+ */
+class AssignAddExpr extends AssignArithmeticOperation, TAssignAddExpr {
+ final override string getAPrimaryQlClass() { result = "AssignAddExpr" }
+}
+
+/**
+ * A `-=` assignment expression.
+ * ```rb
+ * x -= 3
+ * ```
+ */
+class AssignSubExpr extends AssignArithmeticOperation, TAssignSubExpr {
+ final override string getAPrimaryQlClass() { result = "AssignSubExpr" }
+}
+
+/**
+ * A `*=` assignment expression.
+ * ```rb
+ * x *= 10
+ * ```
+ */
+class AssignMulExpr extends AssignArithmeticOperation, TAssignMulExpr {
+ final override string getAPrimaryQlClass() { result = "AssignMulExpr" }
+}
+
+/**
+ * A `/=` assignment expression.
+ * ```rb
+ * x /= y
+ * ```
+ */
+class AssignDivExpr extends AssignArithmeticOperation, TAssignDivExpr {
+ final override string getAPrimaryQlClass() { result = "AssignDivExpr" }
+}
+
+/**
+ * A `%=` assignment expression.
+ * ```rb
+ * x %= 4
+ * ```
+ */
+class AssignModuloExpr extends AssignArithmeticOperation, TAssignModuloExpr {
+ final override string getAPrimaryQlClass() { result = "AssignModuloExpr" }
+}
+
+/**
+ * A `**=` assignment expression.
+ * ```rb
+ * x **= 2
+ * ```
+ */
+class AssignExponentExpr extends AssignArithmeticOperation, TAssignExponentExpr {
+ final override string getAPrimaryQlClass() { result = "AssignExponentExpr" }
+}
+
+/**
+ * A logical assignment operation: `&&=` and `||=`.
+ */
+class AssignLogicalOperation extends AssignOperation, TAssignLogicalOperation { }
+
+/**
+ * A logical AND assignment operation.
+ * ```rb
+ * x &&= y.even?
+ * ```
+ */
+class AssignLogicalAndExpr extends AssignLogicalOperation, TAssignLogicalAndExpr {
+ final override string getAPrimaryQlClass() { result = "AssignLogicalAndExpr" }
+}
+
+/**
+ * A logical OR assignment operation.
+ * ```rb
+ * x ||= y
+ * ```
+ */
+class AssignLogicalOrExpr extends AssignLogicalOperation, TAssignLogicalOrExpr {
+ final override string getAPrimaryQlClass() { result = "AssignLogicalOrExpr" }
+}
+
+/**
+ * A bitwise assignment operation: `<<=`, `>>=`, `&=`, `|=` and `^=`.
+ */
+class AssignBitwiseOperation extends AssignOperation, TAssignBitwiseOperation { }
+
+/**
+ * A left-shift assignment operation.
+ * ```rb
+ * x <<= 3
+ * ```
+ */
+class AssignLShiftExpr extends AssignBitwiseOperation, TAssignLShiftExpr {
+ final override string getAPrimaryQlClass() { result = "AssignLShiftExpr" }
+}
+
+/**
+ * A right-shift assignment operation.
+ * ```rb
+ * x >>= 3
+ * ```
+ */
+class AssignRShiftExpr extends AssignBitwiseOperation, TAssignRShiftExpr {
+ final override string getAPrimaryQlClass() { result = "AssignRShiftExpr" }
+}
+
+/**
+ * A bitwise AND assignment operation.
+ * ```rb
+ * x &= 0xff
+ * ```
+ */
+class AssignBitwiseAndExpr extends AssignBitwiseOperation, TAssignBitwiseAndExpr {
+ final override string getAPrimaryQlClass() { result = "AssignBitwiseAndExpr" }
+}
+
+/**
+ * A bitwise OR assignment operation.
+ * ```rb
+ * x |= 0x01
+ * ```
+ */
+class AssignBitwiseOrExpr extends AssignBitwiseOperation, TAssignBitwiseOrExpr {
+ final override string getAPrimaryQlClass() { result = "AssignBitwiseOrExpr" }
+}
+
+/**
+ * An XOR (exclusive OR) assignment operation.
+ * ```rb
+ * x ^= y
+ * ```
+ */
+class AssignBitwiseXorExpr extends AssignBitwiseOperation, TAssignBitwiseXorExpr {
+ final override string getAPrimaryQlClass() { result = "AssignBitwiseXorExpr" }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Parameter.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Parameter.qll
new file mode 100644
index 00000000000..6e6b5395d43
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Parameter.qll
@@ -0,0 +1,248 @@
+private import codeql.ruby.AST
+private import internal.AST
+private import internal.Variable
+private import internal.Parameter
+private import internal.TreeSitter
+
+/** A parameter. */
+class Parameter extends AstNode, TParameter {
+ /** Gets the callable that this parameter belongs to. */
+ final Callable getCallable() { result.getAParameter() = this }
+
+ /** Gets the zero-based position of this parameter. */
+ final int getPosition() { this = any(Callable c).getParameter(result) }
+
+ /** Gets a variable introduced by this parameter. */
+ LocalVariable getAVariable() { none() }
+
+ /** Gets the variable named `name` introduced by this parameter. */
+ final LocalVariable getVariable(string name) {
+ result = this.getAVariable() and
+ result.getName() = name
+ }
+}
+
+/**
+ * A parameter defined using a pattern.
+ *
+ * This includes both simple parameters and tuple parameters.
+ */
+class PatternParameter extends Parameter, Pattern, TPatternParameter {
+ override LocalVariable getAVariable() { result = Pattern.super.getAVariable() }
+}
+
+/** A parameter defined using a tuple pattern. */
+class TuplePatternParameter extends PatternParameter, TuplePattern, TTuplePatternParameter {
+ final override LocalVariable getAVariable() { result = TuplePattern.super.getAVariable() }
+
+ final override string getAPrimaryQlClass() { result = "TuplePatternParameter" }
+
+ override AstNode getAChild(string pred) { result = TuplePattern.super.getAChild(pred) }
+}
+
+/** A named parameter. */
+class NamedParameter extends Parameter, TNamedParameter {
+ /** Gets the name of this parameter. */
+ string getName() { none() }
+
+ /** Holds if the name of this parameter is `name`. */
+ final predicate hasName(string name) { this.getName() = name }
+
+ /** Gets the variable introduced by this parameter. */
+ LocalVariable getVariable() { none() }
+
+ override LocalVariable getAVariable() { result = this.getVariable() }
+
+ /** Gets an access to this parameter. */
+ final VariableAccess getAnAccess() { result = this.getVariable().getAnAccess() }
+
+ /** Gets the access that defines the underlying local variable. */
+ final VariableAccess getDefiningAccess() { result = this.getVariable().getDefiningAccess() }
+
+ override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getDefiningAccess" and
+ result = this.getDefiningAccess()
+ }
+}
+
+/** A simple (normal) parameter. */
+class SimpleParameter extends NamedParameter, PatternParameter, VariablePattern, TSimpleParameter {
+ private Ruby::Identifier g;
+
+ SimpleParameter() { this = TSimpleParameter(g) }
+
+ final override string getName() { result = g.getValue() }
+
+ final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g) }
+
+ final override LocalVariable getAVariable() { result = this.getVariable() }
+
+ final override string getAPrimaryQlClass() { result = "SimpleParameter" }
+
+ final override string toString() { result = this.getName() }
+}
+
+/**
+ * A parameter that is a block. For example, `&bar` in the following code:
+ * ```rb
+ * def foo(&bar)
+ * bar.call if block_given?
+ * end
+ * ```
+ */
+class BlockParameter extends NamedParameter, TBlockParameter {
+ private Ruby::BlockParameter g;
+
+ BlockParameter() { this = TBlockParameter(g) }
+
+ final override string getName() { result = g.getName().getValue() }
+
+ final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g.getName()) }
+
+ final override string toString() { result = "&" + this.getName() }
+
+ final override string getAPrimaryQlClass() { result = "BlockParameter" }
+}
+
+/**
+ * A hash-splat (or double-splat) parameter. For example, `**options` in the
+ * following code:
+ * ```rb
+ * def foo(bar, **options)
+ * ...
+ * end
+ * ```
+ */
+class HashSplatParameter extends NamedParameter, THashSplatParameter {
+ private Ruby::HashSplatParameter g;
+
+ HashSplatParameter() { this = THashSplatParameter(g) }
+
+ final override string getAPrimaryQlClass() { result = "HashSplatParameter" }
+
+ final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g.getName()) }
+
+ final override string toString() { result = "**" + this.getName() }
+
+ final override string getName() { result = g.getName().getValue() }
+}
+
+/**
+ * A keyword parameter, including a default value if the parameter is optional.
+ * For example, in the following example, `foo` is a keyword parameter with a
+ * default value of `0`, and `bar` is a mandatory keyword parameter with no
+ * default value mandatory parameter).
+ * ```rb
+ * def f(foo: 0, bar:)
+ * foo * 10 + bar
+ * end
+ * ```
+ */
+class KeywordParameter extends NamedParameter, TKeywordParameter {
+ private Ruby::KeywordParameter g;
+
+ KeywordParameter() { this = TKeywordParameter(g) }
+
+ final override string getAPrimaryQlClass() { result = "KeywordParameter" }
+
+ final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g.getName()) }
+
+ /**
+ * Gets the default value, i.e. the value assigned to the parameter when one
+ * is not provided by the caller. If the parameter is mandatory and does not
+ * have a default value, this predicate has no result.
+ */
+ final Expr getDefaultValue() { toGenerated(result) = g.getValue() }
+
+ /**
+ * Holds if the parameter is optional. That is, there is a default value that
+ * is used when the caller omits this parameter.
+ */
+ final predicate isOptional() { exists(this.getDefaultValue()) }
+
+ final override string toString() { result = this.getName() }
+
+ final override string getName() { result = g.getName().getValue() }
+
+ final override Location getLocation() { result = g.getName().getLocation() }
+
+ final override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getDefaultValue" and result = this.getDefaultValue()
+ }
+}
+
+/**
+ * An optional parameter. For example, the parameter `name` in the following
+ * code:
+ * ```rb
+ * def say_hello(name = 'Anon')
+ * puts "hello #{name}"
+ * end
+ * ```
+ */
+class OptionalParameter extends NamedParameter, TOptionalParameter {
+ private Ruby::OptionalParameter g;
+
+ OptionalParameter() { this = TOptionalParameter(g) }
+
+ final override string getAPrimaryQlClass() { result = "OptionalParameter" }
+
+ /**
+ * Gets the default value, i.e. the value assigned to the parameter when one
+ * is not provided by the caller.
+ */
+ final Expr getDefaultValue() { toGenerated(result) = g.getValue() }
+
+ final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g.getName()) }
+
+ final override string toString() { result = this.getName() }
+
+ final override string getName() { result = g.getName().getValue() }
+
+ final override Location getLocation() { result = g.getName().getLocation() }
+
+ final override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getDefaultValue" and result = this.getDefaultValue()
+ }
+}
+
+/**
+ * A splat parameter. For example, `*values` in the following code:
+ * ```rb
+ * def foo(bar, *values)
+ * ...
+ * end
+ * ```
+ */
+class SplatParameter extends NamedParameter, TSplatParameter {
+ private Ruby::SplatParameter g;
+
+ SplatParameter() { this = TSplatParameter(g) }
+
+ final override string getAPrimaryQlClass() { result = "SplatParameter" }
+
+ final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g.getName()) }
+
+ final override string toString() { result = "*" + this.getName() }
+
+ final override string getName() { result = g.getName().getValue() }
+}
+
+/**
+ * A special `...` parameter that forwards positional/keyword/block arguments:
+ * ```rb
+ * def foo(...)
+ * end
+ * ```
+ */
+class ForwardParameter extends Parameter, TForwardParameter {
+ final override string getAPrimaryQlClass() { result = "ForwardParameter" }
+
+ final override string toString() { result = "..." }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Pattern.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Pattern.qll
new file mode 100644
index 00000000000..b275a8d813a
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Pattern.qll
@@ -0,0 +1,96 @@
+private import codeql.ruby.AST
+private import codeql.Locations
+private import internal.AST
+private import internal.Pattern
+private import internal.TreeSitter
+private import internal.Variable
+
+/** A pattern. */
+class Pattern extends AstNode {
+ Pattern() {
+ explicitAssignmentNode(toGenerated(this), _)
+ or
+ implicitAssignmentNode(toGenerated(this))
+ or
+ implicitParameterAssignmentNode(toGenerated(this), _)
+ or
+ this = getSynthChild(any(AssignExpr ae), 0)
+ }
+
+ /** Gets a variable used in (or introduced by) this pattern. */
+ Variable getAVariable() { none() }
+}
+
+private class LhsExpr_ =
+ TVariableAccess or TTokenConstantAccess or TScopeResolutionConstantAccess or TMethodCall or
+ TSimpleParameter;
+
+/**
+ * A "left-hand-side" expression. An `LhsExpr` can occur on the left-hand side of
+ * operator assignments (`AssignOperation`), in patterns (`Pattern`) on the left-hand side of
+ * an assignment (`AssignExpr`) or for loop (`ForExpr`), and as the exception
+ * variable of a `rescue` clause (`RescueClause`).
+ *
+ * An `LhsExpr` can be a simple variable, a constant, a call, or an element reference:
+ * ```rb
+ * var = 1
+ * var += 1
+ * E = 1
+ * foo.bar = 1
+ * foo[0] = 1
+ * rescue E => var
+ * ```
+ */
+class LhsExpr extends Pattern, LhsExpr_, Expr {
+ override Variable getAVariable() { result = this.(VariableAccess).getVariable() }
+}
+
+private class TVariablePattern = TVariableAccess or TSimpleParameter;
+
+/** A simple variable pattern. */
+class VariablePattern extends Pattern, LhsExpr, TVariablePattern { }
+
+/**
+ * A tuple pattern.
+ *
+ * This includes both tuple patterns in parameters and assignments. Example patterns:
+ * ```rb
+ * a, self.b = value
+ * (a, b), c[3] = value
+ * a, b, *rest, c, d = value
+ * ```
+ */
+class TuplePattern extends Pattern, TTuplePattern {
+ override string getAPrimaryQlClass() { result = "TuplePattern" }
+
+ private TuplePatternImpl getImpl() { result = toGenerated(this) }
+
+ private Ruby::AstNode getChild(int i) { result = this.getImpl().getChildNode(i) }
+
+ /** Gets the `i`th pattern in this tuple pattern. */
+ final Pattern getElement(int i) {
+ exists(Ruby::AstNode c | c = this.getChild(i) |
+ toGenerated(result) = c.(Ruby::RestAssignment).getChild()
+ or
+ toGenerated(result) = c
+ )
+ }
+
+ /** Gets a sub pattern in this tuple pattern. */
+ final Pattern getAnElement() { result = this.getElement(_) }
+
+ /**
+ * Gets the index of the pattern with the `*` marker on it, if it exists.
+ * In the example below the index is `2`.
+ * ```rb
+ * a, b, *rest, c, d = value
+ * ```
+ */
+ final int getRestIndex() { result = this.getImpl().getRestIndex() }
+
+ override Variable getAVariable() { result = this.getElement(_).getAVariable() }
+
+ override string toString() { result = "(..., ...)" }
+
+ override AstNode getAChild(string pred) { pred = "getElement" and result = this.getElement(_) }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Scope.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Scope.qll
new file mode 100644
index 00000000000..fe3b1b45cd1
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Scope.qll
@@ -0,0 +1,24 @@
+private import codeql.ruby.AST
+private import internal.AST
+private import internal.Scope
+private import internal.TreeSitter
+
+class Scope extends AstNode, TScopeType {
+ private Scope::Range range;
+
+ Scope() { range = toGenerated(this) }
+
+ /** Gets the scope in which this scope is nested, if any. */
+ Scope getOuterScope() { toGenerated(result) = range.getOuterScope() }
+
+ /** Gets a variable that is declared in this scope. */
+ final Variable getAVariable() { result.getDeclaringScope() = this }
+
+ /** Gets the variable declared in this scope with the given name, if any. */
+ final Variable getVariable(string name) {
+ result = this.getAVariable() and
+ result.getName() = name
+ }
+}
+
+class SelfScope extends Scope, TSelfScopeType { }
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Statement.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Statement.qll
new file mode 100644
index 00000000000..feddd8fa3f0
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Statement.qll
@@ -0,0 +1,251 @@
+private import codeql.ruby.AST
+private import codeql.ruby.CFG
+private import internal.AST
+private import internal.TreeSitter
+private import internal.Variable
+private import codeql.ruby.controlflow.internal.ControlFlowGraphImpl
+
+/**
+ * A statement.
+ *
+ * This is the root QL class for all statements.
+ */
+class Stmt extends AstNode, TStmt {
+ /** Gets a control-flow node for this statement, if any. */
+ CfgNodes::AstCfgNode getAControlFlowNode() { result.getNode() = this }
+
+ /** Gets a control-flow entry node for this statement, if any */
+ AstNode getAControlFlowEntryNode() { result = getAControlFlowEntryNode(this) }
+
+ /** Gets the control-flow scope of this statement, if any. */
+ CfgScope getCfgScope() { result = getCfgScope(this) }
+
+ /** Gets the enclosing callable, if any. */
+ Callable getEnclosingCallable() { result = this.getCfgScope() }
+}
+
+/**
+ * An empty statement (`;`).
+ */
+class EmptyStmt extends Stmt, TEmptyStmt {
+ final override string getAPrimaryQlClass() { result = "EmptyStmt" }
+
+ final override string toString() { result = ";" }
+}
+
+/**
+ * A `begin` statement.
+ * ```rb
+ * begin
+ * puts "hello world"
+ * end
+ * ```
+ */
+class BeginExpr extends BodyStmt, TBeginExpr {
+ final override string getAPrimaryQlClass() { result = "BeginExpr" }
+
+ final override string toString() { result = "begin ... " }
+}
+
+/**
+ * A `BEGIN` block.
+ * ```rb
+ * BEGIN { puts "starting ..." }
+ * ```
+ */
+class BeginBlock extends StmtSequence, TBeginBlock {
+ private Ruby::BeginBlock g;
+
+ BeginBlock() { this = TBeginBlock(g) }
+
+ final override string getAPrimaryQlClass() { result = "BeginBlock" }
+
+ final override string toString() { result = "BEGIN { ... }" }
+
+ final override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
+}
+
+/**
+ * An `END` block.
+ * ```rb
+ * END { puts "shutting down" }
+ * ```
+ */
+class EndBlock extends StmtSequence, TEndBlock {
+ private Ruby::EndBlock g;
+
+ EndBlock() { this = TEndBlock(g) }
+
+ final override string getAPrimaryQlClass() { result = "EndBlock" }
+
+ final override string toString() { result = "END { ... }" }
+
+ final override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
+}
+
+/**
+ * An `undef` statement. For example:
+ * ```rb
+ * - undef method_name
+ * - undef &&, :method_name
+ * - undef :"method_#{ name }"
+ * ```
+ */
+class UndefStmt extends Stmt, TUndefStmt {
+ private Ruby::Undef g;
+
+ UndefStmt() { this = TUndefStmt(g) }
+
+ /** Gets the `n`th method name to undefine. */
+ final MethodName getMethodName(int n) { toGenerated(result) = g.getChild(n) }
+
+ /** Gets a method name to undefine. */
+ final MethodName getAMethodName() { result = this.getMethodName(_) }
+
+ final override string getAPrimaryQlClass() { result = "UndefStmt" }
+
+ final override string toString() { result = "undef ..." }
+
+ final override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getMethodName" and result = this.getMethodName(_)
+ }
+}
+
+/**
+ * An `alias` statement. For example:
+ * ```rb
+ * - alias alias_name method_name
+ * - alias foo :method_name
+ * - alias bar :"method_#{ name }"
+ * ```
+ */
+class AliasStmt extends Stmt, TAliasStmt {
+ private Ruby::Alias g;
+
+ AliasStmt() { this = TAliasStmt(g) }
+
+ /** Gets the new method name. */
+ final MethodName getNewName() { toGenerated(result) = g.getName() }
+
+ /** Gets the original method name. */
+ final MethodName getOldName() { toGenerated(result) = g.getAlias() }
+
+ final override string getAPrimaryQlClass() { result = "AliasStmt" }
+
+ final override string toString() { result = "alias ..." }
+
+ final override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getNewName" and result = this.getNewName()
+ or
+ pred = "getOldName" and result = this.getOldName()
+ }
+}
+
+/**
+ * A statement that may return a value: `return`, `break` and `next`.
+ *
+ * ```rb
+ * return
+ * return value
+ * break
+ * break value
+ * next
+ * next value
+ * ```
+ */
+class ReturningStmt extends Stmt, TReturningStmt {
+ private Ruby::ArgumentList getArgumentList() {
+ result = any(Ruby::Return g | this = TReturnStmt(g)).getChild()
+ or
+ result = any(Ruby::Break g | this = TBreakStmt(g)).getChild()
+ or
+ result = any(Ruby::Next g | this = TNextStmt(g)).getChild()
+ }
+
+ /** Gets the returned value, if any. */
+ final Expr getValue() {
+ toGenerated(result) =
+ any(Ruby::AstNode res |
+ exists(Ruby::ArgumentList a, int c |
+ a = this.getArgumentList() and c = count(a.getChild(_))
+ |
+ res = a.getChild(0) and c = 1
+ or
+ res = a and c > 1
+ )
+ )
+ }
+
+ final override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "getValue" and result = this.getValue()
+ }
+}
+
+/**
+ * A `return` statement.
+ * ```rb
+ * return
+ * return value
+ * ```
+ */
+class ReturnStmt extends ReturningStmt, TReturnStmt {
+ final override string getAPrimaryQlClass() { result = "ReturnStmt" }
+
+ final override string toString() { result = "return" }
+}
+
+/**
+ * A `break` statement.
+ * ```rb
+ * break
+ * break value
+ * ```
+ */
+class BreakStmt extends ReturningStmt, TBreakStmt {
+ final override string getAPrimaryQlClass() { result = "BreakStmt" }
+
+ final override string toString() { result = "break" }
+}
+
+/**
+ * A `next` statement.
+ * ```rb
+ * next
+ * next value
+ * ```
+ */
+class NextStmt extends ReturningStmt, TNextStmt {
+ final override string getAPrimaryQlClass() { result = "NextStmt" }
+
+ final override string toString() { result = "next" }
+}
+
+/**
+ * A `redo` statement.
+ * ```rb
+ * redo
+ * ```
+ */
+class RedoStmt extends Stmt, TRedoStmt {
+ final override string getAPrimaryQlClass() { result = "RedoStmt" }
+
+ final override string toString() { result = "redo" }
+}
+
+/**
+ * A `retry` statement.
+ * ```rb
+ * retry
+ * ```
+ */
+class RetryStmt extends Stmt, TRetryStmt {
+ final override string getAPrimaryQlClass() { result = "RetryStmt" }
+
+ final override string toString() { result = "retry" }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Variable.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Variable.qll
new file mode 100644
index 00000000000..12be2f99f5b
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/Variable.qll
@@ -0,0 +1,204 @@
+/** Provides classes for modeling program variables. */
+
+private import codeql.ruby.AST
+private import codeql.Locations
+private import internal.AST
+private import internal.TreeSitter
+private import internal.Variable
+
+/** A variable declared in a scope. */
+class Variable instanceof VariableImpl {
+ /** Gets the name of this variable. */
+ final string getName() { result = super.getNameImpl() }
+
+ /** Holds if the name of this variable is `name`. */
+ final predicate hasName(string name) { this.getName() = name }
+
+ /** Gets a textual representation of this variable. */
+ final string toString() { result = this.getName() }
+
+ /** Gets the location of this variable. */
+ final Location getLocation() { result = super.getLocationImpl() }
+
+ /** Gets the scope this variable is declared in. */
+ final Scope getDeclaringScope() {
+ toGenerated(result) = this.(VariableReal).getDeclaringScopeImpl()
+ }
+
+ /** Gets an access to this variable. */
+ VariableAccess getAnAccess() { result.getVariable() = this }
+}
+
+/** A local variable. */
+class LocalVariable extends Variable, TLocalVariable {
+ override LocalVariableAccess getAnAccess() { result.getVariable() = this }
+
+ /** Gets the access where this local variable is first introduced. */
+ VariableAccess getDefiningAccess() { result = this.(LocalVariableReal).getDefiningAccessImpl() }
+
+ /**
+ * Holds if this variable is captured. For example in
+ *
+ * ```rb
+ * def m x
+ * x.times do |y|
+ * puts x
+ * end
+ * puts x
+ * end
+ * ```
+ *
+ * `x` is a captured variable, whereas `y` is not.
+ */
+ predicate isCaptured() { this.getAnAccess().isCapturedAccess() }
+}
+
+/** A global variable. */
+class GlobalVariable extends Variable instanceof GlobalVariableImpl {
+ final override GlobalVariableAccess getAnAccess() { result.getVariable() = this }
+}
+
+/** An instance variable. */
+class InstanceVariable extends Variable instanceof InstanceVariableImpl {
+ /** Holds is this variable is a class instance variable. */
+ final predicate isClassInstanceVariable() { super.isClassInstanceVariable() }
+
+ final override InstanceVariableAccess getAnAccess() { result.getVariable() = this }
+}
+
+/** A class variable. */
+class ClassVariable extends Variable instanceof ClassVariableImpl {
+ final override ClassVariableAccess getAnAccess() { result.getVariable() = this }
+}
+
+/** A `self` variable. */
+class SelfVariable extends LocalVariable instanceof SelfVariableImpl { }
+
+/** An access to a variable. */
+class VariableAccess extends Expr instanceof VariableAccessImpl {
+ /** Gets the variable this identifier refers to. */
+ final Variable getVariable() { result = super.getVariableImpl() }
+
+ /**
+ * Holds if this access is a write access belonging to the explicit
+ * assignment `assignment`. For example, in
+ *
+ * ```rb
+ * a, b = foo
+ * ```
+ *
+ * both `a` and `b` are write accesses belonging to the same assignment.
+ */
+ predicate isExplicitWrite(AstNode assignment) {
+ explicitWriteAccess(toGenerated(this), toGenerated(assignment))
+ or
+ this = assignment.(AssignExpr).getLeftOperand()
+ }
+
+ /**
+ * Holds if this access is a write access belonging to an implicit assignment.
+ * For example, in
+ *
+ * ```rb
+ * def m elements
+ * for e in elements do
+ * puts e
+ * end
+ * end
+ * ```
+ *
+ * the access to `elements` in the parameter list is an implicit assignment,
+ * as is the first access to `e`.
+ */
+ predicate isImplicitWrite() { implicitWriteAccess(toGenerated(this)) }
+
+ final override string toString() { result = VariableAccessImpl.super.toString() }
+}
+
+/** An access to a variable where the value is updated. */
+class VariableWriteAccess extends VariableAccess {
+ VariableWriteAccess() {
+ this.isExplicitWrite(_) or
+ this.isImplicitWrite()
+ }
+}
+
+/** An access to a variable where the value is read. */
+class VariableReadAccess extends VariableAccess {
+ VariableReadAccess() { not this instanceof VariableWriteAccess }
+}
+
+/** An access to a local variable. */
+class LocalVariableAccess extends VariableAccess instanceof LocalVariableAccessImpl {
+ override string getAPrimaryQlClass() { result = "LocalVariableAccess" }
+
+ /**
+ * Holds if this access is a captured variable access. For example in
+ *
+ * ```rb
+ * def m x
+ * x.times do |y|
+ * puts x
+ * end
+ * puts x
+ * end
+ * ```
+ *
+ * the access to `x` in the first `puts x` is a captured access, while
+ * the access to `x` in the second `puts x` is not.
+ */
+ predicate isCapturedAccess() { isCapturedAccess(this) }
+}
+
+/** An access to a local variable where the value is updated. */
+class LocalVariableWriteAccess extends LocalVariableAccess, VariableWriteAccess { }
+
+/** An access to a local variable where the value is read. */
+class LocalVariableReadAccess extends LocalVariableAccess, VariableReadAccess { }
+
+/** An access to a global variable. */
+class GlobalVariableAccess extends VariableAccess instanceof GlobalVariableAccessImpl {
+ final override string getAPrimaryQlClass() { result = "GlobalVariableAccess" }
+}
+
+/** An access to a global variable where the value is updated. */
+class GlobalVariableWriteAccess extends GlobalVariableAccess, VariableWriteAccess { }
+
+/** An access to a global variable where the value is read. */
+class GlobalVariableReadAccess extends GlobalVariableAccess, VariableReadAccess { }
+
+/** An access to an instance variable. */
+class InstanceVariableAccess extends VariableAccess instanceof InstanceVariableAccessImpl {
+ final override string getAPrimaryQlClass() { result = "InstanceVariableAccess" }
+}
+
+/** An access to an instance variable where the value is updated. */
+class InstanceVariableWriteAccess extends InstanceVariableAccess, VariableWriteAccess { }
+
+/** An access to an instance variable where the value is read. */
+class InstanceVariableReadAccess extends InstanceVariableAccess, VariableReadAccess { }
+
+/** An access to a class variable. */
+class ClassVariableAccess extends VariableAccess instanceof ClassVariableAccessRealImpl {
+ final override string getAPrimaryQlClass() { result = "ClassVariableAccess" }
+}
+
+/** An access to a class variable where the value is updated. */
+class ClassVariableWriteAccess extends ClassVariableAccess, VariableWriteAccess { }
+
+/** An access to a class variable where the value is read. */
+class ClassVariableReadAccess extends ClassVariableAccess, VariableReadAccess { }
+
+/** An access to the `self` variable */
+class SelfVariableAccess extends LocalVariableAccess instanceof SelfVariableAccessImpl {
+ final override string getAPrimaryQlClass() { result = "SelfVariableAccess" }
+}
+
+/** An access to the `self` variable where the value is read. */
+class SelfVariableReadAccess extends SelfVariableAccess, VariableReadAccess {
+ // We override the definition in `LocalVariableAccess` because it gives the
+ // wrong result for synthesised `self` variables.
+ override predicate isCapturedAccess() {
+ this.getVariable().getDeclaringScope() != this.getCfgScope()
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/AST.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/AST.qll
new file mode 100644
index 00000000000..349b28474ba
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/AST.qll
@@ -0,0 +1,754 @@
+import codeql.Locations
+private import TreeSitter
+private import codeql.ruby.ast.internal.Call
+private import codeql.ruby.ast.internal.Parameter
+private import codeql.ruby.ast.internal.Variable
+private import codeql.ruby.AST as AST
+private import Synthesis
+
+module MethodName {
+ predicate range(Ruby::UnderscoreMethodName g) {
+ exists(Ruby::Undef u | u.getChild(_) = g)
+ or
+ exists(Ruby::Alias a | a.getName() = g or a.getAlias() = g)
+ }
+
+ class Token =
+ @ruby_setter or @ruby_token_class_variable or @ruby_token_constant or
+ @ruby_token_global_variable or @ruby_token_identifier or @ruby_token_instance_variable or
+ @ruby_token_operator;
+}
+
+private predicate mkSynthChild(SynthKind kind, AST::AstNode parent, int i) {
+ any(Synthesis s).child(parent, i, SynthChild(kind))
+}
+
+cached
+private module Cached {
+ cached
+ newtype TAstNode =
+ TAddExprReal(Ruby::Binary g) { g instanceof @ruby_binary_plus } or
+ TAddExprSynth(AST::AstNode parent, int i) { mkSynthChild(AddExprKind(), parent, i) } or
+ TAliasStmt(Ruby::Alias g) or
+ TArgumentList(Ruby::AstNode g) {
+ (
+ g.getParent() instanceof Ruby::Break or
+ g.getParent() instanceof Ruby::Return or
+ g.getParent() instanceof Ruby::Next or
+ g.getParent() instanceof Ruby::Assignment or
+ g.getParent() instanceof Ruby::OperatorAssignment
+ ) and
+ (
+ strictcount(g.(Ruby::ArgumentList).getChild(_)) > 1
+ or
+ g instanceof Ruby::RightAssignmentList
+ )
+ } or
+ TAssignAddExpr(Ruby::OperatorAssignment g) { g instanceof @ruby_operator_assignment_plusequal } or
+ TAssignBitwiseAndExpr(Ruby::OperatorAssignment g) {
+ g instanceof @ruby_operator_assignment_ampersandequal
+ } or
+ TAssignBitwiseOrExpr(Ruby::OperatorAssignment g) {
+ g instanceof @ruby_operator_assignment_pipeequal
+ } or
+ TAssignBitwiseXorExpr(Ruby::OperatorAssignment g) {
+ g instanceof @ruby_operator_assignment_caretequal
+ } or
+ TAssignDivExpr(Ruby::OperatorAssignment g) { g instanceof @ruby_operator_assignment_slashequal } or
+ TAssignExponentExpr(Ruby::OperatorAssignment g) {
+ g instanceof @ruby_operator_assignment_starstarequal
+ } or
+ TAssignExprReal(Ruby::Assignment g) or
+ TAssignExprSynth(AST::AstNode parent, int i) { mkSynthChild(AssignExprKind(), parent, i) } or
+ TAssignLShiftExpr(Ruby::OperatorAssignment g) {
+ g instanceof @ruby_operator_assignment_langlelangleequal
+ } or
+ TAssignLogicalAndExpr(Ruby::OperatorAssignment g) {
+ g instanceof @ruby_operator_assignment_ampersandampersandequal
+ } or
+ TAssignLogicalOrExpr(Ruby::OperatorAssignment g) {
+ g instanceof @ruby_operator_assignment_pipepipeequal
+ } or
+ TAssignModuloExpr(Ruby::OperatorAssignment g) {
+ g instanceof @ruby_operator_assignment_percentequal
+ } or
+ TAssignMulExpr(Ruby::OperatorAssignment g) { g instanceof @ruby_operator_assignment_starequal } or
+ TAssignRShiftExpr(Ruby::OperatorAssignment g) {
+ g instanceof @ruby_operator_assignment_ranglerangleequal
+ } or
+ TAssignSubExpr(Ruby::OperatorAssignment g) { g instanceof @ruby_operator_assignment_minusequal } or
+ TBareStringLiteral(Ruby::BareString g) or
+ TBareSymbolLiteral(Ruby::BareSymbol g) or
+ TBeginBlock(Ruby::BeginBlock g) or
+ TBeginExpr(Ruby::Begin g) or
+ TBitwiseAndExprReal(Ruby::Binary g) { g instanceof @ruby_binary_ampersand } or
+ TBitwiseAndExprSynth(AST::AstNode parent, int i) {
+ mkSynthChild(BitwiseAndExprKind(), parent, i)
+ } or
+ TBitwiseOrExprReal(Ruby::Binary g) { g instanceof @ruby_binary_pipe } or
+ TBitwiseOrExprSynth(AST::AstNode parent, int i) { mkSynthChild(BitwiseOrExprKind(), parent, i) } or
+ TBitwiseXorExprReal(Ruby::Binary g) { g instanceof @ruby_binary_caret } or
+ TBitwiseXorExprSynth(AST::AstNode parent, int i) {
+ mkSynthChild(BitwiseXorExprKind(), parent, i)
+ } or
+ TBlockArgument(Ruby::BlockArgument g) or
+ TBlockParameter(Ruby::BlockParameter g) or
+ TBraceBlock(Ruby::Block g) { not g.getParent() instanceof Ruby::Lambda } or
+ TBreakStmt(Ruby::Break g) or
+ TCaseEqExpr(Ruby::Binary g) { g instanceof @ruby_binary_equalequalequal } or
+ TCaseExpr(Ruby::Case g) or
+ TCharacterLiteral(Ruby::Character g) or
+ TClassDeclaration(Ruby::Class g) or
+ TClassVariableAccessReal(Ruby::ClassVariable g, AST::ClassVariable v) {
+ ClassVariableAccess::range(g, v)
+ } or
+ TClassVariableAccessSynth(AST::AstNode parent, int i, AST::ClassVariable v) {
+ mkSynthChild(ClassVariableAccessKind(v), parent, i)
+ } or
+ TComplementExpr(Ruby::Unary g) { g instanceof @ruby_unary_tilde } or
+ TComplexLiteral(Ruby::Complex g) or
+ TConstantReadAccessSynth(AST::AstNode parent, int i, string value) {
+ mkSynthChild(ConstantReadAccessKind(value), parent, i)
+ } or
+ TDefinedExpr(Ruby::Unary g) { g instanceof @ruby_unary_definedquestion } or
+ TDelimitedSymbolLiteral(Ruby::DelimitedSymbol g) or
+ TDestructuredLeftAssignment(Ruby::DestructuredLeftAssignment g) {
+ not strictcount(int i | exists(g.getParent().(Ruby::LeftAssignmentList).getChild(i))) = 1
+ } or
+ TDivExprReal(Ruby::Binary g) { g instanceof @ruby_binary_slash } or
+ TDivExprSynth(AST::AstNode parent, int i) { mkSynthChild(DivExprKind(), parent, i) } or
+ TDo(Ruby::Do g) or
+ TDoBlock(Ruby::DoBlock g) { not g.getParent() instanceof Ruby::Lambda } or
+ TElementReference(Ruby::ElementReference g) or
+ TElse(Ruby::Else g) or
+ TElsif(Ruby::Elsif g) or
+ TEmptyStmt(Ruby::EmptyStatement g) or
+ TEndBlock(Ruby::EndBlock g) or
+ TEnsure(Ruby::Ensure g) or
+ TEqExpr(Ruby::Binary g) { g instanceof @ruby_binary_equalequal } or
+ TExponentExprReal(Ruby::Binary g) { g instanceof @ruby_binary_starstar } or
+ TExponentExprSynth(AST::AstNode parent, int i) { mkSynthChild(ExponentExprKind(), parent, i) } or
+ TFalseLiteral(Ruby::False g) or
+ TFloatLiteral(Ruby::Float g) { not any(Ruby::Rational r).getChild() = g } or
+ TForExpr(Ruby::For g) or
+ TForIn(Ruby::In g) or // TODO REMOVE
+ TForwardParameter(Ruby::ForwardParameter g) or
+ TForwardArgument(Ruby::ForwardArgument g) or
+ TGEExpr(Ruby::Binary g) { g instanceof @ruby_binary_rangleequal } or
+ TGTExpr(Ruby::Binary g) { g instanceof @ruby_binary_rangle } or
+ TGlobalVariableAccessReal(Ruby::GlobalVariable g, AST::GlobalVariable v) {
+ GlobalVariableAccess::range(g, v)
+ } or
+ TGlobalVariableAccessSynth(AST::AstNode parent, int i, AST::GlobalVariable v) {
+ mkSynthChild(GlobalVariableAccessKind(v), parent, i)
+ } or
+ THashKeySymbolLiteral(Ruby::HashKeySymbol g) or
+ THashLiteral(Ruby::Hash g) or
+ THashSplatExpr(Ruby::HashSplatArgument g) or
+ THashSplatParameter(Ruby::HashSplatParameter g) or
+ THereDoc(Ruby::HeredocBeginning g) or
+ TIdentifierMethodCall(Ruby::Identifier g) { isIdentifierMethodCall(g) } or
+ TIf(Ruby::If g) or
+ TIfModifierExpr(Ruby::IfModifier g) or
+ TInstanceVariableAccessReal(Ruby::InstanceVariable g, AST::InstanceVariable v) {
+ InstanceVariableAccess::range(g, v)
+ } or
+ TInstanceVariableAccessSynth(AST::AstNode parent, int i, AST::InstanceVariable v) {
+ mkSynthChild(InstanceVariableAccessKind(v), parent, i)
+ } or
+ TIntegerLiteralReal(Ruby::Integer g) { not any(Ruby::Rational r).getChild() = g } or
+ TIntegerLiteralSynth(AST::AstNode parent, int i, int value) {
+ mkSynthChild(IntegerLiteralKind(value), parent, i)
+ } or
+ TKeywordParameter(Ruby::KeywordParameter g) or
+ TLEExpr(Ruby::Binary g) { g instanceof @ruby_binary_langleequal } or
+ TLShiftExprReal(Ruby::Binary g) { g instanceof @ruby_binary_langlelangle } or
+ TLShiftExprSynth(AST::AstNode parent, int i) { mkSynthChild(LShiftExprKind(), parent, i) } or
+ TLTExpr(Ruby::Binary g) { g instanceof @ruby_binary_langle } or
+ TLambda(Ruby::Lambda g) or
+ TLeftAssignmentList(Ruby::LeftAssignmentList g) or
+ TLocalVariableAccessReal(Ruby::Identifier g, TLocalVariableReal v) {
+ LocalVariableAccess::range(g, v)
+ } or
+ TLocalVariableAccessSynth(AST::AstNode parent, int i, AST::LocalVariable v) {
+ mkSynthChild(LocalVariableAccessRealKind(v), parent, i)
+ or
+ mkSynthChild(LocalVariableAccessSynthKind(v), parent, i)
+ } or
+ TLogicalAndExprReal(Ruby::Binary g) {
+ g instanceof @ruby_binary_and or g instanceof @ruby_binary_ampersandampersand
+ } or
+ TLogicalAndExprSynth(AST::AstNode parent, int i) {
+ mkSynthChild(LogicalAndExprKind(), parent, i)
+ } or
+ TLogicalOrExprReal(Ruby::Binary g) {
+ g instanceof @ruby_binary_or or g instanceof @ruby_binary_pipepipe
+ } or
+ TLogicalOrExprSynth(AST::AstNode parent, int i) { mkSynthChild(LogicalOrExprKind(), parent, i) } or
+ TMethod(Ruby::Method g) or
+ TMethodCallSynth(AST::AstNode parent, int i, string name, boolean setter, int arity) {
+ mkSynthChild(MethodCallKind(name, setter, arity), parent, i)
+ } or
+ TModuleDeclaration(Ruby::Module g) or
+ TModuloExprReal(Ruby::Binary g) { g instanceof @ruby_binary_percent } or
+ TModuloExprSynth(AST::AstNode parent, int i) { mkSynthChild(ModuloExprKind(), parent, i) } or
+ TMulExprReal(Ruby::Binary g) { g instanceof @ruby_binary_star } or
+ TMulExprSynth(AST::AstNode parent, int i) { mkSynthChild(MulExprKind(), parent, i) } or
+ TNEExpr(Ruby::Binary g) { g instanceof @ruby_binary_bangequal } or
+ TNextStmt(Ruby::Next g) or
+ TNilLiteral(Ruby::Nil g) or
+ TNoRegExpMatchExpr(Ruby::Binary g) { g instanceof @ruby_binary_bangtilde } or
+ TNotExpr(Ruby::Unary g) { g instanceof @ruby_unary_bang or g instanceof @ruby_unary_not } or
+ TOptionalParameter(Ruby::OptionalParameter g) or
+ TPair(Ruby::Pair g) or
+ TParenthesizedExpr(Ruby::ParenthesizedStatements g) or
+ TRShiftExprReal(Ruby::Binary g) { g instanceof @ruby_binary_ranglerangle } or
+ TRShiftExprSynth(AST::AstNode parent, int i) { mkSynthChild(RShiftExprKind(), parent, i) } or
+ TRangeLiteralReal(Ruby::Range g) or
+ TRangeLiteralSynth(AST::AstNode parent, int i, boolean inclusive) {
+ mkSynthChild(RangeLiteralKind(inclusive), parent, i)
+ } or
+ TRationalLiteral(Ruby::Rational g) or
+ TRedoStmt(Ruby::Redo g) or
+ TRegExpLiteral(Ruby::Regex g) or
+ TRegExpMatchExpr(Ruby::Binary g) { g instanceof @ruby_binary_equaltilde } or
+ TRegularArrayLiteral(Ruby::Array g) or
+ TRegularMethodCall(Ruby::Call g) { isRegularMethodCall(g) } or
+ TRegularStringLiteral(Ruby::String g) or
+ TRegularSuperCall(Ruby::Call g) { g.getMethod() instanceof Ruby::Super } or
+ TRescueClause(Ruby::Rescue g) or
+ TRescueModifierExpr(Ruby::RescueModifier g) or
+ TRetryStmt(Ruby::Retry g) or
+ TReturnStmt(Ruby::Return g) or
+ TScopeResolutionConstantAccess(Ruby::ScopeResolution g, Ruby::Constant constant) {
+ constant = g.getName() and
+ (
+ // A tree-sitter `scope_resolution` node with a `constant` name field is a
+ // read of that constant in any context where an identifier would be a
+ // vcall.
+ vcall(g)
+ or
+ explicitAssignmentNode(g, _)
+ )
+ } or
+ TScopeResolutionMethodCall(Ruby::ScopeResolution g, Ruby::Identifier i) {
+ isScopeResolutionMethodCall(g, i)
+ } or
+ TSelfReal(Ruby::Self g) or
+ TSelfSynth(AST::AstNode parent, int i, AST::SelfVariable v) {
+ mkSynthChild(SelfKind(v), parent, i)
+ } or
+ TSimpleParameter(Ruby::Identifier g) { g instanceof Parameter::Range } or
+ TSimpleSymbolLiteral(Ruby::SimpleSymbol g) or
+ TSingletonClass(Ruby::SingletonClass g) or
+ TSingletonMethod(Ruby::SingletonMethod g) or
+ TSpaceshipExpr(Ruby::Binary g) { g instanceof @ruby_binary_langleequalrangle } or
+ TSplatExprReal(Ruby::SplatArgument g) or
+ TSplatExprSynth(AST::AstNode parent, int i) { mkSynthChild(SplatExprKind(), parent, i) } or
+ TSplatParameter(Ruby::SplatParameter g) or
+ TStmtSequenceSynth(AST::AstNode parent, int i) { mkSynthChild(StmtSequenceKind(), parent, i) } or
+ TStringArrayLiteral(Ruby::StringArray g) or
+ TStringConcatenation(Ruby::ChainedString g) or
+ TStringEscapeSequenceComponent(Ruby::EscapeSequence g) or
+ TStringInterpolationComponent(Ruby::Interpolation g) or
+ TStringTextComponent(Ruby::Token g) {
+ g instanceof Ruby::StringContent or g instanceof Ruby::HeredocContent
+ } or
+ TSubExprReal(Ruby::Binary g) { g instanceof @ruby_binary_minus } or
+ TSubExprSynth(AST::AstNode parent, int i) { mkSynthChild(SubExprKind(), parent, i) } or
+ TSubshellLiteral(Ruby::Subshell g) or
+ TSymbolArrayLiteral(Ruby::SymbolArray g) or
+ TTernaryIfExpr(Ruby::Conditional g) or
+ TThen(Ruby::Then g) or
+ TTokenConstantAccess(Ruby::Constant g) {
+ // A tree-sitter `constant` token is a read of that constant in any context
+ // where an identifier would be a vcall.
+ vcall(g)
+ or
+ explicitAssignmentNode(g, _)
+ } or
+ TTokenMethodName(MethodName::Token g) { MethodName::range(g) } or
+ TTokenSuperCall(Ruby::Super g) { vcall(g) } or
+ TToplevel(Ruby::Program g) or
+ TTrueLiteral(Ruby::True g) or
+ TTuplePatternParameter(Ruby::DestructuredParameter g) or
+ TUnaryMinusExpr(Ruby::Unary g) { g instanceof @ruby_unary_minus } or
+ TUnaryPlusExpr(Ruby::Unary g) { g instanceof @ruby_unary_plus } or
+ TUndefStmt(Ruby::Undef g) or
+ TUnlessExpr(Ruby::Unless g) or
+ TUnlessModifierExpr(Ruby::UnlessModifier g) or
+ TUntilExpr(Ruby::Until g) or
+ TUntilModifierExpr(Ruby::UntilModifier g) or
+ TWhenExpr(Ruby::When g) or
+ TWhileExpr(Ruby::While g) or
+ TWhileModifierExpr(Ruby::WhileModifier g) or
+ TYieldCall(Ruby::Yield g)
+
+ class TAstNodeReal =
+ TAddExprReal or TAliasStmt or TArgumentList or TAssignAddExpr or TAssignBitwiseAndExpr or
+ TAssignBitwiseOrExpr or TAssignBitwiseXorExpr or TAssignDivExpr or TAssignExponentExpr or
+ TAssignExprReal or TAssignLShiftExpr or TAssignLogicalAndExpr or TAssignLogicalOrExpr or
+ TAssignModuloExpr or TAssignMulExpr or TAssignRShiftExpr or TAssignSubExpr or
+ TBareStringLiteral or TBareSymbolLiteral or TBeginBlock or TBeginExpr or
+ TBitwiseAndExprReal or TBitwiseOrExprReal or TBitwiseXorExprReal or TBlockArgument or
+ TBlockParameter or TBraceBlock or TBreakStmt or TCaseEqExpr or TCaseExpr or
+ TCharacterLiteral or TClassDeclaration or TClassVariableAccessReal or TComplementExpr or
+ TComplexLiteral or TDefinedExpr or TDelimitedSymbolLiteral or TDestructuredLeftAssignment or
+ TDivExprReal or TDo or TDoBlock or TElementReference or TElse or TElsif or TEmptyStmt or
+ TEndBlock or TEnsure or TEqExpr or TExponentExprReal or TFalseLiteral or TFloatLiteral or
+ TForExpr or TForIn or TForwardParameter or TForwardArgument or TGEExpr or TGTExpr or
+ TGlobalVariableAccessReal or THashKeySymbolLiteral or THashLiteral or THashSplatExpr or
+ THashSplatParameter or THereDoc or TIdentifierMethodCall or TIf or TIfModifierExpr or
+ TInstanceVariableAccessReal or TIntegerLiteralReal or TKeywordParameter or TLEExpr or
+ TLShiftExprReal or TLTExpr or TLambda or TLeftAssignmentList or TLocalVariableAccessReal or
+ TLogicalAndExprReal or TLogicalOrExprReal or TMethod or TModuleDeclaration or
+ TModuloExprReal or TMulExprReal or TNEExpr or TNextStmt or TNilLiteral or
+ TNoRegExpMatchExpr or TNotExpr or TOptionalParameter or TPair or TParenthesizedExpr or
+ TRShiftExprReal or TRangeLiteralReal or TRationalLiteral or TRedoStmt or TRegExpLiteral or
+ TRegExpMatchExpr or TRegularArrayLiteral or TRegularMethodCall or TRegularStringLiteral or
+ TRegularSuperCall or TRescueClause or TRescueModifierExpr or TRetryStmt or TReturnStmt or
+ TScopeResolutionConstantAccess or TScopeResolutionMethodCall or TSelfReal or
+ TSimpleParameter or TSimpleSymbolLiteral or TSingletonClass or TSingletonMethod or
+ TSpaceshipExpr or TSplatExprReal or TSplatParameter or TStringArrayLiteral or
+ TStringConcatenation or TStringEscapeSequenceComponent or TStringInterpolationComponent or
+ TStringTextComponent or TSubExprReal or TSubshellLiteral or TSymbolArrayLiteral or
+ TTernaryIfExpr or TThen or TTokenConstantAccess or TTokenMethodName or TTokenSuperCall or
+ TToplevel or TTrueLiteral or TTuplePatternParameter or TUnaryMinusExpr or TUnaryPlusExpr or
+ TUndefStmt or TUnlessExpr or TUnlessModifierExpr or TUntilExpr or TUntilModifierExpr or
+ TWhenExpr or TWhileExpr or TWhileModifierExpr or TYieldCall;
+
+ class TAstNodeSynth =
+ TAddExprSynth or TAssignExprSynth or TBitwiseAndExprSynth or TBitwiseOrExprSynth or
+ TBitwiseXorExprSynth or TClassVariableAccessSynth or TConstantReadAccessSynth or
+ TDivExprSynth or TExponentExprSynth or TGlobalVariableAccessSynth or
+ TInstanceVariableAccessSynth or TIntegerLiteralSynth or TLShiftExprSynth or
+ TLocalVariableAccessSynth or TLogicalAndExprSynth or TLogicalOrExprSynth or
+ TMethodCallSynth or TModuloExprSynth or TMulExprSynth or TRShiftExprSynth or
+ TRangeLiteralSynth or TSelfSynth or TSplatExprSynth or TStmtSequenceSynth or TSubExprSynth;
+
+ /**
+ * Gets the underlying TreeSitter entity for a given AST node. This does not
+ * include synthesized AST nodes, because they are not the primary AST node
+ * for any given generated node.
+ */
+ cached
+ Ruby::AstNode toGenerated(TAstNodeReal n) {
+ n = TAddExprReal(result) or
+ n = TAliasStmt(result) or
+ n = TArgumentList(result) or
+ n = TAssignAddExpr(result) or
+ n = TAssignBitwiseAndExpr(result) or
+ n = TAssignBitwiseOrExpr(result) or
+ n = TAssignBitwiseXorExpr(result) or
+ n = TAssignDivExpr(result) or
+ n = TAssignExponentExpr(result) or
+ n = TAssignExprReal(result) or
+ n = TAssignLShiftExpr(result) or
+ n = TAssignLogicalAndExpr(result) or
+ n = TAssignLogicalOrExpr(result) or
+ n = TAssignModuloExpr(result) or
+ n = TAssignMulExpr(result) or
+ n = TAssignRShiftExpr(result) or
+ n = TAssignSubExpr(result) or
+ n = TBareStringLiteral(result) or
+ n = TBareSymbolLiteral(result) or
+ n = TBeginBlock(result) or
+ n = TBeginExpr(result) or
+ n = TBitwiseAndExprReal(result) or
+ n = TBitwiseOrExprReal(result) or
+ n = TBitwiseXorExprReal(result) or
+ n = TBlockArgument(result) or
+ n = TBlockParameter(result) or
+ n = TBraceBlock(result) or
+ n = TBreakStmt(result) or
+ n = TCaseEqExpr(result) or
+ n = TCaseExpr(result) or
+ n = TCharacterLiteral(result) or
+ n = TClassDeclaration(result) or
+ n = TClassVariableAccessReal(result, _) or
+ n = TComplementExpr(result) or
+ n = TComplexLiteral(result) or
+ n = TDefinedExpr(result) or
+ n = TDelimitedSymbolLiteral(result) or
+ n = TDestructuredLeftAssignment(result) or
+ n = TDivExprReal(result) or
+ n = TDo(result) or
+ n = TDoBlock(result) or
+ n = TElementReference(result) or
+ n = TElse(result) or
+ n = TElsif(result) or
+ n = TEmptyStmt(result) or
+ n = TEndBlock(result) or
+ n = TEnsure(result) or
+ n = TEqExpr(result) or
+ n = TExponentExprReal(result) or
+ n = TFalseLiteral(result) or
+ n = TFloatLiteral(result) or
+ n = TForExpr(result) or
+ n = TForIn(result) or // TODO REMOVE
+ n = TForwardArgument(result) or
+ n = TForwardParameter(result) or
+ n = TGEExpr(result) or
+ n = TGTExpr(result) or
+ n = TGlobalVariableAccessReal(result, _) or
+ n = THashKeySymbolLiteral(result) or
+ n = THashLiteral(result) or
+ n = THashSplatExpr(result) or
+ n = THashSplatParameter(result) or
+ n = THereDoc(result) or
+ n = TIdentifierMethodCall(result) or
+ n = TIf(result) or
+ n = TIfModifierExpr(result) or
+ n = TInstanceVariableAccessReal(result, _) or
+ n = TIntegerLiteralReal(result) or
+ n = TKeywordParameter(result) or
+ n = TLEExpr(result) or
+ n = TLShiftExprReal(result) or
+ n = TLTExpr(result) or
+ n = TLambda(result) or
+ n = TLeftAssignmentList(result) or
+ n = TLocalVariableAccessReal(result, _) or
+ n = TLogicalAndExprReal(result) or
+ n = TLogicalOrExprReal(result) or
+ n = TMethod(result) or
+ n = TModuleDeclaration(result) or
+ n = TModuloExprReal(result) or
+ n = TMulExprReal(result) or
+ n = TNEExpr(result) or
+ n = TNextStmt(result) or
+ n = TNilLiteral(result) or
+ n = TNoRegExpMatchExpr(result) or
+ n = TNotExpr(result) or
+ n = TOptionalParameter(result) or
+ n = TPair(result) or
+ n = TParenthesizedExpr(result) or
+ n = TRShiftExprReal(result) or
+ n = TRangeLiteralReal(result) or
+ n = TRationalLiteral(result) or
+ n = TRedoStmt(result) or
+ n = TRegExpLiteral(result) or
+ n = TRegExpMatchExpr(result) or
+ n = TRegularArrayLiteral(result) or
+ n = TRegularMethodCall(result) or
+ n = TRegularStringLiteral(result) or
+ n = TRegularSuperCall(result) or
+ n = TRescueClause(result) or
+ n = TRescueModifierExpr(result) or
+ n = TRetryStmt(result) or
+ n = TReturnStmt(result) or
+ n = TScopeResolutionConstantAccess(result, _) or
+ n = TScopeResolutionMethodCall(result, _) or
+ n = TSelfReal(result) or
+ n = TSimpleParameter(result) or
+ n = TSimpleSymbolLiteral(result) or
+ n = TSingletonClass(result) or
+ n = TSingletonMethod(result) or
+ n = TSpaceshipExpr(result) or
+ n = TSplatExprReal(result) or
+ n = TSplatParameter(result) or
+ n = TStringArrayLiteral(result) or
+ n = TStringConcatenation(result) or
+ n = TStringEscapeSequenceComponent(result) or
+ n = TStringInterpolationComponent(result) or
+ n = TStringTextComponent(result) or
+ n = TSubExprReal(result) or
+ n = TSubshellLiteral(result) or
+ n = TSymbolArrayLiteral(result) or
+ n = TTernaryIfExpr(result) or
+ n = TThen(result) or
+ n = TTokenConstantAccess(result) or
+ n = TTokenMethodName(result) or
+ n = TTokenSuperCall(result) or
+ n = TToplevel(result) or
+ n = TTrueLiteral(result) or
+ n = TTuplePatternParameter(result) or
+ n = TUnaryMinusExpr(result) or
+ n = TUnaryPlusExpr(result) or
+ n = TUndefStmt(result) or
+ n = TUnlessExpr(result) or
+ n = TUnlessModifierExpr(result) or
+ n = TUntilExpr(result) or
+ n = TUntilModifierExpr(result) or
+ n = TWhenExpr(result) or
+ n = TWhileExpr(result) or
+ n = TWhileModifierExpr(result) or
+ n = TYieldCall(result)
+ }
+
+ /** Gets the `i`th synthesized child of `parent`. */
+ cached
+ AST::AstNode getSynthChild(AST::AstNode parent, int i) {
+ result = TAddExprSynth(parent, i)
+ or
+ result = TAssignExprSynth(parent, i)
+ or
+ result = TBitwiseAndExprSynth(parent, i)
+ or
+ result = TBitwiseOrExprSynth(parent, i)
+ or
+ result = TBitwiseXorExprSynth(parent, i)
+ or
+ result = TClassVariableAccessSynth(parent, i, _)
+ or
+ result = TConstantReadAccessSynth(parent, i, _)
+ or
+ result = TDivExprSynth(parent, i)
+ or
+ result = TExponentExprSynth(parent, i)
+ or
+ result = TGlobalVariableAccessSynth(parent, i, _)
+ or
+ result = TInstanceVariableAccessSynth(parent, i, _)
+ or
+ result = TIntegerLiteralSynth(parent, i, _)
+ or
+ result = TLShiftExprSynth(parent, i)
+ or
+ result = TLocalVariableAccessSynth(parent, i, _)
+ or
+ result = TLogicalAndExprSynth(parent, i)
+ or
+ result = TLogicalOrExprSynth(parent, i)
+ or
+ result = TMethodCallSynth(parent, i, _, _, _)
+ or
+ result = TModuloExprSynth(parent, i)
+ or
+ result = TMulExprSynth(parent, i)
+ or
+ result = TRangeLiteralSynth(parent, i, _)
+ or
+ result = TRShiftExprSynth(parent, i)
+ or
+ result = TSelfSynth(parent, i, _)
+ or
+ result = TSplatExprSynth(parent, i)
+ or
+ result = TStmtSequenceSynth(parent, i)
+ or
+ result = TSubExprSynth(parent, i)
+ }
+
+ /**
+ * Holds if the `i`th child of `parent` is `child`. Either `parent` or
+ * `child` (or both) is a synthesized node.
+ */
+ cached
+ predicate synthChild(AST::AstNode parent, int i, AST::AstNode child) {
+ child = getSynthChild(parent, i)
+ or
+ any(Synthesis s).child(parent, i, RealChildRef(child))
+ or
+ any(Synthesis s).child(parent, i, SynthChildRef(child))
+ }
+
+ /**
+ * Like `toGenerated`, but also returns generated nodes for synthesized AST
+ * nodes.
+ */
+ cached
+ Ruby::AstNode toGeneratedInclSynth(AST::AstNode n) {
+ result = toGenerated(n)
+ or
+ not exists(toGenerated(n)) and
+ exists(AST::AstNode parent |
+ synthChild(parent, _, n) and
+ result = toGeneratedInclSynth(parent)
+ )
+ }
+
+ cached
+ Location getLocation(AST::AstNode n) {
+ synthLocation(n, result)
+ or
+ n.isSynthesized() and
+ not synthLocation(n, _) and
+ result = getLocation(n.getParent())
+ or
+ result = toGenerated(n).getLocation()
+ }
+}
+
+import Cached
+
+TAstNodeReal fromGenerated(Ruby::AstNode n) { n = toGenerated(result) }
+
+class TCall = TMethodCall or TYieldCall;
+
+class TMethodCall =
+ TMethodCallSynth or TIdentifierMethodCall or TScopeResolutionMethodCall or TRegularMethodCall or
+ TElementReference or TSuperCall or TUnaryOperation or TBinaryOperation;
+
+class TSuperCall = TTokenSuperCall or TRegularSuperCall;
+
+class TConstantAccess =
+ TTokenConstantAccess or TScopeResolutionConstantAccess or TNamespace or TConstantReadAccessSynth;
+
+class TControlExpr = TConditionalExpr or TCaseExpr or TLoop;
+
+class TConditionalExpr =
+ TIfExpr or TUnlessExpr or TIfModifierExpr or TUnlessModifierExpr or TTernaryIfExpr;
+
+class TIfExpr = TIf or TElsif;
+
+class TConditionalLoop = TWhileExpr or TUntilExpr or TWhileModifierExpr or TUntilModifierExpr;
+
+class TLoop = TConditionalLoop or TForExpr;
+
+class TSelf = TSelfReal or TSelfSynth;
+
+class TExpr =
+ TSelf or TArgumentList or TRescueClause or TRescueModifierExpr or TPair or TStringConcatenation or
+ TCall or TBlockArgument or TConstantAccess or TControlExpr or TWhenExpr or TLiteral or
+ TCallable or TVariableAccess or TStmtSequence or TOperation or TSimpleParameter or
+ TForwardArgument;
+
+class TSplatExpr = TSplatExprReal or TSplatExprSynth;
+
+class TStmtSequence =
+ TBeginBlock or TEndBlock or TThen or TElse or TDo or TEnsure or TStringInterpolationComponent or
+ TBlock or TBodyStmt or TParenthesizedExpr or TStmtSequenceSynth;
+
+class TBodyStmt = TBeginExpr or TModuleBase or TMethod or TLambda or TDoBlock or TSingletonMethod;
+
+class TLiteral =
+ TNumericLiteral or TNilLiteral or TBooleanLiteral or TStringlikeLiteral or TCharacterLiteral or
+ TArrayLiteral or THashLiteral or TRangeLiteral or TTokenMethodName;
+
+class TNumericLiteral = TIntegerLiteral or TFloatLiteral or TRationalLiteral or TComplexLiteral;
+
+class TIntegerLiteral = TIntegerLiteralReal or TIntegerLiteralSynth;
+
+class TBooleanLiteral = TTrueLiteral or TFalseLiteral;
+
+class TStringComponent =
+ TStringTextComponent or TStringEscapeSequenceComponent or TStringInterpolationComponent;
+
+class TStringlikeLiteral =
+ TStringLiteral or TRegExpLiteral or TSymbolLiteral or TSubshellLiteral or THereDoc;
+
+class TStringLiteral = TRegularStringLiteral or TBareStringLiteral;
+
+class TSymbolLiteral = TSimpleSymbolLiteral or TComplexSymbolLiteral or THashKeySymbolLiteral;
+
+class TComplexSymbolLiteral = TDelimitedSymbolLiteral or TBareSymbolLiteral;
+
+class TArrayLiteral = TRegularArrayLiteral or TStringArrayLiteral or TSymbolArrayLiteral;
+
+class TCallable = TMethodBase or TLambda or TBlock;
+
+class TMethodBase = TMethod or TSingletonMethod;
+
+class TBlock = TDoBlock or TBraceBlock;
+
+class TModuleBase = TToplevel or TNamespace or TSingletonClass;
+
+class TNamespace = TClassDeclaration or TModuleDeclaration;
+
+class TOperation = TUnaryOperation or TBinaryOperation or TAssignment;
+
+class TUnaryOperation =
+ TUnaryLogicalOperation or TUnaryArithmeticOperation or TUnaryBitwiseOperation or TDefinedExpr or
+ TSplatExpr or THashSplatExpr;
+
+class TUnaryLogicalOperation = TNotExpr;
+
+class TUnaryArithmeticOperation = TUnaryPlusExpr or TUnaryMinusExpr;
+
+class TUnaryBitwiseOperation = TComplementExpr;
+
+class TBinaryOperation =
+ TBinaryArithmeticOperation or TBinaryLogicalOperation or TBinaryBitwiseOperation or
+ TComparisonOperation or TSpaceshipExpr or TRegExpMatchExpr or TNoRegExpMatchExpr;
+
+class TBinaryArithmeticOperation =
+ TAddExpr or TSubExpr or TMulExpr or TDivExpr or TModuloExpr or TExponentExpr;
+
+class TAddExpr = TAddExprReal or TAddExprSynth;
+
+class TSubExpr = TSubExprReal or TSubExprSynth;
+
+class TMulExpr = TMulExprReal or TMulExprSynth;
+
+class TDivExpr = TDivExprReal or TDivExprSynth;
+
+class TModuloExpr = TModuloExprReal or TModuloExprSynth;
+
+class TExponentExpr = TExponentExprReal or TExponentExprSynth;
+
+class TBinaryLogicalOperation = TLogicalAndExpr or TLogicalOrExpr;
+
+class TLogicalAndExpr = TLogicalAndExprReal or TLogicalAndExprSynth;
+
+class TLogicalOrExpr = TLogicalOrExprReal or TLogicalOrExprSynth;
+
+class TBinaryBitwiseOperation =
+ TLShiftExpr or TRShiftExpr or TBitwiseAndExpr or TBitwiseOrExpr or TBitwiseXorExpr;
+
+class TLShiftExpr = TLShiftExprReal or TLShiftExprSynth;
+
+class TRangeLiteral = TRangeLiteralReal or TRangeLiteralSynth;
+
+class TRShiftExpr = TRShiftExprReal or TRShiftExprSynth;
+
+class TBitwiseAndExpr = TBitwiseAndExprReal or TBitwiseAndExprSynth;
+
+class TBitwiseOrExpr = TBitwiseOrExprReal or TBitwiseOrExprSynth;
+
+class TBitwiseXorExpr = TBitwiseXorExprReal or TBitwiseXorExprSynth;
+
+class TComparisonOperation = TEqualityOperation or TRelationalOperation;
+
+class TEqualityOperation = TEqExpr or TNEExpr or TCaseEqExpr;
+
+class TRelationalOperation = TGTExpr or TGEExpr or TLTExpr or TLEExpr;
+
+class TAssignExpr = TAssignExprReal or TAssignExprSynth;
+
+class TAssignment = TAssignExpr or TAssignOperation;
+
+class TAssignOperation =
+ TAssignArithmeticOperation or TAssignLogicalOperation or TAssignBitwiseOperation;
+
+class TAssignArithmeticOperation =
+ TAssignAddExpr or TAssignSubExpr or TAssignMulExpr or TAssignDivExpr or TAssignModuloExpr or
+ TAssignExponentExpr;
+
+class TAssignLogicalOperation = TAssignLogicalAndExpr or TAssignLogicalOrExpr;
+
+class TAssignBitwiseOperation =
+ TAssignLShiftExpr or TAssignRShiftExpr or TAssignBitwiseAndExpr or TAssignBitwiseOrExpr or
+ TAssignBitwiseXorExpr;
+
+class TStmt =
+ TEmptyStmt or TBodyStmt or TStmtSequence or TUndefStmt or TAliasStmt or TReturningStmt or
+ TRedoStmt or TRetryStmt or TExpr;
+
+class TReturningStmt = TReturnStmt or TBreakStmt or TNextStmt;
+
+class TParameter =
+ TPatternParameter or TBlockParameter or THashSplatParameter or TKeywordParameter or
+ TOptionalParameter or TSplatParameter or TForwardParameter;
+
+class TPatternParameter = TSimpleParameter or TTuplePatternParameter;
+
+class TNamedParameter =
+ TSimpleParameter or TBlockParameter or THashSplatParameter or TKeywordParameter or
+ TOptionalParameter or TSplatParameter;
+
+class TTuplePattern = TTuplePatternParameter or TDestructuredLeftAssignment or TLeftAssignmentList;
+
+class TVariableAccess =
+ TLocalVariableAccess or TGlobalVariableAccess or TInstanceVariableAccess or
+ TClassVariableAccess or TSelfVariableAccess;
+
+class TLocalVariableAccess =
+ TLocalVariableAccessReal or TLocalVariableAccessSynth or TSelfVariableAccess;
+
+class TGlobalVariableAccess = TGlobalVariableAccessReal or TGlobalVariableAccessSynth;
+
+class TInstanceVariableAccess = TInstanceVariableAccessReal or TInstanceVariableAccessSynth;
+
+class TClassVariableAccess = TClassVariableAccessReal or TClassVariableAccessSynth;
+
+class TSelfVariableAccess = TSelfReal or TSelfSynth;
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Call.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Call.qll
new file mode 100644
index 00000000000..eea4392a4fb
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Call.qll
@@ -0,0 +1,186 @@
+private import TreeSitter
+private import Variable
+private import codeql.ruby.AST
+private import codeql.ruby.ast.internal.AST
+
+predicate isIdentifierMethodCall(Ruby::Identifier g) { vcall(g) and not access(g, _) }
+
+predicate isRegularMethodCall(Ruby::Call g) { not g.getMethod() instanceof Ruby::Super }
+
+predicate isScopeResolutionMethodCall(Ruby::ScopeResolution g, Ruby::Identifier i) {
+ i = g.getName() and
+ not exists(Ruby::Call c | c.getMethod() = g)
+}
+
+abstract class CallImpl extends Expr, TCall {
+ abstract AstNode getArgumentImpl(int n);
+
+ /**
+ * It is not possible to define this predicate as
+ *
+ * ```ql
+ * result = count(this.getArgumentImpl(_))
+ * ```
+ *
+ * since that will result in a non-monotonicity error.
+ */
+ abstract int getNumberOfArgumentsImpl();
+}
+
+abstract class MethodCallImpl extends CallImpl, TMethodCall {
+ abstract AstNode getReceiverImpl();
+
+ abstract string getMethodNameImpl();
+
+ abstract Block getBlockImpl();
+}
+
+class MethodCallSynth extends MethodCallImpl, TMethodCallSynth {
+ final override string getMethodNameImpl() {
+ exists(boolean setter, string name | this = TMethodCallSynth(_, _, name, setter, _) |
+ setter = true and result = name + "="
+ or
+ setter = false and result = name
+ )
+ }
+
+ final override AstNode getReceiverImpl() { synthChild(this, 0, result) }
+
+ final override AstNode getArgumentImpl(int n) { synthChild(this, n + 1, result) and n >= 0 }
+
+ final override int getNumberOfArgumentsImpl() { this = TMethodCallSynth(_, _, _, _, result) }
+
+ final override Block getBlockImpl() { none() }
+}
+
+class IdentifierMethodCall extends MethodCallImpl, TIdentifierMethodCall {
+ private Ruby::Identifier g;
+
+ IdentifierMethodCall() { this = TIdentifierMethodCall(g) }
+
+ final override string getMethodNameImpl() { result = g.getValue() }
+
+ final override AstNode getReceiverImpl() { result = TSelfSynth(this, 0, _) }
+
+ final override Expr getArgumentImpl(int n) { none() }
+
+ final override int getNumberOfArgumentsImpl() { result = 0 }
+
+ final override Block getBlockImpl() { none() }
+}
+
+class ScopeResolutionMethodCall extends MethodCallImpl, TScopeResolutionMethodCall {
+ private Ruby::ScopeResolution g;
+ private Ruby::Identifier i;
+
+ ScopeResolutionMethodCall() { this = TScopeResolutionMethodCall(g, i) }
+
+ final override string getMethodNameImpl() { result = i.getValue() }
+
+ final override Expr getReceiverImpl() { toGenerated(result) = g.getScope() }
+
+ final override Expr getArgumentImpl(int n) { none() }
+
+ final override int getNumberOfArgumentsImpl() { result = 0 }
+
+ final override Block getBlockImpl() { none() }
+}
+
+class RegularMethodCall extends MethodCallImpl, TRegularMethodCall {
+ private Ruby::Call g;
+
+ RegularMethodCall() { this = TRegularMethodCall(g) }
+
+ final override Expr getReceiverImpl() {
+ toGenerated(result) = g.getReceiver()
+ or
+ not exists(g.getReceiver()) and
+ toGenerated(result) = g.getMethod().(Ruby::ScopeResolution).getScope()
+ or
+ result = TSelfSynth(this, 0, _)
+ }
+
+ final override string getMethodNameImpl() {
+ isRegularMethodCall(g) and
+ (
+ result = "call" and g.getMethod() instanceof Ruby::ArgumentList
+ or
+ result = g.getMethod().(Ruby::Token).getValue()
+ or
+ result = g.getMethod().(Ruby::ScopeResolution).getName().(Ruby::Token).getValue()
+ )
+ }
+
+ final override Expr getArgumentImpl(int n) {
+ toGenerated(result) = g.getArguments().getChild(n)
+ or
+ toGenerated(result) = g.getMethod().(Ruby::ArgumentList).getChild(n)
+ }
+
+ final override int getNumberOfArgumentsImpl() {
+ result =
+ count(g.getArguments().getChild(_)) + count(g.getMethod().(Ruby::ArgumentList).getChild(_))
+ }
+
+ final override Block getBlockImpl() { toGenerated(result) = g.getBlock() }
+}
+
+class ElementReferenceImpl extends MethodCallImpl, TElementReference {
+ private Ruby::ElementReference g;
+
+ ElementReferenceImpl() { this = TElementReference(g) }
+
+ final override Expr getReceiverImpl() { toGenerated(result) = g.getObject() }
+
+ final override Expr getArgumentImpl(int n) { toGenerated(result) = g.getChild(n) }
+
+ final override int getNumberOfArgumentsImpl() { result = count(g.getChild(_)) }
+
+ final override string getMethodNameImpl() { result = "[]" }
+
+ final override Block getBlockImpl() { none() }
+}
+
+abstract class SuperCallImpl extends MethodCallImpl, TSuperCall { }
+
+class TokenSuperCall extends SuperCallImpl, TTokenSuperCall {
+ private Ruby::Super g;
+
+ TokenSuperCall() { this = TTokenSuperCall(g) }
+
+ final override string getMethodNameImpl() { result = g.getValue() }
+
+ final override Expr getReceiverImpl() { none() }
+
+ final override Expr getArgumentImpl(int n) { none() }
+
+ final override int getNumberOfArgumentsImpl() { result = 0 }
+
+ final override Block getBlockImpl() { none() }
+}
+
+class RegularSuperCall extends SuperCallImpl, TRegularSuperCall {
+ private Ruby::Call g;
+
+ RegularSuperCall() { this = TRegularSuperCall(g) }
+
+ final override string getMethodNameImpl() { result = g.getMethod().(Ruby::Super).getValue() }
+
+ final override Expr getReceiverImpl() { none() }
+
+ final override Expr getArgumentImpl(int n) { toGenerated(result) = g.getArguments().getChild(n) }
+
+ final override int getNumberOfArgumentsImpl() { result = count(g.getArguments().getChild(_)) }
+
+ final override Block getBlockImpl() { toGenerated(result) = g.getBlock() }
+}
+
+class YieldCallImpl extends CallImpl, TYieldCall {
+ Ruby::Yield g;
+
+ YieldCallImpl() { this = TYieldCall(g) }
+
+ final override Expr getArgumentImpl(int n) { toGenerated(result) = g.getChild().getChild(n) }
+
+ final override int getNumberOfArgumentsImpl() { result = count(g.getChild().getChild(_)) }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Erb.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Erb.qll
new file mode 100644
index 00000000000..7a69bf5b783
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Erb.qll
@@ -0,0 +1,43 @@
+import codeql.Locations
+private import TreeSitter
+private import codeql.ruby.ast.Erb
+
+cached
+private module Cached {
+ cached
+ newtype TAstNode =
+ TCommentDirective(Erb::CommentDirective g) or
+ TDirective(Erb::Directive g) or
+ TGraphqlDirective(Erb::GraphqlDirective g) or
+ TOutputDirective(Erb::OutputDirective g) or
+ TTemplate(Erb::Template g) or
+ TToken(Erb::Token g) or
+ TComment(Erb::Comment g) or
+ TCode(Erb::Code g)
+
+ /**
+ * Gets the underlying TreeSitter entity for a given erb AST node.
+ */
+ cached
+ Erb::AstNode toGenerated(ErbAstNode n) {
+ n = TCommentDirective(result) or
+ n = TDirective(result) or
+ n = TGraphqlDirective(result) or
+ n = TOutputDirective(result) or
+ n = TTemplate(result) or
+ n = TToken(result) or
+ n = TComment(result) or
+ n = TCode(result)
+ }
+
+ cached
+ Location getLocation(ErbAstNode n) { result = toGenerated(n).getLocation() }
+}
+
+import Cached
+
+TAstNode fromGenerated(Erb::AstNode n) { n = toGenerated(result) }
+
+class TDirectiveNode = TCommentDirective or TDirective or TGraphqlDirective or TOutputDirective;
+
+class TTokenNode = TToken or TComment or TCode;
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Module.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Module.qll
new file mode 100644
index 00000000000..1a0dd6cc539
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Module.qll
@@ -0,0 +1,414 @@
+private import codeql.Locations
+private import codeql.ruby.AST
+private import codeql.ruby.ast.Call
+private import codeql.ruby.ast.Constant
+private import codeql.ruby.ast.Expr
+private import codeql.ruby.ast.Module
+private import codeql.ruby.ast.Operation
+private import codeql.ruby.ast.Scope
+
+// Names of built-in modules and classes
+private string builtin() {
+ result =
+ [
+ "Object", "Kernel", "BasicObject", "Class", "Module", "NilClass", "FalseClass", "TrueClass",
+ "Numeric", "Integer", "Float", "Rational", "Complex", "Array", "Hash", "Symbol", "Proc"
+ ]
+}
+
+cached
+private module Cached {
+ cached
+ newtype TModule =
+ TResolved(string qName) {
+ qName = builtin()
+ or
+ qName = namespaceDeclaration(_)
+ } or
+ TUnresolved(Namespace n) { not exists(namespaceDeclaration(n)) }
+
+ cached
+ string namespaceDeclaration(Namespace n) {
+ isToplevel(n) and result = n.getName()
+ or
+ not isToplevel(n) and
+ not exists(n.getScopeExpr()) and
+ result = scopeAppend(namespaceDeclaration(n.getEnclosingModule()), n.getName())
+ or
+ exists(string container |
+ TResolved(container) = resolveScopeExpr(n.getScopeExpr()) and
+ result = scopeAppend(container, n.getName())
+ )
+ }
+
+ cached
+ Module getSuperClass(Module cls) {
+ cls = TResolved("Object") and result = TResolved("BasicObject")
+ or
+ cls = TResolved(["Module", "Numeric", "Array", "Hash", "FalseClass", "TrueClass", "NilClass"]) and
+ result = TResolved("Object")
+ or
+ cls = TResolved(["Integer", "Float", "Rational", "Complex"]) and
+ result = TResolved("Numeric")
+ or
+ cls = TResolved("Class") and
+ result = TResolved("Module")
+ or
+ not cls = TResolved(builtin()) and
+ (
+ exists(ClassDeclaration d |
+ d = cls.getADeclaration() and
+ result = resolveScopeExpr(d.getSuperclassExpr())
+ )
+ or
+ result = TResolved("Object") and
+ forex(ClassDeclaration d | d = cls.getADeclaration() |
+ not exists(resolveScopeExpr(d.getSuperclassExpr()))
+ )
+ )
+ }
+
+ cached
+ Module getAnIncludedModule(Module m) {
+ m = TResolved("Object") and result = TResolved("Kernel")
+ or
+ exists(IncludeOrPrependCall c |
+ c.getMethodName() = "include" and
+ (
+ m = resolveScopeExpr(c.getReceiver())
+ or
+ m = enclosingModule(c).getModule() and
+ c.getReceiver() instanceof Self
+ ) and
+ result = resolveScopeExpr(c.getAnArgument())
+ )
+ }
+
+ cached
+ Module getAPrependedModule(Module m) {
+ exists(IncludeOrPrependCall c |
+ c.getMethodName() = "prepend" and
+ (
+ m = resolveScopeExpr(c.getReceiver())
+ or
+ m = enclosingModule(c).getModule() and
+ c.getReceiver() instanceof Self
+ ) and
+ result = resolveScopeExpr(c.getAnArgument())
+ )
+ }
+
+ /**
+ * Resolve class or module read access to a qualified module name.
+ */
+ cached
+ TResolved resolveScopeExpr(ConstantReadAccess r) {
+ exists(string qname | qname = resolveConstant(r) and result = TResolved(qname))
+ }
+
+ pragma[nomagic]
+ private string constantDefinition1(ConstantReadAccess r) {
+ exists(ConstantWriteAccess w | result = constantDefinition0(w) |
+ r = w.getScopeExpr()
+ or
+ r = w.(ClassDeclaration).getSuperclassExpr()
+ )
+ }
+
+ /**
+ * Resolve constant access (class, module or otherwise) to a qualified module name.
+ * `resolveScopeExpr/1` picks the best (lowest priority number) result of
+ * `resolveScopeExpr/2` that resolves to a constant definition. If the constant
+ * definition is a Namespace then it is returned, if it's a constant assignment then
+ * the right-hand side of the assignment is resolved.
+ */
+ cached
+ string resolveConstant(ConstantReadAccess r) {
+ exists(string qname |
+ qname =
+ min(string qn, int p |
+ isDefinedConstant(qn) and
+ qn = resolveScopeExpr(r, p) and
+ // prevent classes/modules that contain/extend themselves
+ not qn = constantDefinition1(r)
+ |
+ qn order by p
+ )
+ |
+ result = qname
+ or
+ exists(ConstantAssignment a |
+ qname = constantDefinition0(a) and
+ result = resolveConstant(a.getParent().(Assignment).getRightOperand())
+ )
+ )
+ }
+
+ cached
+ Method lookupMethod(Module m, string name) { TMethod(result) = lookupMethodOrConst(m, name) }
+
+ cached
+ Expr lookupConst(Module m, string name) {
+ TExpr(result) = lookupMethodOrConst(m, name)
+ or
+ exists(AssignExpr ae, ConstantWriteAccess w |
+ w = ae.getLeftOperand() and
+ w.getName() = name and
+ m = resolveScopeExpr(w.getScopeExpr()) and
+ result = ae.getRightOperand()
+ )
+ }
+}
+
+import Cached
+
+private predicate isToplevel(ConstantAccess n) {
+ not exists(n.getScopeExpr()) and
+ (
+ n.hasGlobalScope()
+ or
+ n.getEnclosingModule() instanceof Toplevel
+ )
+}
+
+private predicate isDefinedConstant(string qualifiedModuleName) {
+ qualifiedModuleName = [builtin(), constantDefinition0(_)]
+}
+
+private int maxDepth() { result = 1 + max(int level | exists(enclosing(_, level))) }
+
+private ModuleBase enclosing(ModuleBase m, int level) {
+ result = m and level = 0
+ or
+ result = enclosing(m.getEnclosingModule(), level - 1)
+}
+
+pragma[noinline]
+private Namespace enclosingNameSpaceConstantReadAccess(
+ ConstantReadAccess c, int priority, string name
+) {
+ result = enclosing(c.getEnclosingModule(), priority) and
+ name = c.getName()
+}
+
+/**
+ * Resolve constant read access (typically a scope expression) to a qualified name. The
+ * `priority` value indicates the precedence of the solution with respect to the lookup order.
+ * A constant name without scope specifier is resolved against its enclosing modules (inner-most first);
+ * if the constant is not found in any of the enclosing modules, then the constant will be resolved
+ * with respect to the ancestors (prepends, includes, super classes, and their ancestors) of the
+ * directly enclosing module.
+ */
+private string resolveScopeExpr(ConstantReadAccess c, int priority) {
+ c.hasGlobalScope() and result = c.getName() and priority = 0
+ or
+ exists(string name |
+ result = qualifiedModuleName(resolveScopeExprConstantReadAccess(c, priority, name), name)
+ )
+ or
+ not exists(c.getScopeExpr()) and
+ not c.hasGlobalScope() and
+ (
+ exists(string name |
+ exists(Namespace n |
+ n = enclosingNameSpaceConstantReadAccess(c, priority, name) and
+ result = qualifiedModuleName(constantDefinition0(n), name)
+ )
+ or
+ result =
+ qualifiedModuleName(ancestors(qualifiedModuleNameConstantReadAccess(c, name),
+ priority - maxDepth()), name)
+ )
+ or
+ priority = maxDepth() + 4 and
+ qualifiedModuleNameConstantReadAccess(c, result) != "BasicObject"
+ )
+}
+
+pragma[nomagic]
+private string resolveScopeExprConstantReadAccess(ConstantReadAccess c, int priority, string name) {
+ result = resolveScopeExpr(c.getScopeExpr(), priority) and
+ name = c.getName()
+}
+
+bindingset[qualifier, name]
+private string scopeAppend(string qualifier, string name) {
+ if qualifier = "Object" then result = name else result = qualifier + "::" + name
+}
+
+private string qualifiedModuleName(ModuleBase m) {
+ result = "Object" and m instanceof Toplevel
+ or
+ result = constantDefinition0(m)
+}
+
+pragma[noinline]
+private string qualifiedModuleNameConstantWriteAccess(ConstantWriteAccess c, string name) {
+ result = qualifiedModuleName(c.getEnclosingModule()) and
+ name = c.getName()
+}
+
+pragma[noinline]
+private string qualifiedModuleNameConstantReadAccess(ConstantReadAccess c, string name) {
+ result = qualifiedModuleName(c.getEnclosingModule()) and
+ name = c.getName()
+}
+
+/**
+ * Get a qualified name for a constant definition. May return multiple qualified
+ * names because we over-approximate when resolving scope resolutions and ignore
+ * lookup order precedence. Taking lookup order into account here would lead to
+ * non-monotonic recursion.
+ */
+private string constantDefinition0(ConstantWriteAccess c) {
+ c.hasGlobalScope() and result = c.getName()
+ or
+ result = scopeAppend(resolveScopeExpr(c.getScopeExpr(), _), c.getName())
+ or
+ not exists(c.getScopeExpr()) and
+ not c.hasGlobalScope() and
+ exists(string name | result = scopeAppend(qualifiedModuleNameConstantWriteAccess(c, name), name))
+}
+
+/**
+ * The qualified names of the ancestors of a class/module. The ancestors should be an ordered list
+ * of the ancestores of `prepend`ed modules, the module itself , the ancestors or `include`d modules
+ * and the ancestors of the super class. The priority value only distinguishes the kind of ancestor,
+ * it does not order the ancestors within a group of the same kind. This is an over-approximation, however,
+ * computing the precise order is tricky because it depends on the evaluation/file loading order.
+ */
+// TODO: the order of super classes can be determined more precisely even without knowing the evaluation
+// order, so we should be able to make this more precise.
+private string ancestors(string qname, int priority) {
+ result = ancestors(prepends(qname), _) and priority = 0
+ or
+ result = qname and priority = 1 and isDefinedConstant(qname)
+ or
+ result = ancestors(includes(qname), _) and priority = 2
+ or
+ result = ancestors(superclass(qname), _) and priority = 3
+}
+
+private class IncludeOrPrependCall extends MethodCall {
+ IncludeOrPrependCall() { this.getMethodName() = ["include", "prepend"] }
+
+ string getAModule() { result = resolveScopeExpr(this.getAnArgument(), _) }
+
+ string getTarget() {
+ result = resolveScopeExpr(this.getReceiver(), _)
+ or
+ result = qualifiedModuleName(enclosingModule(this)) and
+ (
+ this.getReceiver() instanceof Self
+ or
+ not exists(this.getReceiver())
+ )
+ }
+}
+
+/**
+ * A variant of AstNode::getEnclosingModule that excludes
+ * results that are enclosed in a block. This is a bit wrong because
+ * it could lead to false negatives. However, `include` statements in
+ * blocks are very rare in normal code. The majority of cases are in calls
+ * to methods like `module_eval` and `Rspec.describe` / `Rspec.context`. These
+ * methods evaluate the block in the context of some other module/class instead of
+ * the enclosing one.
+ */
+private ModuleBase enclosingModule(AstNode node) { result = parent*(node).getParent() }
+
+private AstNode parent(AstNode n) {
+ result = n.getParent() and
+ not result instanceof ModuleBase and
+ not result instanceof Block
+}
+
+private string prepends(string qname) {
+ exists(IncludeOrPrependCall m |
+ m.getMethodName() = "prepend" and
+ qname = m.getTarget() and
+ result = m.getAModule()
+ )
+}
+
+private string includes(string qname) {
+ qname = "Object" and
+ result = "Kernel"
+ or
+ exists(IncludeOrPrependCall m |
+ m.getMethodName() = "include" and
+ qname = m.getTarget() and
+ result = m.getAModule()
+ )
+}
+
+private Expr superexpr(string qname) {
+ exists(ClassDeclaration c | qname = constantDefinition0(c) and result = c.getSuperclassExpr())
+}
+
+private string superclass(string qname) {
+ qname = "Object" and result = "BasicObject"
+ or
+ result = resolveScopeExpr(superexpr(qname), _)
+}
+
+private string qualifiedModuleName(string container, string name) {
+ isDefinedConstant(result) and
+ (
+ container = result.regexpCapture("(.+)::([^:]+)", 1) and
+ name = result.regexpCapture("(.+)::([^:]+)", 2)
+ or
+ container = "Object" and name = result
+ )
+}
+
+private Module getAncestors(Module m) {
+ result = m or
+ result = getAncestors(m.getAnIncludedModule()) or
+ result = getAncestors(m.getAPrependedModule())
+}
+
+private newtype TMethodOrExpr =
+ TMethod(Method m) or
+ TExpr(Expr e)
+
+private TMethodOrExpr getMethodOrConst(TModule owner, string name) {
+ exists(ModuleBase m | m.getModule() = owner |
+ result = TMethod(m.getMethod(name))
+ or
+ result = TExpr(m.getConstant(name))
+ )
+}
+
+module ExposedForTestingOnly {
+ Method getMethod(TModule owner, string name) { TMethod(result) = getMethodOrConst(owner, name) }
+
+ Expr getConst(TModule owner, string name) { TExpr(result) = getMethodOrConst(owner, name) }
+}
+
+private TMethodOrExpr lookupMethodOrConst0(Module m, string name) {
+ result = lookupMethodOrConst0(m.getAPrependedModule(), name)
+ or
+ not exists(getMethodOrConst(getAncestors(m.getAPrependedModule()), name)) and
+ (
+ result = getMethodOrConst(m, name)
+ or
+ not exists(getMethodOrConst(m, name)) and
+ result = lookupMethodOrConst0(m.getAnIncludedModule(), name)
+ )
+}
+
+private AstNode getNode(TMethodOrExpr e) { e = TMethod(result) or e = TExpr(result) }
+
+private TMethodOrExpr lookupMethodOrConst(Module m, string name) {
+ result = lookupMethodOrConst0(m, name)
+ or
+ not exists(lookupMethodOrConst0(m, name)) and
+ result = lookupMethodOrConst(m.getSuperClass(), name) and
+ // For now, we restrict the scope of top-level declarations to their file.
+ // This may remove some plausible targets, but also removes a lot of
+ // implausible targets
+ if getNode(result).getEnclosingModule() instanceof Toplevel
+ then getNode(result).getFile() = m.getADeclaration().getFile()
+ else any()
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Operation.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Operation.qll
new file mode 100644
index 00000000000..3571c97e9dc
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Operation.qll
@@ -0,0 +1,198 @@
+private import codeql.ruby.AST
+private import AST
+private import TreeSitter
+private import Call
+
+abstract class OperationImpl extends Expr, TOperation {
+ abstract string getOperatorImpl();
+
+ abstract Expr getAnOperandImpl();
+}
+
+abstract class UnaryOperationImpl extends OperationImpl, MethodCallImpl, TUnaryOperation {
+ abstract Expr getOperandImpl();
+
+ final override Expr getAnOperandImpl() { result = this.getOperandImpl() }
+
+ final override string getMethodNameImpl() { result = this.getOperatorImpl() }
+
+ final override AstNode getReceiverImpl() { result = this.getOperandImpl() }
+
+ final override Expr getArgumentImpl(int n) { none() }
+
+ final override int getNumberOfArgumentsImpl() { result = 0 }
+
+ final override Block getBlockImpl() { none() }
+}
+
+class UnaryOperationGenerated extends UnaryOperationImpl {
+ private Ruby::Unary g;
+
+ UnaryOperationGenerated() { g = toGenerated(this) }
+
+ final override Expr getOperandImpl() { toGenerated(result) = g.getOperand() }
+
+ final override string getOperatorImpl() { result = g.getOperator() }
+}
+
+class SplatExprReal extends UnaryOperationImpl, TSplatExprReal {
+ private Ruby::SplatArgument g;
+
+ SplatExprReal() { this = TSplatExprReal(g) }
+
+ final override string getOperatorImpl() { result = "*" }
+
+ final override Expr getOperandImpl() { toGenerated(result) = g.getChild() }
+}
+
+class SplatExprSynth extends UnaryOperationImpl, TSplatExprSynth {
+ final override string getOperatorImpl() { result = "*" }
+
+ final override Expr getOperandImpl() { synthChild(this, 0, result) }
+}
+
+class HashSplatExprImpl extends UnaryOperationImpl, THashSplatExpr {
+ private Ruby::HashSplatArgument g;
+
+ HashSplatExprImpl() { this = THashSplatExpr(g) }
+
+ final override Expr getOperandImpl() { toGenerated(result) = g.getChild() }
+
+ final override string getOperatorImpl() { result = "**" }
+}
+
+abstract class BinaryOperationImpl extends OperationImpl, MethodCallImpl, TBinaryOperation {
+ abstract Stmt getLeftOperandImpl();
+
+ abstract Stmt getRightOperandImpl();
+
+ final override Expr getAnOperandImpl() {
+ result = this.getLeftOperandImpl()
+ or
+ result = this.getRightOperandImpl()
+ }
+
+ final override string getMethodNameImpl() { result = this.getOperatorImpl() }
+
+ final override AstNode getReceiverImpl() { result = this.getLeftOperandImpl() }
+
+ final override Expr getArgumentImpl(int n) { n = 0 and result = this.getRightOperandImpl() }
+
+ final override int getNumberOfArgumentsImpl() { result = 1 }
+
+ final override Block getBlockImpl() { none() }
+}
+
+class BinaryOperationReal extends BinaryOperationImpl {
+ private Ruby::Binary g;
+
+ BinaryOperationReal() { g = toGenerated(this) }
+
+ final override string getOperatorImpl() { result = g.getOperator() }
+
+ final override Stmt getLeftOperandImpl() { toGenerated(result) = g.getLeft() }
+
+ final override Stmt getRightOperandImpl() { toGenerated(result) = g.getRight() }
+}
+
+abstract class BinaryOperationSynth extends BinaryOperationImpl {
+ final override Stmt getLeftOperandImpl() { synthChild(this, 0, result) }
+
+ final override Stmt getRightOperandImpl() { synthChild(this, 1, result) }
+}
+
+class AddExprSynth extends BinaryOperationSynth, TAddExprSynth {
+ final override string getOperatorImpl() { result = "+" }
+}
+
+class SubExprSynth extends BinaryOperationSynth, TSubExprSynth {
+ final override string getOperatorImpl() { result = "-" }
+}
+
+class MulExprSynth extends BinaryOperationSynth, TMulExprSynth {
+ final override string getOperatorImpl() { result = "*" }
+}
+
+class DivExprSynth extends BinaryOperationSynth, TDivExprSynth {
+ final override string getOperatorImpl() { result = "/" }
+}
+
+class ModuloExprSynth extends BinaryOperationSynth, TModuloExprSynth {
+ final override string getOperatorImpl() { result = "%" }
+}
+
+class ExponentExprSynth extends BinaryOperationSynth, TExponentExprSynth {
+ final override string getOperatorImpl() { result = "**" }
+}
+
+class LogicalAndExprSynth extends BinaryOperationSynth, TLogicalAndExprSynth {
+ final override string getOperatorImpl() { result = "&&" }
+}
+
+class LogicalOrExprSynth extends BinaryOperationSynth, TLogicalOrExprSynth {
+ final override string getOperatorImpl() { result = "||" }
+}
+
+class LShiftExprSynth extends BinaryOperationSynth, TLShiftExprSynth {
+ final override string getOperatorImpl() { result = "<<" }
+}
+
+class RShiftExprSynth extends BinaryOperationSynth, TRShiftExprSynth {
+ final override string getOperatorImpl() { result = ">>" }
+}
+
+class BitwiseAndSynthExpr extends BinaryOperationSynth, TBitwiseAndExprSynth {
+ final override string getOperatorImpl() { result = "&" }
+}
+
+class BitwiseOrSynthExpr extends BinaryOperationSynth, TBitwiseOrExprSynth {
+ final override string getOperatorImpl() { result = "|" }
+}
+
+class BitwiseXorSynthExpr extends BinaryOperationSynth, TBitwiseXorExprSynth {
+ final override string getOperatorImpl() { result = "^" }
+}
+
+abstract class AssignmentImpl extends OperationImpl, TAssignment {
+ abstract Pattern getLeftOperandImpl();
+
+ abstract Expr getRightOperandImpl();
+
+ final override Expr getAnOperandImpl() {
+ result = this.getLeftOperandImpl()
+ or
+ result = this.getRightOperandImpl()
+ }
+}
+
+class AssignExprReal extends AssignmentImpl, TAssignExprReal {
+ private Ruby::Assignment g;
+
+ AssignExprReal() { this = TAssignExprReal(g) }
+
+ final override string getOperatorImpl() { result = "=" }
+
+ final override Pattern getLeftOperandImpl() { toGenerated(result) = g.getLeft() }
+
+ final override Expr getRightOperandImpl() { toGenerated(result) = g.getRight() }
+}
+
+class AssignExprSynth extends AssignmentImpl, TAssignExprSynth {
+ final override string getOperatorImpl() { result = "=" }
+
+ final override Pattern getLeftOperandImpl() { synthChild(this, 0, result) }
+
+ final override Expr getRightOperandImpl() { synthChild(this, 1, result) }
+}
+
+class AssignOperationImpl extends AssignmentImpl, TAssignOperation {
+ Ruby::OperatorAssignment g;
+
+ AssignOperationImpl() { g = toGenerated(this) }
+
+ final override string getOperatorImpl() { result = g.getOperator() }
+
+ final override Pattern getLeftOperandImpl() { toGenerated(result) = g.getLeft() }
+
+ final override Expr getRightOperandImpl() { toGenerated(result) = g.getRight() }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Parameter.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Parameter.qll
new file mode 100644
index 00000000000..f888d89c1ac
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Parameter.qll
@@ -0,0 +1,19 @@
+private import codeql.ruby.AST
+private import AST
+private import TreeSitter
+
+module Parameter {
+ class Range extends Ruby::AstNode {
+ private int pos;
+
+ Range() {
+ this = any(Ruby::BlockParameters bp).getChild(pos)
+ or
+ this = any(Ruby::MethodParameters mp).getChild(pos)
+ or
+ this = any(Ruby::LambdaParameters lp).getChild(pos)
+ }
+
+ int getPosition() { result = pos }
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Pattern.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Pattern.qll
new file mode 100644
index 00000000000..ce18e77f222
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Pattern.qll
@@ -0,0 +1,32 @@
+private import codeql.ruby.AST
+private import AST
+private import TreeSitter
+
+abstract class TuplePatternImpl extends Ruby::AstNode {
+ abstract Ruby::AstNode getChildNode(int i);
+
+ final int getRestIndex() {
+ result = unique(int i | this.getChildNode(i) instanceof Ruby::RestAssignment)
+ }
+}
+
+class TuplePatternParameterImpl extends TuplePatternImpl, Ruby::DestructuredParameter {
+ override Ruby::AstNode getChildNode(int i) { result = this.getChild(i) }
+}
+
+class DestructuredLeftAssignmentImpl extends TuplePatternImpl, Ruby::DestructuredLeftAssignment {
+ override Ruby::AstNode getChildNode(int i) { result = this.getChild(i) }
+}
+
+class LeftAssignmentListImpl extends TuplePatternImpl, Ruby::LeftAssignmentList {
+ override Ruby::AstNode getChildNode(int i) {
+ this =
+ any(Ruby::LeftAssignmentList lal |
+ if
+ strictcount(int j | exists(lal.getChild(j))) = 1 and
+ lal.getChild(0) instanceof Ruby::DestructuredLeftAssignment
+ then result = lal.getChild(0).(Ruby::DestructuredLeftAssignment).getChild(i)
+ else result = lal.getChild(i)
+ )
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Scope.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Scope.qll
new file mode 100644
index 00000000000..cab6a7ad3b1
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Scope.qll
@@ -0,0 +1,130 @@
+private import TreeSitter
+private import codeql.ruby.ast.Scope
+private import codeql.ruby.ast.internal.AST
+private import codeql.ruby.ast.internal.Parameter
+
+class TScopeType = TMethodBase or TModuleLike or TBlockLike;
+
+/**
+ * The scope of a `self` variable.
+ * This differs from a normal scope because it is not affected by blocks or lambdas.
+ */
+class TSelfScopeType = TMethodBase or TModuleBase;
+
+private class TBlockLike = TDoBlock or TLambda or TBlock or TEndBlock;
+
+private class TModuleLike = TToplevel or TModuleDeclaration or TClassDeclaration or TSingletonClass;
+
+module Scope {
+ class TypeRange = Callable::TypeRange or ModuleBase::TypeRange or @ruby_end_block;
+
+ class Range extends Ruby::AstNode, TypeRange {
+ Range() { not this = any(Ruby::Lambda l).getBody() }
+
+ ModuleBase::Range getEnclosingModule() {
+ result = this
+ or
+ not this instanceof ModuleBase::Range and result = this.getOuterScope().getEnclosingModule()
+ }
+
+ MethodBase::Range getEnclosingMethod() {
+ result = this
+ or
+ not this instanceof MethodBase::Range and
+ not this instanceof ModuleBase::Range and
+ result = this.getOuterScope().getEnclosingMethod()
+ }
+
+ SelfBase::Range getEnclosingSelfScope() {
+ this instanceof SelfBase::Range and result = this
+ or
+ not this instanceof SelfBase::Range and result = this.getOuterScope().getEnclosingSelfScope()
+ }
+
+ Range getOuterScope() { result = scopeOf(this) }
+ }
+}
+
+module MethodBase {
+ class TypeRange = @ruby_method or @ruby_singleton_method;
+
+ class Range extends Scope::Range, TypeRange { }
+}
+
+module Callable {
+ class TypeRange = MethodBase::TypeRange or @ruby_do_block or @ruby_lambda or @ruby_block;
+
+ class Range extends Scope::Range, TypeRange {
+ Parameter::Range getParameter(int i) {
+ result = this.(Ruby::Method).getParameters().getChild(i) or
+ result = this.(Ruby::SingletonMethod).getParameters().getChild(i) or
+ result = this.(Ruby::DoBlock).getParameters().getChild(i) or
+ result = this.(Ruby::Lambda).getParameters().getChild(i) or
+ result = this.(Ruby::Block).getParameters().getChild(i)
+ }
+ }
+}
+
+module ModuleBase {
+ class TypeRange = @ruby_program or @ruby_module or @ruby_class or @ruby_singleton_class;
+
+ class Range extends Scope::Range, TypeRange { }
+}
+
+module SelfBase {
+ class TypeRange = MethodBase::TypeRange or ModuleBase::TypeRange;
+
+ /**
+ * A `self` variable can appear in a class, module, method or at the top level.
+ */
+ class Range extends Scope::Range, TypeRange { }
+}
+
+pragma[noinline]
+private predicate rankHeredocBody(File f, Ruby::HeredocBody b, int i) {
+ b =
+ rank[i](Ruby::HeredocBody b0 |
+ f = b0.getLocation().getFile()
+ |
+ b0 order by b0.getLocation().getStartLine(), b0.getLocation().getStartColumn()
+ )
+}
+
+Ruby::HeredocBody getHereDocBody(Ruby::HeredocBeginning g) {
+ exists(int i, File f |
+ g =
+ rank[i](Ruby::HeredocBeginning b |
+ f = b.getLocation().getFile()
+ |
+ b order by b.getLocation().getStartLine(), b.getLocation().getStartColumn()
+ ) and
+ rankHeredocBody(f, result, i)
+ )
+}
+
+private Ruby::AstNode parentOf(Ruby::AstNode n) {
+ n = getHereDocBody(result)
+ or
+ exists(Ruby::AstNode parent | parent = n.getParent() |
+ if
+ n =
+ [
+ parent.(Ruby::Module).getName(), parent.(Ruby::Class).getName(),
+ parent.(Ruby::Class).getSuperclass(), parent.(Ruby::SingletonClass).getValue(),
+ parent.(Ruby::Method).getName(), parent.(Ruby::SingletonMethod).getName(),
+ parent.(Ruby::SingletonMethod).getObject()
+ ]
+ then result = parent.getParent()
+ else result = parent
+ )
+}
+
+/** Gets the enclosing scope of a node */
+cached
+Scope::Range scopeOf(Ruby::AstNode n) {
+ exists(Ruby::AstNode p | p = parentOf(n) |
+ p = result
+ or
+ not p instanceof Scope::Range and result = scopeOf(p)
+ )
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Synthesis.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Synthesis.qll
new file mode 100644
index 00000000000..6683d025536
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Synthesis.qll
@@ -0,0 +1,816 @@
+/** Provides predicates for synthesizing AST nodes. */
+
+private import AST
+private import TreeSitter
+private import codeql.ruby.ast.internal.Call
+private import codeql.ruby.ast.internal.Variable
+private import codeql.ruby.ast.internal.Pattern
+private import codeql.ruby.ast.internal.Scope
+private import codeql.ruby.AST
+
+/** A synthesized AST node kind. */
+newtype SynthKind =
+ AddExprKind() or
+ AssignExprKind() or
+ BitwiseAndExprKind() or
+ BitwiseOrExprKind() or
+ BitwiseXorExprKind() or
+ ClassVariableAccessKind(ClassVariable v) or
+ DivExprKind() or
+ ExponentExprKind() or
+ GlobalVariableAccessKind(GlobalVariable v) or
+ InstanceVariableAccessKind(InstanceVariable v) or
+ IntegerLiteralKind(int i) { i in [-1000 .. 1000] } or
+ LShiftExprKind() or
+ LocalVariableAccessRealKind(LocalVariableReal v) or
+ LocalVariableAccessSynthKind(TLocalVariableSynth v) or
+ LogicalAndExprKind() or
+ LogicalOrExprKind() or
+ MethodCallKind(string name, boolean setter, int arity) {
+ any(Synthesis s).methodCall(name, setter, arity)
+ } or
+ ModuloExprKind() or
+ MulExprKind() or
+ RangeLiteralKind(boolean inclusive) { inclusive in [false, true] } or
+ RShiftExprKind() or
+ SplatExprKind() or
+ StmtSequenceKind() or
+ SelfKind(SelfVariable v) or
+ SubExprKind() or
+ ConstantReadAccessKind(string value) { any(Synthesis s).constantReadAccess(value) }
+
+/**
+ * An AST child.
+ *
+ * Either a new synthesized node or a reference to an existing node.
+ */
+newtype Child =
+ SynthChild(SynthKind k) or
+ RealChildRef(TAstNodeReal n) or
+ SynthChildRef(TAstNodeSynth n)
+
+/**
+ * The purpose of this inlined predicate is to split up child references into
+ * those that are from real AST nodes (for which there will be no recursion
+ * through `RealChildRef`), and those that are synthesized recursively
+ * (for which there will be recursion through `SynthChildRef`).
+ *
+ * This performs much better than having a combined `ChildRef` that includes
+ * both real and synthesized AST nodes, since the recursion happening in
+ * `Synthesis::child/3` is non-linear.
+ */
+pragma[inline]
+private Child childRef(TAstNode n) {
+ result = RealChildRef(n)
+ or
+ result = SynthChildRef(n)
+}
+
+private newtype TSynthesis = MkSynthesis()
+
+/** A class used for synthesizing AST nodes. */
+class Synthesis extends TSynthesis {
+ /**
+ * Holds if a node should be synthesized as the `i`th child of `parent`, or if
+ * a non-synthesized node should be the `i`th child of synthesized node `parent`.
+ *
+ * `i = -1` is used to represent that the synthesized node is a desugared version
+ * of its parent.
+ */
+ predicate child(AstNode parent, int i, Child child) { none() }
+
+ /**
+ * Holds if synthesized node `n` should have location `l`. Synthesized nodes for
+ * which this predicate does not hold, inherit their location (recursively) from
+ * their parent node.
+ */
+ predicate location(AstNode n, Location l) { none() }
+
+ /**
+ * Holds if a local variable, identified by `i`, should be synthesized for AST
+ * node `n`.
+ */
+ predicate localVariable(AstNode n, int i) { none() }
+
+ /**
+ * Holds if a method call to `name` with arity `arity` is needed.
+ */
+ predicate methodCall(string name, boolean setter, int arity) { none() }
+
+ /**
+ * Holds if a constant read access of `name` is needed.
+ */
+ predicate constantReadAccess(string name) { none() }
+
+ /**
+ * Holds if `n` should be excluded from `ControlFlowTree` in the CFG construction.
+ */
+ predicate excludeFromControlFlowTree(AstNode n) { none() }
+
+ final string toString() { none() }
+}
+
+private class Desugared extends AstNode {
+ Desugared() { this = any(AstNode sugar).getDesugared() }
+
+ AstNode getADescendant() { result = this.getAChild*() }
+}
+
+/**
+ * Gets the desugaring level of `n`. That is, the number of desugaring
+ * transformations required before the context in which `n` occurs is
+ * fully desugared.
+ */
+int desugarLevel(AstNode n) { result = count(Desugared desugared | n = desugared.getADescendant()) }
+
+/**
+ * Use this predicate in `Synthesis::child` to generate an assignment of `value` to
+ * synthesized variable `v`, where the assignment is a child of `assignParent` at
+ * index `assignIndex`.
+ */
+bindingset[v, assignParent, assignIndex, value]
+private predicate assign(
+ AstNode parent, int i, Child child, TLocalVariableSynth v, AstNode assignParent, int assignIndex,
+ AstNode value
+) {
+ parent = assignParent and
+ i = assignIndex and
+ child = SynthChild(AssignExprKind())
+ or
+ parent = TAssignExprSynth(assignParent, assignIndex) and
+ (
+ i = 0 and
+ child = SynthChild(LocalVariableAccessSynthKind(v))
+ or
+ i = 1 and
+ child = childRef(value)
+ )
+}
+
+/** Holds if synthesized node `n` should have location `l`. */
+predicate synthLocation(AstNode n, Location l) {
+ n.isSynthesized() and any(Synthesis s).location(n, l)
+}
+
+private predicate hasLocation(AstNode n, Location l) {
+ l = toGenerated(n).getLocation()
+ or
+ synthLocation(n, l)
+}
+
+private module ImplicitSelfSynthesis {
+ pragma[nomagic]
+ private predicate identifierMethodCallSelfSynthesis(AstNode mc, int i, Child child) {
+ child = SynthChild(SelfKind(TSelfVariable(scopeOf(toGenerated(mc)).getEnclosingSelfScope()))) and
+ mc = TIdentifierMethodCall(_) and
+ i = 0
+ }
+
+ private class IdentifierMethodCallSelfSynthesis extends Synthesis {
+ final override predicate child(AstNode parent, int i, Child child) {
+ identifierMethodCallSelfSynthesis(parent, i, child)
+ }
+ }
+
+ pragma[nomagic]
+ private predicate regularMethodCallSelfSynthesis(TRegularMethodCall mc, int i, Child child) {
+ exists(Ruby::AstNode g |
+ mc = TRegularMethodCall(g) and
+ // If there's no explicit receiver (or scope resolution that acts like a
+ // receiver), then the receiver is implicitly `self`. N.B. `::Foo()` is
+ // not valid Ruby.
+ not exists(g.(Ruby::Call).getReceiver()) and
+ not exists(g.(Ruby::Call).getMethod().(Ruby::ScopeResolution).getScope())
+ ) and
+ child = SynthChild(SelfKind(TSelfVariable(scopeOf(toGenerated(mc)).getEnclosingSelfScope()))) and
+ i = 0
+ }
+
+ private class RegularMethodCallSelfSynthesis extends Synthesis {
+ final override predicate child(AstNode parent, int i, Child child) {
+ regularMethodCallSelfSynthesis(parent, i, child)
+ }
+ }
+}
+
+private module SetterDesugar {
+ /** An assignment where the left-hand side is a method call. */
+ private class SetterAssignExpr extends AssignExpr {
+ private MethodCall mc;
+
+ pragma[nomagic]
+ SetterAssignExpr() { mc = this.getLeftOperand() }
+
+ MethodCall getMethodCall() { result = mc }
+
+ pragma[nomagic]
+ MethodCallKind getCallKind(boolean setter, int arity) {
+ result = MethodCallKind(mc.getMethodName(), setter, arity)
+ }
+
+ pragma[nomagic]
+ Expr getReceiver() { result = mc.getReceiver() }
+
+ pragma[nomagic]
+ Expr getArgument(int i) { result = mc.getArgument(i) }
+
+ pragma[nomagic]
+ int getNumberOfArguments() { result = mc.getNumberOfArguments() }
+
+ pragma[nomagic]
+ Location getMethodCallLocation() { hasLocation(mc, result) }
+ }
+
+ pragma[nomagic]
+ private predicate setterMethodCallSynthesis(AstNode parent, int i, Child child) {
+ exists(SetterAssignExpr sae |
+ parent = sae and
+ i = -1 and
+ child = SynthChild(StmtSequenceKind())
+ or
+ exists(AstNode seq | seq = TStmtSequenceSynth(sae, -1) |
+ parent = seq and
+ i = 0 and
+ child = SynthChild(sae.getCallKind(true, sae.getNumberOfArguments() + 1))
+ or
+ exists(AstNode call | call = TMethodCallSynth(seq, 0, _, _, _) |
+ parent = call and
+ i = 0 and
+ child = childRef(sae.getReceiver())
+ or
+ parent = call and
+ child = childRef(sae.getArgument(i - 1))
+ or
+ exists(int valueIndex | valueIndex = sae.getNumberOfArguments() + 1 |
+ parent = call and
+ i = valueIndex and
+ child = SynthChild(AssignExprKind())
+ or
+ parent = TAssignExprSynth(call, valueIndex) and
+ (
+ i = 0 and
+ child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(sae, 0)))
+ or
+ i = 1 and
+ child = childRef(sae.getRightOperand())
+ )
+ )
+ )
+ or
+ parent = seq and
+ i = 1 and
+ child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(sae, 0)))
+ )
+ )
+ }
+
+ /**
+ * ```rb
+ * x.foo = y
+ * ```
+ *
+ * desugars to
+ *
+ * ```rb
+ * x.foo=(__synth_0 = y);
+ * __synth_0;
+ * ```
+ */
+ private class SetterMethodCallSynthesis extends Synthesis {
+ final override predicate child(AstNode parent, int i, Child child) {
+ setterMethodCallSynthesis(parent, i, child)
+ }
+
+ final override predicate location(AstNode n, Location l) {
+ exists(SetterAssignExpr sae, StmtSequence seq |
+ seq = sae.getDesugared() and
+ l = sae.getMethodCallLocation() and
+ n = seq.getAStmt()
+ )
+ }
+
+ final override predicate excludeFromControlFlowTree(AstNode n) {
+ n = any(SetterAssignExpr sae).getMethodCall()
+ }
+
+ final override predicate localVariable(AstNode n, int i) {
+ n instanceof SetterAssignExpr and
+ i = 0
+ }
+
+ final override predicate methodCall(string name, boolean setter, int arity) {
+ exists(SetterAssignExpr sae |
+ name = sae.getMethodCall().getMethodName() and
+ setter = true and
+ arity = sae.getNumberOfArguments() + 1
+ )
+ }
+ }
+}
+
+private module AssignOperationDesugar {
+ /**
+ * Gets the operator kind to synthesize for operator assignment `ao`.
+ */
+ private SynthKind getKind(AssignOperation ao) {
+ ao instanceof AssignAddExpr and result = AddExprKind()
+ or
+ ao instanceof AssignSubExpr and result = SubExprKind()
+ or
+ ao instanceof AssignMulExpr and result = MulExprKind()
+ or
+ ao instanceof AssignDivExpr and result = DivExprKind()
+ or
+ ao instanceof AssignModuloExpr and result = ModuloExprKind()
+ or
+ ao instanceof AssignExponentExpr and result = ExponentExprKind()
+ or
+ ao instanceof AssignLogicalAndExpr and result = LogicalAndExprKind()
+ or
+ ao instanceof AssignLogicalOrExpr and result = LogicalOrExprKind()
+ or
+ ao instanceof AssignLShiftExpr and result = LShiftExprKind()
+ or
+ ao instanceof AssignRShiftExpr and result = RShiftExprKind()
+ or
+ ao instanceof AssignBitwiseAndExpr and result = BitwiseAndExprKind()
+ or
+ ao instanceof AssignBitwiseOrExpr and result = BitwiseOrExprKind()
+ or
+ ao instanceof AssignBitwiseXorExpr and result = BitwiseXorExprKind()
+ }
+
+ private Location getAssignOperationLocation(AssignOperation ao) {
+ exists(Ruby::OperatorAssignment g, Ruby::Token op |
+ g = toGenerated(ao) and
+ op.getParent() = g and
+ op.getParentIndex() = 1 and
+ result = op.getLocation()
+ )
+ }
+
+ /** An assignment operation where the left-hand side is a variable. */
+ private class VariableAssignOperation extends AssignOperation {
+ private Variable v;
+
+ pragma[nomagic]
+ VariableAssignOperation() { v = this.getLeftOperand().(VariableAccess).getVariable() }
+
+ pragma[nomagic]
+ SynthKind getVariableAccessKind() {
+ result in [
+ LocalVariableAccessRealKind(v).(SynthKind), InstanceVariableAccessKind(v),
+ ClassVariableAccessKind(v), GlobalVariableAccessKind(v)
+ ]
+ }
+ }
+
+ pragma[nomagic]
+ private predicate variableAssignOperationSynthesis(AstNode parent, int i, Child child) {
+ exists(VariableAssignOperation vao |
+ parent = vao and
+ i = -1 and
+ child = SynthChild(AssignExprKind())
+ or
+ exists(AstNode assign | assign = TAssignExprSynth(vao, -1) |
+ parent = assign and
+ i = 0 and
+ child = childRef(vao.getLeftOperand())
+ or
+ parent = assign and
+ i = 1 and
+ child = SynthChild(getKind(vao))
+ or
+ parent = getSynthChild(assign, 1) and
+ (
+ i = 0 and
+ child = SynthChild(vao.getVariableAccessKind())
+ or
+ i = 1 and
+ child = childRef(vao.getRightOperand())
+ )
+ )
+ )
+ }
+
+ /**
+ * ```rb
+ * x += y
+ * ```
+ *
+ * desugars to
+ *
+ * ```rb
+ * x = x + y
+ * ```
+ *
+ * when `x` is a variable.
+ */
+ private class VariableAssignOperationSynthesis extends Synthesis {
+ final override predicate child(AstNode parent, int i, Child child) {
+ variableAssignOperationSynthesis(parent, i, child)
+ }
+
+ final override predicate location(AstNode n, Location l) {
+ exists(VariableAssignOperation vao, BinaryOperation bo |
+ bo = vao.getDesugared().(AssignExpr).getRightOperand()
+ |
+ n = bo and
+ l = getAssignOperationLocation(vao)
+ or
+ n = bo.getLeftOperand() and
+ hasLocation(vao.getLeftOperand(), l)
+ )
+ }
+ }
+
+ /** An assignment operation where the left-hand side is a method call. */
+ private class SetterAssignOperation extends AssignOperation {
+ private MethodCall mc;
+
+ pragma[nomagic]
+ SetterAssignOperation() { mc = this.getLeftOperand() }
+
+ MethodCall getMethodCall() { result = mc }
+
+ pragma[nomagic]
+ MethodCallKind getCallKind(boolean setter, int arity) {
+ result = MethodCallKind(mc.getMethodName(), setter, arity)
+ }
+
+ pragma[nomagic]
+ Expr getReceiver() { result = mc.getReceiver() }
+
+ pragma[nomagic]
+ Expr getArgument(int i) { result = mc.getArgument(i) }
+
+ pragma[nomagic]
+ int getNumberOfArguments() { result = mc.getNumberOfArguments() }
+
+ pragma[nomagic]
+ Location getMethodCallLocation() { hasLocation(mc, result) }
+ }
+
+ pragma[nomagic]
+ private predicate methodCallAssignOperationSynthesis(AstNode parent, int i, Child child) {
+ exists(SetterAssignOperation sao |
+ parent = sao and
+ i = -1 and
+ child = SynthChild(StmtSequenceKind())
+ or
+ exists(AstNode seq | seq = TStmtSequenceSynth(sao, -1) |
+ // `__synth__0 = foo`
+ assign(parent, i, child, TLocalVariableSynth(sao, 0), seq, 0, sao.getReceiver())
+ or
+ // `__synth__1 = bar`
+ exists(Expr arg, int j | arg = sao.getArgument(j - 1) |
+ assign(parent, i, child, TLocalVariableSynth(sao, j), seq, j, arg)
+ )
+ or
+ // `__synth__2 = __synth__0.[](__synth__1) + y`
+ exists(int opAssignIndex | opAssignIndex = sao.getNumberOfArguments() + 1 |
+ parent = seq and
+ i = opAssignIndex and
+ child = SynthChild(AssignExprKind())
+ or
+ exists(AstNode assign | assign = TAssignExprSynth(seq, opAssignIndex) |
+ parent = assign and
+ i = 0 and
+ child =
+ SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(sao, opAssignIndex)))
+ or
+ parent = assign and
+ i = 1 and
+ child = SynthChild(getKind(sao))
+ or
+ // `__synth__0.[](__synth__1) + y`
+ exists(AstNode op | op = getSynthChild(assign, 1) |
+ parent = op and
+ i = 0 and
+ child = SynthChild(sao.getCallKind(false, sao.getNumberOfArguments()))
+ or
+ parent = TMethodCallSynth(op, 0, _, _, _) and
+ child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(sao, i))) and
+ i in [0 .. sao.getNumberOfArguments()]
+ or
+ parent = op and
+ i = 1 and
+ child = childRef(sao.getRightOperand())
+ )
+ )
+ or
+ // `__synth__0.[]=(__synth__1, __synth__2);`
+ parent = seq and
+ i = opAssignIndex + 1 and
+ child = SynthChild(sao.getCallKind(true, opAssignIndex))
+ or
+ exists(AstNode setter | setter = TMethodCallSynth(seq, opAssignIndex + 1, _, _, _) |
+ parent = setter and
+ child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(sao, i))) and
+ i in [0 .. sao.getNumberOfArguments()]
+ or
+ parent = setter and
+ i = opAssignIndex + 1 and
+ child =
+ SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(sao, opAssignIndex)))
+ )
+ or
+ parent = seq and
+ i = opAssignIndex + 2 and
+ child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(sao, opAssignIndex)))
+ )
+ )
+ )
+ }
+
+ /**
+ * ```rb
+ * foo[bar] += y
+ * ```
+ *
+ * desugars to
+ *
+ * ```rb
+ * __synth__0 = foo;
+ * __synth__1 = bar;
+ * __synth__2 = __synth__0.[](__synth__1) + y;
+ * __synth__0.[]=(__synth__1, __synth__2);
+ * __synth__2;
+ * ```
+ */
+ private class MethodCallAssignOperationSynthesis extends Synthesis {
+ final override predicate child(AstNode parent, int i, Child child) {
+ methodCallAssignOperationSynthesis(parent, i, child)
+ }
+
+ final override predicate location(AstNode n, Location l) {
+ exists(SetterAssignOperation sao, StmtSequence seq | seq = sao.getDesugared() |
+ n = seq.getStmt(0) and
+ hasLocation(sao.getReceiver(), l)
+ or
+ exists(int i |
+ n = seq.getStmt(i + 1) and
+ hasLocation(sao.getArgument(i), l)
+ )
+ or
+ exists(AssignExpr ae, int opAssignIndex |
+ opAssignIndex = sao.getNumberOfArguments() + 1 and
+ ae = seq.getStmt(opAssignIndex)
+ |
+ l = getAssignOperationLocation(sao) and
+ n = ae
+ or
+ exists(BinaryOperation bo | bo = ae.getRightOperand() |
+ n = bo.getLeftOperand() and
+ l = sao.getMethodCallLocation()
+ or
+ exists(MethodCall mc | mc = bo.getLeftOperand() |
+ n = mc.getReceiver() and
+ hasLocation(sao.getReceiver(), l)
+ or
+ exists(int i |
+ n = mc.getArgument(i) and
+ hasLocation(sao.getArgument(i), l)
+ )
+ )
+ )
+ or
+ exists(MethodCall mc | mc = seq.getStmt(opAssignIndex + 1) |
+ n = mc and
+ l = sao.getMethodCallLocation()
+ or
+ n = mc.getReceiver() and
+ hasLocation(sao.getReceiver(), l)
+ or
+ exists(int i | n = mc.getArgument(i) |
+ hasLocation(sao.getArgument(i), l)
+ or
+ i = opAssignIndex and
+ l = getAssignOperationLocation(sao)
+ )
+ )
+ or
+ n = seq.getStmt(opAssignIndex + 2) and
+ l = getAssignOperationLocation(sao)
+ )
+ )
+ }
+
+ final override predicate localVariable(AstNode n, int i) {
+ n = any(SetterAssignOperation sao | i in [0 .. sao.getNumberOfArguments() + 1])
+ }
+
+ final override predicate methodCall(string name, boolean setter, int arity) {
+ exists(SetterAssignOperation sao | name = sao.getMethodCall().getMethodName() |
+ setter = false and
+ arity = sao.getNumberOfArguments()
+ or
+ setter = true and
+ arity = sao.getNumberOfArguments() + 1
+ )
+ }
+
+ final override predicate excludeFromControlFlowTree(AstNode n) {
+ n = any(SetterAssignOperation sao).getMethodCall()
+ }
+ }
+}
+
+private module CompoundAssignDesugar {
+ /** An assignment where the left-hand side is a tuple pattern. */
+ private class TupleAssignExpr extends AssignExpr {
+ private TuplePattern tp;
+
+ pragma[nomagic]
+ TupleAssignExpr() { tp = this.getLeftOperand() }
+
+ TuplePattern getTuplePattern() { result = tp }
+
+ pragma[nomagic]
+ Pattern getElement(int i) { result = tp.getElement(i) }
+
+ pragma[nomagic]
+ int getNumberOfElements() {
+ toGenerated(tp) = any(TuplePatternImpl impl | result = count(impl.getChildNode(_)))
+ }
+
+ pragma[nomagic]
+ int getRestIndexOrNumberOfElements() {
+ result = tp.getRestIndex()
+ or
+ toGenerated(tp) = any(TuplePatternImpl impl | not exists(impl.getRestIndex())) and
+ result = this.getNumberOfElements()
+ }
+ }
+
+ pragma[nomagic]
+ private predicate compoundAssignSynthesis(AstNode parent, int i, Child child) {
+ exists(TupleAssignExpr tae |
+ parent = tae and
+ i = -1 and
+ child = SynthChild(StmtSequenceKind())
+ or
+ exists(AstNode seq | seq = TStmtSequenceSynth(tae, -1) |
+ parent = seq and
+ i = 0 and
+ child = SynthChild(AssignExprKind())
+ or
+ exists(AstNode assign | assign = TAssignExprSynth(seq, 0) |
+ parent = assign and
+ i = 0 and
+ child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(tae, 0)))
+ or
+ parent = assign and
+ i = 1 and
+ child = SynthChild(SplatExprKind())
+ or
+ parent = TSplatExprSynth(assign, 1) and
+ i = 0 and
+ child = childRef(tae.getRightOperand())
+ )
+ or
+ exists(Pattern p, int j, int restIndex |
+ p = tae.getElement(j) and
+ restIndex = tae.getRestIndexOrNumberOfElements()
+ |
+ parent = seq and
+ i = j + 1 and
+ child = SynthChild(AssignExprKind())
+ or
+ exists(AstNode assign | assign = TAssignExprSynth(seq, j + 1) |
+ parent = assign and
+ i = 0 and
+ child = childRef(p)
+ or
+ parent = assign and
+ i = 1 and
+ child = SynthChild(MethodCallKind("[]", false, 1))
+ or
+ parent = TMethodCallSynth(assign, 1, _, _, _) and
+ i = 0 and
+ child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(tae, 0)))
+ or
+ j < restIndex and
+ parent = TMethodCallSynth(assign, 1, _, _, _) and
+ i = 1 and
+ child = SynthChild(IntegerLiteralKind(j))
+ or
+ j = restIndex and
+ (
+ parent = TMethodCallSynth(assign, 1, _, _, _) and
+ i = 1 and
+ child = SynthChild(RangeLiteralKind(true))
+ or
+ exists(AstNode call |
+ call = TMethodCallSynth(assign, 1, _, _, _) and
+ parent = TRangeLiteralSynth(call, 1, _)
+ |
+ i = 0 and
+ child = SynthChild(IntegerLiteralKind(j))
+ or
+ i = 1 and
+ child = SynthChild(IntegerLiteralKind(restIndex - tae.getNumberOfElements()))
+ )
+ )
+ or
+ j > restIndex and
+ parent = TMethodCallSynth(assign, 1, _, _, _) and
+ i = 1 and
+ child = SynthChild(IntegerLiteralKind(j - tae.getNumberOfElements()))
+ )
+ )
+ )
+ )
+ }
+
+ /**
+ * ```rb
+ * x, *y, z = w
+ * ```
+ * desugars to
+ *
+ * ```rb
+ * __synth__0 = *w;
+ * x = __synth__0[0];
+ * y = __synth__0[1..-2];
+ * z = __synth__0[-1];
+ * ```
+ */
+ private class CompoundAssignSynthesis extends Synthesis {
+ final override predicate child(AstNode parent, int i, Child child) {
+ compoundAssignSynthesis(parent, i, child)
+ }
+
+ final override predicate location(AstNode n, Location l) {
+ exists(TupleAssignExpr tae, StmtSequence seq | seq = tae.getDesugared() |
+ n = seq.getStmt(0) and
+ hasLocation(tae.getRightOperand(), l)
+ or
+ exists(Pattern p, int j |
+ p = tae.getElement(j) and
+ n = seq.getStmt(j + 1) and
+ hasLocation(p, l)
+ )
+ )
+ }
+
+ final override predicate localVariable(AstNode n, int i) {
+ n instanceof TupleAssignExpr and
+ i = 0
+ }
+
+ final override predicate methodCall(string name, boolean setter, int arity) {
+ name = "[]" and
+ setter = false and
+ arity = 1
+ }
+
+ final override predicate excludeFromControlFlowTree(AstNode n) {
+ n = any(TupleAssignExpr tae).getTuplePattern()
+ }
+ }
+}
+
+private module ArrayLiteralDesugar {
+ pragma[nomagic]
+ private predicate arrayLiteralSynthesis(AstNode parent, int i, Child child) {
+ exists(ArrayLiteral al |
+ parent = al and
+ i = -1 and
+ child = SynthChild(MethodCallKind("[]", false, al.getNumberOfElements() + 1))
+ or
+ exists(AstNode mc | mc = TMethodCallSynth(al, -1, _, _, _) |
+ parent = mc and
+ i = 0 and
+ child = SynthChild(ConstantReadAccessKind("::Array"))
+ or
+ parent = mc and
+ child = childRef(al.getElement(i - 1))
+ )
+ )
+ }
+
+ /**
+ * ```rb
+ * [1, 2, 3]
+ * ```
+ * desugars to
+ *
+ * ```rb
+ * ::Array.[](1, 2, 3)
+ * ```
+ */
+ private class CompoundAssignSynthesis extends Synthesis {
+ final override predicate child(AstNode parent, int i, Child child) {
+ arrayLiteralSynthesis(parent, i, child)
+ }
+
+ final override predicate methodCall(string name, boolean setter, int arity) {
+ name = "[]" and
+ setter = false and
+ arity = any(ArrayLiteral al).getNumberOfElements() + 1
+ }
+
+ final override predicate constantReadAccess(string name) { name = "::Array" }
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/TreeSitter.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/TreeSitter.qll
new file mode 100644
index 00000000000..c4af728e5fe
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/TreeSitter.qll
@@ -0,0 +1,1999 @@
+/*
+ * CodeQL library for Ruby
+ * Automatically generated from the tree-sitter grammar; do not edit
+ */
+
+import codeql.Locations as L
+
+module Ruby {
+ /** The base class for all AST nodes */
+ class AstNode extends @ruby_ast_node {
+ /** Gets a string representation of this element. */
+ string toString() { result = this.getAPrimaryQlClass() }
+
+ /** Gets the location of this element. */
+ L::Location getLocation() { none() }
+
+ /** Gets the parent of this element. */
+ AstNode getParent() { ruby_ast_node_parent(this, result, _) }
+
+ /** Gets the index of this node among the children of its parent. */
+ int getParentIndex() { ruby_ast_node_parent(this, _, result) }
+
+ /** Gets a field or child node of this node. */
+ AstNode getAFieldOrChild() { none() }
+
+ /** Gets the name of the primary QL class for this element. */
+ string getAPrimaryQlClass() { result = "???" }
+
+ /** Gets a comma-separated list of the names of the primary CodeQL classes to which this element belongs. */
+ string getPrimaryQlClasses() { result = concat(this.getAPrimaryQlClass(), ",") }
+ }
+
+ /** A token. */
+ class Token extends @ruby_token, AstNode {
+ /** Gets the value of this token. */
+ string getValue() { ruby_tokeninfo(this, _, result, _) }
+
+ /** Gets the location of this token. */
+ override L::Location getLocation() { ruby_tokeninfo(this, _, _, result) }
+
+ /** Gets a string representation of this element. */
+ override string toString() { result = this.getValue() }
+
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Token" }
+ }
+
+ /** A reserved word. */
+ class ReservedWord extends @ruby_reserved_word, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "ReservedWord" }
+ }
+
+ class UnderscoreArg extends @ruby_underscore_arg, AstNode { }
+
+ class UnderscoreLhs extends @ruby_underscore_lhs, AstNode { }
+
+ class UnderscoreMethodName extends @ruby_underscore_method_name, AstNode { }
+
+ class UnderscorePrimary extends @ruby_underscore_primary, AstNode { }
+
+ class UnderscoreStatement extends @ruby_underscore_statement, AstNode { }
+
+ class UnderscoreVariable extends @ruby_underscore_variable, AstNode { }
+
+ /** A class representing `alias` nodes. */
+ class Alias extends @ruby_alias, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Alias" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_alias_def(this, _, _, result) }
+
+ /** Gets the node corresponding to the field `alias`. */
+ UnderscoreMethodName getAlias() { ruby_alias_def(this, result, _, _) }
+
+ /** Gets the node corresponding to the field `name`. */
+ UnderscoreMethodName getName() { ruby_alias_def(this, _, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_alias_def(this, result, _, _) or ruby_alias_def(this, _, result, _)
+ }
+ }
+
+ /** A class representing `argument_list` nodes. */
+ class ArgumentList extends @ruby_argument_list, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "ArgumentList" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_argument_list_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_argument_list_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_argument_list_child(this, _, result) }
+ }
+
+ /** A class representing `array` nodes. */
+ class Array extends @ruby_array, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Array" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_array_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_array_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_array_child(this, _, result) }
+ }
+
+ /** A class representing `assignment` nodes. */
+ class Assignment extends @ruby_assignment, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Assignment" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_assignment_def(this, _, _, result) }
+
+ /** Gets the node corresponding to the field `left`. */
+ AstNode getLeft() { ruby_assignment_def(this, result, _, _) }
+
+ /** Gets the node corresponding to the field `right`. */
+ AstNode getRight() { ruby_assignment_def(this, _, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_assignment_def(this, result, _, _) or ruby_assignment_def(this, _, result, _)
+ }
+ }
+
+ /** A class representing `bare_string` nodes. */
+ class BareString extends @ruby_bare_string, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "BareString" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_bare_string_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_bare_string_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_bare_string_child(this, _, result) }
+ }
+
+ /** A class representing `bare_symbol` nodes. */
+ class BareSymbol extends @ruby_bare_symbol, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "BareSymbol" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_bare_symbol_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_bare_symbol_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_bare_symbol_child(this, _, result) }
+ }
+
+ /** A class representing `begin` nodes. */
+ class Begin extends @ruby_begin, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Begin" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_begin_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_begin_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_begin_child(this, _, result) }
+ }
+
+ /** A class representing `begin_block` nodes. */
+ class BeginBlock extends @ruby_begin_block, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "BeginBlock" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_begin_block_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_begin_block_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_begin_block_child(this, _, result) }
+ }
+
+ /** A class representing `binary` nodes. */
+ class Binary extends @ruby_binary, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Binary" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_binary_def(this, _, _, _, result) }
+
+ /** Gets the node corresponding to the field `left`. */
+ AstNode getLeft() { ruby_binary_def(this, result, _, _, _) }
+
+ /** Gets the node corresponding to the field `operator`. */
+ string getOperator() {
+ exists(int value | ruby_binary_def(this, _, value, _, _) |
+ result = "!=" and value = 0
+ or
+ result = "!~" and value = 1
+ or
+ result = "%" and value = 2
+ or
+ result = "&" and value = 3
+ or
+ result = "&&" and value = 4
+ or
+ result = "*" and value = 5
+ or
+ result = "**" and value = 6
+ or
+ result = "+" and value = 7
+ or
+ result = "-" and value = 8
+ or
+ result = "/" and value = 9
+ or
+ result = "<" and value = 10
+ or
+ result = "<<" and value = 11
+ or
+ result = "<=" and value = 12
+ or
+ result = "<=>" and value = 13
+ or
+ result = "==" and value = 14
+ or
+ result = "===" and value = 15
+ or
+ result = "=~" and value = 16
+ or
+ result = ">" and value = 17
+ or
+ result = ">=" and value = 18
+ or
+ result = ">>" and value = 19
+ or
+ result = "^" and value = 20
+ or
+ result = "and" and value = 21
+ or
+ result = "or" and value = 22
+ or
+ result = "|" and value = 23
+ or
+ result = "||" and value = 24
+ )
+ }
+
+ /** Gets the node corresponding to the field `right`. */
+ AstNode getRight() { ruby_binary_def(this, _, _, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_binary_def(this, result, _, _, _) or ruby_binary_def(this, _, _, result, _)
+ }
+ }
+
+ /** A class representing `block` nodes. */
+ class Block extends @ruby_block, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Block" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_block_def(this, result) }
+
+ /** Gets the node corresponding to the field `parameters`. */
+ BlockParameters getParameters() { ruby_block_parameters(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_block_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_block_parameters(this, result) or ruby_block_child(this, _, result)
+ }
+ }
+
+ /** A class representing `block_argument` nodes. */
+ class BlockArgument extends @ruby_block_argument, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "BlockArgument" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_block_argument_def(this, _, result) }
+
+ /** Gets the child of this node. */
+ UnderscoreArg getChild() { ruby_block_argument_def(this, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_block_argument_def(this, result, _) }
+ }
+
+ /** A class representing `block_parameter` nodes. */
+ class BlockParameter extends @ruby_block_parameter, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "BlockParameter" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_block_parameter_def(this, _, result) }
+
+ /** Gets the node corresponding to the field `name`. */
+ Identifier getName() { ruby_block_parameter_def(this, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_block_parameter_def(this, result, _) }
+ }
+
+ /** A class representing `block_parameters` nodes. */
+ class BlockParameters extends @ruby_block_parameters, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "BlockParameters" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_block_parameters_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_block_parameters_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_block_parameters_child(this, _, result) }
+ }
+
+ /** A class representing `break` nodes. */
+ class Break extends @ruby_break, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Break" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_break_def(this, result) }
+
+ /** Gets the child of this node. */
+ ArgumentList getChild() { ruby_break_child(this, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_break_child(this, result) }
+ }
+
+ /** A class representing `call` nodes. */
+ class Call extends @ruby_call, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Call" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_call_def(this, _, result) }
+
+ /** Gets the node corresponding to the field `arguments`. */
+ ArgumentList getArguments() { ruby_call_arguments(this, result) }
+
+ /** Gets the node corresponding to the field `block`. */
+ AstNode getBlock() { ruby_call_block(this, result) }
+
+ /** Gets the node corresponding to the field `method`. */
+ AstNode getMethod() { ruby_call_def(this, result, _) }
+
+ /** Gets the node corresponding to the field `receiver`. */
+ AstNode getReceiver() { ruby_call_receiver(this, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_call_arguments(this, result) or
+ ruby_call_block(this, result) or
+ ruby_call_def(this, result, _) or
+ ruby_call_receiver(this, result)
+ }
+ }
+
+ /** A class representing `case` nodes. */
+ class Case extends @ruby_case__, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Case" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_case_def(this, result) }
+
+ /** Gets the node corresponding to the field `value`. */
+ UnderscoreStatement getValue() { ruby_case_value(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_case_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_case_value(this, result) or ruby_case_child(this, _, result)
+ }
+ }
+
+ /** A class representing `chained_string` nodes. */
+ class ChainedString extends @ruby_chained_string, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "ChainedString" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_chained_string_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ String getChild(int i) { ruby_chained_string_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_chained_string_child(this, _, result) }
+ }
+
+ /** A class representing `character` tokens. */
+ class Character extends @ruby_token_character, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Character" }
+ }
+
+ /** A class representing `class` nodes. */
+ class Class extends @ruby_class, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Class" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_class_def(this, _, result) }
+
+ /** Gets the node corresponding to the field `name`. */
+ AstNode getName() { ruby_class_def(this, result, _) }
+
+ /** Gets the node corresponding to the field `superclass`. */
+ Superclass getSuperclass() { ruby_class_superclass(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_class_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_class_def(this, result, _) or
+ ruby_class_superclass(this, result) or
+ ruby_class_child(this, _, result)
+ }
+ }
+
+ /** A class representing `class_variable` tokens. */
+ class ClassVariable extends @ruby_token_class_variable, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "ClassVariable" }
+ }
+
+ /** A class representing `comment` tokens. */
+ class Comment extends @ruby_token_comment, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Comment" }
+ }
+
+ /** A class representing `complex` tokens. */
+ class Complex extends @ruby_token_complex, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Complex" }
+ }
+
+ /** A class representing `conditional` nodes. */
+ class Conditional extends @ruby_conditional, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Conditional" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_conditional_def(this, _, _, _, result) }
+
+ /** Gets the node corresponding to the field `alternative`. */
+ UnderscoreArg getAlternative() { ruby_conditional_def(this, result, _, _, _) }
+
+ /** Gets the node corresponding to the field `condition`. */
+ UnderscoreArg getCondition() { ruby_conditional_def(this, _, result, _, _) }
+
+ /** Gets the node corresponding to the field `consequence`. */
+ UnderscoreArg getConsequence() { ruby_conditional_def(this, _, _, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_conditional_def(this, result, _, _, _) or
+ ruby_conditional_def(this, _, result, _, _) or
+ ruby_conditional_def(this, _, _, result, _)
+ }
+ }
+
+ /** A class representing `constant` tokens. */
+ class Constant extends @ruby_token_constant, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Constant" }
+ }
+
+ /** A class representing `delimited_symbol` nodes. */
+ class DelimitedSymbol extends @ruby_delimited_symbol, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "DelimitedSymbol" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_delimited_symbol_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_delimited_symbol_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_delimited_symbol_child(this, _, result) }
+ }
+
+ /** A class representing `destructured_left_assignment` nodes. */
+ class DestructuredLeftAssignment extends @ruby_destructured_left_assignment, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "DestructuredLeftAssignment" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_destructured_left_assignment_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_destructured_left_assignment_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_destructured_left_assignment_child(this, _, result) }
+ }
+
+ /** A class representing `destructured_parameter` nodes. */
+ class DestructuredParameter extends @ruby_destructured_parameter, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "DestructuredParameter" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_destructured_parameter_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_destructured_parameter_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_destructured_parameter_child(this, _, result) }
+ }
+
+ /** A class representing `do` nodes. */
+ class Do extends @ruby_do, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Do" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_do_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_do_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_do_child(this, _, result) }
+ }
+
+ /** A class representing `do_block` nodes. */
+ class DoBlock extends @ruby_do_block, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "DoBlock" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_do_block_def(this, result) }
+
+ /** Gets the node corresponding to the field `parameters`. */
+ BlockParameters getParameters() { ruby_do_block_parameters(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_do_block_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_do_block_parameters(this, result) or ruby_do_block_child(this, _, result)
+ }
+ }
+
+ /** A class representing `element_reference` nodes. */
+ class ElementReference extends @ruby_element_reference, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "ElementReference" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_element_reference_def(this, _, result) }
+
+ /** Gets the node corresponding to the field `object`. */
+ UnderscorePrimary getObject() { ruby_element_reference_def(this, result, _) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_element_reference_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_element_reference_def(this, result, _) or ruby_element_reference_child(this, _, result)
+ }
+ }
+
+ /** A class representing `else` nodes. */
+ class Else extends @ruby_else, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Else" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_else_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_else_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_else_child(this, _, result) }
+ }
+
+ /** A class representing `elsif` nodes. */
+ class Elsif extends @ruby_elsif, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Elsif" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_elsif_def(this, _, result) }
+
+ /** Gets the node corresponding to the field `alternative`. */
+ AstNode getAlternative() { ruby_elsif_alternative(this, result) }
+
+ /** Gets the node corresponding to the field `condition`. */
+ UnderscoreStatement getCondition() { ruby_elsif_def(this, result, _) }
+
+ /** Gets the node corresponding to the field `consequence`. */
+ Then getConsequence() { ruby_elsif_consequence(this, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_elsif_alternative(this, result) or
+ ruby_elsif_def(this, result, _) or
+ ruby_elsif_consequence(this, result)
+ }
+ }
+
+ /** A class representing `empty_statement` tokens. */
+ class EmptyStatement extends @ruby_token_empty_statement, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "EmptyStatement" }
+ }
+
+ /** A class representing `end_block` nodes. */
+ class EndBlock extends @ruby_end_block, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "EndBlock" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_end_block_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_end_block_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_end_block_child(this, _, result) }
+ }
+
+ /** A class representing `ensure` nodes. */
+ class Ensure extends @ruby_ensure, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Ensure" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_ensure_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_ensure_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_ensure_child(this, _, result) }
+ }
+
+ /** A class representing `escape_sequence` tokens. */
+ class EscapeSequence extends @ruby_token_escape_sequence, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "EscapeSequence" }
+ }
+
+ /** A class representing `exception_variable` nodes. */
+ class ExceptionVariable extends @ruby_exception_variable, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "ExceptionVariable" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_exception_variable_def(this, _, result) }
+
+ /** Gets the child of this node. */
+ UnderscoreLhs getChild() { ruby_exception_variable_def(this, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_exception_variable_def(this, result, _) }
+ }
+
+ /** A class representing `exceptions` nodes. */
+ class Exceptions extends @ruby_exceptions, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Exceptions" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_exceptions_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_exceptions_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_exceptions_child(this, _, result) }
+ }
+
+ /** A class representing `false` tokens. */
+ class False extends @ruby_token_false, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "False" }
+ }
+
+ /** A class representing `float` tokens. */
+ class Float extends @ruby_token_float, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Float" }
+ }
+
+ /** A class representing `for` nodes. */
+ class For extends @ruby_for, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "For" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_for_def(this, _, _, _, result) }
+
+ /** Gets the node corresponding to the field `body`. */
+ Do getBody() { ruby_for_def(this, result, _, _, _) }
+
+ /** Gets the node corresponding to the field `pattern`. */
+ AstNode getPattern() { ruby_for_def(this, _, result, _, _) }
+
+ /** Gets the node corresponding to the field `value`. */
+ In getValue() { ruby_for_def(this, _, _, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_for_def(this, result, _, _, _) or
+ ruby_for_def(this, _, result, _, _) or
+ ruby_for_def(this, _, _, result, _)
+ }
+ }
+
+ /** A class representing `forward_argument` tokens. */
+ class ForwardArgument extends @ruby_token_forward_argument, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "ForwardArgument" }
+ }
+
+ /** A class representing `forward_parameter` tokens. */
+ class ForwardParameter extends @ruby_token_forward_parameter, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "ForwardParameter" }
+ }
+
+ /** A class representing `global_variable` tokens. */
+ class GlobalVariable extends @ruby_token_global_variable, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "GlobalVariable" }
+ }
+
+ /** A class representing `hash` nodes. */
+ class Hash extends @ruby_hash, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Hash" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_hash_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_hash_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_hash_child(this, _, result) }
+ }
+
+ /** A class representing `hash_key_symbol` tokens. */
+ class HashKeySymbol extends @ruby_token_hash_key_symbol, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "HashKeySymbol" }
+ }
+
+ /** A class representing `hash_splat_argument` nodes. */
+ class HashSplatArgument extends @ruby_hash_splat_argument, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "HashSplatArgument" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_hash_splat_argument_def(this, _, result) }
+
+ /** Gets the child of this node. */
+ UnderscoreArg getChild() { ruby_hash_splat_argument_def(this, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_hash_splat_argument_def(this, result, _) }
+ }
+
+ /** A class representing `hash_splat_parameter` nodes. */
+ class HashSplatParameter extends @ruby_hash_splat_parameter, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "HashSplatParameter" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_hash_splat_parameter_def(this, result) }
+
+ /** Gets the node corresponding to the field `name`. */
+ Identifier getName() { ruby_hash_splat_parameter_name(this, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_hash_splat_parameter_name(this, result) }
+ }
+
+ /** A class representing `heredoc_beginning` tokens. */
+ class HeredocBeginning extends @ruby_token_heredoc_beginning, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "HeredocBeginning" }
+ }
+
+ /** A class representing `heredoc_body` nodes. */
+ class HeredocBody extends @ruby_heredoc_body, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "HeredocBody" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_heredoc_body_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_heredoc_body_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_heredoc_body_child(this, _, result) }
+ }
+
+ /** A class representing `heredoc_content` tokens. */
+ class HeredocContent extends @ruby_token_heredoc_content, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "HeredocContent" }
+ }
+
+ /** A class representing `heredoc_end` tokens. */
+ class HeredocEnd extends @ruby_token_heredoc_end, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "HeredocEnd" }
+ }
+
+ /** A class representing `identifier` tokens. */
+ class Identifier extends @ruby_token_identifier, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Identifier" }
+ }
+
+ /** A class representing `if` nodes. */
+ class If extends @ruby_if, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "If" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_if_def(this, _, result) }
+
+ /** Gets the node corresponding to the field `alternative`. */
+ AstNode getAlternative() { ruby_if_alternative(this, result) }
+
+ /** Gets the node corresponding to the field `condition`. */
+ UnderscoreStatement getCondition() { ruby_if_def(this, result, _) }
+
+ /** Gets the node corresponding to the field `consequence`. */
+ Then getConsequence() { ruby_if_consequence(this, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_if_alternative(this, result) or
+ ruby_if_def(this, result, _) or
+ ruby_if_consequence(this, result)
+ }
+ }
+
+ /** A class representing `if_modifier` nodes. */
+ class IfModifier extends @ruby_if_modifier, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "IfModifier" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_if_modifier_def(this, _, _, result) }
+
+ /** Gets the node corresponding to the field `body`. */
+ UnderscoreStatement getBody() { ruby_if_modifier_def(this, result, _, _) }
+
+ /** Gets the node corresponding to the field `condition`. */
+ AstNode getCondition() { ruby_if_modifier_def(this, _, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_if_modifier_def(this, result, _, _) or ruby_if_modifier_def(this, _, result, _)
+ }
+ }
+
+ /** A class representing `in` nodes. */
+ class In extends @ruby_in, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "In" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_in_def(this, _, result) }
+
+ /** Gets the child of this node. */
+ UnderscoreArg getChild() { ruby_in_def(this, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_in_def(this, result, _) }
+ }
+
+ /** A class representing `instance_variable` tokens. */
+ class InstanceVariable extends @ruby_token_instance_variable, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "InstanceVariable" }
+ }
+
+ /** A class representing `integer` tokens. */
+ class Integer extends @ruby_token_integer, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Integer" }
+ }
+
+ /** A class representing `interpolation` nodes. */
+ class Interpolation extends @ruby_interpolation, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Interpolation" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_interpolation_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_interpolation_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_interpolation_child(this, _, result) }
+ }
+
+ /** A class representing `keyword_parameter` nodes. */
+ class KeywordParameter extends @ruby_keyword_parameter, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "KeywordParameter" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_keyword_parameter_def(this, _, result) }
+
+ /** Gets the node corresponding to the field `name`. */
+ Identifier getName() { ruby_keyword_parameter_def(this, result, _) }
+
+ /** Gets the node corresponding to the field `value`. */
+ UnderscoreArg getValue() { ruby_keyword_parameter_value(this, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_keyword_parameter_def(this, result, _) or ruby_keyword_parameter_value(this, result)
+ }
+ }
+
+ /** A class representing `lambda` nodes. */
+ class Lambda extends @ruby_lambda, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Lambda" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_lambda_def(this, _, result) }
+
+ /** Gets the node corresponding to the field `body`. */
+ AstNode getBody() { ruby_lambda_def(this, result, _) }
+
+ /** Gets the node corresponding to the field `parameters`. */
+ LambdaParameters getParameters() { ruby_lambda_parameters(this, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_lambda_def(this, result, _) or ruby_lambda_parameters(this, result)
+ }
+ }
+
+ /** A class representing `lambda_parameters` nodes. */
+ class LambdaParameters extends @ruby_lambda_parameters, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "LambdaParameters" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_lambda_parameters_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_lambda_parameters_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_lambda_parameters_child(this, _, result) }
+ }
+
+ /** A class representing `left_assignment_list` nodes. */
+ class LeftAssignmentList extends @ruby_left_assignment_list, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "LeftAssignmentList" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_left_assignment_list_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_left_assignment_list_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_left_assignment_list_child(this, _, result) }
+ }
+
+ /** A class representing `method` nodes. */
+ class Method extends @ruby_method, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Method" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_method_def(this, _, result) }
+
+ /** Gets the node corresponding to the field `name`. */
+ UnderscoreMethodName getName() { ruby_method_def(this, result, _) }
+
+ /** Gets the node corresponding to the field `parameters`. */
+ MethodParameters getParameters() { ruby_method_parameters(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_method_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_method_def(this, result, _) or
+ ruby_method_parameters(this, result) or
+ ruby_method_child(this, _, result)
+ }
+ }
+
+ /** A class representing `method_parameters` nodes. */
+ class MethodParameters extends @ruby_method_parameters, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "MethodParameters" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_method_parameters_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_method_parameters_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_method_parameters_child(this, _, result) }
+ }
+
+ /** A class representing `module` nodes. */
+ class Module extends @ruby_module, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Module" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_module_def(this, _, result) }
+
+ /** Gets the node corresponding to the field `name`. */
+ AstNode getName() { ruby_module_def(this, result, _) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_module_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_module_def(this, result, _) or ruby_module_child(this, _, result)
+ }
+ }
+
+ /** A class representing `next` nodes. */
+ class Next extends @ruby_next, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Next" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_next_def(this, result) }
+
+ /** Gets the child of this node. */
+ ArgumentList getChild() { ruby_next_child(this, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_next_child(this, result) }
+ }
+
+ /** A class representing `nil` tokens. */
+ class Nil extends @ruby_token_nil, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Nil" }
+ }
+
+ /** A class representing `operator` tokens. */
+ class Operator extends @ruby_token_operator, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Operator" }
+ }
+
+ /** A class representing `operator_assignment` nodes. */
+ class OperatorAssignment extends @ruby_operator_assignment, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "OperatorAssignment" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_operator_assignment_def(this, _, _, _, result) }
+
+ /** Gets the node corresponding to the field `left`. */
+ UnderscoreLhs getLeft() { ruby_operator_assignment_def(this, result, _, _, _) }
+
+ /** Gets the node corresponding to the field `operator`. */
+ string getOperator() {
+ exists(int value | ruby_operator_assignment_def(this, _, value, _, _) |
+ result = "%=" and value = 0
+ or
+ result = "&&=" and value = 1
+ or
+ result = "&=" and value = 2
+ or
+ result = "**=" and value = 3
+ or
+ result = "*=" and value = 4
+ or
+ result = "+=" and value = 5
+ or
+ result = "-=" and value = 6
+ or
+ result = "/=" and value = 7
+ or
+ result = "<<=" and value = 8
+ or
+ result = ">>=" and value = 9
+ or
+ result = "^=" and value = 10
+ or
+ result = "|=" and value = 11
+ or
+ result = "||=" and value = 12
+ )
+ }
+
+ /** Gets the node corresponding to the field `right`. */
+ AstNode getRight() { ruby_operator_assignment_def(this, _, _, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_operator_assignment_def(this, result, _, _, _) or
+ ruby_operator_assignment_def(this, _, _, result, _)
+ }
+ }
+
+ /** A class representing `optional_parameter` nodes. */
+ class OptionalParameter extends @ruby_optional_parameter, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "OptionalParameter" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_optional_parameter_def(this, _, _, result) }
+
+ /** Gets the node corresponding to the field `name`. */
+ Identifier getName() { ruby_optional_parameter_def(this, result, _, _) }
+
+ /** Gets the node corresponding to the field `value`. */
+ UnderscoreArg getValue() { ruby_optional_parameter_def(this, _, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_optional_parameter_def(this, result, _, _) or
+ ruby_optional_parameter_def(this, _, result, _)
+ }
+ }
+
+ /** A class representing `pair` nodes. */
+ class Pair extends @ruby_pair, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Pair" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_pair_def(this, _, _, result) }
+
+ /** Gets the node corresponding to the field `key`. */
+ AstNode getKey() { ruby_pair_def(this, result, _, _) }
+
+ /** Gets the node corresponding to the field `value`. */
+ UnderscoreArg getValue() { ruby_pair_def(this, _, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_pair_def(this, result, _, _) or ruby_pair_def(this, _, result, _)
+ }
+ }
+
+ /** A class representing `parenthesized_statements` nodes. */
+ class ParenthesizedStatements extends @ruby_parenthesized_statements, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "ParenthesizedStatements" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_parenthesized_statements_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_parenthesized_statements_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_parenthesized_statements_child(this, _, result) }
+ }
+
+ /** A class representing `pattern` nodes. */
+ class Pattern extends @ruby_pattern, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Pattern" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_pattern_def(this, _, result) }
+
+ /** Gets the child of this node. */
+ AstNode getChild() { ruby_pattern_def(this, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_pattern_def(this, result, _) }
+ }
+
+ /** A class representing `program` nodes. */
+ class Program extends @ruby_program, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Program" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_program_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_program_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_program_child(this, _, result) }
+ }
+
+ /** A class representing `range` nodes. */
+ class Range extends @ruby_range, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Range" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_range_def(this, _, result) }
+
+ /** Gets the node corresponding to the field `begin`. */
+ UnderscoreArg getBegin() { ruby_range_begin(this, result) }
+
+ /** Gets the node corresponding to the field `end`. */
+ UnderscoreArg getEnd() { ruby_range_end(this, result) }
+
+ /** Gets the node corresponding to the field `operator`. */
+ string getOperator() {
+ exists(int value | ruby_range_def(this, value, _) |
+ result = ".." and value = 0
+ or
+ result = "..." and value = 1
+ )
+ }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_range_begin(this, result) or ruby_range_end(this, result)
+ }
+ }
+
+ /** A class representing `rational` nodes. */
+ class Rational extends @ruby_rational, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Rational" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_rational_def(this, _, result) }
+
+ /** Gets the child of this node. */
+ AstNode getChild() { ruby_rational_def(this, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_rational_def(this, result, _) }
+ }
+
+ /** A class representing `redo` nodes. */
+ class Redo extends @ruby_redo, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Redo" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_redo_def(this, result) }
+
+ /** Gets the child of this node. */
+ ArgumentList getChild() { ruby_redo_child(this, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_redo_child(this, result) }
+ }
+
+ /** A class representing `regex` nodes. */
+ class Regex extends @ruby_regex, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Regex" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_regex_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_regex_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_regex_child(this, _, result) }
+ }
+
+ /** A class representing `rescue` nodes. */
+ class Rescue extends @ruby_rescue, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Rescue" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_rescue_def(this, result) }
+
+ /** Gets the node corresponding to the field `body`. */
+ Then getBody() { ruby_rescue_body(this, result) }
+
+ /** Gets the node corresponding to the field `exceptions`. */
+ Exceptions getExceptions() { ruby_rescue_exceptions(this, result) }
+
+ /** Gets the node corresponding to the field `variable`. */
+ ExceptionVariable getVariable() { ruby_rescue_variable(this, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_rescue_body(this, result) or
+ ruby_rescue_exceptions(this, result) or
+ ruby_rescue_variable(this, result)
+ }
+ }
+
+ /** A class representing `rescue_modifier` nodes. */
+ class RescueModifier extends @ruby_rescue_modifier, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "RescueModifier" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_rescue_modifier_def(this, _, _, result) }
+
+ /** Gets the node corresponding to the field `body`. */
+ UnderscoreStatement getBody() { ruby_rescue_modifier_def(this, result, _, _) }
+
+ /** Gets the node corresponding to the field `handler`. */
+ AstNode getHandler() { ruby_rescue_modifier_def(this, _, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_rescue_modifier_def(this, result, _, _) or ruby_rescue_modifier_def(this, _, result, _)
+ }
+ }
+
+ /** A class representing `rest_assignment` nodes. */
+ class RestAssignment extends @ruby_rest_assignment, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "RestAssignment" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_rest_assignment_def(this, result) }
+
+ /** Gets the child of this node. */
+ UnderscoreLhs getChild() { ruby_rest_assignment_child(this, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_rest_assignment_child(this, result) }
+ }
+
+ /** A class representing `retry` nodes. */
+ class Retry extends @ruby_retry, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Retry" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_retry_def(this, result) }
+
+ /** Gets the child of this node. */
+ ArgumentList getChild() { ruby_retry_child(this, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_retry_child(this, result) }
+ }
+
+ /** A class representing `return` nodes. */
+ class Return extends @ruby_return, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Return" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_return_def(this, result) }
+
+ /** Gets the child of this node. */
+ ArgumentList getChild() { ruby_return_child(this, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_return_child(this, result) }
+ }
+
+ /** A class representing `right_assignment_list` nodes. */
+ class RightAssignmentList extends @ruby_right_assignment_list, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "RightAssignmentList" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_right_assignment_list_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_right_assignment_list_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_right_assignment_list_child(this, _, result) }
+ }
+
+ /** A class representing `scope_resolution` nodes. */
+ class ScopeResolution extends @ruby_scope_resolution, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "ScopeResolution" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_scope_resolution_def(this, _, result) }
+
+ /** Gets the node corresponding to the field `name`. */
+ AstNode getName() { ruby_scope_resolution_def(this, result, _) }
+
+ /** Gets the node corresponding to the field `scope`. */
+ UnderscorePrimary getScope() { ruby_scope_resolution_scope(this, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_scope_resolution_def(this, result, _) or ruby_scope_resolution_scope(this, result)
+ }
+ }
+
+ /** A class representing `self` tokens. */
+ class Self extends @ruby_token_self, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Self" }
+ }
+
+ /** A class representing `setter` nodes. */
+ class Setter extends @ruby_setter, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Setter" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_setter_def(this, _, result) }
+
+ /** Gets the node corresponding to the field `name`. */
+ Identifier getName() { ruby_setter_def(this, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_setter_def(this, result, _) }
+ }
+
+ /** A class representing `simple_symbol` tokens. */
+ class SimpleSymbol extends @ruby_token_simple_symbol, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "SimpleSymbol" }
+ }
+
+ /** A class representing `singleton_class` nodes. */
+ class SingletonClass extends @ruby_singleton_class, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "SingletonClass" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_singleton_class_def(this, _, result) }
+
+ /** Gets the node corresponding to the field `value`. */
+ UnderscoreArg getValue() { ruby_singleton_class_def(this, result, _) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_singleton_class_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_singleton_class_def(this, result, _) or ruby_singleton_class_child(this, _, result)
+ }
+ }
+
+ /** A class representing `singleton_method` nodes. */
+ class SingletonMethod extends @ruby_singleton_method, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "SingletonMethod" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_singleton_method_def(this, _, _, result) }
+
+ /** Gets the node corresponding to the field `name`. */
+ UnderscoreMethodName getName() { ruby_singleton_method_def(this, result, _, _) }
+
+ /** Gets the node corresponding to the field `object`. */
+ AstNode getObject() { ruby_singleton_method_def(this, _, result, _) }
+
+ /** Gets the node corresponding to the field `parameters`. */
+ MethodParameters getParameters() { ruby_singleton_method_parameters(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_singleton_method_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_singleton_method_def(this, result, _, _) or
+ ruby_singleton_method_def(this, _, result, _) or
+ ruby_singleton_method_parameters(this, result) or
+ ruby_singleton_method_child(this, _, result)
+ }
+ }
+
+ /** A class representing `splat_argument` nodes. */
+ class SplatArgument extends @ruby_splat_argument, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "SplatArgument" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_splat_argument_def(this, _, result) }
+
+ /** Gets the child of this node. */
+ UnderscoreArg getChild() { ruby_splat_argument_def(this, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_splat_argument_def(this, result, _) }
+ }
+
+ /** A class representing `splat_parameter` nodes. */
+ class SplatParameter extends @ruby_splat_parameter, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "SplatParameter" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_splat_parameter_def(this, result) }
+
+ /** Gets the node corresponding to the field `name`. */
+ Identifier getName() { ruby_splat_parameter_name(this, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_splat_parameter_name(this, result) }
+ }
+
+ /** A class representing `string` nodes. */
+ class String extends @ruby_string__, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "String" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_string_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_string_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_string_child(this, _, result) }
+ }
+
+ /** A class representing `string_array` nodes. */
+ class StringArray extends @ruby_string_array, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "StringArray" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_string_array_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ BareString getChild(int i) { ruby_string_array_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_string_array_child(this, _, result) }
+ }
+
+ /** A class representing `string_content` tokens. */
+ class StringContent extends @ruby_token_string_content, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "StringContent" }
+ }
+
+ /** A class representing `subshell` nodes. */
+ class Subshell extends @ruby_subshell, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Subshell" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_subshell_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_subshell_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_subshell_child(this, _, result) }
+ }
+
+ /** A class representing `super` tokens. */
+ class Super extends @ruby_token_super, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Super" }
+ }
+
+ /** A class representing `superclass` nodes. */
+ class Superclass extends @ruby_superclass, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Superclass" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_superclass_def(this, _, result) }
+
+ /** Gets the child of this node. */
+ AstNode getChild() { ruby_superclass_def(this, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_superclass_def(this, result, _) }
+ }
+
+ /** A class representing `symbol_array` nodes. */
+ class SymbolArray extends @ruby_symbol_array, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "SymbolArray" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_symbol_array_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ BareSymbol getChild(int i) { ruby_symbol_array_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_symbol_array_child(this, _, result) }
+ }
+
+ /** A class representing `then` nodes. */
+ class Then extends @ruby_then, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Then" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_then_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { ruby_then_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_then_child(this, _, result) }
+ }
+
+ /** A class representing `true` tokens. */
+ class True extends @ruby_token_true, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "True" }
+ }
+
+ /** A class representing `unary` nodes. */
+ class Unary extends @ruby_unary, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Unary" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_unary_def(this, _, _, result) }
+
+ /** Gets the node corresponding to the field `operand`. */
+ AstNode getOperand() { ruby_unary_def(this, result, _, _) }
+
+ /** Gets the node corresponding to the field `operator`. */
+ string getOperator() {
+ exists(int value | ruby_unary_def(this, _, value, _) |
+ result = "!" and value = 0
+ or
+ result = "+" and value = 1
+ or
+ result = "-" and value = 2
+ or
+ result = "defined?" and value = 3
+ or
+ result = "not" and value = 4
+ or
+ result = "~" and value = 5
+ )
+ }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_unary_def(this, result, _, _) }
+ }
+
+ /** A class representing `undef` nodes. */
+ class Undef extends @ruby_undef, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Undef" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_undef_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ UnderscoreMethodName getChild(int i) { ruby_undef_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_undef_child(this, _, result) }
+ }
+
+ /** A class representing `uninterpreted` tokens. */
+ class Uninterpreted extends @ruby_token_uninterpreted, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Uninterpreted" }
+ }
+
+ /** A class representing `unless` nodes. */
+ class Unless extends @ruby_unless, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Unless" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_unless_def(this, _, result) }
+
+ /** Gets the node corresponding to the field `alternative`. */
+ AstNode getAlternative() { ruby_unless_alternative(this, result) }
+
+ /** Gets the node corresponding to the field `condition`. */
+ UnderscoreStatement getCondition() { ruby_unless_def(this, result, _) }
+
+ /** Gets the node corresponding to the field `consequence`. */
+ Then getConsequence() { ruby_unless_consequence(this, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_unless_alternative(this, result) or
+ ruby_unless_def(this, result, _) or
+ ruby_unless_consequence(this, result)
+ }
+ }
+
+ /** A class representing `unless_modifier` nodes. */
+ class UnlessModifier extends @ruby_unless_modifier, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "UnlessModifier" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_unless_modifier_def(this, _, _, result) }
+
+ /** Gets the node corresponding to the field `body`. */
+ UnderscoreStatement getBody() { ruby_unless_modifier_def(this, result, _, _) }
+
+ /** Gets the node corresponding to the field `condition`. */
+ AstNode getCondition() { ruby_unless_modifier_def(this, _, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_unless_modifier_def(this, result, _, _) or ruby_unless_modifier_def(this, _, result, _)
+ }
+ }
+
+ /** A class representing `until` nodes. */
+ class Until extends @ruby_until, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Until" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_until_def(this, _, _, result) }
+
+ /** Gets the node corresponding to the field `body`. */
+ Do getBody() { ruby_until_def(this, result, _, _) }
+
+ /** Gets the node corresponding to the field `condition`. */
+ UnderscoreStatement getCondition() { ruby_until_def(this, _, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_until_def(this, result, _, _) or ruby_until_def(this, _, result, _)
+ }
+ }
+
+ /** A class representing `until_modifier` nodes. */
+ class UntilModifier extends @ruby_until_modifier, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "UntilModifier" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_until_modifier_def(this, _, _, result) }
+
+ /** Gets the node corresponding to the field `body`. */
+ UnderscoreStatement getBody() { ruby_until_modifier_def(this, result, _, _) }
+
+ /** Gets the node corresponding to the field `condition`. */
+ AstNode getCondition() { ruby_until_modifier_def(this, _, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_until_modifier_def(this, result, _, _) or ruby_until_modifier_def(this, _, result, _)
+ }
+ }
+
+ /** A class representing `when` nodes. */
+ class When extends @ruby_when, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "When" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_when_def(this, result) }
+
+ /** Gets the node corresponding to the field `body`. */
+ Then getBody() { ruby_when_body(this, result) }
+
+ /** Gets the node corresponding to the field `pattern`. */
+ Pattern getPattern(int i) { ruby_when_pattern(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_when_body(this, result) or ruby_when_pattern(this, _, result)
+ }
+ }
+
+ /** A class representing `while` nodes. */
+ class While extends @ruby_while, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "While" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_while_def(this, _, _, result) }
+
+ /** Gets the node corresponding to the field `body`. */
+ Do getBody() { ruby_while_def(this, result, _, _) }
+
+ /** Gets the node corresponding to the field `condition`. */
+ UnderscoreStatement getCondition() { ruby_while_def(this, _, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_while_def(this, result, _, _) or ruby_while_def(this, _, result, _)
+ }
+ }
+
+ /** A class representing `while_modifier` nodes. */
+ class WhileModifier extends @ruby_while_modifier, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "WhileModifier" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_while_modifier_def(this, _, _, result) }
+
+ /** Gets the node corresponding to the field `body`. */
+ UnderscoreStatement getBody() { ruby_while_modifier_def(this, result, _, _) }
+
+ /** Gets the node corresponding to the field `condition`. */
+ AstNode getCondition() { ruby_while_modifier_def(this, _, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() {
+ ruby_while_modifier_def(this, result, _, _) or ruby_while_modifier_def(this, _, result, _)
+ }
+ }
+
+ /** A class representing `yield` nodes. */
+ class Yield extends @ruby_yield, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Yield" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { ruby_yield_def(this, result) }
+
+ /** Gets the child of this node. */
+ ArgumentList getChild() { ruby_yield_child(this, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { ruby_yield_child(this, result) }
+ }
+}
+
+module Erb {
+ /** The base class for all AST nodes */
+ class AstNode extends @erb_ast_node {
+ /** Gets a string representation of this element. */
+ string toString() { result = this.getAPrimaryQlClass() }
+
+ /** Gets the location of this element. */
+ L::Location getLocation() { none() }
+
+ /** Gets the parent of this element. */
+ AstNode getParent() { erb_ast_node_parent(this, result, _) }
+
+ /** Gets the index of this node among the children of its parent. */
+ int getParentIndex() { erb_ast_node_parent(this, _, result) }
+
+ /** Gets a field or child node of this node. */
+ AstNode getAFieldOrChild() { none() }
+
+ /** Gets the name of the primary QL class for this element. */
+ string getAPrimaryQlClass() { result = "???" }
+
+ /** Gets a comma-separated list of the names of the primary CodeQL classes to which this element belongs. */
+ string getPrimaryQlClasses() { result = concat(this.getAPrimaryQlClass(), ",") }
+ }
+
+ /** A token. */
+ class Token extends @erb_token, AstNode {
+ /** Gets the value of this token. */
+ string getValue() { erb_tokeninfo(this, _, result, _) }
+
+ /** Gets the location of this token. */
+ override L::Location getLocation() { erb_tokeninfo(this, _, _, result) }
+
+ /** Gets a string representation of this element. */
+ override string toString() { result = this.getValue() }
+
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Token" }
+ }
+
+ /** A reserved word. */
+ class ReservedWord extends @erb_reserved_word, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "ReservedWord" }
+ }
+
+ /** A class representing `code` tokens. */
+ class Code extends @erb_token_code, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Code" }
+ }
+
+ /** A class representing `comment` tokens. */
+ class Comment extends @erb_token_comment, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Comment" }
+ }
+
+ /** A class representing `comment_directive` nodes. */
+ class CommentDirective extends @erb_comment_directive, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "CommentDirective" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { erb_comment_directive_def(this, _, result) }
+
+ /** Gets the child of this node. */
+ Comment getChild() { erb_comment_directive_def(this, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { erb_comment_directive_def(this, result, _) }
+ }
+
+ /** A class representing `content` tokens. */
+ class Content extends @erb_token_content, Token {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Content" }
+ }
+
+ /** A class representing `directive` nodes. */
+ class Directive extends @erb_directive, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Directive" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { erb_directive_def(this, _, result) }
+
+ /** Gets the child of this node. */
+ Code getChild() { erb_directive_def(this, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { erb_directive_def(this, result, _) }
+ }
+
+ /** A class representing `graphql_directive` nodes. */
+ class GraphqlDirective extends @erb_graphql_directive, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "GraphqlDirective" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { erb_graphql_directive_def(this, _, result) }
+
+ /** Gets the child of this node. */
+ Code getChild() { erb_graphql_directive_def(this, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { erb_graphql_directive_def(this, result, _) }
+ }
+
+ /** A class representing `output_directive` nodes. */
+ class OutputDirective extends @erb_output_directive, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "OutputDirective" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { erb_output_directive_def(this, _, result) }
+
+ /** Gets the child of this node. */
+ Code getChild() { erb_output_directive_def(this, result, _) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { erb_output_directive_def(this, result, _) }
+ }
+
+ /** A class representing `template` nodes. */
+ class Template extends @erb_template, AstNode {
+ /** Gets the name of the primary QL class for this element. */
+ override string getAPrimaryQlClass() { result = "Template" }
+
+ /** Gets the location of this element. */
+ override L::Location getLocation() { erb_template_def(this, result) }
+
+ /** Gets the `i`th child of this node. */
+ AstNode getChild(int i) { erb_template_child(this, i, result) }
+
+ /** Gets a field or child node of this node. */
+ override AstNode getAFieldOrChild() { erb_template_child(this, _, result) }
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Variable.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Variable.qll
new file mode 100644
index 00000000000..ebe88f13107
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/ast/internal/Variable.qll
@@ -0,0 +1,642 @@
+private import TreeSitter
+private import codeql.Locations
+private import codeql.ruby.AST
+private import codeql.ruby.ast.internal.AST
+private import codeql.ruby.ast.internal.Parameter
+private import codeql.ruby.ast.internal.Scope
+private import codeql.ruby.ast.internal.Synthesis
+
+/**
+ * Holds if `n` is in the left-hand-side of an explicit assignment `assignment`.
+ */
+predicate explicitAssignmentNode(Ruby::AstNode n, Ruby::AstNode assignment) {
+ n = assignment.(Ruby::Assignment).getLeft()
+ or
+ n = assignment.(Ruby::OperatorAssignment).getLeft()
+ or
+ exists(Ruby::AstNode parent |
+ parent = n.getParent() and
+ explicitAssignmentNode(parent, assignment)
+ |
+ parent instanceof Ruby::DestructuredLeftAssignment
+ or
+ parent instanceof Ruby::LeftAssignmentList
+ or
+ parent instanceof Ruby::RestAssignment
+ )
+}
+
+/** Holds if `n` is inside an implicit assignment. */
+predicate implicitAssignmentNode(Ruby::AstNode n) {
+ n = any(Ruby::ExceptionVariable ev).getChild()
+ or
+ n = any(Ruby::For for).getPattern()
+ or
+ implicitAssignmentNode(n.getParent())
+}
+
+/** Holds if `n` is inside a parameter. */
+predicate implicitParameterAssignmentNode(Ruby::AstNode n, Callable::Range c) {
+ n = c.getParameter(_)
+ or
+ implicitParameterAssignmentNode(n.getParent().(Ruby::DestructuredParameter), c)
+}
+
+private predicate instanceVariableAccess(
+ Ruby::InstanceVariable var, string name, Scope::Range scope, boolean instance
+) {
+ name = var.getValue() and
+ scope = enclosingModuleOrClass(var) and
+ if hasEnclosingMethod(var) then instance = true else instance = false
+}
+
+private predicate classVariableAccess(Ruby::ClassVariable var, string name, Scope::Range scope) {
+ name = var.getValue() and
+ scope = enclosingModuleOrClass(var)
+}
+
+private predicate hasEnclosingMethod(Ruby::AstNode node) {
+ exists(Scope::Range s | scopeOf(node) = s and exists(s.getEnclosingMethod()))
+}
+
+private ModuleBase::Range enclosingModuleOrClass(Ruby::AstNode node) {
+ exists(Scope::Range s | scopeOf(node) = s and result = s.getEnclosingModule())
+}
+
+private predicate parameterAssignment(Callable::Range scope, string name, Ruby::Identifier i) {
+ implicitParameterAssignmentNode(i, scope) and
+ name = i.getValue()
+}
+
+/** Holds if `scope` defines `name` in its parameter declaration at `i`. */
+private predicate scopeDefinesParameterVariable(
+ Callable::Range scope, string name, Ruby::Identifier i
+) {
+ // In case of overlapping parameter names (e.g. `_`), only the first
+ // parameter will give rise to a variable
+ i =
+ min(Ruby::Identifier other |
+ parameterAssignment(scope, name, other)
+ |
+ other order by other.getLocation().getStartLine(), other.getLocation().getStartColumn()
+ )
+ or
+ exists(Parameter::Range p |
+ p = scope.getParameter(_) and
+ name = i.getValue()
+ |
+ i = p.(Ruby::BlockParameter).getName() or
+ i = p.(Ruby::HashSplatParameter).getName() or
+ i = p.(Ruby::KeywordParameter).getName() or
+ i = p.(Ruby::OptionalParameter).getName() or
+ i = p.(Ruby::SplatParameter).getName()
+ )
+}
+
+/** Holds if `name` is assigned in `scope` at `i`. */
+private predicate scopeAssigns(Scope::Range scope, string name, Ruby::Identifier i) {
+ (explicitAssignmentNode(i, _) or implicitAssignmentNode(i)) and
+ name = i.getValue() and
+ scope = scopeOf(i)
+}
+
+cached
+private module Cached {
+ cached
+ newtype TVariable =
+ TGlobalVariable(string name) { name = any(Ruby::GlobalVariable var).getValue() } or
+ TClassVariable(Scope::Range scope, string name, Ruby::AstNode decl) {
+ decl =
+ min(Ruby::ClassVariable other |
+ classVariableAccess(other, name, scope)
+ |
+ other order by other.getLocation().getStartLine(), other.getLocation().getStartColumn()
+ )
+ } or
+ TInstanceVariable(Scope::Range scope, string name, boolean instance, Ruby::AstNode decl) {
+ decl =
+ min(Ruby::InstanceVariable other |
+ instanceVariableAccess(other, name, scope, instance)
+ |
+ other order by other.getLocation().getStartLine(), other.getLocation().getStartColumn()
+ )
+ } or
+ TLocalVariableReal(Scope::Range scope, string name, Ruby::Identifier i) {
+ scopeDefinesParameterVariable(scope, name, i)
+ or
+ i =
+ min(Ruby::Identifier other |
+ scopeAssigns(scope, name, other)
+ |
+ other order by other.getLocation().getStartLine(), other.getLocation().getStartColumn()
+ ) and
+ not scopeDefinesParameterVariable(scope, name, _) and
+ not inherits(scope, name, _)
+ } or
+ TSelfVariable(SelfBase::Range scope) or
+ TLocalVariableSynth(AstNode n, int i) { any(Synthesis s).localVariable(n, i) }
+
+ // Db types that can be vcalls
+ private class VcallToken =
+ @ruby_scope_resolution or @ruby_token_constant or @ruby_token_identifier or @ruby_token_super;
+
+ /**
+ * Holds if `i` is an `identifier` node occurring in the context where it
+ * should be considered a VCALL. VCALL is the term that MRI/Ripper uses
+ * internally when there's an identifier without arguments or parentheses,
+ * i.e. it *might* be a method call, but it might also be a variable access,
+ * depending on the bindings in the current scope.
+ * ```rb
+ * foo # in MRI this is a VCALL, and the predicate should hold for this
+ * bar() # in MRI this would be an FCALL. Tree-sitter gives us a `call` node,
+ * # and the `method` field will be an `identifier`, but this predicate
+ * # will not hold for that identifier.
+ * ```
+ */
+ cached
+ predicate vcall(VcallToken i) {
+ i = any(Ruby::ArgumentList x).getChild(_)
+ or
+ i = any(Ruby::Array x).getChild(_)
+ or
+ i = any(Ruby::Assignment x).getRight()
+ or
+ i = any(Ruby::Begin x).getChild(_)
+ or
+ i = any(Ruby::BeginBlock x).getChild(_)
+ or
+ i = any(Ruby::Binary x).getLeft()
+ or
+ i = any(Ruby::Binary x).getRight()
+ or
+ i = any(Ruby::Block x).getChild(_)
+ or
+ i = any(Ruby::BlockArgument x).getChild()
+ or
+ i = any(Ruby::Call x).getReceiver()
+ or
+ i = any(Ruby::Case x).getValue()
+ or
+ i = any(Ruby::Class x).getChild(_)
+ or
+ i = any(Ruby::Conditional x).getCondition()
+ or
+ i = any(Ruby::Conditional x).getConsequence()
+ or
+ i = any(Ruby::Conditional x).getAlternative()
+ or
+ i = any(Ruby::Do x).getChild(_)
+ or
+ i = any(Ruby::DoBlock x).getChild(_)
+ or
+ i = any(Ruby::ElementReference x).getChild(_)
+ or
+ i = any(Ruby::ElementReference x).getObject()
+ or
+ i = any(Ruby::Else x).getChild(_)
+ or
+ i = any(Ruby::Elsif x).getCondition()
+ or
+ i = any(Ruby::EndBlock x).getChild(_)
+ or
+ i = any(Ruby::Ensure x).getChild(_)
+ or
+ i = any(Ruby::Exceptions x).getChild(_)
+ or
+ i = any(Ruby::HashSplatArgument x).getChild()
+ or
+ i = any(Ruby::If x).getCondition()
+ or
+ i = any(Ruby::IfModifier x).getCondition()
+ or
+ i = any(Ruby::IfModifier x).getBody()
+ or
+ i = any(Ruby::In x).getChild()
+ or
+ i = any(Ruby::Interpolation x).getChild(_)
+ or
+ i = any(Ruby::KeywordParameter x).getValue()
+ or
+ i = any(Ruby::Method x).getChild(_)
+ or
+ i = any(Ruby::Module x).getChild(_)
+ or
+ i = any(Ruby::OperatorAssignment x).getRight()
+ or
+ i = any(Ruby::OptionalParameter x).getValue()
+ or
+ i = any(Ruby::Pair x).getKey()
+ or
+ i = any(Ruby::Pair x).getValue()
+ or
+ i = any(Ruby::ParenthesizedStatements x).getChild(_)
+ or
+ i = any(Ruby::Pattern x).getChild()
+ or
+ i = any(Ruby::Program x).getChild(_)
+ or
+ i = any(Ruby::Range x).getBegin()
+ or
+ i = any(Ruby::Range x).getEnd()
+ or
+ i = any(Ruby::RescueModifier x).getBody()
+ or
+ i = any(Ruby::RescueModifier x).getHandler()
+ or
+ i = any(Ruby::RightAssignmentList x).getChild(_)
+ or
+ i = any(Ruby::ScopeResolution x).getScope()
+ or
+ i = any(Ruby::SingletonClass x).getValue()
+ or
+ i = any(Ruby::SingletonClass x).getChild(_)
+ or
+ i = any(Ruby::SingletonMethod x).getChild(_)
+ or
+ i = any(Ruby::SingletonMethod x).getObject()
+ or
+ i = any(Ruby::SplatArgument x).getChild()
+ or
+ i = any(Ruby::Superclass x).getChild()
+ or
+ i = any(Ruby::Then x).getChild(_)
+ or
+ i = any(Ruby::Unary x).getOperand()
+ or
+ i = any(Ruby::Unless x).getCondition()
+ or
+ i = any(Ruby::UnlessModifier x).getCondition()
+ or
+ i = any(Ruby::UnlessModifier x).getBody()
+ or
+ i = any(Ruby::Until x).getCondition()
+ or
+ i = any(Ruby::UntilModifier x).getCondition()
+ or
+ i = any(Ruby::UntilModifier x).getBody()
+ or
+ i = any(Ruby::While x).getCondition()
+ or
+ i = any(Ruby::WhileModifier x).getCondition()
+ or
+ i = any(Ruby::WhileModifier x).getBody()
+ }
+
+ cached
+ predicate access(Ruby::Identifier access, VariableReal variable) {
+ exists(string name |
+ variable.getNameImpl() = name and
+ name = access.getValue()
+ |
+ variable.getDeclaringScopeImpl() = scopeOf(access) and
+ not access.getLocation().strictlyBefore(variable.getLocationImpl()) and
+ // In case of overlapping parameter names, later parameters should not
+ // be considered accesses to the first parameter
+ if parameterAssignment(_, _, access)
+ then scopeDefinesParameterVariable(_, _, access)
+ else any()
+ or
+ exists(Scope::Range declScope |
+ variable.getDeclaringScopeImpl() = declScope and
+ inherits(scopeOf(access), name, declScope)
+ )
+ )
+ }
+
+ private class Access extends Ruby::Token {
+ Access() {
+ access(this, _) or
+ this instanceof Ruby::GlobalVariable or
+ this instanceof Ruby::InstanceVariable or
+ this instanceof Ruby::ClassVariable or
+ this instanceof Ruby::Self
+ }
+ }
+
+ cached
+ predicate explicitWriteAccess(Access access, Ruby::AstNode assignment) {
+ explicitAssignmentNode(access, assignment)
+ }
+
+ cached
+ predicate implicitWriteAccess(Access access) {
+ implicitAssignmentNode(access)
+ or
+ scopeDefinesParameterVariable(_, _, access)
+ }
+
+ cached
+ predicate isCapturedAccess(LocalVariableAccess access) {
+ toGenerated(access.getVariable().getDeclaringScope()) != scopeOf(toGenerated(access))
+ }
+
+ cached
+ predicate instanceVariableAccess(Ruby::InstanceVariable var, InstanceVariable v) {
+ exists(string name, Scope::Range scope, boolean instance |
+ v = TInstanceVariable(scope, name, instance, _) and
+ instanceVariableAccess(var, name, scope, instance)
+ )
+ }
+
+ cached
+ predicate classVariableAccess(Ruby::ClassVariable var, ClassVariable variable) {
+ exists(Scope::Range scope, string name |
+ variable = TClassVariable(scope, name, _) and
+ classVariableAccess(var, name, scope)
+ )
+ }
+}
+
+import Cached
+
+/** Holds if this scope inherits `name` from an outer scope `outer`. */
+private predicate inherits(Scope::Range scope, string name, Scope::Range outer) {
+ (scope instanceof Ruby::Block or scope instanceof Ruby::DoBlock) and
+ not scopeDefinesParameterVariable(scope, name, _) and
+ (
+ outer = scope.getOuterScope() and
+ (
+ scopeDefinesParameterVariable(outer, name, _)
+ or
+ exists(Ruby::Identifier i |
+ scopeAssigns(outer, name, i) and
+ i.getLocation().strictlyBefore(scope.getLocation())
+ )
+ )
+ or
+ inherits(scope.getOuterScope(), name, outer)
+ )
+}
+
+abstract class VariableImpl extends TVariable {
+ abstract string getNameImpl();
+
+ final string toString() { result = this.getNameImpl() }
+
+ abstract Location getLocationImpl();
+}
+
+class TVariableReal =
+ TGlobalVariable or TClassVariable or TInstanceVariable or TLocalVariableReal or TSelfVariable;
+
+class TLocalVariable = TLocalVariableReal or TLocalVariableSynth or TSelfVariable;
+
+/**
+ * This class only exists to avoid negative recursion warnings. Ideally,
+ * we would use `VariableImpl` directly, but that results in incorrect
+ * negative recursion warnings. Adding new root-defs for the predicates
+ * below works around this.
+ */
+abstract class VariableReal extends TVariableReal {
+ abstract string getNameImpl();
+
+ abstract Location getLocationImpl();
+
+ abstract Scope::Range getDeclaringScopeImpl();
+
+ final string toString() { result = this.getNameImpl() }
+}
+
+// Convert extensions of `VariableReal` into extensions of `VariableImpl`
+private class VariableRealAdapter extends VariableImpl, TVariableReal instanceof VariableReal {
+ final override string getNameImpl() { result = VariableReal.super.getNameImpl() }
+
+ final override Location getLocationImpl() { result = VariableReal.super.getLocationImpl() }
+}
+
+class LocalVariableReal extends VariableReal, TLocalVariableReal {
+ private Scope::Range scope;
+ private string name;
+ private Ruby::Identifier i;
+
+ LocalVariableReal() { this = TLocalVariableReal(scope, name, i) }
+
+ final override string getNameImpl() { result = name }
+
+ final override Location getLocationImpl() { result = i.getLocation() }
+
+ final override Scope::Range getDeclaringScopeImpl() { result = scope }
+
+ final VariableAccess getDefiningAccessImpl() { toGenerated(result) = i }
+}
+
+class LocalVariableSynth extends VariableImpl, TLocalVariableSynth {
+ private AstNode n;
+ private int i;
+
+ LocalVariableSynth() { this = TLocalVariableSynth(n, i) }
+
+ final override string getNameImpl() {
+ exists(int level | level = desugarLevel(n) |
+ if level > 0 then result = "__synth__" + i + "__" + level else result = "__synth__" + i
+ )
+ }
+
+ final override Location getLocationImpl() { result = n.getLocation() }
+}
+
+class GlobalVariableImpl extends VariableReal, TGlobalVariable {
+ private string name;
+
+ GlobalVariableImpl() { this = TGlobalVariable(name) }
+
+ final override string getNameImpl() { result = name }
+
+ final override Location getLocationImpl() { none() }
+
+ final override Scope::Range getDeclaringScopeImpl() { none() }
+}
+
+class InstanceVariableImpl extends VariableReal, TInstanceVariable {
+ private ModuleBase::Range scope;
+ private boolean instance;
+ private string name;
+ private Ruby::AstNode decl;
+
+ InstanceVariableImpl() { this = TInstanceVariable(scope, name, instance, decl) }
+
+ final override string getNameImpl() { result = name }
+
+ final predicate isClassInstanceVariable() { instance = false }
+
+ final override Location getLocationImpl() { result = decl.getLocation() }
+
+ final override Scope::Range getDeclaringScopeImpl() { result = scope }
+}
+
+class ClassVariableImpl extends VariableReal, TClassVariable {
+ private ModuleBase::Range scope;
+ private string name;
+ private Ruby::AstNode decl;
+
+ ClassVariableImpl() { this = TClassVariable(scope, name, decl) }
+
+ final override string getNameImpl() { result = name }
+
+ final override Location getLocationImpl() { result = decl.getLocation() }
+
+ final override Scope::Range getDeclaringScopeImpl() { result = scope }
+}
+
+class SelfVariableImpl extends VariableReal, TSelfVariable {
+ private SelfBase::Range scope;
+
+ SelfVariableImpl() { this = TSelfVariable(scope) }
+
+ final override string getNameImpl() { result = "self" }
+
+ final override Location getLocationImpl() { result = scope.getLocation() }
+
+ final override Scope::Range getDeclaringScopeImpl() { result = scope }
+}
+
+abstract class VariableAccessImpl extends Expr, TVariableAccess {
+ abstract VariableImpl getVariableImpl();
+}
+
+module LocalVariableAccess {
+ predicate range(Ruby::Identifier id, TLocalVariableReal v) {
+ access(id, v) and
+ (
+ explicitWriteAccess(id, _)
+ or
+ implicitWriteAccess(id)
+ or
+ vcall(id)
+ )
+ }
+}
+
+class TVariableAccessReal =
+ TLocalVariableAccessReal or TGlobalVariableAccess or TInstanceVariableAccess or
+ TClassVariableAccess;
+
+abstract class LocalVariableAccessImpl extends VariableAccessImpl, TLocalVariableAccess { }
+
+private class LocalVariableAccessReal extends LocalVariableAccessImpl, TLocalVariableAccessReal {
+ private Ruby::Identifier g;
+ private LocalVariable v;
+
+ LocalVariableAccessReal() { this = TLocalVariableAccessReal(g, v) }
+
+ final override LocalVariable getVariableImpl() { result = v }
+
+ final override string toString() { result = g.getValue() }
+}
+
+private class LocalVariableAccessSynth extends LocalVariableAccessImpl, TLocalVariableAccessSynth {
+ private LocalVariable v;
+
+ LocalVariableAccessSynth() { this = TLocalVariableAccessSynth(_, _, v) }
+
+ final override LocalVariable getVariableImpl() { result = v }
+
+ final override string toString() { result = v.getName() }
+}
+
+module GlobalVariableAccess {
+ predicate range(Ruby::GlobalVariable n, GlobalVariableImpl v) { n.getValue() = v.getNameImpl() }
+}
+
+abstract class GlobalVariableAccessImpl extends VariableAccessImpl, TGlobalVariableAccess { }
+
+private class GlobalVariableAccessReal extends GlobalVariableAccessImpl, TGlobalVariableAccessReal {
+ private Ruby::GlobalVariable g;
+ private GlobalVariable v;
+
+ GlobalVariableAccessReal() { this = TGlobalVariableAccessReal(g, v) }
+
+ final override GlobalVariable getVariableImpl() { result = v }
+
+ final override string toString() { result = g.getValue() }
+}
+
+private class GlobalVariableAccessSynth extends GlobalVariableAccessImpl, TGlobalVariableAccessSynth {
+ private GlobalVariable v;
+
+ GlobalVariableAccessSynth() { this = TGlobalVariableAccessSynth(_, _, v) }
+
+ final override GlobalVariable getVariableImpl() { result = v }
+
+ final override string toString() { result = v.getName() }
+}
+
+module InstanceVariableAccess {
+ predicate range(Ruby::InstanceVariable n, InstanceVariable v) { instanceVariableAccess(n, v) }
+}
+
+abstract class InstanceVariableAccessImpl extends VariableAccessImpl, TInstanceVariableAccess { }
+
+private class InstanceVariableAccessReal extends InstanceVariableAccessImpl,
+ TInstanceVariableAccessReal {
+ private Ruby::InstanceVariable g;
+ private InstanceVariable v;
+
+ InstanceVariableAccessReal() { this = TInstanceVariableAccessReal(g, v) }
+
+ final override InstanceVariable getVariableImpl() { result = v }
+
+ final override string toString() { result = g.getValue() }
+}
+
+private class InstanceVariableAccessSynth extends InstanceVariableAccessImpl,
+ TInstanceVariableAccessSynth {
+ private InstanceVariable v;
+
+ InstanceVariableAccessSynth() { this = TInstanceVariableAccessSynth(_, _, v) }
+
+ final override InstanceVariable getVariableImpl() { result = v }
+
+ final override string toString() { result = v.getName() }
+}
+
+module ClassVariableAccess {
+ predicate range(Ruby::ClassVariable n, ClassVariable v) { classVariableAccess(n, v) }
+}
+
+abstract class ClassVariableAccessRealImpl extends VariableAccessImpl, TClassVariableAccess { }
+
+private class ClassVariableAccessReal extends ClassVariableAccessRealImpl, TClassVariableAccessReal {
+ private Ruby::ClassVariable g;
+ private ClassVariable v;
+
+ ClassVariableAccessReal() { this = TClassVariableAccessReal(g, v) }
+
+ final override ClassVariable getVariableImpl() { result = v }
+
+ final override string toString() { result = g.getValue() }
+}
+
+private class ClassVariableAccessSynth extends ClassVariableAccessRealImpl,
+ TClassVariableAccessSynth {
+ private ClassVariable v;
+
+ ClassVariableAccessSynth() { this = TClassVariableAccessSynth(_, _, v) }
+
+ final override ClassVariable getVariableImpl() { result = v }
+
+ final override string toString() { result = v.getName() }
+}
+
+abstract class SelfVariableAccessImpl extends LocalVariableAccessImpl, TSelfVariableAccess { }
+
+private class SelfVariableAccessReal extends SelfVariableAccessImpl, TSelfReal {
+ private Ruby::Self self;
+ private SelfVariable var;
+
+ SelfVariableAccessReal() { this = TSelfReal(self) and var = TSelfVariable(scopeOf(self)) }
+
+ final override SelfVariable getVariableImpl() { result = var }
+
+ final override string toString() { result = var.toString() }
+}
+
+private class SelfVariableAccessSynth extends SelfVariableAccessImpl, TSelfSynth {
+ private SelfVariable v;
+
+ SelfVariableAccessSynth() { this = TSelfSynth(_, _, v) }
+
+ final override LocalVariable getVariableImpl() { result = v }
+
+ final override string toString() { result = v.getName() }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/BasicBlocks.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/BasicBlocks.qll
new file mode 100644
index 00000000000..e0e1199b41c
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/BasicBlocks.qll
@@ -0,0 +1,414 @@
+/** Provides classes representing basic blocks. */
+
+private import codeql.Locations
+private import codeql.ruby.AST
+private import codeql.ruby.ast.internal.AST
+private import codeql.ruby.ast.internal.TreeSitter
+private import codeql.ruby.controlflow.ControlFlowGraph
+private import internal.ControlFlowGraphImpl
+private import CfgNodes
+private import SuccessorTypes
+
+/**
+ * A basic block, that is, a maximal straight-line sequence of control flow nodes
+ * without branches or joins.
+ */
+class BasicBlock extends TBasicBlockStart {
+ /** Gets the scope of this basic block. */
+ CfgScope getScope() { result = this.getAPredecessor().getScope() }
+
+ /** Gets an immediate successor of this basic block, if any. */
+ BasicBlock getASuccessor() { result = this.getASuccessor(_) }
+
+ /** Gets an immediate successor of this basic block of a given type, if any. */
+ BasicBlock getASuccessor(SuccessorType t) {
+ result.getFirstNode() = this.getLastNode().getASuccessor(t)
+ }
+
+ /** Gets an immediate predecessor of this basic block, if any. */
+ BasicBlock getAPredecessor() { result.getASuccessor() = this }
+
+ /** Gets an immediate predecessor of this basic block of a given type, if any. */
+ BasicBlock getAPredecessor(SuccessorType t) { result.getASuccessor(t) = this }
+
+ /** Gets the control flow node at a specific (zero-indexed) position in this basic block. */
+ CfgNode getNode(int pos) { bbIndex(this.getFirstNode(), result, pos) }
+
+ /** Gets a control flow node in this basic block. */
+ CfgNode getANode() { result = this.getNode(_) }
+
+ /** Gets the first control flow node in this basic block. */
+ CfgNode getFirstNode() { this = TBasicBlockStart(result) }
+
+ /** Gets the last control flow node in this basic block. */
+ CfgNode getLastNode() { result = this.getNode(this.length() - 1) }
+
+ /** Gets the length of this basic block. */
+ int length() { result = strictcount(this.getANode()) }
+
+ /**
+ * Holds if this basic block immediately dominates basic block `bb`.
+ *
+ * That is, all paths reaching basic block `bb` from some entry point
+ * basic block must go through this basic block (which is an immediate
+ * predecessor of `bb`).
+ *
+ * Example:
+ *
+ * ```rb
+ * def m b
+ * if b
+ * return 0
+ * end
+ * return 1
+ * end
+ * ```
+ *
+ * The basic block starting on line 2 immediately dominates the
+ * basic block on line 5 (all paths from the entry point of `m`
+ * to `return 1` must go through the `if` block).
+ */
+ predicate immediatelyDominates(BasicBlock bb) { bbIDominates(this, bb) }
+
+ /**
+ * Holds if this basic block strictly dominates basic block `bb`.
+ *
+ * That is, all paths reaching basic block `bb` from some entry point
+ * basic block must go through this basic block (which must be different
+ * from `bb`).
+ *
+ * Example:
+ *
+ * ```rb
+ * def m b
+ * if b
+ * return 0
+ * end
+ * return 1
+ * end
+ * ```
+ *
+ * The basic block starting on line 2 strictly dominates the
+ * basic block on line 5 (all paths from the entry point of `m`
+ * to `return 1` must go through the `if` block).
+ */
+ predicate strictlyDominates(BasicBlock bb) { bbIDominates+(this, bb) }
+
+ /**
+ * Holds if this basic block dominates basic block `bb`.
+ *
+ * That is, all paths reaching basic block `bb` from some entry point
+ * basic block must go through this basic block.
+ *
+ * Example:
+ *
+ * ```rb
+ * def m b
+ * if b
+ * return 0
+ * end
+ * return 1
+ * end
+ * ```
+ *
+ * The basic block starting on line 2 dominates the basic
+ * basic block on line 5 (all paths from the entry point of `m`
+ * to `return 1` must go through the `if` block).
+ */
+ predicate dominates(BasicBlock bb) {
+ bb = this or
+ this.strictlyDominates(bb)
+ }
+
+ /**
+ * Holds if `df` is in the dominance frontier of this basic block.
+ * That is, this basic block dominates a predecessor of `df`, but
+ * does not dominate `df` itself.
+ *
+ * Example:
+ *
+ * ```rb
+ * def m x
+ * if x < 0
+ * x = -x
+ * if x > 10
+ * x = x - 1
+ * end
+ * end
+ * puts x
+ * end
+ * ```
+ *
+ * The basic block on line 8 is in the dominance frontier
+ * of the basic block starting on line 3 because that block
+ * dominates the basic block on line 4, which is a predecessor of
+ * `puts x`. Also, the basic block starting on line 3 does not
+ * dominate the basic block on line 8.
+ */
+ predicate inDominanceFrontier(BasicBlock df) {
+ this.dominatesPredecessor(df) and
+ not this.strictlyDominates(df)
+ }
+
+ /**
+ * Holds if this basic block dominates a predecessor of `df`.
+ */
+ private predicate dominatesPredecessor(BasicBlock df) { this.dominates(df.getAPredecessor()) }
+
+ /**
+ * Gets the basic block that immediately dominates this basic block, if any.
+ *
+ * That is, all paths reaching this basic block from some entry point
+ * basic block must go through the result, which is an immediate basic block
+ * predecessor of this basic block.
+ *
+ * Example:
+ *
+ * ```rb
+ * def m b
+ * if b
+ * return 0
+ * end
+ * return 1
+ * end
+ * ```
+ *
+ * The basic block starting on line 2 is an immediate dominator of
+ * the basic block on line 5 (all paths from the entry point of `m`
+ * to `return 1` must go through the `if` block, and the `if` block
+ * is an immediate predecessor of `return 1`).
+ */
+ BasicBlock getImmediateDominator() { bbIDominates(result, this) }
+
+ /**
+ * Holds if this basic block strictly post-dominates basic block `bb`.
+ *
+ * That is, all paths reaching a normal exit point basic block from basic
+ * block `bb` must go through this basic block (which must be different
+ * from `bb`).
+ *
+ * Example:
+ *
+ * ```rb
+ * def m b
+ * if b
+ * puts "b"
+ * end
+ * puts "m"
+ * end
+ * ```
+ *
+ * The basic block on line 5 strictly post-dominates the basic block on
+ * line 3 (all paths to the exit point of `m` from `puts "b"` must go
+ * through `puts "m"`).
+ */
+ predicate strictlyPostDominates(BasicBlock bb) { bbIPostDominates+(this, bb) }
+
+ /**
+ * Holds if this basic block post-dominates basic block `bb`.
+ *
+ * That is, all paths reaching a normal exit point basic block from basic
+ * block `bb` must go through this basic block.
+ *
+ * Example:
+ *
+ * ```rb
+ * def m b
+ * if b
+ * puts "b"
+ * end
+ * puts "m"
+ * end
+ * ```
+ *
+ * The basic block on line 5 post-dominates the basic block on line 3
+ * (all paths to the exit point of `m` from `puts "b"` must go through
+ * `puts "m"`).
+ */
+ predicate postDominates(BasicBlock bb) {
+ this.strictlyPostDominates(bb) or
+ this = bb
+ }
+
+ /** Holds if this basic block is in a loop in the control flow graph. */
+ predicate inLoop() { this.getASuccessor+() = this }
+
+ /** Gets a textual representation of this basic block. */
+ string toString() { result = this.getFirstNode().toString() }
+
+ /** Gets the location of this basic block. */
+ Location getLocation() { result = this.getFirstNode().getLocation() }
+}
+
+cached
+private module Cached {
+ /** Internal representation of basic blocks. */
+ cached
+ newtype TBasicBlock = TBasicBlockStart(CfgNode cfn) { startsBB(cfn) }
+
+ /** Holds if `cfn` starts a new basic block. */
+ private predicate startsBB(CfgNode cfn) {
+ not exists(cfn.getAPredecessor()) and exists(cfn.getASuccessor())
+ or
+ cfn.isJoin()
+ or
+ cfn.getAPredecessor().isBranch()
+ }
+
+ /**
+ * Holds if `succ` is a control flow successor of `pred` within
+ * the same basic block.
+ */
+ private predicate intraBBSucc(CfgNode pred, CfgNode succ) {
+ succ = pred.getASuccessor() and
+ not startsBB(succ)
+ }
+
+ /**
+ * Holds if `cfn` is the `i`th node in basic block `bb`.
+ *
+ * In other words, `i` is the shortest distance from a node `bb`
+ * that starts a basic block to `cfn` along the `intraBBSucc` relation.
+ */
+ cached
+ predicate bbIndex(CfgNode bbStart, CfgNode cfn, int i) =
+ shortestDistances(startsBB/1, intraBBSucc/2)(bbStart, cfn, i)
+
+ /**
+ * Holds if the first node of basic block `succ` is a control flow
+ * successor of the last node of basic block `pred`.
+ */
+ private predicate succBB(BasicBlock pred, BasicBlock succ) { succ = pred.getASuccessor() }
+
+ /** Holds if `dom` is an immediate dominator of `bb`. */
+ cached
+ predicate bbIDominates(BasicBlock dom, BasicBlock bb) =
+ idominance(entryBB/1, succBB/2)(_, dom, bb)
+
+ /** Holds if `pred` is a basic block predecessor of `succ`. */
+ private predicate predBB(BasicBlock succ, BasicBlock pred) { succBB(pred, succ) }
+
+ /** Holds if `bb` is an exit basic block that represents normal exit. */
+ private predicate normalExitBB(BasicBlock bb) { bb.getANode().(AnnotatedExitNode).isNormal() }
+
+ /** Holds if `dom` is an immediate post-dominator of `bb`. */
+ cached
+ predicate bbIPostDominates(BasicBlock dom, BasicBlock bb) =
+ idominance(normalExitBB/1, predBB/2)(_, dom, bb)
+
+ /**
+ * Gets the `i`th predecessor of join block `jb`, with respect to some
+ * arbitrary order.
+ */
+ cached
+ JoinBlockPredecessor getJoinBlockPredecessor(JoinBlock jb, int i) {
+ result =
+ rank[i + 1](JoinBlockPredecessor jbp |
+ jbp = jb.getAPredecessor()
+ |
+ jbp order by JoinBlockPredecessors::getId(jbp), JoinBlockPredecessors::getSplitString(jbp)
+ )
+ }
+}
+
+private import Cached
+
+/** Holds if `bb` is an entry basic block. */
+private predicate entryBB(BasicBlock bb) { bb.getFirstNode() instanceof EntryNode }
+
+/**
+ * An entry basic block, that is, a basic block whose first node is
+ * an entry node.
+ */
+class EntryBasicBlock extends BasicBlock {
+ EntryBasicBlock() { entryBB(this) }
+
+ override CfgScope getScope() { this.getFirstNode() = TEntryNode(result) }
+}
+
+/**
+ * An annotated exit basic block, that is, a basic block whose last node is
+ * an annotated exit node.
+ */
+class AnnotatedExitBasicBlock extends BasicBlock {
+ private boolean normal;
+
+ AnnotatedExitBasicBlock() {
+ exists(AnnotatedExitNode n |
+ n = this.getANode() and
+ if n.isNormal() then normal = true else normal = false
+ )
+ }
+
+ /** Holds if this block represent a normal exit. */
+ final predicate isNormal() { normal = true }
+}
+
+/**
+ * An exit basic block, that is, a basic block whose last node is
+ * an exit node.
+ */
+class ExitBasicBlock extends BasicBlock {
+ ExitBasicBlock() { this.getLastNode() instanceof ExitNode }
+}
+
+private module JoinBlockPredecessors {
+ private predicate id(Ruby::AstNode x, Ruby::AstNode y) { x = y }
+
+ private predicate idOf(Ruby::AstNode x, int y) = equivalenceRelation(id/2)(x, y)
+
+ int getId(JoinBlockPredecessor jbp) {
+ idOf(toGeneratedInclSynth(jbp.getFirstNode().(AstCfgNode).getNode()), result)
+ or
+ idOf(toGeneratedInclSynth(jbp.(EntryBasicBlock).getScope()), result)
+ }
+
+ string getSplitString(JoinBlockPredecessor jbp) {
+ result = jbp.getFirstNode().(AstCfgNode).getSplitsString()
+ or
+ not exists(jbp.getFirstNode().(AstCfgNode).getSplitsString()) and
+ result = ""
+ }
+}
+
+/** A basic block with more than one predecessor. */
+class JoinBlock extends BasicBlock {
+ JoinBlock() { this.getFirstNode().isJoin() }
+
+ /**
+ * Gets the `i`th predecessor of this join block, with respect to some
+ * arbitrary order.
+ */
+ JoinBlockPredecessor getJoinBlockPredecessor(int i) { result = getJoinBlockPredecessor(this, i) }
+}
+
+/** A basic block that is an immediate predecessor of a join block. */
+class JoinBlockPredecessor extends BasicBlock {
+ JoinBlockPredecessor() { this.getASuccessor() instanceof JoinBlock }
+}
+
+/** A basic block that terminates in a condition, splitting the subsequent control flow. */
+class ConditionBlock extends BasicBlock {
+ ConditionBlock() { this.getLastNode().isCondition() }
+
+ /**
+ * Holds if basic block `succ` is immediately controlled by this basic
+ * block with conditional value `s`. That is, `succ` is an immediate
+ * successor of this block, and `succ` can only be reached from
+ * the callable entry point by going via the `s` edge out of this basic block.
+ */
+ pragma[nomagic]
+ predicate immediatelyControls(BasicBlock succ, BooleanSuccessor s) {
+ succ = this.getASuccessor(s) and
+ forall(BasicBlock pred | pred = succ.getAPredecessor() and pred != this | succ.dominates(pred))
+ }
+
+ /**
+ * Holds if basic block `controlled` is controlled by this basic block with
+ * conditional value `s`. That is, `controlled` can only be reached from
+ * the callable entry point by going via the `s` edge out of this basic block.
+ */
+ predicate controls(BasicBlock controlled, BooleanSuccessor s) {
+ exists(BasicBlock succ | this.immediatelyControls(succ, s) | succ.dominates(controlled))
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/CfgNodes.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/CfgNodes.qll
new file mode 100644
index 00000000000..a754bae9ef7
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/CfgNodes.qll
@@ -0,0 +1,496 @@
+/** Provides classes representing nodes in a control flow graph. */
+
+private import codeql.ruby.AST
+private import codeql.ruby.controlflow.BasicBlocks
+private import codeql.ruby.dataflow.SSA
+private import ControlFlowGraph
+private import internal.ControlFlowGraphImpl
+private import internal.Splitting
+
+/** An entry node for a given scope. */
+class EntryNode extends CfgNode, TEntryNode {
+ private CfgScope scope;
+
+ EntryNode() { this = TEntryNode(scope) }
+
+ final override EntryBasicBlock getBasicBlock() { result = CfgNode.super.getBasicBlock() }
+
+ final override Location getLocation() { result = scope.getLocation() }
+
+ final override string toString() { result = "enter " + scope }
+}
+
+/** An exit node for a given scope, annotated with the type of exit. */
+class AnnotatedExitNode extends CfgNode, TAnnotatedExitNode {
+ private CfgScope scope;
+ private boolean normal;
+
+ AnnotatedExitNode() { this = TAnnotatedExitNode(scope, normal) }
+
+ /** Holds if this node represent a normal exit. */
+ final predicate isNormal() { normal = true }
+
+ final override AnnotatedExitBasicBlock getBasicBlock() { result = CfgNode.super.getBasicBlock() }
+
+ final override Location getLocation() { result = scope.getLocation() }
+
+ final override string toString() {
+ exists(string s |
+ normal = true and s = "normal"
+ or
+ normal = false and s = "abnormal"
+ |
+ result = "exit " + scope + " (" + s + ")"
+ )
+ }
+}
+
+/** An exit node for a given scope. */
+class ExitNode extends CfgNode, TExitNode {
+ private CfgScope scope;
+
+ ExitNode() { this = TExitNode(scope) }
+
+ final override Location getLocation() { result = scope.getLocation() }
+
+ final override string toString() { result = "exit " + scope }
+}
+
+/**
+ * A node for an AST node.
+ *
+ * Each AST node maps to zero or more `AstCfgNode`s: zero when the node is unreachable
+ * (dead) code or not important for control flow, and multiple when there are different
+ * splits for the AST node.
+ */
+class AstCfgNode extends CfgNode, TElementNode {
+ private Splits splits;
+ private AstNode n;
+
+ AstCfgNode() { this = TElementNode(n, splits) }
+
+ final override AstNode getNode() { result = n }
+
+ override Location getLocation() { result = n.getLocation() }
+
+ final override string toString() {
+ exists(string s | s = n.toString() |
+ result = "[" + this.getSplitsString() + "] " + s
+ or
+ not exists(this.getSplitsString()) and result = s
+ )
+ }
+
+ /** Gets a comma-separated list of strings for each split in this node, if any. */
+ final string getSplitsString() {
+ result = splits.toString() and
+ result != ""
+ }
+
+ /** Gets a split for this control flow node, if any. */
+ final Split getASplit() { result = splits.getASplit() }
+}
+
+/** A control-flow node that wraps an AST expression. */
+class ExprCfgNode extends AstCfgNode {
+ Expr e;
+
+ ExprCfgNode() { e = this.getNode() }
+
+ /** Gets the underlying expression. */
+ Expr getExpr() { result = e }
+
+ private ExprCfgNode getSource() {
+ exists(Ssa::WriteDefinition def |
+ def.assigns(result) and
+ this = def.getARead()
+ )
+ }
+
+ /** Gets the textual (constant) value of this expression, if any. */
+ cached
+ string getValueText() { result = this.getSource().getValueText() }
+}
+
+/** A control-flow node that wraps a return-like statement. */
+class ReturningCfgNode extends AstCfgNode {
+ ReturningStmt s;
+
+ ReturningCfgNode() { s = this.getNode() }
+
+ /** Gets the node of the returned value, if any. */
+ ExprCfgNode getReturnedValueNode() {
+ result = this.getAPredecessor() and
+ result.getNode() = s.getValue()
+ }
+}
+
+/** A control-flow node that wraps a `StringComponent` AST expression. */
+class StringComponentCfgNode extends AstCfgNode {
+ StringComponentCfgNode() { this.getNode() instanceof StringComponent }
+}
+
+private Expr desugar(Expr n) {
+ result = n.getDesugared()
+ or
+ not exists(n.getDesugared()) and
+ result = n
+}
+
+/**
+ * A class for mapping parent-child AST nodes to parent-child CFG nodes.
+ */
+abstract private class ExprChildMapping extends Expr {
+ /**
+ * Holds if `child` is a (possibly nested) child of this expression
+ * for which we would like to find a matching CFG child.
+ */
+ abstract predicate relevantChild(Expr child);
+
+ pragma[nomagic]
+ private predicate reachesBasicBlock(Expr child, CfgNode cfn, BasicBlock bb) {
+ this.relevantChild(child) and
+ cfn = this.getAControlFlowNode() and
+ bb.getANode() = cfn
+ or
+ exists(BasicBlock mid |
+ this.reachesBasicBlock(child, cfn, mid) and
+ bb = mid.getAPredecessor() and
+ not mid.getANode().getNode() = child
+ )
+ }
+
+ /**
+ * Holds if there is a control-flow path from `cfn` to `cfnChild`, where `cfn`
+ * is a control-flow node for this expression, and `cfnChild` is a control-flow
+ * node for `child`.
+ *
+ * The path never escapes the syntactic scope of this expression.
+ */
+ cached
+ predicate hasCfgChild(Expr child, CfgNode cfn, CfgNode cfnChild) {
+ this.reachesBasicBlock(child, cfn, cfnChild.getBasicBlock()) and
+ cfnChild = desugar(child).getAControlFlowNode()
+ }
+}
+
+/** Provides classes for control-flow nodes that wrap AST expressions. */
+module ExprNodes {
+ private class LiteralChildMapping extends ExprChildMapping, Literal {
+ override predicate relevantChild(Expr e) { none() }
+ }
+
+ /** A control-flow node that wraps an `ArrayLiteral` AST expression. */
+ class LiteralCfgNode extends ExprCfgNode {
+ override LiteralChildMapping e;
+
+ override Literal getExpr() { result = super.getExpr() }
+
+ override string getValueText() { result = e.getValueText() }
+ }
+
+ private class AssignExprChildMapping extends ExprChildMapping, AssignExpr {
+ override predicate relevantChild(Expr e) { e = this.getAnOperand() }
+ }
+
+ /** A control-flow node that wraps an `AssignExpr` AST expression. */
+ class AssignExprCfgNode extends ExprCfgNode {
+ override AssignExprChildMapping e;
+
+ final override AssignExpr getExpr() { result = ExprCfgNode.super.getExpr() }
+
+ /** Gets the LHS of this assignment. */
+ final ExprCfgNode getLhs() { e.hasCfgChild(e.getLeftOperand(), this, result) }
+
+ /** Gets the RHS of this assignment. */
+ final ExprCfgNode getRhs() { e.hasCfgChild(e.getRightOperand(), this, result) }
+ }
+
+ private class OperationExprChildMapping extends ExprChildMapping, Operation {
+ override predicate relevantChild(Expr e) { e = this.getAnOperand() }
+ }
+
+ /** A control-flow node that wraps an `Operation` AST expression. */
+ class OperationCfgNode extends ExprCfgNode {
+ override OperationExprChildMapping e;
+
+ override Operation getExpr() { result = super.getExpr() }
+
+ /** Gets an operand of this operation. */
+ final ExprCfgNode getAnOperand() { e.hasCfgChild(e.getAnOperand(), this, result) }
+ }
+
+ /** A control-flow node that wraps a `BinaryOperation` AST expression. */
+ class BinaryOperationCfgNode extends OperationCfgNode {
+ private BinaryOperation bo;
+
+ BinaryOperationCfgNode() { e = bo }
+
+ override BinaryOperation getExpr() { result = super.getExpr() }
+
+ /** Gets the left operand of this binary operation. */
+ final ExprCfgNode getLeftOperand() { e.hasCfgChild(bo.getLeftOperand(), this, result) }
+
+ /** Gets the right operand of this binary operation. */
+ final ExprCfgNode getRightOperand() { e.hasCfgChild(bo.getRightOperand(), this, result) }
+
+ final override string getValueText() {
+ exists(string left, string right, string op |
+ left = this.getLeftOperand().getValueText() and
+ right = this.getRightOperand().getValueText() and
+ op = this.getExpr().getOperator()
+ |
+ op = "+" and
+ (
+ result = (left.toInt() + right.toInt()).toString()
+ or
+ not (exists(left.toInt()) and exists(right.toInt())) and
+ result = (left.toFloat() + right.toFloat()).toString()
+ or
+ not (exists(left.toFloat()) and exists(right.toFloat())) and
+ exists(int l, int r, int limit |
+ l = left.length() and
+ r = right.length() and
+ limit = 10000
+ |
+ if l > limit
+ then result = left.prefix(limit) + "..."
+ else
+ if l + r > limit
+ then result = left + right.prefix(limit - l) + "..."
+ else result = left + right
+ )
+ )
+ or
+ op = "-" and
+ (
+ result = (left.toInt() - right.toInt()).toString()
+ or
+ not (exists(left.toInt()) and exists(right.toInt())) and
+ result = (left.toFloat() - right.toFloat()).toString()
+ )
+ or
+ op = "*" and
+ (
+ result = (left.toInt() * right.toInt()).toString()
+ or
+ not (exists(left.toInt()) and exists(right.toInt())) and
+ result = (left.toFloat() * right.toFloat()).toString()
+ )
+ or
+ op = "/" and
+ (
+ result = (left.toInt() / right.toInt()).toString()
+ or
+ not (exists(left.toInt()) and exists(right.toInt())) and
+ result = (left.toFloat() / right.toFloat()).toString()
+ )
+ )
+ }
+ }
+
+ private class BlockArgumentChildMapping extends ExprChildMapping, BlockArgument {
+ override predicate relevantChild(Expr e) { e = this.getValue() }
+ }
+
+ /** A control-flow node that wraps a `BlockArgument` AST expression. */
+ class BlockArgumentCfgNode extends ExprCfgNode {
+ override BlockArgumentChildMapping e;
+
+ final override BlockArgument getExpr() { result = ExprCfgNode.super.getExpr() }
+
+ /** Gets the value of this block argument. */
+ final ExprCfgNode getValue() { e.hasCfgChild(e.getValue(), this, result) }
+ }
+
+ private class CallExprChildMapping extends ExprChildMapping, Call {
+ override predicate relevantChild(Expr e) {
+ e = [this.getAnArgument(), this.(MethodCall).getReceiver(), this.(MethodCall).getBlock()]
+ }
+ }
+
+ /** A control-flow node that wraps a `Call` AST expression. */
+ class CallCfgNode extends ExprCfgNode {
+ override CallExprChildMapping e;
+
+ override Call getExpr() { result = super.getExpr() }
+
+ /** Gets the `n`th argument of this call. */
+ final ExprCfgNode getArgument(int n) { e.hasCfgChild(e.getArgument(n), this, result) }
+
+ /** Gets the the keyword argument whose key is `keyword` of this call. */
+ final ExprCfgNode getKeywordArgument(string keyword) {
+ e.hasCfgChild(e.getKeywordArgument(keyword), this, result)
+ }
+
+ /** Gets the number of arguments of this call. */
+ final int getNumberOfArguments() { result = e.getNumberOfArguments() }
+
+ /** Gets the receiver of this call. */
+ final ExprCfgNode getReceiver() { e.hasCfgChild(e.(MethodCall).getReceiver(), this, result) }
+
+ /** Gets the block of this call. */
+ final ExprCfgNode getBlock() { e.hasCfgChild(e.(MethodCall).getBlock(), this, result) }
+ }
+
+ private class CaseExprChildMapping extends ExprChildMapping, CaseExpr {
+ override predicate relevantChild(Expr e) { e = this.getValue() or e = this.getBranch(_) }
+ }
+
+ /** A control-flow node that wraps a `MethodCall` AST expression. */
+ class MethodCallCfgNode extends CallCfgNode {
+ MethodCallCfgNode() { super.getExpr() instanceof MethodCall }
+
+ override MethodCall getExpr() { result = super.getExpr() }
+ }
+
+ /** A control-flow node that wraps a `CaseExpr` AST expression. */
+ class CaseExprCfgNode extends ExprCfgNode {
+ override CaseExprChildMapping e;
+
+ final override CaseExpr getExpr() { result = ExprCfgNode.super.getExpr() }
+
+ /** Gets the expression being compared, if any. */
+ final ExprCfgNode getValue() { e.hasCfgChild(e.getValue(), this, result) }
+
+ /**
+ * Gets the `n`th branch of this case expression.
+ */
+ final ExprCfgNode getBranch(int n) { e.hasCfgChild(e.getBranch(n), this, result) }
+ }
+
+ private class ConditionalExprChildMapping extends ExprChildMapping, ConditionalExpr {
+ override predicate relevantChild(Expr e) { e = this.getCondition() or e = this.getBranch(_) }
+ }
+
+ /** A control-flow node that wraps a `ConditionalExpr` AST expression. */
+ class ConditionalExprCfgNode extends ExprCfgNode {
+ override ConditionalExprChildMapping e;
+
+ final override ConditionalExpr getExpr() { result = ExprCfgNode.super.getExpr() }
+
+ /** Gets the condition expression. */
+ final ExprCfgNode getCondition() { e.hasCfgChild(e.getCondition(), this, result) }
+
+ /**
+ * Gets the branch of this conditional expression that is taken when the condition
+ * evaluates to cond, if any.
+ */
+ final ExprCfgNode getBranch(boolean cond) { e.hasCfgChild(e.getBranch(cond), this, result) }
+ }
+
+ private class ConstantAccessChildMapping extends ExprChildMapping, ConstantAccess {
+ override predicate relevantChild(Expr e) { e = this.getScopeExpr() }
+ }
+
+ /** A control-flow node that wraps a `ConditionalExpr` AST expression. */
+ class ConstantAccessCfgNode extends ExprCfgNode {
+ override ConstantAccessChildMapping e;
+
+ final override ConstantAccess getExpr() { result = super.getExpr() }
+
+ /** Gets the scope expression. */
+ final ExprCfgNode getScopeExpr() { e.hasCfgChild(e.getScopeExpr(), this, result) }
+ }
+
+ private class StmtSequenceChildMapping extends ExprChildMapping, StmtSequence {
+ override predicate relevantChild(Expr e) { e = this.getLastStmt() }
+ }
+
+ /** A control-flow node that wraps a `StmtSequence` AST expression. */
+ class StmtSequenceCfgNode extends ExprCfgNode {
+ override StmtSequenceChildMapping e;
+
+ final override StmtSequence getExpr() { result = ExprCfgNode.super.getExpr() }
+
+ /** Gets the last statement in this sequence, if any. */
+ final ExprCfgNode getLastStmt() { e.hasCfgChild(e.getLastStmt(), this, result) }
+ }
+
+ private class ForExprChildMapping extends ExprChildMapping, ForExpr {
+ override predicate relevantChild(Expr e) { e = this.getValue() }
+ }
+
+ /** A control-flow node that wraps a `ForExpr` AST expression. */
+ class ForExprCfgNode extends ExprCfgNode {
+ override ForExprChildMapping e;
+
+ final override ForExpr getExpr() { result = ExprCfgNode.super.getExpr() }
+
+ /** Gets the value being iterated over. */
+ final ExprCfgNode getValue() { e.hasCfgChild(e.getValue(), this, result) }
+ }
+
+ /** A control-flow node that wraps a `ParenthesizedExpr` AST expression. */
+ class ParenthesizedExprCfgNode extends StmtSequenceCfgNode {
+ ParenthesizedExprCfgNode() { this.getExpr() instanceof ParenthesizedExpr }
+ }
+
+ /** A control-flow node that wraps a `VariableReadAccess` AST expression. */
+ class VariableReadAccessCfgNode extends ExprCfgNode {
+ override VariableReadAccess e;
+
+ final override VariableReadAccess getExpr() { result = ExprCfgNode.super.getExpr() }
+ }
+
+ /** A control-flow node that wraps a `InstanceVariableWriteAccess` AST expression. */
+ class InstanceVariableWriteAccessCfgNode extends ExprCfgNode {
+ override InstanceVariableWriteAccess e;
+
+ final override InstanceVariableWriteAccess getExpr() { result = ExprCfgNode.super.getExpr() }
+ }
+
+ /** A control-flow node that wraps a `StringInterpolationComponent` AST expression. */
+ class StringInterpolationComponentCfgNode extends StmtSequenceCfgNode {
+ StringInterpolationComponentCfgNode() { this.getNode() instanceof StringInterpolationComponent }
+ }
+
+ private class StringlikeLiteralChildMapping extends ExprChildMapping, StringlikeLiteral {
+ override predicate relevantChild(Expr e) { e = this.getComponent(_) }
+ }
+
+ /** A control-flow node that wraps a `StringlikeLiteral` AST expression. */
+ class StringlikeLiteralCfgNode extends ExprCfgNode {
+ override StringlikeLiteralChildMapping e;
+
+ final override StringlikeLiteral getExpr() { result = super.getExpr() }
+
+ /** Gets a component of this `StringlikeLiteral` */
+ StringComponentCfgNode getAComponent() { e.hasCfgChild(e.getComponent(_), this, result) }
+ }
+
+ /** A control-flow node that wraps a `StringLiteral` AST expression. */
+ class StringLiteralCfgNode extends ExprCfgNode {
+ override StringLiteral e;
+
+ final override StringLiteral getExpr() { result = super.getExpr() }
+ }
+
+ /** A control-flow node that wraps a `RegExpLiteral` AST expression. */
+ class RegExpLiteralCfgNode extends ExprCfgNode {
+ override RegExpLiteral e;
+
+ final override RegExpLiteral getExpr() { result = super.getExpr() }
+ }
+
+ /** A control-flow node that wraps a `ComparisonOperation` AST expression. */
+ class ComparisonOperationCfgNode extends BinaryOperationCfgNode {
+ ComparisonOperationCfgNode() { e instanceof ComparisonOperation }
+
+ override ComparisonOperation getExpr() { result = super.getExpr() }
+ }
+
+ /** A control-flow node that wraps a `RelationalOperation` AST expression. */
+ class RelationalOperationCfgNode extends ComparisonOperationCfgNode {
+ RelationalOperationCfgNode() { e instanceof RelationalOperation }
+
+ final override RelationalOperation getExpr() { result = super.getExpr() }
+ }
+
+ /** A control-flow node that wraps an `ElementReference` AST expression. */
+ class ElementReferenceCfgNode extends MethodCallCfgNode {
+ ElementReferenceCfgNode() { e instanceof ElementReference }
+
+ final override ElementReference getExpr() { result = super.getExpr() }
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/ControlFlowGraph.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/ControlFlowGraph.qll
new file mode 100644
index 00000000000..4b2785265cf
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/ControlFlowGraph.qll
@@ -0,0 +1,341 @@
+/** Provides classes representing the control flow graph. */
+
+private import codeql.Locations
+private import codeql.ruby.AST
+private import codeql.ruby.controlflow.BasicBlocks
+private import SuccessorTypes
+private import internal.ControlFlowGraphImpl
+private import internal.Splitting
+private import internal.Completion
+
+/** An AST node with an associated control-flow graph. */
+class CfgScope extends Scope instanceof CfgScope::Range_ {
+ /** Gets the CFG scope that this scope is nested under, if any. */
+ final CfgScope getOuterCfgScope() {
+ exists(AstNode parent |
+ parent = this.getParent() and
+ result = getCfgScope(parent)
+ )
+ }
+}
+
+/**
+ * A control flow node.
+ *
+ * A control flow node is a node in the control flow graph (CFG). There is a
+ * many-to-one relationship between CFG nodes and AST nodes.
+ *
+ * Only nodes that can be reached from an entry point are included in the CFG.
+ */
+class CfgNode extends TNode {
+ /** Gets a textual representation of this control flow node. */
+ string toString() { none() }
+
+ /** Gets the AST node that this node corresponds to, if any. */
+ AstNode getNode() { none() }
+
+ /** Gets the location of this control flow node. */
+ Location getLocation() { none() }
+
+ /** Gets the file of this control flow node. */
+ final File getFile() { result = this.getLocation().getFile() }
+
+ /** Holds if this control flow node has conditional successors. */
+ final predicate isCondition() { exists(this.getASuccessor(any(BooleanSuccessor bs))) }
+
+ /** Gets the scope of this node. */
+ final CfgScope getScope() { result = this.getBasicBlock().getScope() }
+
+ /** Gets the basic block that this control flow node belongs to. */
+ BasicBlock getBasicBlock() { result.getANode() = this }
+
+ /** Gets a successor node of a given type, if any. */
+ final CfgNode getASuccessor(SuccessorType t) { result = getASuccessor(this, t) }
+
+ /** Gets an immediate successor, if any. */
+ final CfgNode getASuccessor() { result = this.getASuccessor(_) }
+
+ /** Gets an immediate predecessor node of a given flow type, if any. */
+ final CfgNode getAPredecessor(SuccessorType t) { result.getASuccessor(t) = this }
+
+ /** Gets an immediate predecessor, if any. */
+ final CfgNode getAPredecessor() { result = this.getAPredecessor(_) }
+
+ /** Holds if this node has more than one predecessor. */
+ final predicate isJoin() { strictcount(this.getAPredecessor()) > 1 }
+
+ /** Holds if this node has more than one successor. */
+ final predicate isBranch() { strictcount(this.getASuccessor()) > 1 }
+}
+
+/** The type of a control flow successor. */
+class SuccessorType extends TSuccessorType {
+ /** Gets a textual representation of successor type. */
+ string toString() { none() }
+}
+
+/** Provides different types of control flow successor types. */
+module SuccessorTypes {
+ /** A normal control flow successor. */
+ class NormalSuccessor extends SuccessorType, TSuccessorSuccessor {
+ final override string toString() { result = "successor" }
+ }
+
+ /**
+ * A conditional control flow successor. Either a Boolean successor (`BooleanSuccessor`),
+ * an emptiness successor (`EmptinessSuccessor`), or a matching successor
+ * (`MatchingSuccessor`)
+ */
+ class ConditionalSuccessor extends SuccessorType {
+ boolean value;
+
+ ConditionalSuccessor() {
+ this = TBooleanSuccessor(value) or
+ this = TEmptinessSuccessor(value) or
+ this = TMatchingSuccessor(value)
+ }
+
+ /** Gets the Boolean value of this successor. */
+ final boolean getValue() { result = value }
+
+ override string toString() { result = this.getValue().toString() }
+ }
+
+ /**
+ * A Boolean control flow successor.
+ *
+ * For example, in
+ *
+ * ```rb
+ * if x >= 0
+ * puts "positive"
+ * else
+ * puts "negative"
+ * end
+ * ```
+ *
+ * `x >= 0` has both a `true` successor and a `false` successor.
+ */
+ class BooleanSuccessor extends ConditionalSuccessor, TBooleanSuccessor { }
+
+ /**
+ * An emptiness control flow successor.
+ *
+ * For example, this program fragment:
+ *
+ * ```rb
+ * for arg in args do
+ * puts arg
+ * end
+ * puts "done";
+ * ```
+ *
+ * has a control flow graph containing emptiness successors:
+ *
+ * ```
+ * args
+ * |
+ * for------<-----
+ * / \ \
+ * / \ |
+ * / \ |
+ * / \ |
+ * empty non-empty |
+ * | \ |
+ * puts "done" \ |
+ * arg |
+ * | |
+ * puts arg |
+ * \___/
+ * ```
+ */
+ class EmptinessSuccessor extends ConditionalSuccessor, TEmptinessSuccessor {
+ override string toString() { if value = true then result = "empty" else result = "non-empty" }
+ }
+
+ /**
+ * A matching control flow successor.
+ *
+ * For example, this program fragment:
+ *
+ * ```rb
+ * case x
+ * when 1 then puts "one"
+ * else puts "not one"
+ * end
+ * ```
+ *
+ * has a control flow graph containing matching successors:
+ *
+ * ```
+ * x
+ * |
+ * 1
+ * / \
+ * / \
+ * / \
+ * / \
+ * match non-match
+ * | |
+ * puts "one" puts "not one"
+ * ```
+ */
+ class MatchingSuccessor extends ConditionalSuccessor, TMatchingSuccessor {
+ override string toString() { if value = true then result = "match" else result = "no-match" }
+ }
+
+ /**
+ * A `return` control flow successor.
+ *
+ * Example:
+ *
+ * ```rb
+ * def sum(x,y)
+ * return x + y
+ * end
+ * ```
+ *
+ * The exit node of `sum` is a `return` successor of the `return x + y`
+ * statement.
+ */
+ class ReturnSuccessor extends SuccessorType, TReturnSuccessor {
+ final override string toString() { result = "return" }
+ }
+
+ /**
+ * A `break` control flow successor.
+ *
+ * Example:
+ *
+ * ```rb
+ * def m
+ * while x >= 0
+ * x -= 1
+ * if num > 100
+ * break
+ * end
+ * end
+ * puts "done"
+ * end
+ * ```
+ *
+ * The node `puts "done"` is `break` successor of the node `break`.
+ */
+ class BreakSuccessor extends SuccessorType, TBreakSuccessor {
+ final override string toString() { result = "break" }
+ }
+
+ /**
+ * A `next` control flow successor.
+ *
+ * Example:
+ *
+ * ```rb
+ * def m
+ * while x >= 0
+ * x -= 1
+ * if num > 100
+ * next
+ * end
+ * end
+ * puts "done"
+ * end
+ * ```
+ *
+ * The node `x >= 0` is `next` successor of the node `next`.
+ */
+ class NextSuccessor extends SuccessorType, TNextSuccessor {
+ final override string toString() { result = "next" }
+ }
+
+ /**
+ * A `redo` control flow successor.
+ *
+ * Example:
+ *
+ * Example:
+ *
+ * ```rb
+ * def m
+ * while x >= 0
+ * x -= 1
+ * if num > 100
+ * redo
+ * end
+ * end
+ * puts "done"
+ * end
+ * ```
+ *
+ * The node `x -= 1` is `redo` successor of the node `redo`.
+ */
+ class RedoSuccessor extends SuccessorType, TRedoSuccessor {
+ final override string toString() { result = "redo" }
+ }
+
+ /**
+ * A `retry` control flow successor.
+ *
+ * Example:
+ *
+ * Example:
+ *
+ * ```rb
+ * def m
+ * begin
+ * puts "Retry"
+ * raise
+ * rescue
+ * retry
+ * end
+ * end
+ * ```
+ *
+ * The node `puts "Retry"` is `retry` successor of the node `retry`.
+ */
+ class RetrySuccessor extends SuccessorType, TRetrySuccessor {
+ final override string toString() { result = "retry" }
+ }
+
+ /**
+ * An exceptional control flow successor.
+ *
+ * Example:
+ *
+ * ```rb
+ * def m x
+ * if x > 2
+ * raise "x > 2"
+ * end
+ * puts "x <= 2"
+ * end
+ * ```
+ *
+ * The exit node of `m` is an exceptional successor of the node
+ * `raise "x > 2"`.
+ */
+ class RaiseSuccessor extends SuccessorType, TRaiseSuccessor {
+ final override string toString() { result = "raise" }
+ }
+
+ /**
+ * An exit control flow successor.
+ *
+ * Example:
+ *
+ * ```rb
+ * def m x
+ * if x > 2
+ * exit 1
+ * end
+ * puts "x <= 2"
+ * end
+ * ```
+ *
+ * The exit node of `m` is an exit successor of the node
+ * `exit 1`.
+ */
+ class ExitSuccessor extends SuccessorType, TExitSuccessor {
+ final override string toString() { result = "exit" }
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/internal/Completion.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/internal/Completion.qll
new file mode 100644
index 00000000000..e7f64d1318e
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/internal/Completion.qll
@@ -0,0 +1,507 @@
+/**
+ * Provides classes representing control flow completions.
+ *
+ * A completion represents how a statement or expression terminates.
+ */
+
+private import codeql.ruby.AST
+private import codeql.ruby.ast.internal.AST
+private import codeql.ruby.controlflow.ControlFlowGraph
+private import ControlFlowGraphImpl
+private import NonReturning
+private import SuccessorTypes
+
+private newtype TCompletion =
+ TSimpleCompletion() or
+ TBooleanCompletion(boolean b) { b in [false, true] } or
+ TEmptinessCompletion(boolean isEmpty) { isEmpty in [false, true] } or
+ TMatchingCompletion(boolean isMatch) { isMatch in [false, true] } or
+ TReturnCompletion() or
+ TBreakCompletion() or
+ TNextCompletion() or
+ TRedoCompletion() or
+ TRetryCompletion() or
+ TRaiseCompletion() or // TODO: Add exception type?
+ TExitCompletion() or
+ TNestedCompletion(Completion inner, Completion outer, int nestLevel) {
+ inner = TBreakCompletion() and
+ outer instanceof NonNestedNormalCompletion and
+ nestLevel = 0
+ or
+ inner instanceof NormalCompletion and
+ nestedEnsureCompletion(outer, nestLevel)
+ }
+
+pragma[noinline]
+private predicate nestedEnsureCompletion(Completion outer, int nestLevel) {
+ (
+ outer = TReturnCompletion()
+ or
+ outer = TBreakCompletion()
+ or
+ outer = TNextCompletion()
+ or
+ outer = TRedoCompletion()
+ or
+ outer = TRetryCompletion()
+ or
+ outer = TRaiseCompletion()
+ or
+ outer = TExitCompletion()
+ ) and
+ nestLevel = any(Trees::BodyStmtTree t).getNestLevel()
+}
+
+pragma[noinline]
+private predicate completionIsValidForStmt(AstNode n, Completion c) {
+ n = TForIn(_) and
+ c instanceof EmptinessCompletion
+ or
+ n instanceof BreakStmt and
+ c = TBreakCompletion()
+ or
+ n instanceof NextStmt and
+ c = TNextCompletion()
+ or
+ n instanceof RedoStmt and
+ c = TRedoCompletion()
+ or
+ n instanceof ReturnStmt and
+ c = TReturnCompletion()
+}
+
+/**
+ * Holds if `c` happens in an exception-aware context, that is, it may be
+ * `rescue`d or `ensure`d. In such cases, we assume that the target of `c`
+ * may raise an exception (in addition to evaluating normally).
+ */
+private predicate mayRaise(Call c) {
+ exists(Trees::BodyStmtTree bst | c = bst.getBodyChild(_, true).getAChild*() |
+ exists(bst.getARescue())
+ or
+ exists(bst.getEnsure())
+ )
+}
+
+/** A completion of a statement or an expression. */
+abstract class Completion extends TCompletion {
+ /** Holds if this completion is valid for node `n`. */
+ predicate isValidFor(AstNode n) {
+ this = n.(NonReturningCall).getACompletion()
+ or
+ completionIsValidForStmt(n, this)
+ or
+ mustHaveBooleanCompletion(n) and
+ (
+ exists(boolean value | isBooleanConstant(n, value) | this = TBooleanCompletion(value))
+ or
+ not isBooleanConstant(n, _) and
+ this = TBooleanCompletion(_)
+ )
+ or
+ mustHaveMatchingCompletion(n) and
+ this = TMatchingCompletion(_)
+ or
+ n = any(RescueModifierExpr parent).getBody() and this = TRaiseCompletion()
+ or
+ mayRaise(n) and
+ this = TRaiseCompletion()
+ or
+ not n instanceof NonReturningCall and
+ not completionIsValidForStmt(n, _) and
+ not mustHaveBooleanCompletion(n) and
+ not mustHaveMatchingCompletion(n) and
+ this = TSimpleCompletion()
+ }
+
+ /**
+ * Holds if this completion will continue a loop when it is the completion
+ * of a loop body.
+ */
+ predicate continuesLoop() {
+ this instanceof NormalCompletion or
+ this instanceof NextCompletion
+ }
+
+ /**
+ * Gets the inner completion. This is either the inner completion,
+ * when the completion is nested, or the completion itself.
+ */
+ Completion getInnerCompletion() { result = this }
+
+ /**
+ * Gets the outer completion. This is either the outer completion,
+ * when the completion is nested, or the completion itself.
+ */
+ Completion getOuterCompletion() { result = this }
+
+ /** Gets a successor type that matches this completion. */
+ abstract SuccessorType getAMatchingSuccessorType();
+
+ /** Gets a textual representation of this completion. */
+ abstract string toString();
+}
+
+/** Holds if node `n` has the Boolean constant value `value`. */
+private predicate isBooleanConstant(AstNode n, boolean value) {
+ mustHaveBooleanCompletion(n) and
+ (
+ n.(BooleanLiteral).isTrue() and
+ value = true
+ or
+ n.(BooleanLiteral).isFalse() and
+ value = false
+ )
+}
+
+/**
+ * Holds if a normal completion of `n` must be a Boolean completion.
+ */
+private predicate mustHaveBooleanCompletion(AstNode n) {
+ inBooleanContext(n) and
+ not n instanceof NonReturningCall
+}
+
+/**
+ * Holds if `n` is used in a Boolean context. That is, the value
+ * that `n` evaluates to determines a true/false branch successor.
+ */
+private predicate inBooleanContext(AstNode n) {
+ exists(ConditionalExpr i |
+ n = i.getCondition()
+ or
+ inBooleanContext(i) and
+ n = i.getBranch(_)
+ )
+ or
+ n = any(ConditionalLoop parent).getCondition()
+ or
+ exists(LogicalAndExpr parent |
+ n = parent.getLeftOperand()
+ or
+ inBooleanContext(parent) and
+ n = parent.getRightOperand()
+ )
+ or
+ exists(LogicalOrExpr parent |
+ n = parent.getLeftOperand()
+ or
+ inBooleanContext(parent) and
+ n = parent.getRightOperand()
+ )
+ or
+ n = any(NotExpr parent | inBooleanContext(parent)).getOperand()
+ or
+ n = any(StmtSequence parent | inBooleanContext(parent)).getLastStmt()
+ or
+ exists(CaseExpr c, WhenExpr w |
+ not exists(c.getValue()) and
+ c.getAWhenBranch() = w and
+ w.getPattern(_) = n
+ )
+}
+
+/**
+ * Holds if a normal completion of `n` must be a matching completion.
+ */
+private predicate mustHaveMatchingCompletion(AstNode n) {
+ inMatchingContext(n) and
+ not n instanceof NonReturningCall
+}
+
+/**
+ * Holds if `n` is used in a matching context. That is, whether or
+ * not the value of `n` matches, determines the successor.
+ */
+private predicate inMatchingContext(AstNode n) {
+ n = any(RescueClause r).getException(_)
+ or
+ exists(CaseExpr c, WhenExpr w |
+ exists(c.getValue()) and
+ c.getAWhenBranch() = w and
+ w.getPattern(_) = n
+ )
+ or
+ n.(Trees::DefaultValueParameterTree).hasDefaultValue()
+}
+
+/**
+ * A completion that represents normal evaluation of a statement or an
+ * expression.
+ */
+abstract class NormalCompletion extends Completion { }
+
+abstract private class NonNestedNormalCompletion extends NormalCompletion { }
+
+/** A simple (normal) completion. */
+class SimpleCompletion extends NonNestedNormalCompletion, TSimpleCompletion {
+ override NormalSuccessor getAMatchingSuccessorType() { any() }
+
+ override string toString() { result = "simple" }
+}
+
+/**
+ * A completion that represents evaluation of an expression, whose value determines
+ * the successor. Either a Boolean completion (`BooleanCompletion`), an emptiness
+ * completion (`EmptinessCompletion`), or a matching completion (`MatchingCompletion`).
+ */
+abstract class ConditionalCompletion extends NonNestedNormalCompletion {
+ boolean value;
+
+ bindingset[value]
+ ConditionalCompletion() { any() }
+
+ /** Gets the Boolean value of this conditional completion. */
+ final boolean getValue() { result = value }
+}
+
+/**
+ * A completion that represents evaluation of an expression
+ * with a Boolean value.
+ */
+class BooleanCompletion extends ConditionalCompletion, TBooleanCompletion {
+ BooleanCompletion() { this = TBooleanCompletion(value) }
+
+ /** Gets the dual Boolean completion. */
+ BooleanCompletion getDual() { result = TBooleanCompletion(value.booleanNot()) }
+
+ override BooleanSuccessor getAMatchingSuccessorType() { result.getValue() = value }
+
+ override string toString() { result = value.toString() }
+}
+
+/** A Boolean `true` completion. */
+class TrueCompletion extends BooleanCompletion {
+ TrueCompletion() { this.getValue() = true }
+}
+
+/** A Boolean `false` completion. */
+class FalseCompletion extends BooleanCompletion {
+ FalseCompletion() { this.getValue() = false }
+}
+
+/**
+ * A completion that represents evaluation of an emptiness test, for example
+ * a test in a `for in` statement.
+ */
+class EmptinessCompletion extends ConditionalCompletion, TEmptinessCompletion {
+ EmptinessCompletion() { this = TEmptinessCompletion(value) }
+
+ override EmptinessSuccessor getAMatchingSuccessorType() { result.getValue() = value }
+
+ override string toString() { if value = true then result = "empty" else result = "non-empty" }
+}
+
+/**
+ * A completion that represents evaluation of a matching test, for example
+ * a test in a `rescue` statement.
+ */
+class MatchingCompletion extends ConditionalCompletion, TMatchingCompletion {
+ MatchingCompletion() { this = TMatchingCompletion(value) }
+
+ override MatchingSuccessor getAMatchingSuccessorType() { result.getValue() = value }
+
+ override string toString() { if value = true then result = "match" else result = "no-match" }
+}
+
+/**
+ * A completion that represents evaluation of a statement or an
+ * expression resulting in a return.
+ */
+class ReturnCompletion extends Completion {
+ ReturnCompletion() {
+ this = TReturnCompletion() or
+ this = TNestedCompletion(_, TReturnCompletion(), _)
+ }
+
+ override ReturnSuccessor getAMatchingSuccessorType() { any() }
+
+ override string toString() {
+ // `NestedCompletion` defines `toString()` for the other case
+ this = TReturnCompletion() and result = "return"
+ }
+}
+
+/**
+ * A completion that represents evaluation of a statement or an
+ * expression resulting in a break from a loop.
+ */
+class BreakCompletion extends Completion {
+ BreakCompletion() {
+ this = TBreakCompletion() or
+ this = TNestedCompletion(_, TBreakCompletion(), _)
+ }
+
+ override BreakSuccessor getAMatchingSuccessorType() { any() }
+
+ override string toString() {
+ // `NestedCompletion` defines `toString()` for the other case
+ this = TBreakCompletion() and result = "break"
+ }
+}
+
+/**
+ * A completion that represents evaluation of a statement or an
+ * expression resulting in a continuation of a loop.
+ */
+class NextCompletion extends Completion {
+ NextCompletion() {
+ this = TNextCompletion() or
+ this = TNestedCompletion(_, TNextCompletion(), _)
+ }
+
+ override NextSuccessor getAMatchingSuccessorType() { any() }
+
+ override string toString() {
+ // `NestedCompletion` defines `toString()` for the other case
+ this = TNextCompletion() and result = "next"
+ }
+}
+
+/**
+ * A completion that represents evaluation of a statement or an
+ * expression resulting in a redo of a loop iteration.
+ */
+class RedoCompletion extends Completion {
+ RedoCompletion() {
+ this = TRedoCompletion() or
+ this = TNestedCompletion(_, TRedoCompletion(), _)
+ }
+
+ override RedoSuccessor getAMatchingSuccessorType() { any() }
+
+ override string toString() {
+ // `NestedCompletion` defines `toString()` for the other case
+ this = TRedoCompletion() and result = "redo"
+ }
+}
+
+/**
+ * A completion that represents evaluation of a statement or an
+ * expression resulting in a retry.
+ */
+class RetryCompletion extends Completion {
+ RetryCompletion() {
+ this = TRetryCompletion() or
+ this = TNestedCompletion(_, TRetryCompletion(), _)
+ }
+
+ override RetrySuccessor getAMatchingSuccessorType() { any() }
+
+ override string toString() {
+ // `NestedCompletion` defines `toString()` for the other case
+ this = TRetryCompletion() and result = "retry"
+ }
+}
+
+/**
+ * A completion that represents evaluation of a statement or an
+ * expression resulting in a thrown exception.
+ */
+class RaiseCompletion extends Completion {
+ RaiseCompletion() {
+ this = TRaiseCompletion() or
+ this = TNestedCompletion(_, TRaiseCompletion(), _)
+ }
+
+ override RaiseSuccessor getAMatchingSuccessorType() { any() }
+
+ override string toString() {
+ // `NestedCompletion` defines `toString()` for the other case
+ this = TRaiseCompletion() and result = "raise"
+ }
+}
+
+/**
+ * A completion that represents evaluation of a statement or an
+ * expression resulting in an abort/exit.
+ */
+class ExitCompletion extends Completion {
+ ExitCompletion() {
+ this = TExitCompletion() or
+ this = TNestedCompletion(_, TExitCompletion(), _)
+ }
+
+ override ExitSuccessor getAMatchingSuccessorType() { any() }
+
+ override string toString() {
+ // `NestedCompletion` defines `toString()` for the other case
+ this = TExitCompletion() and result = "exit"
+ }
+}
+
+/**
+ * A nested completion. For example, in
+ *
+ * ```rb
+ * def m
+ * while x >= 0
+ * x -= 1
+ * if num > 100
+ * break
+ * end
+ * end
+ * puts "done"
+ * end
+ * ```
+ *
+ * the `while` loop can have a nested completion where the inner completion
+ * is a `break` and the outer completion is a simple successor.
+ */
+abstract class NestedCompletion extends Completion, TNestedCompletion {
+ Completion inner;
+ Completion outer;
+ int nestLevel;
+
+ NestedCompletion() { this = TNestedCompletion(inner, outer, nestLevel) }
+
+ /** Gets a completion that is compatible with the inner completion. */
+ abstract Completion getAnInnerCompatibleCompletion();
+
+ /** Gets the level of this nested completion. */
+ final int getNestLevel() { result = nestLevel }
+
+ override string toString() { result = outer + " [" + inner + "] (" + nestLevel + ")" }
+}
+
+class NestedBreakCompletion extends NormalCompletion, NestedCompletion {
+ NestedBreakCompletion() {
+ inner = TBreakCompletion() and
+ outer instanceof NonNestedNormalCompletion
+ }
+
+ override BreakCompletion getInnerCompletion() { result = inner }
+
+ override NonNestedNormalCompletion getOuterCompletion() { result = outer }
+
+ override Completion getAnInnerCompatibleCompletion() {
+ result = inner and
+ outer = TSimpleCompletion()
+ or
+ result = TNestedCompletion(outer, inner, _)
+ }
+
+ override SuccessorType getAMatchingSuccessorType() {
+ outer instanceof SimpleCompletion and
+ result instanceof BreakSuccessor
+ or
+ result = outer.(ConditionalCompletion).getAMatchingSuccessorType()
+ }
+}
+
+class NestedEnsureCompletion extends NestedCompletion {
+ NestedEnsureCompletion() {
+ inner instanceof NormalCompletion and
+ nestedEnsureCompletion(outer, nestLevel)
+ }
+
+ override NormalCompletion getInnerCompletion() { result = inner }
+
+ override Completion getOuterCompletion() { result = outer }
+
+ override Completion getAnInnerCompatibleCompletion() {
+ result.getOuterCompletion() = this.getInnerCompletion()
+ }
+
+ override SuccessorType getAMatchingSuccessorType() { none() }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImpl.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImpl.qll
new file mode 100644
index 00000000000..198329eff1e
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImpl.qll
@@ -0,0 +1,1173 @@
+/**
+ * Provides auxiliary classes and predicates used to construct the basic successor
+ * relation on control flow elements.
+ *
+ * The implementation is centered around the concept of a _completion_, which
+ * models how the execution of a statement or expression terminates.
+ * Completions are represented as an algebraic data type `Completion` defined in
+ * `Completion.qll`.
+ *
+ * The CFG is built by structural recursion over the AST. To achieve this the
+ * CFG edges related to a given AST node, `n`, are divided into three categories:
+ *
+ * 1. The in-going edge that points to the first CFG node to execute when
+ * `n` is going to be executed.
+ * 2. The out-going edges for control flow leaving `n` that are going to some
+ * other node in the surrounding context of `n`.
+ * 3. The edges that have both of their end-points entirely within the AST
+ * node and its children.
+ *
+ * The edges in (1) and (2) are inherently non-local and are therefore
+ * initially calculated as half-edges, that is, the single node, `k`, of the
+ * edge contained within `n`, by the predicates `k = first(n)` and `k = last(n, _)`,
+ * respectively. The edges in (3) can then be enumerated directly by the predicate
+ * `succ` by calling `first` and `last` recursively on the children of `n` and
+ * connecting the end-points. This yields the entire CFG, since all edges are in
+ * (3) for _some_ AST node.
+ *
+ * The second parameter of `last` is the completion, which is necessary to distinguish
+ * the out-going edges from `n`. Note that the completion changes as the calculation of
+ * `last` proceeds outward through the AST; for example, a `BreakCompletion` is
+ * caught up by its surrounding loop and turned into a `NormalCompletion`.
+ */
+
+private import codeql.ruby.AST
+private import codeql.ruby.ast.internal.AST as ASTInternal
+private import codeql.ruby.ast.internal.Scope
+private import codeql.ruby.ast.Scope
+private import codeql.ruby.ast.internal.TreeSitter
+private import codeql.ruby.ast.internal.Variable
+private import codeql.ruby.controlflow.ControlFlowGraph
+private import Completion
+import ControlFlowGraphImplShared
+
+module CfgScope {
+ abstract class Range_ extends AstNode {
+ abstract predicate entry(AstNode first);
+
+ abstract predicate exit(AstNode last, Completion c);
+ }
+
+ private class ToplevelScope extends Range_, Toplevel {
+ final override predicate entry(AstNode first) { first(this, first) }
+
+ final override predicate exit(AstNode last, Completion c) { last(this, last, c) }
+ }
+
+ private class EndBlockScope extends Range_, EndBlock {
+ final override predicate entry(AstNode first) {
+ first(this.(Trees::EndBlockTree).getBodyChild(0, _), first)
+ }
+
+ final override predicate exit(AstNode last, Completion c) {
+ last(this.(Trees::EndBlockTree).getLastBodyChild(), last, c)
+ }
+ }
+
+ private class BodyStmtCallableScope extends Range_, ASTInternal::TBodyStmt, Callable {
+ final override predicate entry(AstNode first) { this.(Trees::BodyStmtTree).firstInner(first) }
+
+ final override predicate exit(AstNode last, Completion c) {
+ this.(Trees::BodyStmtTree).lastInner(last, c)
+ }
+ }
+
+ private class BraceBlockScope extends Range_, BraceBlock {
+ final override predicate entry(AstNode first) {
+ first(this.(Trees::BraceBlockTree).getBodyChild(0, _), first)
+ }
+
+ final override predicate exit(AstNode last, Completion c) {
+ last(this.(Trees::BraceBlockTree).getLastBodyChild(), last, c)
+ }
+ }
+}
+
+/** Holds if `first` is first executed when entering `scope`. */
+pragma[nomagic]
+predicate succEntry(CfgScope::Range_ scope, AstNode first) { scope.entry(first) }
+
+/** Holds if `last` with completion `c` can exit `scope`. */
+pragma[nomagic]
+predicate succExit(CfgScope::Range_ scope, AstNode last, Completion c) { scope.exit(last, c) }
+
+// TODO: remove this class; it should be replaced with an implicit non AST node
+private class ForIn extends AstNode, ASTInternal::TForIn {
+ final override string toString() { result = "In" }
+}
+
+// TODO: remove this class; it should be replaced with an implicit non AST node
+private class ForRange extends ForExpr {
+ override AstNode getAChild(string pred) {
+ result = super.getAChild(pred)
+ or
+ pred = "" and
+ result = this.getIn()
+ }
+
+ ForIn getIn() {
+ result = ASTInternal::TForIn(ASTInternal::toGenerated(this).(Ruby::For).getValue())
+ }
+}
+
+/** Defines the CFG by dispatch on the various AST types. */
+module Trees {
+ private class AliasStmtTree extends StandardPreOrderTree, AliasStmt {
+ final override ControlFlowTree getChildElement(int i) {
+ result = this.getNewName() and i = 0
+ or
+ result = this.getOldName() and i = 1
+ }
+ }
+
+ private class ArgumentListTree extends StandardTree, ArgumentList {
+ final override ControlFlowTree getChildElement(int i) { result = this.getElement(i) }
+
+ final override predicate first(AstNode first) { first(this.getFirstChildElement(), first) }
+
+ final override predicate last(AstNode last, Completion c) {
+ last(this.getLastChildElement(), last, c)
+ }
+ }
+
+ private class AssignExprTree extends StandardPostOrderTree, AssignExpr {
+ AssignExprTree() {
+ exists(Expr left | left = this.getLeftOperand() |
+ left instanceof VariableAccess or
+ left instanceof ConstantAccess
+ )
+ }
+
+ final override ControlFlowTree getChildElement(int i) {
+ result = this.getLeftOperand() and i = 0
+ or
+ result = this.getRightOperand() and i = 1
+ }
+ }
+
+ private class BeginTree extends BodyStmtTree, BeginExpr {
+ final override predicate first(AstNode first) { this.firstInner(first) }
+
+ final override predicate last(AstNode last, Completion c) { this.lastInner(last, c) }
+
+ final override predicate propagatesAbnormal(AstNode child) { none() }
+ }
+
+ private class BlockArgumentTree extends StandardPostOrderTree, BlockArgument {
+ final override ControlFlowTree getChildElement(int i) { result = this.getValue() and i = 0 }
+ }
+
+ abstract private class NonDefaultValueParameterTree extends ControlFlowTree, NamedParameter {
+ final override predicate first(AstNode first) {
+ this.getDefiningAccess().(ControlFlowTree).first(first)
+ }
+
+ final override predicate last(AstNode last, Completion c) {
+ this.getDefiningAccess().(ControlFlowTree).last(last, c)
+ }
+
+ override predicate propagatesAbnormal(AstNode child) {
+ this.getDefiningAccess().(ControlFlowTree).propagatesAbnormal(child)
+ }
+
+ final override predicate succ(AstNode pred, AstNode succ, Completion c) { none() }
+ }
+
+ private class BlockParameterTree extends NonDefaultValueParameterTree, BlockParameter { }
+
+ abstract class BodyStmtTree extends StmtSequenceTree, BodyStmt {
+ override predicate first(AstNode first) { first = this }
+
+ predicate firstInner(AstNode first) {
+ first(this.getBodyChild(0, _), first)
+ or
+ not exists(this.getBodyChild(_, _)) and
+ (
+ first(this.getRescue(_), first)
+ or
+ not exists(this.getRescue(_)) and
+ first(this.getEnsure(), first)
+ )
+ }
+
+ predicate lastInner(AstNode last, Completion c) {
+ exists(boolean ensurable | last = this.getAnEnsurePredecessor(c, ensurable) |
+ not this.hasEnsure()
+ or
+ ensurable = false
+ )
+ or
+ // If the body completes normally, take the completion from the `ensure` block
+ this.lastEnsure(last, c, any(NormalCompletion nc), _)
+ or
+ // If the `ensure` block completes normally, it inherits any non-normal
+ // completion from the body
+ c =
+ any(NestedEnsureCompletion nec |
+ this.lastEnsure(last, nec.getAnInnerCompatibleCompletion(), nec.getOuterCompletion(),
+ nec.getNestLevel())
+ )
+ or
+ not exists(this.getBodyChild(_, _)) and
+ not exists(this.getRescue(_)) and
+ this.lastEnsure0(last, c)
+ or
+ last([this.getEnsure(), this.getBodyChild(_, false)], last, c) and
+ not c instanceof NormalCompletion
+ }
+
+ override predicate succ(AstNode pred, AstNode succ, Completion c) {
+ // Normal left-to-right evaluation in the body
+ exists(int i |
+ last(this.getBodyChild(i, _), pred, c) and
+ first(this.getBodyChild(i + 1, _), succ) and
+ c instanceof NormalCompletion
+ )
+ or
+ // Exceptional flow from body to first `rescue`
+ this.lastBody(pred, c, true) and
+ first(this.getRescue(0), succ) and
+ c instanceof RaiseCompletion
+ or
+ // Flow from one `rescue` clause to the next when there is no match
+ exists(RescueTree rescue, int i | rescue = this.getRescue(i) |
+ rescue.lastNoMatch(pred, c) and
+ first(this.getRescue(i + 1), succ)
+ )
+ or
+ // Flow from body to `else` block when no exception
+ this.lastBody(pred, c, _) and
+ first(this.getElse(), succ) and
+ c instanceof NormalCompletion
+ or
+ // Flow into `ensure` block
+ pred = this.getAnEnsurePredecessor(c, true) and
+ first(this.getEnsure(), succ)
+ }
+
+ /**
+ * Gets a last element from this block that may finish with completion `c`, such
+ * that control may be transferred to the `ensure` block (if it exists), but only
+ * if `ensurable = true`.
+ */
+ pragma[nomagic]
+ private AstNode getAnEnsurePredecessor(Completion c, boolean ensurable) {
+ this.lastBody(result, c, ensurable) and
+ (
+ // Any non-throw completion will always continue directly to the `ensure` block,
+ // unless there is an `else` block
+ not c instanceof RaiseCompletion and
+ not exists(this.getElse())
+ or
+ // Any completion will continue to the `ensure` block when there are no `rescue`
+ // blocks
+ not exists(this.getRescue(_))
+ )
+ or
+ // Last element from any matching `rescue` block continues to the `ensure` block
+ this.getRescue(_).(RescueTree).lastMatch(result, c) and
+ ensurable = true
+ or
+ // If the last `rescue` block does not match, continue to the `ensure` block
+ exists(int lst, MatchingCompletion mc |
+ this.getRescue(lst).(RescueTree).lastNoMatch(result, mc) and
+ mc.getValue() = false and
+ not exists(this.getRescue(lst + 1)) and
+ c =
+ any(NestedEnsureCompletion nec |
+ nec.getOuterCompletion() instanceof RaiseCompletion and
+ nec.getInnerCompletion() = mc and
+ nec.getNestLevel() = 0
+ ) and
+ ensurable = true
+ )
+ or
+ // Last element of `else` block continues to the `ensure` block
+ last(this.getElse(), result, c) and
+ ensurable = true
+ }
+
+ pragma[nomagic]
+ private predicate lastEnsure0(AstNode last, Completion c) { last(this.getEnsure(), last, c) }
+
+ /**
+ * Gets a descendant that belongs to the `ensure` block of this block, if any.
+ * Nested `ensure` blocks are not included.
+ */
+ pragma[nomagic]
+ AstNode getAnEnsureDescendant() {
+ result = this.getEnsure()
+ or
+ exists(AstNode mid |
+ mid = this.getAnEnsureDescendant() and
+ result = mid.getAChild() and
+ getCfgScope(result) = getCfgScope(mid) and
+ not exists(BodyStmt nestedBlock |
+ result = nestedBlock.getEnsure() and
+ nestedBlock != this
+ )
+ )
+ }
+
+ /**
+ * Holds if `innerBlock` has an `ensure` block and is immediately nested inside the
+ * `ensure` block of this block.
+ */
+ private predicate nestedEnsure(BodyStmtTree innerBlock) {
+ exists(StmtSequence innerEnsure |
+ innerEnsure = this.getAnEnsureDescendant().getAChild() and
+ getCfgScope(innerEnsure) = getCfgScope(this) and
+ innerEnsure = innerBlock.(BodyStmt).getEnsure()
+ )
+ }
+
+ /**
+ * Gets the `ensure`-nesting level of this block. That is, the number of `ensure`
+ * blocks that this block is nested under.
+ */
+ int getNestLevel() { result = count(BodyStmtTree outer | outer.nestedEnsure+(this)) }
+
+ pragma[nomagic]
+ private predicate lastEnsure(
+ AstNode last, NormalCompletion ensure, Completion outer, int nestLevel
+ ) {
+ this.lastEnsure0(last, ensure) and
+ exists(
+ this.getAnEnsurePredecessor(any(Completion c0 | outer = c0.getOuterCompletion()), true)
+ ) and
+ nestLevel = this.getNestLevel()
+ }
+
+ /**
+ * Holds if `last` is a last element in the body of this block. `ensurable`
+ * indicates whether `last` may be a predecessor of an `ensure` block.
+ */
+ pragma[nomagic]
+ private predicate lastBody(AstNode last, Completion c, boolean ensurable) {
+ exists(boolean rescuable |
+ if c instanceof RaiseCompletion then ensurable = rescuable else ensurable = true
+ |
+ last(this.getBodyChild(_, rescuable), last, c) and
+ not c instanceof NormalCompletion
+ or
+ exists(int lst |
+ last(this.getBodyChild(lst, rescuable), last, c) and
+ not exists(this.getBodyChild(lst + 1, _))
+ )
+ )
+ }
+ }
+
+ private class BooleanLiteralTree extends LeafTree, BooleanLiteral { }
+
+ class BraceBlockTree extends StmtSequenceTree, BraceBlock {
+ final override predicate propagatesAbnormal(AstNode child) { none() }
+
+ final override AstNode getBodyChild(int i, boolean rescuable) {
+ result = this.getParameter(i) and rescuable = false
+ or
+ result = StmtSequenceTree.super.getBodyChild(i - this.getNumberOfParameters(), rescuable)
+ }
+
+ override predicate first(AstNode first) { first = this }
+
+ override predicate succ(AstNode pred, AstNode succ, Completion c) {
+ // Normal left-to-right evaluation in the body
+ exists(int i |
+ last(this.getBodyChild(i, _), pred, c) and
+ first(this.getBodyChild(i + 1, _), succ) and
+ c instanceof NormalCompletion
+ )
+ }
+ }
+
+ private class CallTree extends StandardPostOrderTree, Call {
+ CallTree() {
+ // Logical operations are handled separately
+ not this instanceof UnaryLogicalOperation and
+ not this instanceof BinaryLogicalOperation
+ }
+
+ override ControlFlowTree getChildElement(int i) { result = this.getArgument(i) }
+ }
+
+ private class CaseTree extends PreOrderTree, CaseExpr {
+ final override predicate propagatesAbnormal(AstNode child) {
+ child = this.getValue() or child = this.getABranch()
+ }
+
+ final override predicate last(AstNode last, Completion c) {
+ last(this.getValue(), last, c) and not exists(this.getABranch())
+ or
+ last(this.getAWhenBranch().getBody(), last, c)
+ or
+ exists(int i, ControlFlowTree lastBranch |
+ lastBranch = this.getBranch(i) and
+ not exists(this.getBranch(i + 1)) and
+ last(lastBranch, last, c)
+ )
+ }
+
+ final override predicate succ(AstNode pred, AstNode succ, Completion c) {
+ exists(AstNode next |
+ pred = this and
+ first(next, succ) and
+ c instanceof SimpleCompletion
+ |
+ next = this.getValue()
+ or
+ not exists(this.getValue()) and
+ next = this.getBranch(0)
+ )
+ or
+ last(this.getValue(), pred, c) and
+ first(this.getBranch(0), succ) and
+ c instanceof SimpleCompletion
+ or
+ exists(int i, WhenTree branch | branch = this.getBranch(i) |
+ last(branch.getLastPattern(), pred, c) and
+ first(this.getBranch(i + 1), succ) and
+ c.(ConditionalCompletion).getValue() = false
+ )
+ }
+ }
+
+ private class CharacterTree extends LeafTree, CharacterLiteral { }
+
+ private class ClassDeclarationTree extends NamespaceTree, ClassDeclaration {
+ /** Gets the `i`th child in the body of this block. */
+ final override AstNode getBodyChild(int i, boolean rescuable) {
+ result = this.getScopeExpr() and i = 0 and rescuable = false
+ or
+ result = this.getSuperclassExpr() and
+ i = count(this.getScopeExpr()) and
+ rescuable = true
+ or
+ result =
+ super
+ .getBodyChild(i - count(this.getScopeExpr()) - count(this.getSuperclassExpr()),
+ rescuable)
+ }
+ }
+
+ private class ClassVariableTree extends LeafTree, ClassVariableAccess { }
+
+ private class ConditionalExprTree extends PostOrderTree, ConditionalExpr {
+ final override predicate propagatesAbnormal(AstNode child) {
+ child = this.getCondition() or child = this.getBranch(_)
+ }
+
+ final override predicate first(AstNode first) { first(this.getCondition(), first) }
+
+ final override predicate succ(AstNode pred, AstNode succ, Completion c) {
+ exists(boolean b |
+ last(this.getCondition(), pred, c) and
+ b = c.(BooleanCompletion).getValue()
+ |
+ first(this.getBranch(b), succ)
+ or
+ not exists(this.getBranch(b)) and
+ succ = this
+ )
+ or
+ last(this.getBranch(_), pred, c) and
+ succ = this and
+ c instanceof NormalCompletion
+ }
+ }
+
+ private class ConditionalLoopTree extends PostOrderTree, ConditionalLoop {
+ final override predicate propagatesAbnormal(AstNode child) { child = this.getCondition() }
+
+ final override predicate first(AstNode first) { first(this.getCondition(), first) }
+
+ final override predicate succ(AstNode pred, AstNode succ, Completion c) {
+ last(this.getCondition(), pred, c) and
+ this.entersLoopWhenConditionIs(c.(BooleanCompletion).getValue()) and
+ first(this.getBody(), succ)
+ or
+ last(this.getBody(), pred, c) and
+ first(this.getCondition(), succ) and
+ c.continuesLoop()
+ or
+ last(this.getBody(), pred, c) and
+ first(this.getBody(), succ) and
+ c instanceof RedoCompletion
+ or
+ succ = this and
+ (
+ last(this.getCondition(), pred, c) and
+ this.entersLoopWhenConditionIs(c.(BooleanCompletion).getValue().booleanNot())
+ or
+ last(this.getBody(), pred, c) and
+ not c.continuesLoop() and
+ not c instanceof BreakCompletion and
+ not c instanceof RedoCompletion
+ or
+ last(this.getBody(), pred, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion())
+ )
+ }
+ }
+
+ private class ConstantAccessTree extends PostOrderTree, ConstantAccess {
+ ConstantAccessTree() {
+ not this instanceof ClassDeclaration and
+ not this instanceof ModuleDeclaration
+ }
+
+ final override predicate propagatesAbnormal(AstNode child) { child = this.getScopeExpr() }
+
+ final override predicate first(AstNode first) {
+ first(this.getScopeExpr(), first)
+ or
+ not exists(this.getScopeExpr()) and
+ first = this
+ }
+
+ final override predicate succ(AstNode pred, AstNode succ, Completion c) {
+ last(this.getScopeExpr(), pred, c) and
+ succ = this and
+ c instanceof NormalCompletion
+ }
+ }
+
+ /** A parameter that may have a default value. */
+ abstract class DefaultValueParameterTree extends ControlFlowTree {
+ abstract Expr getDefaultValueExpr();
+
+ abstract AstNode getAccessNode();
+
+ predicate hasDefaultValue() { exists(this.getDefaultValueExpr()) }
+
+ final override predicate propagatesAbnormal(AstNode child) {
+ child = this.getDefaultValueExpr() or child = this.getAccessNode()
+ }
+
+ final override predicate first(AstNode first) { first = this.getAccessNode() }
+
+ final override predicate last(AstNode last, Completion c) {
+ last(this.getDefaultValueExpr(), last, c) and
+ c instanceof NormalCompletion
+ or
+ last = this.getAccessNode() and
+ (
+ not this.hasDefaultValue() and
+ c instanceof SimpleCompletion
+ or
+ this.hasDefaultValue() and
+ c.(MatchingCompletion).getValue() = true
+ )
+ }
+
+ final override predicate succ(AstNode pred, AstNode succ, Completion c) {
+ pred = this.getAccessNode() and
+ first(this.getDefaultValueExpr(), succ) and
+ c.(MatchingCompletion).getValue() = false
+ }
+ }
+
+ private class DesugaredTree extends ControlFlowTree {
+ ControlFlowTree desugared;
+
+ DesugaredTree() { desugared = this.getDesugared() }
+
+ final override predicate propagatesAbnormal(AstNode child) {
+ desugared.propagatesAbnormal(child)
+ }
+
+ final override predicate first(AstNode first) { desugared.first(first) }
+
+ final override predicate last(AstNode last, Completion c) { desugared.last(last, c) }
+
+ final override predicate succ(AstNode pred, AstNode succ, Completion c) { none() }
+ }
+
+ private class DoBlockTree extends BodyStmtTree, DoBlock {
+ /** Gets the `i`th child in the body of this block. */
+ final override AstNode getBodyChild(int i, boolean rescuable) {
+ result = this.getParameter(i) and rescuable = false
+ or
+ result = BodyStmtTree.super.getBodyChild(i - this.getNumberOfParameters(), rescuable)
+ }
+
+ override predicate propagatesAbnormal(AstNode child) { none() }
+ }
+
+ private class EmptyStatementTree extends LeafTree, EmptyStmt { }
+
+ class EndBlockTree extends StmtSequenceTree, EndBlock {
+ override predicate first(AstNode first) { first = this }
+
+ override predicate succ(AstNode pred, AstNode succ, Completion c) {
+ // Normal left-to-right evaluation in the body
+ exists(int i |
+ last(this.getBodyChild(i, _), pred, c) and
+ first(this.getBodyChild(i + 1, _), succ) and
+ c instanceof NormalCompletion
+ )
+ }
+ }
+
+ private class ForwardParameterTree extends LeafTree, ForwardParameter { }
+
+ private class ForInTree extends LeafTree, ForIn { }
+
+ /**
+ * Control flow of a for-in loop
+ *
+ * For example, this program fragment:
+ *
+ * ```rb
+ * for arg in args do
+ * puts arg
+ * end
+ * puts "done";
+ * ```
+ *
+ * has the following control flow graph:
+ *
+ * ```
+ * args
+ * |
+ * in------<-----
+ * / \ \
+ * / \ |
+ * / \ |
+ * / \ |
+ * empty non-empty |
+ * | \ |
+ * for \ |
+ * | arg |
+ * | | |
+ * puts "done" puts arg |
+ * \___/
+ * ```
+ */
+ private class ForTree extends PostOrderTree, ForRange {
+ final override predicate propagatesAbnormal(AstNode child) {
+ child = this.getPattern() or child = this.getValue()
+ }
+
+ final override predicate first(AstNode first) { first(this.getValue(), first) }
+
+ /**
+ * for pattern in array do body end
+ * ```
+ * array +-> in +--[non empty]--> pattern -> body -> in
+ * |--[empty]--> for
+ * ```
+ */
+ final override predicate succ(AstNode pred, AstNode succ, Completion c) {
+ last(this.getValue(), pred, c) and
+ first(this.getIn(), succ) and
+ c instanceof SimpleCompletion
+ or
+ last(this.getIn(), pred, c) and
+ first(this.getPattern(), succ) and
+ c.(EmptinessCompletion).getValue() = false
+ or
+ last(this.getPattern(), pred, c) and
+ first(this.getBody(), succ) and
+ c instanceof NormalCompletion
+ or
+ last(this.getBody(), pred, c) and
+ first(this.getIn(), succ) and
+ c.continuesLoop()
+ or
+ last(this.getBody(), pred, c) and
+ first(this.getBody(), succ) and
+ c instanceof RedoCompletion
+ or
+ succ = this and
+ (
+ last(this.getIn(), pred, c) and
+ c.(EmptinessCompletion).getValue() = true
+ or
+ last(this.getBody(), pred, c) and
+ not c.continuesLoop() and
+ not c instanceof BreakCompletion and
+ not c instanceof RedoCompletion
+ or
+ last(this.getBody(), pred, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion())
+ )
+ }
+ }
+
+ private class GlobalVariableTree extends LeafTree, GlobalVariableAccess { }
+
+ private class HashLiteralTree extends StandardPostOrderTree, HashLiteral {
+ final override ControlFlowTree getChildElement(int i) { result = this.getElement(i) }
+ }
+
+ private class HashSplatParameterTree extends NonDefaultValueParameterTree, HashSplatParameter { }
+
+ private class HereDocTree extends StandardPreOrderTree, HereDoc {
+ final override ControlFlowTree getChildElement(int i) { result = this.getComponent(i) }
+ }
+
+ private class InstanceVariableTree extends LeafTree, InstanceVariableAccess { }
+
+ private class KeywordParameterTree extends DefaultValueParameterTree, KeywordParameter {
+ final override Expr getDefaultValueExpr() { result = this.getDefaultValue() }
+
+ final override AstNode getAccessNode() { result = this.getDefiningAccess() }
+ }
+
+ private class LambdaTree extends BodyStmtTree, Lambda {
+ final override predicate propagatesAbnormal(AstNode child) { none() }
+
+ /** Gets the `i`th child in the body of this block. */
+ final override AstNode getBodyChild(int i, boolean rescuable) {
+ result = this.getParameter(i) and rescuable = false
+ or
+ result = BodyStmtTree.super.getBodyChild(i - this.getNumberOfParameters(), rescuable)
+ }
+ }
+
+ private class LocalVariableAccessTree extends LeafTree, LocalVariableAccess { }
+
+ private class LogicalAndTree extends PostOrderTree, LogicalAndExpr {
+ final override predicate propagatesAbnormal(AstNode child) { child = this.getAnOperand() }
+
+ final override predicate first(AstNode first) { first(this.getLeftOperand(), first) }
+
+ final override predicate succ(AstNode pred, AstNode succ, Completion c) {
+ last(this.getLeftOperand(), pred, c) and
+ c instanceof TrueCompletion and
+ first(this.getRightOperand(), succ)
+ or
+ last(this.getLeftOperand(), pred, c) and
+ c instanceof FalseCompletion and
+ succ = this
+ or
+ last(this.getRightOperand(), pred, c) and
+ c instanceof NormalCompletion and
+ succ = this
+ }
+ }
+
+ private class LogicalNotTree extends PostOrderTree, NotExpr {
+ final override predicate propagatesAbnormal(AstNode child) { child = this.getOperand() }
+
+ final override predicate first(AstNode first) { first(this.getOperand(), first) }
+
+ final override predicate succ(AstNode pred, AstNode succ, Completion c) {
+ succ = this and
+ last(this.getOperand(), pred, c) and
+ c instanceof NormalCompletion
+ }
+ }
+
+ private class LogicalOrTree extends PostOrderTree, LogicalOrExpr {
+ final override predicate propagatesAbnormal(AstNode child) { child = this.getAnOperand() }
+
+ final override predicate first(AstNode first) { first(this.getLeftOperand(), first) }
+
+ final override predicate succ(AstNode pred, AstNode succ, Completion c) {
+ last(this.getLeftOperand(), pred, c) and
+ c instanceof FalseCompletion and
+ first(this.getRightOperand(), succ)
+ or
+ last(this.getLeftOperand(), pred, c) and
+ c instanceof TrueCompletion and
+ succ = this
+ or
+ last(this.getRightOperand(), pred, c) and
+ c instanceof NormalCompletion and
+ succ = this
+ }
+ }
+
+ private class MethodCallTree extends CallTree, MethodCall {
+ final override ControlFlowTree getChildElement(int i) {
+ result = this.getReceiver() and i = 0
+ or
+ result = this.getArgument(i - 1)
+ or
+ result = this.getBlock() and i = 1 + this.getNumberOfArguments()
+ }
+ }
+
+ private class MethodNameTree extends LeafTree, MethodName, ASTInternal::TTokenMethodName { }
+
+ private class MethodTree extends BodyStmtTree, Method {
+ final override predicate propagatesAbnormal(AstNode child) { none() }
+
+ /** Gets the `i`th child in the body of this block. */
+ final override AstNode getBodyChild(int i, boolean rescuable) {
+ result = this.getParameter(i) and rescuable = false
+ or
+ result = BodyStmtTree.super.getBodyChild(i - this.getNumberOfParameters(), rescuable)
+ }
+ }
+
+ private class ModuleDeclarationTree extends NamespaceTree, ModuleDeclaration {
+ /** Gets the `i`th child in the body of this block. */
+ final override AstNode getBodyChild(int i, boolean rescuable) {
+ result = this.getScopeExpr() and i = 0 and rescuable = false
+ or
+ result = NamespaceTree.super.getBodyChild(i - count(this.getScopeExpr()), rescuable)
+ }
+ }
+
+ /**
+ * Namespaces (i.e. modules or classes) behave like other `BodyStmt`s except they are
+ * executed in pre-order rather than post-order. We do this in order to insert a write for
+ * `self` before any subsequent reads in the namespace body.
+ */
+ private class NamespaceTree extends BodyStmtTree, Namespace {
+ final override predicate first(AstNode first) { first = this }
+
+ final override predicate succ(AstNode pred, AstNode succ, Completion c) {
+ BodyStmtTree.super.succ(pred, succ, c)
+ or
+ pred = this and
+ this.firstInner(succ) and
+ c instanceof SimpleCompletion
+ }
+
+ final override predicate last(AstNode last, Completion c) {
+ this.lastInner(last, c)
+ or
+ not exists(this.getAChild(_)) and
+ last = this and
+ c.isValidFor(this)
+ }
+ }
+
+ private class NilTree extends LeafTree, NilLiteral { }
+
+ private class NumericLiteralTree extends LeafTree, NumericLiteral { }
+
+ private class OptionalParameterTree extends DefaultValueParameterTree, OptionalParameter {
+ final override Expr getDefaultValueExpr() { result = this.getDefaultValue() }
+
+ final override AstNode getAccessNode() { result = this.getDefiningAccess() }
+ }
+
+ private class PairTree extends StandardPostOrderTree, Pair {
+ final override ControlFlowTree getChildElement(int i) {
+ result = this.getKey() and i = 0
+ or
+ result = this.getValue() and i = 1
+ }
+ }
+
+ private class RangeLiteralTree extends StandardPostOrderTree, RangeLiteral {
+ final override ControlFlowTree getChildElement(int i) {
+ result = this.getBegin() and i = 0
+ or
+ result = this.getEnd() and i = 1
+ }
+ }
+
+ private class RedoStmtTree extends LeafTree, RedoStmt { }
+
+ private class RescueModifierTree extends PreOrderTree, RescueModifierExpr {
+ final override predicate propagatesAbnormal(AstNode child) { child = this.getHandler() }
+
+ final override predicate last(AstNode last, Completion c) {
+ last(this.getBody(), last, c) and
+ not c instanceof RaiseCompletion
+ or
+ last(this.getHandler(), last, c)
+ }
+
+ final override predicate succ(AstNode pred, AstNode succ, Completion c) {
+ pred = this and
+ first(this.getBody(), succ) and
+ c instanceof SimpleCompletion
+ or
+ last(this.getBody(), pred, c) and
+ c instanceof RaiseCompletion and
+ first(this.getHandler(), succ)
+ }
+ }
+
+ private class RescueTree extends PreOrderTree, RescueClause {
+ final override predicate propagatesAbnormal(AstNode child) { child = this.getAnException() }
+
+ private Expr getLastException() {
+ exists(int i | result = this.getException(i) and not exists(this.getException(i + 1)))
+ }
+
+ predicate lastMatch(AstNode last, Completion c) {
+ last(this.getBody(), last, c)
+ or
+ not exists(this.getBody()) and
+ (
+ last(this.getVariableExpr(), last, c)
+ or
+ not exists(this.getVariableExpr()) and
+ (
+ last(this.getAnException(), last, c) and
+ c.(MatchingCompletion).getValue() = true
+ or
+ not exists(this.getAnException()) and
+ last = this and
+ c.isValidFor(this)
+ )
+ )
+ }
+
+ predicate lastNoMatch(AstNode last, Completion c) {
+ last(this.getLastException(), last, c) and
+ c.(MatchingCompletion).getValue() = false
+ }
+
+ final override predicate last(AstNode last, Completion c) {
+ this.lastNoMatch(last, c)
+ or
+ this.lastMatch(last, c)
+ }
+
+ final override predicate succ(AstNode pred, AstNode succ, Completion c) {
+ exists(AstNode next |
+ pred = this and
+ first(next, succ) and
+ c instanceof SimpleCompletion
+ |
+ next = this.getException(0)
+ or
+ not exists(this.getException(0)) and
+ (
+ next = this.getVariableExpr()
+ or
+ not exists(this.getVariableExpr()) and
+ next = this.getBody()
+ )
+ )
+ or
+ exists(AstNode next |
+ last(this.getAnException(), pred, c) and
+ first(next, succ) and
+ c.(MatchingCompletion).getValue() = true
+ |
+ next = this.getVariableExpr()
+ or
+ not exists(this.getVariableExpr()) and
+ next = this.getBody()
+ )
+ or
+ exists(int i |
+ last(this.getException(i), pred, c) and
+ c.(MatchingCompletion).getValue() = false and
+ first(this.getException(i + 1), succ)
+ )
+ or
+ last(this.getVariableExpr(), pred, c) and
+ first(this.getBody(), succ) and
+ c instanceof NormalCompletion
+ }
+ }
+
+ private class RetryStmtTree extends LeafTree, RetryStmt { }
+
+ private class ReturningStmtTree extends StandardPostOrderTree, ReturningStmt {
+ final override ControlFlowTree getChildElement(int i) { result = this.getValue() and i = 0 }
+ }
+
+ private class SimpleParameterTree extends NonDefaultValueParameterTree, SimpleParameter { }
+
+ // Corner case: For duplicated '_' parameters, only the first occurence has a defining
+ // access. For subsequent parameters we simply include the parameter itself in the CFG
+ private class SimpleParameterTreeDupUnderscore extends LeafTree, SimpleParameter {
+ SimpleParameterTreeDupUnderscore() { not exists(this.getDefiningAccess()) }
+ }
+
+ private class SingletonClassTree extends BodyStmtTree, SingletonClass {
+ final override predicate first(AstNode first) {
+ this.firstInner(first)
+ or
+ not exists(this.getAChild(_)) and
+ first = this
+ }
+
+ final override predicate succ(AstNode pred, AstNode succ, Completion c) {
+ BodyStmtTree.super.succ(pred, succ, c)
+ or
+ succ = this and
+ this.lastInner(pred, c)
+ }
+
+ /** Gets the `i`th child in the body of this block. */
+ final override AstNode getBodyChild(int i, boolean rescuable) {
+ (
+ result = this.getValue() and i = 0 and rescuable = false
+ or
+ result = BodyStmtTree.super.getBodyChild(i - 1, rescuable)
+ )
+ }
+ }
+
+ private class SingletonMethodTree extends BodyStmtTree, SingletonMethod {
+ final override predicate propagatesAbnormal(AstNode child) { none() }
+
+ /** Gets the `i`th child in the body of this block. */
+ final override AstNode getBodyChild(int i, boolean rescuable) {
+ result = this.getParameter(i) and rescuable = false
+ or
+ result = BodyStmtTree.super.getBodyChild(i - this.getNumberOfParameters(), rescuable)
+ }
+
+ override predicate first(AstNode first) { first(this.getObject(), first) }
+
+ override predicate succ(AstNode pred, AstNode succ, Completion c) {
+ BodyStmtTree.super.succ(pred, succ, c)
+ or
+ last(this.getObject(), pred, c) and
+ succ = this and
+ c instanceof NormalCompletion
+ }
+ }
+
+ private class SplatParameterTree extends NonDefaultValueParameterTree, SplatParameter { }
+
+ class StmtSequenceTree extends PostOrderTree, StmtSequence {
+ override predicate propagatesAbnormal(AstNode child) { child = this.getAStmt() }
+
+ override predicate first(AstNode first) { first(this.getStmt(0), first) }
+
+ /** Gets the `i`th child in the body of this body statement. */
+ AstNode getBodyChild(int i, boolean rescuable) {
+ result = this.getStmt(i) and
+ rescuable = true
+ }
+
+ final AstNode getLastBodyChild() {
+ exists(int i |
+ result = this.getBodyChild(i, _) and
+ not exists(this.getBodyChild(i + 1, _))
+ )
+ }
+
+ override predicate succ(AstNode pred, AstNode succ, Completion c) {
+ // Normal left-to-right evaluation in the body
+ exists(int i |
+ last(this.getBodyChild(i, _), pred, c) and
+ first(this.getBodyChild(i + 1, _), succ) and
+ c instanceof NormalCompletion
+ )
+ or
+ succ = this and
+ last(this.getLastBodyChild(), pred, c) and
+ c instanceof NormalCompletion
+ }
+ }
+
+ private class StringConcatenationTree extends StandardTree, StringConcatenation {
+ final override ControlFlowTree getChildElement(int i) { result = this.getString(i) }
+
+ final override predicate first(AstNode first) { first(this.getFirstChildElement(), first) }
+
+ final override predicate last(AstNode last, Completion c) {
+ last(this.getLastChildElement(), last, c)
+ }
+ }
+
+ private class StringlikeLiteralTree extends StandardPostOrderTree, StringlikeLiteral {
+ StringlikeLiteralTree() { not this instanceof HereDoc }
+
+ final override ControlFlowTree getChildElement(int i) { result = this.getComponent(i) }
+ }
+
+ private class ToplevelTree extends BodyStmtTree, Toplevel {
+ final override AstNode getBodyChild(int i, boolean rescuable) {
+ result = this.getBeginBlock(i) and rescuable = true
+ or
+ result = BodyStmtTree.super.getBodyChild(i - count(this.getABeginBlock()), rescuable)
+ }
+
+ final override predicate first(AstNode first) { this.firstInner(first) }
+
+ final override predicate last(AstNode last, Completion c) { this.lastInner(last, c) }
+
+ final override predicate succ(AstNode pred, AstNode succ, Completion c) {
+ BodyStmtTree.super.succ(pred, succ, c)
+ }
+ }
+
+ private class TuplePatternTree extends StandardPostOrderTree, TuplePattern {
+ final override ControlFlowTree getChildElement(int i) { result = this.getElement(i) }
+ }
+
+ private class UndefStmtTree extends StandardPreOrderTree, UndefStmt {
+ final override ControlFlowTree getChildElement(int i) { result = this.getMethodName(i) }
+ }
+
+ private class WhenTree extends PreOrderTree, WhenExpr {
+ final override predicate propagatesAbnormal(AstNode child) { child = this.getAPattern() }
+
+ final Expr getLastPattern() {
+ exists(int i |
+ result = this.getPattern(i) and
+ not exists(this.getPattern(i + 1))
+ )
+ }
+
+ final override predicate last(AstNode last, Completion c) {
+ last(this.getLastPattern(), last, c) and
+ c.(ConditionalCompletion).getValue() = false
+ or
+ last(this.getBody(), last, c)
+ }
+
+ final override predicate succ(AstNode pred, AstNode succ, Completion c) {
+ pred = this and
+ first(this.getPattern(0), succ) and
+ c instanceof SimpleCompletion
+ or
+ exists(int i, Expr p, boolean b |
+ p = this.getPattern(i) and
+ last(p, pred, c) and
+ b = c.(ConditionalCompletion).getValue()
+ |
+ b = true and
+ first(this.getBody(), succ)
+ or
+ b = false and
+ first(this.getPattern(i + 1), succ)
+ )
+ }
+ }
+}
+
+private Scope parent(Scope n) {
+ result = n.getOuterScope() and
+ not n instanceof CfgScope::Range_
+}
+
+/** Gets the CFG scope of node `n`. */
+pragma[inline]
+CfgScope getCfgScope(AstNode n) {
+ exists(AstNode n0 |
+ pragma[only_bind_into](n0) = n and
+ pragma[only_bind_into](result) = getCfgScopeImpl(n0)
+ )
+}
+
+cached
+private module Cached {
+ /** Gets the CFG scope of node `n`. */
+ cached
+ CfgScope getCfgScopeImpl(AstNode n) {
+ forceCachingInSameStage() and
+ result = parent*(ASTInternal::fromGenerated(scopeOf(ASTInternal::toGeneratedInclSynth(n))))
+ }
+
+ cached
+ newtype TSuccessorType =
+ TSuccessorSuccessor() or
+ TBooleanSuccessor(boolean b) { b in [false, true] } or
+ TEmptinessSuccessor(boolean isEmpty) { isEmpty in [false, true] } or
+ TMatchingSuccessor(boolean isMatch) { isMatch in [false, true] } or
+ TReturnSuccessor() or
+ TBreakSuccessor() or
+ TNextSuccessor() or
+ TRedoSuccessor() or
+ TRetrySuccessor() or
+ TRaiseSuccessor() or // TODO: Add exception type?
+ TExitSuccessor()
+}
+
+import Cached
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImplShared.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImplShared.qll
new file mode 100644
index 00000000000..ee901b9192e
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImplShared.qll
@@ -0,0 +1,946 @@
+/** Provides language-independent definitions for AST-to-CFG construction. */
+
+private import ControlFlowGraphImplSpecific
+
+/** An element with associated control flow. */
+abstract class ControlFlowTree extends ControlFlowTreeBase {
+ /** Holds if `first` is the first element executed within this element. */
+ pragma[nomagic]
+ abstract predicate first(ControlFlowElement first);
+
+ /**
+ * Holds if `last` with completion `c` is a potential last element executed
+ * within this element.
+ */
+ pragma[nomagic]
+ abstract predicate last(ControlFlowElement last, Completion c);
+
+ /** Holds if abnormal execution of `child` should propagate upwards. */
+ abstract predicate propagatesAbnormal(ControlFlowElement child);
+
+ /**
+ * Holds if `succ` is a control flow successor for `pred`, given that `pred`
+ * finishes with completion `c`.
+ */
+ pragma[nomagic]
+ abstract predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c);
+}
+
+/**
+ * Holds if `first` is the first element executed within control flow
+ * element `cft`.
+ */
+predicate first(ControlFlowTree cft, ControlFlowElement first) { cft.first(first) }
+
+/**
+ * Holds if `last` with completion `c` is a potential last element executed
+ * within control flow element `cft`.
+ */
+predicate last(ControlFlowTree cft, ControlFlowElement last, Completion c) {
+ cft.last(last, c)
+ or
+ exists(ControlFlowElement cfe |
+ cft.propagatesAbnormal(cfe) and
+ last(cfe, last, c) and
+ not completionIsNormal(c)
+ )
+}
+
+/**
+ * Holds if `succ` is a control flow successor for `pred`, given that `pred`
+ * finishes with completion `c`.
+ */
+pragma[nomagic]
+predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
+ any(ControlFlowTree cft).succ(pred, succ, c)
+}
+
+/** An element that is executed in pre-order. */
+abstract class PreOrderTree extends ControlFlowTree {
+ final override predicate first(ControlFlowElement first) { first = this }
+}
+
+/** An element that is executed in post-order. */
+abstract class PostOrderTree extends ControlFlowTree {
+ override predicate last(ControlFlowElement last, Completion c) {
+ last = this and
+ completionIsValidFor(c, last)
+ }
+}
+
+/**
+ * An element where the children are evaluated following a standard left-to-right
+ * evaluation. The actual evaluation order is determined by the predicate
+ * `getChildElement()`.
+ */
+abstract class StandardTree extends ControlFlowTree {
+ /** Gets the `i`th child element, in order of evaluation. */
+ abstract ControlFlowElement getChildElement(int i);
+
+ private ControlFlowElement getChildElementRanked(int i) {
+ result =
+ rank[i + 1](ControlFlowElement child, int j |
+ child = this.getChildElement(j)
+ |
+ child order by j
+ )
+ }
+
+ /** Gets the first child node of this element. */
+ final ControlFlowElement getFirstChildElement() { result = this.getChildElementRanked(0) }
+
+ /** Gets the last child node of this node. */
+ final ControlFlowElement getLastChildElement() {
+ exists(int last |
+ result = this.getChildElementRanked(last) and
+ not exists(this.getChildElementRanked(last + 1))
+ )
+ }
+
+ /** Holds if this element has no children. */
+ predicate isLeafElement() { not exists(this.getFirstChildElement()) }
+
+ override predicate propagatesAbnormal(ControlFlowElement child) {
+ child = this.getChildElement(_)
+ }
+
+ pragma[nomagic]
+ override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
+ exists(int i |
+ last(this.getChildElementRanked(i), pred, c) and
+ completionIsNormal(c) and
+ first(this.getChildElementRanked(i + 1), succ)
+ )
+ }
+}
+
+/** A standard element that is executed in pre-order. */
+abstract class StandardPreOrderTree extends StandardTree, PreOrderTree {
+ override predicate last(ControlFlowElement last, Completion c) {
+ last(this.getLastChildElement(), last, c)
+ or
+ this.isLeafElement() and
+ completionIsValidFor(c, this) and
+ last = this
+ }
+
+ override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
+ StandardTree.super.succ(pred, succ, c)
+ or
+ pred = this and
+ first(this.getFirstChildElement(), succ) and
+ completionIsSimple(c)
+ }
+}
+
+/** A standard element that is executed in post-order. */
+abstract class StandardPostOrderTree extends StandardTree, PostOrderTree {
+ override predicate first(ControlFlowElement first) {
+ first(this.getFirstChildElement(), first)
+ or
+ not exists(this.getFirstChildElement()) and
+ first = this
+ }
+
+ override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
+ StandardTree.super.succ(pred, succ, c)
+ or
+ last(this.getLastChildElement(), pred, c) and
+ succ = this and
+ completionIsNormal(c)
+ }
+}
+
+/** An element that is a leaf in the control flow graph. */
+abstract class LeafTree extends PreOrderTree, PostOrderTree {
+ override predicate propagatesAbnormal(ControlFlowElement child) { none() }
+
+ override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { none() }
+}
+
+/**
+ * Holds if split kinds `sk1` and `sk2` may overlap. That is, they may apply
+ * to at least one common AST node inside `scope`.
+ */
+private predicate overlapping(CfgScope scope, SplitKind sk1, SplitKind sk2) {
+ exists(ControlFlowElement e |
+ sk1.appliesTo(e) and
+ sk2.appliesTo(e) and
+ scope = getCfgScope(e)
+ )
+}
+
+/**
+ * A split kind. Each control flow node can have at most one split of a
+ * given kind.
+ */
+abstract class SplitKind extends SplitKindBase {
+ /** Gets a split of this kind. */
+ SplitImpl getASplit() { result.getKind() = this }
+
+ /** Holds if some split of this kind applies to AST node `n`. */
+ predicate appliesTo(ControlFlowElement n) { this.getASplit().appliesTo(n) }
+
+ /**
+ * Gets a unique integer representing this split kind. The integer is used
+ * to represent sets of splits as ordered lists.
+ */
+ abstract int getListOrder();
+
+ /** Gets the rank of this split kind among all overlapping kinds for `c`. */
+ private int getRank(CfgScope scope) {
+ this = rank[result](SplitKind sk | overlapping(scope, this, sk) | sk order by sk.getListOrder())
+ }
+
+ /**
+ * Holds if this split kind is enabled for AST node `n`. For performance reasons,
+ * the number of splits is restricted by the `maxSplits()` predicate.
+ */
+ predicate isEnabled(ControlFlowElement n) {
+ this.appliesTo(n) and
+ this.getRank(getCfgScope(n)) <= maxSplits()
+ }
+
+ /**
+ * Gets the rank of this split kind among all the split kinds that apply to
+ * AST node `n`. The rank is based on the order defined by `getListOrder()`.
+ */
+ int getListRank(ControlFlowElement n) {
+ this.isEnabled(n) and
+ this = rank[result](SplitKind sk | sk.appliesTo(n) | sk order by sk.getListOrder())
+ }
+
+ /** Gets a textual representation of this split kind. */
+ abstract string toString();
+}
+
+/** Provides the interface for implementing an entity to split on. */
+abstract class SplitImpl extends Split {
+ /** Gets the kind of this split. */
+ abstract SplitKind getKind();
+
+ /**
+ * Holds if this split is entered when control passes from `pred` to `succ` with
+ * completion `c`.
+ *
+ * Invariant: `hasEntry(pred, succ, c) implies succ(pred, succ, c)`.
+ */
+ abstract predicate hasEntry(ControlFlowElement pred, ControlFlowElement succ, Completion c);
+
+ /**
+ * Holds if this split is entered when control passes from `scope` to the entry point
+ * `first`.
+ *
+ * Invariant: `hasEntryScope(scope, first) implies scopeFirst(scope, first)`.
+ */
+ abstract predicate hasEntryScope(CfgScope scope, ControlFlowElement first);
+
+ /**
+ * Holds if this split is left when control passes from `pred` to `succ` with
+ * completion `c`.
+ *
+ * Invariant: `hasExit(pred, succ, c) implies succ(pred, succ, c)`.
+ */
+ abstract predicate hasExit(ControlFlowElement pred, ControlFlowElement succ, Completion c);
+
+ /**
+ * Holds if this split is left when control passes from `last` out of the enclosing
+ * scope `scope` with completion `c`.
+ *
+ * Invariant: `hasExitScope(scope, last, c) implies scopeLast(scope, last, c)`
+ */
+ abstract predicate hasExitScope(CfgScope scope, ControlFlowElement last, Completion c);
+
+ /**
+ * Holds if this split is maintained when control passes from `pred` to `succ` with
+ * completion `c`.
+ *
+ * Invariant: `hasSuccessor(pred, succ, c) implies succ(pred, succ, c)`
+ */
+ abstract predicate hasSuccessor(ControlFlowElement pred, ControlFlowElement succ, Completion c);
+
+ /** Holds if this split applies to control flow element `cfe`. */
+ final predicate appliesTo(ControlFlowElement cfe) {
+ this.hasEntry(_, cfe, _)
+ or
+ this.hasEntryScope(_, cfe)
+ or
+ exists(ControlFlowElement pred | this.appliesTo(pred) | this.hasSuccessor(pred, cfe, _))
+ }
+
+ /** The `succ` relation restricted to predecessors `pred` that this split applies to. */
+ pragma[noinline]
+ final predicate appliesSucc(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
+ this.appliesTo(pred) and
+ succ(pred, succ, c)
+ }
+}
+
+/**
+ * A set of control flow node splits. The set is represented by a list of splits,
+ * ordered by ascending rank.
+ */
+class Splits extends TSplits {
+ /** Gets a textual representation of this set of splits. */
+ string toString() { result = splitsToString(this) }
+
+ /** Gets a split belonging to this set of splits. */
+ SplitImpl getASplit() {
+ exists(SplitImpl head, Splits tail | this = TSplitsCons(head, tail) |
+ result = head
+ or
+ result = tail.getASplit()
+ )
+ }
+}
+
+private predicate succEntrySplitsFromRank(
+ CfgScope pred, ControlFlowElement succ, Splits splits, int rnk
+) {
+ splits = TSplitsNil() and
+ scopeFirst(pred, succ) and
+ rnk = 0
+ or
+ exists(SplitImpl head, Splits tail | succEntrySplitsCons(pred, succ, head, tail, rnk) |
+ splits = TSplitsCons(head, tail)
+ )
+}
+
+private predicate succEntrySplitsCons(
+ CfgScope pred, ControlFlowElement succ, SplitImpl head, Splits tail, int rnk
+) {
+ succEntrySplitsFromRank(pred, succ, tail, rnk - 1) and
+ head.hasEntryScope(pred, succ) and
+ rnk = head.getKind().getListRank(succ)
+}
+
+/**
+ * Holds if `succ` with splits `succSplits` is the first element that is executed
+ * when entering callable `pred`.
+ */
+pragma[noinline]
+private predicate succEntrySplits(
+ CfgScope pred, ControlFlowElement succ, Splits succSplits, SuccessorType t
+) {
+ exists(int rnk |
+ scopeFirst(pred, succ) and
+ successorTypeIsSimple(t) and
+ succEntrySplitsFromRank(pred, succ, succSplits, rnk)
+ |
+ rnk = 0 and
+ not any(SplitImpl split).hasEntryScope(pred, succ)
+ or
+ rnk = max(SplitImpl split | split.hasEntryScope(pred, succ) | split.getKind().getListRank(succ))
+ )
+}
+
+/**
+ * Holds if `pred` with splits `predSplits` can exit the enclosing callable
+ * `succ` with type `t`.
+ */
+private predicate succExitSplits(
+ ControlFlowElement pred, Splits predSplits, CfgScope succ, SuccessorType t
+) {
+ exists(Reachability::SameSplitsBlock b, Completion c | pred = b.getAnElement() |
+ b.isReachable(predSplits) and
+ t = getAMatchingSuccessorType(c) and
+ scopeLast(succ, pred, c) and
+ forall(SplitImpl predSplit | predSplit = predSplits.getASplit() |
+ predSplit.hasExitScope(succ, pred, c)
+ )
+ )
+}
+
+/**
+ * Provides a predicate for the successor relation with split information,
+ * as well as logic used to construct the type `TSplits` representing sets
+ * of splits. Only sets of splits that can be reached are constructed, hence
+ * the predicates are mutually recursive.
+ *
+ * For the successor relation
+ *
+ * ```ql
+ * succSplits(ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits, Completion c)
+ * ```
+ *
+ * the following invariants are maintained:
+ *
+ * 1. `pred` is reachable with split set `predSplits`.
+ * 2. For all `split` in `predSplits`:
+ * - If `split.hasSuccessor(pred, succ, c)` then `split` in `succSplits`.
+ * 3. For all `split` in `predSplits`:
+ * - If `split.hasExit(pred, succ, c)` and not `split.hasEntry(pred, succ, c)` then
+ * `split` not in `succSplits`.
+ * 4. For all `split` with kind not in `predSplits`:
+ * - If `split.hasEntry(pred, succ, c)` then `split` in `succSplits`.
+ * 5. For all `split` in `succSplits`:
+ * - `split.hasSuccessor(pred, succ, c)` and `split` in `predSplits`, or
+ * - `split.hasEntry(pred, succ, c)`.
+ *
+ * The algorithm divides into four cases:
+ *
+ * 1. The set of splits for the successor is the same as the set of splits
+ * for the predecessor:
+ * a) The successor is in the same `SameSplitsBlock` as the predecessor.
+ * b) The successor is *not* in the same `SameSplitsBlock` as the predecessor.
+ * 2. The set of splits for the successor is different from the set of splits
+ * for the predecessor:
+ * a) The set of splits for the successor is *maybe* non-empty.
+ * b) The set of splits for the successor is *always* empty.
+ *
+ * Only case 2a may introduce new sets of splits, so only predicates from
+ * this case are used in the definition of `TSplits`.
+ *
+ * The predicates in this module are named after the cases above.
+ */
+private module SuccSplits {
+ private predicate succInvariant1(
+ Reachability::SameSplitsBlock b, ControlFlowElement pred, Splits predSplits,
+ ControlFlowElement succ, Completion c
+ ) {
+ pred = b.getAnElement() and
+ b.isReachable(predSplits) and
+ succ(pred, succ, c)
+ }
+
+ private predicate case1b0(
+ ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c
+ ) {
+ exists(Reachability::SameSplitsBlock b |
+ // Invariant 1
+ succInvariant1(b, pred, predSplits, succ, c)
+ |
+ (succ = b.getAnElement() implies succ = b) and
+ // Invariant 4
+ not exists(SplitImpl split | split.hasEntry(pred, succ, c))
+ )
+ }
+
+ /**
+ * Case 1b.
+ *
+ * Invariants 1 and 4 hold in the base case, and invariants 2, 3, and 5 are
+ * maintained for all splits in `predSplits` (= `succSplits`), except
+ * possibly for the splits in `except`.
+ *
+ * The predicate is written using explicit recursion, as opposed to a `forall`,
+ * to avoid negative recursion.
+ */
+ private predicate case1bForall(
+ ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, Splits except
+ ) {
+ case1b0(pred, predSplits, succ, c) and
+ except = predSplits
+ or
+ exists(SplitImpl split |
+ case1bForallCons(pred, predSplits, succ, c, split, except) and
+ split.hasSuccessor(pred, succ, c)
+ )
+ }
+
+ pragma[noinline]
+ private predicate case1bForallCons(
+ ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c,
+ SplitImpl exceptHead, Splits exceptTail
+ ) {
+ case1bForall(pred, predSplits, succ, c, TSplitsCons(exceptHead, exceptTail))
+ }
+
+ private predicate case1(
+ ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c
+ ) {
+ // Case 1a
+ exists(Reachability::SameSplitsBlock b | succInvariant1(b, pred, predSplits, succ, c) |
+ succ = b.getAnElement() and
+ not succ = b
+ )
+ or
+ // Case 1b
+ case1bForall(pred, predSplits, succ, c, TSplitsNil())
+ }
+
+ pragma[noinline]
+ private SplitImpl succInvariant1GetASplit(
+ Reachability::SameSplitsBlock b, ControlFlowElement pred, Splits predSplits,
+ ControlFlowElement succ, Completion c
+ ) {
+ succInvariant1(b, pred, predSplits, succ, c) and
+ result = predSplits.getASplit()
+ }
+
+ private predicate case2aux(
+ ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c
+ ) {
+ exists(Reachability::SameSplitsBlock b |
+ succInvariant1(b, pred, predSplits, succ, c) and
+ (succ = b.getAnElement() implies succ = b)
+ |
+ succInvariant1GetASplit(b, pred, predSplits, succ, c).hasExit(pred, succ, c)
+ or
+ any(SplitImpl split).hasEntry(pred, succ, c)
+ )
+ }
+
+ /**
+ * Holds if `succSplits` should not inherit a split of kind `sk` from
+ * `predSplits`, except possibly because of a split in `except`.
+ *
+ * The predicate is written using explicit recursion, as opposed to a `forall`,
+ * to avoid negative recursion.
+ */
+ private predicate case2aNoneInheritedOfKindForall(
+ ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, SplitKind sk,
+ Splits except
+ ) {
+ case2aux(pred, predSplits, succ, c) and
+ sk.appliesTo(succ) and
+ except = predSplits
+ or
+ exists(Splits mid, SplitImpl split |
+ case2aNoneInheritedOfKindForall(pred, predSplits, succ, c, sk, mid) and
+ mid = TSplitsCons(split, except)
+ |
+ split.getKind() = any(SplitKind sk0 | sk0 != sk and sk0.appliesTo(succ))
+ or
+ split.hasExit(pred, succ, c)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate entryOfKind(
+ ControlFlowElement pred, ControlFlowElement succ, Completion c, SplitImpl split, SplitKind sk
+ ) {
+ split.hasEntry(pred, succ, c) and
+ sk = split.getKind()
+ }
+
+ /** Holds if `succSplits` should not have a split of kind `sk`. */
+ pragma[nomagic]
+ private predicate case2aNoneOfKind(
+ ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, SplitKind sk
+ ) {
+ // None inherited from predecessor
+ case2aNoneInheritedOfKindForall(pred, predSplits, succ, c, sk, TSplitsNil()) and
+ // None newly entered into
+ not entryOfKind(pred, succ, c, _, sk)
+ }
+
+ /** Holds if `succSplits` should not have a split of kind `sk` at rank `rnk`. */
+ pragma[nomagic]
+ private predicate case2aNoneAtRank(
+ ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, int rnk
+ ) {
+ exists(SplitKind sk | case2aNoneOfKind(pred, predSplits, succ, c, sk) |
+ rnk = sk.getListRank(succ)
+ )
+ }
+
+ pragma[nomagic]
+ private SplitImpl case2auxGetAPredecessorSplit(
+ ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c
+ ) {
+ case2aux(pred, predSplits, succ, c) and
+ result = predSplits.getASplit()
+ }
+
+ /** Gets a split that should be in `succSplits`. */
+ pragma[nomagic]
+ private SplitImpl case2aSome(
+ ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, SplitKind sk
+ ) {
+ (
+ // Inherited from predecessor
+ result = case2auxGetAPredecessorSplit(pred, predSplits, succ, c) and
+ result.hasSuccessor(pred, succ, c)
+ or
+ // Newly entered into
+ exists(SplitKind sk0 |
+ case2aNoneInheritedOfKindForall(pred, predSplits, succ, c, sk0, TSplitsNil())
+ |
+ entryOfKind(pred, succ, c, result, sk0)
+ )
+ ) and
+ sk = result.getKind()
+ }
+
+ /** Gets a split that should be in `succSplits` at rank `rnk`. */
+ pragma[nomagic]
+ SplitImpl case2aSomeAtRank(
+ ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, int rnk
+ ) {
+ exists(SplitKind sk | result = case2aSome(pred, predSplits, succ, c, sk) |
+ rnk = sk.getListRank(succ)
+ )
+ }
+
+ /**
+ * Case 2a.
+ *
+ * As opposed to the other cases, in this case we need to construct a new set
+ * of splits `succSplits`. Since this involves constructing the very IPA type,
+ * we cannot recurse directly over the structure of `succSplits`. Instead, we
+ * recurse over the ranks of all splits that *might* be in `succSplits`.
+ *
+ * - Invariant 1 holds in the base case,
+ * - invariant 2 holds for all splits with rank at least `rnk`,
+ * - invariant 3 holds for all splits in `predSplits`,
+ * - invariant 4 holds for all splits in `succSplits` with rank at least `rnk`,
+ * and
+ * - invariant 4 holds for all splits in `succSplits` with rank at least `rnk`.
+ */
+ predicate case2aFromRank(
+ ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits,
+ Completion c, int rnk
+ ) {
+ case2aux(pred, predSplits, succ, c) and
+ succSplits = TSplitsNil() and
+ rnk = max(any(SplitKind sk).getListRank(succ)) + 1
+ or
+ case2aFromRank(pred, predSplits, succ, succSplits, c, rnk + 1) and
+ case2aNoneAtRank(pred, predSplits, succ, c, rnk)
+ or
+ exists(Splits mid, SplitImpl split | split = case2aCons(pred, predSplits, succ, mid, c, rnk) |
+ succSplits = TSplitsCons(split, mid)
+ )
+ }
+
+ pragma[noinline]
+ private SplitImpl case2aCons(
+ ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits,
+ Completion c, int rnk
+ ) {
+ case2aFromRank(pred, predSplits, succ, succSplits, c, rnk + 1) and
+ result = case2aSomeAtRank(pred, predSplits, succ, c, rnk)
+ }
+
+ /**
+ * Case 2b.
+ *
+ * Invariants 1, 4, and 5 hold in the base case, and invariants 2 and 3 are
+ * maintained for all splits in `predSplits`, except possibly for the splits
+ * in `except`.
+ *
+ * The predicate is written using explicit recursion, as opposed to a `forall`,
+ * to avoid negative recursion.
+ */
+ private predicate case2bForall(
+ ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, Splits except
+ ) {
+ // Invariant 1
+ case2aux(pred, predSplits, succ, c) and
+ // Invariants 4 and 5
+ not any(SplitKind sk).appliesTo(succ) and
+ except = predSplits
+ or
+ exists(SplitImpl split | case2bForallCons(pred, predSplits, succ, c, split, except) |
+ // Invariants 2 and 3
+ split.hasExit(pred, succ, c)
+ )
+ }
+
+ pragma[noinline]
+ private predicate case2bForallCons(
+ ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c,
+ SplitImpl exceptHead, Splits exceptTail
+ ) {
+ case2bForall(pred, predSplits, succ, c, TSplitsCons(exceptHead, exceptTail))
+ }
+
+ private predicate case2(
+ ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits,
+ Completion c
+ ) {
+ case2aFromRank(pred, predSplits, succ, succSplits, c, 1)
+ or
+ case2bForall(pred, predSplits, succ, c, TSplitsNil()) and
+ succSplits = TSplitsNil()
+ }
+
+ /**
+ * Holds if `succ` with splits `succSplits` is a successor of type `t` for `pred`
+ * with splits `predSplits`.
+ */
+ predicate succSplits(
+ ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits,
+ Completion c
+ ) {
+ case1(pred, predSplits, succ, c) and
+ succSplits = predSplits
+ or
+ case2(pred, predSplits, succ, succSplits, c)
+ }
+}
+
+import SuccSplits
+
+/** Provides logic for calculating reachable control flow nodes. */
+private module Reachability {
+ /**
+ * Holds if `cfe` is a control flow element where the set of possible splits may
+ * be different from the set of possible splits for one of `cfe`'s predecessors.
+ * That is, `cfe` starts a new block of elements with the same set of splits.
+ */
+ private predicate startsSplits(ControlFlowElement cfe) {
+ scopeFirst(_, cfe)
+ or
+ exists(SplitImpl s |
+ s.hasEntry(_, cfe, _)
+ or
+ s.hasExit(_, cfe, _)
+ )
+ or
+ exists(ControlFlowElement pred, SplitImpl split, Completion c | succ(pred, cfe, c) |
+ split.appliesTo(pred) and
+ not split.hasSuccessor(pred, cfe, c)
+ )
+ }
+
+ private predicate intraSplitsSucc(ControlFlowElement pred, ControlFlowElement succ) {
+ succ(pred, succ, _) and
+ not startsSplits(succ)
+ }
+
+ private predicate splitsBlockContains(ControlFlowElement start, ControlFlowElement cfe) =
+ fastTC(intraSplitsSucc/2)(start, cfe)
+
+ /**
+ * A block of control flow elements where the set of splits is guaranteed
+ * to remain unchanged, represented by the first element in the block.
+ */
+ class SameSplitsBlock extends ControlFlowElement {
+ SameSplitsBlock() { startsSplits(this) }
+
+ /** Gets a control flow element in this block. */
+ ControlFlowElement getAnElement() {
+ splitsBlockContains(this, result)
+ or
+ result = this
+ }
+
+ pragma[noinline]
+ private SameSplitsBlock getASuccessor(Splits predSplits, Splits succSplits) {
+ exists(ControlFlowElement pred | pred = this.getAnElement() |
+ succSplits(pred, predSplits, result, succSplits, _)
+ )
+ }
+
+ /**
+ * Holds if the elements of this block are reachable from a callable entry
+ * point, with the splits `splits`.
+ */
+ predicate isReachable(Splits splits) {
+ // Base case
+ succEntrySplits(_, this, splits, _)
+ or
+ // Recursive case
+ exists(SameSplitsBlock pred, Splits predSplits | pred.isReachable(predSplits) |
+ this = pred.getASuccessor(predSplits, splits)
+ )
+ }
+ }
+}
+
+cached
+private module Cached {
+ /**
+ * If needed, call this predicate from `ControlFlowGraphImplSpecific.qll` in order to
+ * force a stage-dependency on the `ControlFlowGraphImplShared.qll` stage and therby
+ * collapsing the two stages.
+ */
+ cached
+ predicate forceCachingInSameStage() { any() }
+
+ cached
+ newtype TSplits =
+ TSplitsNil() or
+ TSplitsCons(SplitImpl head, Splits tail) {
+ exists(
+ ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, int rnk
+ |
+ case2aFromRank(pred, predSplits, succ, tail, c, rnk + 1) and
+ head = case2aSomeAtRank(pred, predSplits, succ, c, rnk)
+ )
+ or
+ succEntrySplitsCons(_, _, head, tail, _)
+ }
+
+ cached
+ string splitsToString(Splits splits) {
+ splits = TSplitsNil() and
+ result = ""
+ or
+ exists(SplitImpl head, Splits tail, string headString, string tailString |
+ splits = TSplitsCons(head, tail)
+ |
+ headString = head.toString() and
+ tailString = tail.toString() and
+ if tailString = ""
+ then result = headString
+ else
+ if headString = ""
+ then result = tailString
+ else result = headString + ", " + tailString
+ )
+ }
+
+ /**
+ * Internal representation of control flow nodes in the control flow graph.
+ * The control flow graph is pruned for unreachable nodes.
+ */
+ cached
+ newtype TNode =
+ TEntryNode(CfgScope scope) { succEntrySplits(scope, _, _, _) } or
+ TAnnotatedExitNode(CfgScope scope, boolean normal) {
+ exists(Reachability::SameSplitsBlock b, SuccessorType t | b.isReachable(_) |
+ succExitSplits(b.getAnElement(), _, scope, t) and
+ if isAbnormalExitType(t) then normal = false else normal = true
+ )
+ } or
+ TExitNode(CfgScope scope) {
+ exists(Reachability::SameSplitsBlock b | b.isReachable(_) |
+ succExitSplits(b.getAnElement(), _, scope, _)
+ )
+ } or
+ TElementNode(ControlFlowElement cfe, Splits splits) {
+ exists(Reachability::SameSplitsBlock b | b.isReachable(splits) | cfe = b.getAnElement())
+ }
+
+ /** Gets a successor node of a given flow type, if any. */
+ cached
+ TNode getASuccessor(TNode pred, SuccessorType t) {
+ // Callable entry node -> callable body
+ exists(ControlFlowElement succElement, Splits succSplits, CfgScope scope |
+ result = TElementNode(succElement, succSplits) and
+ pred = TEntryNode(scope) and
+ succEntrySplits(scope, succElement, succSplits, t)
+ )
+ or
+ exists(ControlFlowElement predElement, Splits predSplits |
+ pred = TElementNode(predElement, predSplits)
+ |
+ // Element node -> callable exit (annotated)
+ exists(CfgScope scope, boolean normal |
+ result = TAnnotatedExitNode(scope, normal) and
+ succExitSplits(predElement, predSplits, scope, t) and
+ if isAbnormalExitType(t) then normal = false else normal = true
+ )
+ or
+ // Element node -> element node
+ exists(ControlFlowElement succElement, Splits succSplits, Completion c |
+ result = TElementNode(succElement, succSplits)
+ |
+ succSplits(predElement, predSplits, succElement, succSplits, c) and
+ t = getAMatchingSuccessorType(c)
+ )
+ )
+ or
+ // Callable exit (annotated) -> callable exit
+ exists(CfgScope scope |
+ pred = TAnnotatedExitNode(scope, _) and
+ result = TExitNode(scope) and
+ successorTypeIsSimple(t)
+ )
+ }
+
+ /**
+ * Gets a first control flow element executed within `cfe`.
+ */
+ cached
+ ControlFlowElement getAControlFlowEntryNode(ControlFlowElement cfe) { first(cfe, result) }
+
+ /**
+ * Gets a potential last control flow element executed within `cfe`.
+ */
+ cached
+ ControlFlowElement getAControlFlowExitNode(ControlFlowElement cfe) { last(cfe, result, _) }
+}
+
+import Cached
+
+/**
+ * Import this module into a `.ql` file of `@kind graph` to render a CFG. The
+ * graph is restricted to nodes from `RelevantNode`.
+ */
+module TestOutput {
+ abstract class RelevantNode extends Node { }
+
+ query predicate nodes(RelevantNode n, string attr, string val) {
+ attr = "semmle.order" and
+ val =
+ any(int i |
+ n =
+ rank[i](RelevantNode p, Location l |
+ l = p.getLocation()
+ |
+ p
+ order by
+ l.getFile().getBaseName(), l.getFile().getAbsolutePath(), l.getStartLine(),
+ l.getStartColumn()
+ )
+ ).toString()
+ }
+
+ query predicate edges(RelevantNode pred, RelevantNode succ, string attr, string val) {
+ exists(SuccessorType t | succ = getASuccessor(pred, t) |
+ attr = "semmle.label" and
+ if successorTypeIsSimple(t) then val = "" else val = t.toString()
+ )
+ }
+}
+
+/** Provides a set of splitting-related consistency queries. */
+module Consistency {
+ query predicate nonUniqueSetRepresentation(Splits s1, Splits s2) {
+ forex(Split s | s = s1.getASplit() | s = s2.getASplit()) and
+ forex(Split s | s = s2.getASplit() | s = s1.getASplit()) and
+ s1 != s2
+ }
+
+ query predicate breakInvariant2(
+ ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits,
+ SplitImpl split, Completion c
+ ) {
+ succSplits(pred, predSplits, succ, succSplits, c) and
+ split = predSplits.getASplit() and
+ split.hasSuccessor(pred, succ, c) and
+ not split = succSplits.getASplit()
+ }
+
+ query predicate breakInvariant3(
+ ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits,
+ SplitImpl split, Completion c
+ ) {
+ succSplits(pred, predSplits, succ, succSplits, c) and
+ split = predSplits.getASplit() and
+ split.hasExit(pred, succ, c) and
+ not split.hasEntry(pred, succ, c) and
+ split = succSplits.getASplit()
+ }
+
+ query predicate breakInvariant4(
+ ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits,
+ SplitImpl split, Completion c
+ ) {
+ succSplits(pred, predSplits, succ, succSplits, c) and
+ split.hasEntry(pred, succ, c) and
+ not split.getKind() = predSplits.getASplit().getKind() and
+ not split = succSplits.getASplit() and
+ split.getKind().isEnabled(succ)
+ }
+
+ query predicate breakInvariant5(
+ ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits,
+ SplitImpl split, Completion c
+ ) {
+ succSplits(pred, predSplits, succ, succSplits, c) and
+ split = succSplits.getASplit() and
+ not (split.hasSuccessor(pred, succ, c) and split = predSplits.getASplit()) and
+ not split.hasEntry(pred, succ, c)
+ }
+
+ query predicate multipleSuccessors(Node node, SuccessorType t, Node successor) {
+ not node instanceof TEntryNode and
+ strictcount(getASuccessor(node, t)) > 1 and
+ successor = getASuccessor(node, t)
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImplSpecific.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImplSpecific.qll
new file mode 100644
index 00000000000..2d018ff616a
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImplSpecific.qll
@@ -0,0 +1,74 @@
+private import ruby as rb
+private import ControlFlowGraphImpl as Impl
+private import Completion as Comp
+private import codeql.ruby.ast.internal.Synthesis
+private import Splitting as Splitting
+private import codeql.ruby.CFG as CFG
+
+/** The base class for `ControlFlowTree`. */
+class ControlFlowTreeBase extends rb::AstNode {
+ ControlFlowTreeBase() { not any(Synthesis s).excludeFromControlFlowTree(this) }
+}
+
+class ControlFlowElement = rb::AstNode;
+
+class Completion = Comp::Completion;
+
+/**
+ * Hold if `c` represents normal evaluation of a statement or an
+ * expression.
+ */
+predicate completionIsNormal(Completion c) { c instanceof Comp::NormalCompletion }
+
+/**
+ * Hold if `c` represents simple (normal) evaluation of a statement or an
+ * expression.
+ */
+predicate completionIsSimple(Completion c) { c instanceof Comp::SimpleCompletion }
+
+/** Holds if `c` is a valid completion for `e`. */
+predicate completionIsValidFor(Completion c, ControlFlowElement e) { c.isValidFor(e) }
+
+class CfgScope = CFG::CfgScope;
+
+predicate getCfgScope = Impl::getCfgScope/1;
+
+/** Holds if `first` is first executed when entering `scope`. */
+predicate scopeFirst(CfgScope scope, ControlFlowElement first) {
+ scope.(Impl::CfgScope::Range_).entry(first)
+}
+
+/** Holds if `scope` is exited when `last` finishes with completion `c`. */
+predicate scopeLast(CfgScope scope, ControlFlowElement last, Completion c) {
+ scope.(Impl::CfgScope::Range_).exit(last, c)
+}
+
+/** The maximum number of splits allowed for a given node. */
+int maxSplits() { result = 5 }
+
+class SplitKindBase = Splitting::TSplitKind;
+
+class Split = Splitting::Split;
+
+class SuccessorType = CFG::SuccessorType;
+
+/** Gets a successor type that matches completion `c`. */
+SuccessorType getAMatchingSuccessorType(Completion c) { result = c.getAMatchingSuccessorType() }
+
+/**
+ * Hold if `c` represents simple (normal) evaluation of a statement or an
+ * expression.
+ */
+predicate successorTypeIsSimple(SuccessorType t) {
+ t instanceof CFG::SuccessorTypes::NormalSuccessor
+}
+
+/** Holds if `t` is an abnormal exit type out of a CFG scope. */
+predicate isAbnormalExitType(SuccessorType t) {
+ t instanceof CFG::SuccessorTypes::RaiseSuccessor or
+ t instanceof CFG::SuccessorTypes::ExitSuccessor
+}
+
+class Location = rb::Location;
+
+class Node = CFG::CfgNode;
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/internal/NonReturning.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/internal/NonReturning.qll
new file mode 100644
index 00000000000..e1927a0b1c9
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/internal/NonReturning.qll
@@ -0,0 +1,22 @@
+/** Provides a simple analysis for identifying calls that will not return. */
+
+private import codeql.ruby.AST
+private import Completion
+
+/** A call that definitely does not return (conservative analysis). */
+abstract class NonReturningCall extends MethodCall {
+ /** Gets a valid completion for this non-returning call. */
+ abstract Completion getACompletion();
+}
+
+private class RaiseCall extends NonReturningCall {
+ RaiseCall() { this.getMethodName() = "raise" }
+
+ override RaiseCompletion getACompletion() { not result instanceof NestedCompletion }
+}
+
+private class ExitCall extends NonReturningCall {
+ ExitCall() { this.getMethodName() in ["abort", "exit"] }
+
+ override ExitCompletion getACompletion() { not result instanceof NestedCompletion }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/internal/Splitting.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/internal/Splitting.qll
new file mode 100644
index 00000000000..b2068909689
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/controlflow/internal/Splitting.qll
@@ -0,0 +1,336 @@
+/**
+ * Provides classes and predicates relevant for splitting the control flow graph.
+ */
+
+private import codeql.ruby.AST
+private import Completion
+private import ControlFlowGraphImpl
+private import SuccessorTypes
+private import codeql.ruby.controlflow.ControlFlowGraph
+
+cached
+private module Cached {
+ cached
+ newtype TSplitKind =
+ TConditionalCompletionSplitKind() { forceCachingInSameStage() } or
+ TEnsureSplitKind(int nestLevel) { nestLevel = any(Trees::BodyStmtTree t).getNestLevel() }
+
+ cached
+ newtype TSplit =
+ TConditionalCompletionSplit(ConditionalCompletion c) or
+ TEnsureSplit(EnsureSplitting::EnsureSplitType type, int nestLevel) {
+ nestLevel = any(Trees::BodyStmtTree t).getNestLevel()
+ }
+}
+
+import Cached
+
+/** A split for a control flow node. */
+class Split extends TSplit {
+ /** Gets a textual representation of this split. */
+ string toString() { none() }
+}
+
+private module ConditionalCompletionSplitting {
+ /**
+ * A split for conditional completions. For example, in
+ *
+ * ```rb
+ * def method x
+ * if x < 2 and x > 0
+ * puts "x is 1"
+ * end
+ * end
+ * ```
+ *
+ * we record whether `x < 2` and `x > 0` evaluate to `true` or `false`, and
+ * restrict the edges out of `x < 2 and x > 0` accordingly.
+ */
+ class ConditionalCompletionSplit extends Split, TConditionalCompletionSplit {
+ ConditionalCompletion completion;
+
+ ConditionalCompletionSplit() { this = TConditionalCompletionSplit(completion) }
+
+ override string toString() { result = completion.toString() }
+ }
+
+ private class ConditionalCompletionSplitKind extends SplitKind, TConditionalCompletionSplitKind {
+ override int getListOrder() { result = 0 }
+
+ override predicate isEnabled(AstNode n) { this.appliesTo(n) }
+
+ override string toString() { result = "ConditionalCompletion" }
+ }
+
+ int getNextListOrder() { result = 1 }
+
+ private class ConditionalCompletionSplitImpl extends SplitImpl, ConditionalCompletionSplit {
+ override ConditionalCompletionSplitKind getKind() { any() }
+
+ override predicate hasEntry(AstNode pred, AstNode succ, Completion c) {
+ succ(pred, succ, c) and
+ last(succ, _, completion) and
+ (
+ last(succ.(NotExpr).getOperand(), pred, c) and
+ completion.(BooleanCompletion).getDual() = c
+ or
+ last(succ.(LogicalAndExpr).getAnOperand(), pred, c) and
+ completion = c
+ or
+ last(succ.(LogicalOrExpr).getAnOperand(), pred, c) and
+ completion = c
+ or
+ last(succ.(StmtSequence).getLastStmt(), pred, c) and
+ completion = c
+ or
+ last(succ.(ConditionalExpr).getBranch(_), pred, c) and
+ completion = c
+ )
+ }
+
+ override predicate hasEntryScope(CfgScope scope, AstNode succ) { none() }
+
+ override predicate hasExit(AstNode pred, AstNode succ, Completion c) {
+ this.appliesTo(pred) and
+ succ(pred, succ, c) and
+ if c instanceof ConditionalCompletion then completion = c else any()
+ }
+
+ override predicate hasExitScope(CfgScope scope, AstNode last, Completion c) {
+ this.appliesTo(last) and
+ succExit(scope, last, c) and
+ if c instanceof ConditionalCompletion then completion = c else any()
+ }
+
+ override predicate hasSuccessor(AstNode pred, AstNode succ, Completion c) { none() }
+ }
+}
+
+module EnsureSplitting {
+ /**
+ * The type of a split `ensure` node.
+ *
+ * The type represents one of the possible ways of entering an `ensure`
+ * block. For example, if a block ends with a `return` statement, then
+ * the `ensure` block must end with a `return` as well (provided that
+ * the `ensure` block executes normally).
+ */
+ class EnsureSplitType extends SuccessorType {
+ EnsureSplitType() { not this instanceof ConditionalSuccessor }
+
+ /** Holds if this split type matches entry into an `ensure` block with completion `c`. */
+ predicate isSplitForEntryCompletion(Completion c) {
+ if c instanceof NormalCompletion
+ then
+ // If the entry into the `ensure` block completes with any normal completion,
+ // it simply means normal execution after the `ensure` block
+ this instanceof NormalSuccessor
+ else this = c.getAMatchingSuccessorType()
+ }
+ }
+
+ /** A node that belongs to an `ensure` block. */
+ private class EnsureNode extends AstNode {
+ private Trees::BodyStmtTree block;
+
+ EnsureNode() { this = block.getAnEnsureDescendant() }
+
+ int getNestLevel() { result = block.getNestLevel() }
+
+ /** Holds if this node is the entry node in the `ensure` block it belongs to. */
+ predicate isEntryNode() { first(block.getEnsure(), this) }
+ }
+
+ /**
+ * A split for nodes belonging to an `ensure` block, which determines how to
+ * continue execution after leaving the `ensure` block. For example, in
+ *
+ * ```rb
+ * begin
+ * if x
+ * raise "Exception"
+ * end
+ * ensure
+ * puts "Ensure"
+ * end
+ * ```
+ *
+ * all control flow nodes in the `ensure` block have two splits: one representing
+ * normal execution of the body (when `x` evaluates to `true`), and one representing
+ * exceptional execution of the body (when `x` evaluates to `false`).
+ */
+ class EnsureSplit extends Split, TEnsureSplit {
+ private EnsureSplitType type;
+ private int nestLevel;
+
+ EnsureSplit() { this = TEnsureSplit(type, nestLevel) }
+
+ /**
+ * Gets the type of this `ensure` split, that is, how to continue execution after the
+ * `ensure` block.
+ */
+ EnsureSplitType getType() { result = type }
+
+ /** Gets the nesting level. */
+ int getNestLevel() { result = nestLevel }
+
+ override string toString() {
+ if type instanceof NormalSuccessor
+ then result = ""
+ else
+ if nestLevel > 0
+ then result = "ensure(" + nestLevel + "): " + type.toString()
+ else result = "ensure: " + type.toString()
+ }
+ }
+
+ private int getListOrder(EnsureSplitKind kind) {
+ result = ConditionalCompletionSplitting::getNextListOrder() + kind.getNestLevel()
+ }
+
+ int getNextListOrder() {
+ result = max([getListOrder(_) + 1, ConditionalCompletionSplitting::getNextListOrder()])
+ }
+
+ private class EnsureSplitKind extends SplitKind, TEnsureSplitKind {
+ private int nestLevel;
+
+ EnsureSplitKind() { this = TEnsureSplitKind(nestLevel) }
+
+ /** Gets the nesting level. */
+ int getNestLevel() { result = nestLevel }
+
+ override int getListOrder() { result = getListOrder(this) }
+
+ override string toString() { result = "ensure (" + nestLevel + ")" }
+ }
+
+ pragma[noinline]
+ private predicate hasEntry0(AstNode pred, EnsureNode succ, int nestLevel, Completion c) {
+ succ.isEntryNode() and
+ nestLevel = succ.getNestLevel() and
+ succ(pred, succ, c)
+ }
+
+ private class EnsureSplitImpl extends SplitImpl, EnsureSplit {
+ override EnsureSplitKind getKind() { result.getNestLevel() = this.getNestLevel() }
+
+ override predicate hasEntry(AstNode pred, AstNode succ, Completion c) {
+ hasEntry0(pred, succ, this.getNestLevel(), c) and
+ this.getType().isSplitForEntryCompletion(c)
+ }
+
+ override predicate hasEntryScope(CfgScope scope, AstNode first) { none() }
+
+ /**
+ * Holds if this split applies to `pred`, where `pred` is a valid predecessor.
+ */
+ private predicate appliesToPredecessor(AstNode pred) {
+ this.appliesTo(pred) and
+ (succ(pred, _, _) or succExit(_, pred, _))
+ }
+
+ pragma[noinline]
+ private predicate exit0(AstNode pred, Trees::BodyStmtTree block, int nestLevel, Completion c) {
+ this.appliesToPredecessor(pred) and
+ nestLevel = block.getNestLevel() and
+ block.lastInner(pred, c)
+ }
+
+ /**
+ * Holds if `pred` may exit this split with completion `c`. The Boolean
+ * `inherited` indicates whether `c` is an inherited completion from the
+ * body.
+ */
+ private predicate exit(Trees::BodyStmtTree block, AstNode pred, Completion c, boolean inherited) {
+ exists(EnsureSplitType type |
+ this.exit0(pred, block, this.getNestLevel(), c) and
+ type = this.getType()
+ |
+ if last(block.getEnsure(), pred, c)
+ then
+ // `ensure` block can itself exit with completion `c`: either `c` must
+ // match this split, `c` must be an abnormal completion, or this split
+ // does not require another completion to be recovered
+ inherited = false and
+ (
+ type = c.getAMatchingSuccessorType()
+ or
+ not c instanceof NormalCompletion
+ or
+ type instanceof NormalSuccessor
+ )
+ else (
+ // `ensure` block can exit with inherited completion `c`, which must
+ // match this split
+ inherited = true and
+ type = c.getAMatchingSuccessorType() and
+ not type instanceof NormalSuccessor
+ )
+ )
+ or
+ // If this split is normal, and an outer split can exit based on an inherited
+ // completion, we need to exit this split as well. For example, in
+ //
+ // ```rb
+ // def m(b1, b2)
+ // if b1
+ // return
+ // end
+ // ensure
+ // begin
+ // if b2
+ // raise "Exception"
+ // end
+ // ensure
+ // puts "inner ensure"
+ // end
+ // end
+ // ```
+ //
+ // if the outer split for `puts "inner ensure"` is `return` and the inner split
+ // is "normal" (corresponding to `b1 = true` and `b2 = false`), then the inner
+ // split must be able to exit with a `return` completion.
+ this.appliesToPredecessor(pred) and
+ exists(EnsureSplitImpl outer |
+ outer.getNestLevel() = this.getNestLevel() - 1 and
+ outer.exit(_, pred, c, inherited) and
+ this.getType() instanceof NormalSuccessor and
+ inherited = true
+ )
+ }
+
+ override predicate hasExit(AstNode pred, AstNode succ, Completion c) {
+ succ(pred, succ, c) and
+ (
+ this.exit(_, pred, c, _)
+ or
+ this.exit(_, pred, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _)
+ )
+ }
+
+ override predicate hasExitScope(CfgScope scope, AstNode last, Completion c) {
+ succExit(scope, last, c) and
+ (
+ this.exit(_, last, c, _)
+ or
+ this.exit(_, last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _)
+ )
+ }
+
+ override predicate hasSuccessor(AstNode pred, AstNode succ, Completion c) {
+ this.appliesToPredecessor(pred) and
+ succ(pred, succ, c) and
+ succ =
+ any(EnsureNode en |
+ if en.isEntryNode()
+ then
+ // entering a nested `ensure` block
+ en.getNestLevel() > this.getNestLevel()
+ else
+ // staying in the same (possibly nested) `ensure` block as `pred`
+ en.getNestLevel() >= this.getNestLevel()
+ )
+ }
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/BarrierGuards.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/BarrierGuards.qll
new file mode 100644
index 00000000000..0c0ca749eac
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/BarrierGuards.qll
@@ -0,0 +1,75 @@
+/** Provides commonly used barriers to dataflow. */
+
+private import ruby
+private import codeql.ruby.DataFlow
+private import codeql.ruby.CFG
+
+/**
+ * A validation of value by comparing with a constant string value, for example
+ * in:
+ *
+ * ```rb
+ * dir = params[:order]
+ * dir = "DESC" unless dir == "ASC"
+ * User.order("name #{dir}")
+ * ```
+ *
+ * the equality operation guards against `dir` taking arbitrary values when used
+ * in the `order` call.
+ */
+class StringConstCompare extends DataFlow::BarrierGuard,
+ CfgNodes::ExprNodes::ComparisonOperationCfgNode {
+ private CfgNode checkedNode;
+ // The value of the condition that results in the node being validated.
+ private boolean checkedBranch;
+
+ StringConstCompare() {
+ exists(CfgNodes::ExprNodes::StringLiteralCfgNode strLitNode |
+ this.getExpr() instanceof EqExpr and checkedBranch = true
+ or
+ this.getExpr() instanceof CaseEqExpr and checkedBranch = true
+ or
+ this.getExpr() instanceof NEExpr and checkedBranch = false
+ |
+ this.getLeftOperand() = strLitNode and this.getRightOperand() = checkedNode
+ or
+ this.getLeftOperand() = checkedNode and this.getRightOperand() = strLitNode
+ )
+ }
+
+ override predicate checks(CfgNode expr, boolean branch) {
+ expr = checkedNode and branch = checkedBranch
+ }
+}
+
+/**
+ * A validation of a value by checking for inclusion in an array of string
+ * literal values, for example in:
+ *
+ * ```rb
+ * name = params[:user_name]
+ * if %w(alice bob charlie).include? name
+ * User.find_by("username = #{name}")
+ * end
+ * ```
+ *
+ * the `include?` call guards against `name` taking arbitrary values when used
+ * in the `find_by` call.
+ */
+//
+class StringConstArrayInclusionCall extends DataFlow::BarrierGuard,
+ CfgNodes::ExprNodes::MethodCallCfgNode {
+ private CfgNode checkedNode;
+
+ StringConstArrayInclusionCall() {
+ exists(ArrayLiteral aLit |
+ this.getExpr().getMethodName() = "include?" and
+ this.getExpr().getReceiver() = aLit
+ |
+ forall(Expr elem | elem = aLit.getAnElement() | elem instanceof StringLiteral) and
+ this.getArgument(0) = checkedNode
+ )
+ }
+
+ override predicate checks(CfgNode expr, boolean branch) { expr = checkedNode and branch = true }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll
new file mode 100644
index 00000000000..f0ccb42c6e0
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll
@@ -0,0 +1,127 @@
+/** Provides classes and predicates for defining flow summaries. */
+
+import ruby
+import codeql.ruby.DataFlow
+private import internal.FlowSummaryImpl as Impl
+private import internal.DataFlowDispatch
+
+// import all instances below
+private module Summaries {
+ private import codeql.ruby.Frameworks
+}
+
+class SummaryComponent = Impl::Public::SummaryComponent;
+
+/** Provides predicates for constructing summary components. */
+module SummaryComponent {
+ private import Impl::Public::SummaryComponent as SC
+
+ predicate parameter = SC::parameter/1;
+
+ predicate argument = SC::argument/1;
+
+ predicate content = SC::content/1;
+
+ /** Gets a summary component that represents a qualifier. */
+ SummaryComponent qualifier() { result = argument(-1) }
+
+ /** Gets a summary component that represents a block argument. */
+ SummaryComponent block() { result = argument(-2) }
+
+ /** Gets a summary component that represents the return value of a call. */
+ SummaryComponent return() { result = SC::return(any(NormalReturnKind rk)) }
+}
+
+class SummaryComponentStack = Impl::Public::SummaryComponentStack;
+
+/** Provides predicates for constructing stacks of summary components. */
+module SummaryComponentStack {
+ private import Impl::Public::SummaryComponentStack as SCS
+
+ predicate singleton = SCS::singleton/1;
+
+ predicate push = SCS::push/2;
+
+ predicate argument = SCS::argument/1;
+
+ /** Gets a singleton stack representing a qualifier. */
+ SummaryComponentStack qualifier() { result = singleton(SummaryComponent::qualifier()) }
+
+ /** Gets a singleton stack representing a block argument. */
+ SummaryComponentStack block() { result = singleton(SummaryComponent::block()) }
+
+ /** Gets a singleton stack representing the return value of a call. */
+ SummaryComponentStack return() { result = singleton(SummaryComponent::return()) }
+}
+
+/** A callable with a flow summary, identified by a unique string. */
+abstract class SummarizedCallable extends LibraryCallable {
+ bindingset[this]
+ SummarizedCallable() { any() }
+
+ /**
+ * Holds if data may flow from `input` to `output` through this callable.
+ *
+ * `preservesValue` indicates whether this is a value-preserving step
+ * or a taint-step.
+ *
+ * Input specifications are restricted to stacks that end with
+ * `SummaryComponent::argument(_)`, preceded by zero or more
+ * `SummaryComponent::return()` or `SummaryComponent::content(_)` components.
+ *
+ * Output specifications are restricted to stacks that end with
+ * `SummaryComponent::return()` or `SummaryComponent::argument(_)`.
+ *
+ * Output stacks ending with `SummaryComponent::return()` can be preceded by zero
+ * or more `SummaryComponent::content(_)` components.
+ *
+ * Output stacks ending with `SummaryComponent::argument(_)` can be preceded by an
+ * optional `SummaryComponent::parameter(_)` component, which in turn can be preceded
+ * by zero or more `SummaryComponent::content(_)` components.
+ */
+ pragma[nomagic]
+ predicate propagatesFlow(
+ SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
+ ) {
+ none()
+ }
+
+ /**
+ * Same as
+ *
+ * ```ql
+ * propagatesFlow(
+ * SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
+ * )
+ * ```
+ *
+ * but uses an external (string) representation of the input and output stacks.
+ */
+ pragma[nomagic]
+ predicate propagatesFlowExt(string input, string output, boolean preservesValue) { none() }
+
+ /**
+ * Holds if values stored inside `content` are cleared on objects passed as
+ * the `i`th argument to this callable.
+ */
+ pragma[nomagic]
+ predicate clearsContent(int i, DataFlow::Content content) { none() }
+}
+
+private class SummarizedCallableAdapter extends Impl::Public::SummarizedCallable {
+ private SummarizedCallable sc;
+
+ SummarizedCallableAdapter() { this = TLibraryCallable(sc) }
+
+ final override predicate propagatesFlow(
+ SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
+ ) {
+ sc.propagatesFlow(input, output, preservesValue)
+ }
+
+ final override predicate clearsContent(int i, DataFlow::Content content) {
+ sc.clearsContent(i, content)
+ }
+}
+
+class RequiredSummaryComponentStack = Impl::Public::RequiredSummaryComponentStack;
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/RemoteFlowSources.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/RemoteFlowSources.qll
new file mode 100644
index 00000000000..617bfd8678e
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/RemoteFlowSources.qll
@@ -0,0 +1,37 @@
+/**
+ * Provides an extension point for for modeling user-controlled data.
+ * Such data is often used as data-flow sources in security queries.
+ */
+
+private import codeql.ruby.dataflow.internal.DataFlowPublic as DataFlow
+// Need to import since frameworks can extend `RemoteFlowSource::Range`
+private import codeql.ruby.Frameworks
+
+/**
+ * A data flow source of remote user input.
+ *
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `RemoteFlowSource::Range` instead.
+ */
+class RemoteFlowSource extends DataFlow::Node {
+ RemoteFlowSource::Range self;
+
+ RemoteFlowSource() { this = self }
+
+ /** Gets a string that describes the type of this remote flow source. */
+ string getSourceType() { result = self.getSourceType() }
+}
+
+/** Provides a class for modeling new sources of remote user input. */
+module RemoteFlowSource {
+ /**
+ * A data flow source of remote user input.
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `RemoteFlowSource` instead.
+ */
+ abstract class Range extends DataFlow::Node {
+ /** Gets a string that describes the type of this remote flow source. */
+ abstract string getSourceType();
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/SSA.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/SSA.qll
new file mode 100644
index 00000000000..a5fb21c1d93
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/SSA.qll
@@ -0,0 +1,403 @@
+/**
+ * Provides the module `Ssa` for working with static single assignment (SSA) form.
+ */
+
+/**
+ * Provides classes for working with static single assignment (SSA) form.
+ */
+module Ssa {
+ private import codeql.Locations
+ private import codeql.ruby.CFG
+ private import codeql.ruby.ast.Variable
+ private import internal.SsaImplCommon as SsaImplCommon
+ private import internal.SsaImpl as SsaImpl
+ private import CfgNodes::ExprNodes
+
+ /** A static single assignment (SSA) definition. */
+ class Definition extends SsaImplCommon::Definition {
+ /**
+ * Gets the control flow node of this SSA definition, if any. Phi nodes are
+ * examples of SSA definitions without a control flow node, as they are
+ * modelled at index `-1` in the relevant basic block.
+ */
+ final CfgNode getControlFlowNode() {
+ exists(BasicBlock bb, int i | this.definesAt(_, bb, i) | result = bb.getNode(i))
+ }
+
+ /**
+ * Gets a control-flow node that reads the value of this SSA definition.
+ *
+ * Example:
+ *
+ * ```rb
+ * def m b # defines b_0
+ * i = 0 # defines i_0
+ * puts i # reads i_0
+ * puts i + 1 # reads i_0
+ * if b # reads b_0
+ * i = 1 # defines i_1
+ * puts i # reads i_1
+ * puts i + 1 # reads i_1
+ * else
+ * i = 2 # defines i_2
+ * puts i # reads i_2
+ * puts i + 1 # reads i_2
+ * end
+ * # defines i_3 = phi(i_1, i_2)
+ * puts i # reads i3
+ * end
+ * ```
+ */
+ final VariableReadAccessCfgNode getARead() { result = SsaImpl::getARead(this) }
+
+ /**
+ * Gets a first control-flow node that reads the value of this SSA definition.
+ * That is, a read that can be reached from this definition without passing
+ * through other reads.
+ *
+ * Example:
+ *
+ * ```rb
+ * def m b # defines b_0
+ * i = 0 # defines i_0
+ * puts i # first read of i_0
+ * puts i + 1
+ * if b # first read of b_0
+ * i = 1 # defines i_1
+ * puts i # first read of i_1
+ * puts i + 1
+ * else
+ * i = 2 # defines i_2
+ * puts i # first read of i_2
+ * puts i + 1
+ * end
+ * # defines i_3 = phi(i_1, i_2)
+ * puts i # first read of i3
+ * end
+ * ```
+ */
+ final VariableReadAccessCfgNode getAFirstRead() { SsaImpl::firstRead(this, result) }
+
+ /**
+ * Gets a last control-flow node that reads the value of this SSA definition.
+ * That is, a read that can reach the end of the enclosing CFG scope, or another
+ * SSA definition for the source variable, without passing through any other read.
+ *
+ * Example:
+ *
+ * ```rb
+ * def m b # defines b_0
+ * i = 0 # defines i_0
+ * puts i
+ * puts i + 1 # last read of i_0
+ * if b # last read of b_0
+ * i = 1 # defines i_1
+ * puts i
+ * puts i + 1 # last read of i_1
+ * else
+ * i = 2 # defines i_2
+ * puts i
+ * puts i + 1 # last read of i_2
+ * end
+ * # defines i_3 = phi(i_1, i_2)
+ * puts i # last read of i3
+ * end
+ * ```
+ */
+ final VariableReadAccessCfgNode getALastRead() { SsaImpl::lastRead(this, result) }
+
+ /**
+ * Holds if `read1` and `read2` are adjacent reads of this SSA definition.
+ * That is, `read2` can be reached from `read1` without passing through
+ * another read.
+ *
+ * Example:
+ *
+ * ```rb
+ * def m b
+ * i = 0 # defines i_0
+ * puts i # reads i_0 (read1)
+ * puts i + 1 # reads i_0 (read2)
+ * if b
+ * i = 1 # defines i_1
+ * puts i # reads i_1 (read1)
+ * puts i + 1 # reads i_1 (read2)
+ * else
+ * i = 2 # defines i_2
+ * puts i # reads i_2 (read1)
+ * puts i + 1 # reads i_2 (read2)
+ * end
+ * puts i
+ * end
+ * ```
+ */
+ final predicate hasAdjacentReads(
+ VariableReadAccessCfgNode read1, VariableReadAccessCfgNode read2
+ ) {
+ SsaImpl::adjacentReadPair(this, read1, read2)
+ }
+
+ /**
+ * Gets an SSA definition whose value can flow to this one in one step. This
+ * includes inputs to phi nodes and the prior definitions of uncertain writes.
+ */
+ private Definition getAPhiInputOrPriorDefinition() {
+ result = this.(PhiNode).getAnInput() or
+ result = this.(CapturedCallDefinition).getPriorDefinition()
+ }
+
+ /**
+ * Gets a definition that ultimately defines this SSA definition and is
+ * not itself a phi node.
+ *
+ * Example:
+ *
+ * ```rb
+ * def m b
+ * i = 0 # defines i_0
+ * puts i
+ * puts i + 1
+ * if b
+ * i = 1 # defines i_1
+ * puts i
+ * puts i + 1
+ * else
+ * i = 2 # defines i_2
+ * puts i
+ * puts i + 1
+ * end
+ * # defines i_3 = phi(i_1, i_2); ultimate definitions are i_1 and i_2
+ * puts i
+ * end
+ * ```
+ */
+ final Definition getAnUltimateDefinition() {
+ result = this.getAPhiInputOrPriorDefinition*() and
+ not result instanceof PhiNode
+ }
+
+ override string toString() { result = this.getControlFlowNode().toString() }
+
+ /** Gets the location of this SSA definition. */
+ Location getLocation() { result = this.getControlFlowNode().getLocation() }
+ }
+
+ /**
+ * An SSA definition that corresponds to a write. For example `x = 10` in
+ *
+ * ```rb
+ * x = 10
+ * puts x
+ * ```
+ */
+ class WriteDefinition extends Definition, SsaImplCommon::WriteDefinition {
+ private VariableWriteAccess write;
+
+ WriteDefinition() {
+ exists(BasicBlock bb, int i, Variable v |
+ this.definesAt(v, bb, i) and
+ SsaImpl::variableWriteActual(bb, i, v, write)
+ )
+ }
+
+ /** Gets the underlying write access. */
+ final VariableWriteAccess getWriteAccess() { result = write }
+
+ /**
+ * Holds if this SSA definition represents a direct assignment of `value`
+ * to the underlying variable.
+ */
+ predicate assigns(CfgNodes::ExprCfgNode value) {
+ exists(CfgNodes::ExprNodes::AssignExprCfgNode a, BasicBlock bb, int i |
+ this.definesAt(_, bb, i) and
+ a = bb.getNode(i) and
+ value = a.getRhs()
+ )
+ }
+
+ final override string toString() { result = Definition.super.toString() }
+
+ final override Location getLocation() { result = this.getControlFlowNode().getLocation() }
+ }
+
+ /**
+ * An SSA definition that corresponds to the value of `self` upon entry to a method, class or module.
+ */
+ class SelfDefinition extends Definition, SsaImplCommon::WriteDefinition {
+ private SelfVariable v;
+
+ SelfDefinition() {
+ exists(BasicBlock bb, int i |
+ this.definesAt(v, bb, i) and
+ not SsaImpl::capturedEntryWrite(bb, i, v)
+ )
+ }
+
+ final override string toString() { result = "self (" + v.getDeclaringScope() + ")" }
+
+ final override Location getLocation() { result = this.getControlFlowNode().getLocation() }
+ }
+
+ /**
+ * An SSA definition inserted at the beginning of a scope to represent an
+ * uninitialized local variable. For example, in
+ *
+ * ```rb
+ * def m
+ * x = 10 if b
+ * puts x
+ * end
+ * ```
+ *
+ * since the assignment to `x` is conditional, an unitialized definition for
+ * `x` is inserted at the start of `m`.
+ */
+ class UninitializedDefinition extends Definition, SsaImplCommon::WriteDefinition {
+ UninitializedDefinition() {
+ exists(BasicBlock bb, int i, Variable v |
+ this.definesAt(v, bb, i) and
+ SsaImpl::uninitializedWrite(bb, i, v)
+ )
+ }
+
+ final override string toString() { result = "" }
+
+ final override Location getLocation() { result = this.getBasicBlock().getLocation() }
+ }
+
+ /**
+ * An SSA definition inserted at the beginning of a scope to represent a
+ * captured local variable. For example, in
+ *
+ * ```rb
+ * def m x
+ * y = 0
+ * x.times do |x|
+ * y += x
+ * end
+ * return y
+ * end
+ * ```
+ *
+ * an entry definition for `y` is inserted at the start of the `do` block.
+ */
+ class CapturedEntryDefinition extends Definition, SsaImplCommon::WriteDefinition {
+ CapturedEntryDefinition() {
+ exists(BasicBlock bb, int i, Variable v |
+ this.definesAt(v, bb, i) and
+ SsaImpl::capturedEntryWrite(bb, i, v)
+ )
+ }
+
+ final override string toString() { result = "" }
+
+ override Location getLocation() { result = this.getBasicBlock().getLocation() }
+ }
+
+ /**
+ * An SSA definition inserted at a call that may update the value of a captured
+ * variable. For example, in
+ *
+ * ```rb
+ * def m x
+ * y = 0
+ * x.times do |x|
+ * y += x
+ * end
+ * return y
+ * end
+ * ```
+ *
+ * a definition for `y` is inserted at the call to `times`.
+ */
+ class CapturedCallDefinition extends Definition, SsaImplCommon::UncertainWriteDefinition {
+ CapturedCallDefinition() {
+ exists(Variable v, BasicBlock bb, int i |
+ this.definesAt(v, bb, i) and
+ SsaImpl::capturedCallWrite(bb, i, v)
+ )
+ }
+
+ /**
+ * Gets the immediately preceding definition. Since this update is uncertain,
+ * the value from the preceding definition might still be valid.
+ */
+ final Definition getPriorDefinition() { result = SsaImpl::uncertainWriteDefinitionInput(this) }
+
+ override string toString() { result = this.getControlFlowNode().toString() }
+ }
+
+ /**
+ * A phi node. For example, in
+ *
+ * ```rb
+ * if b
+ * x = 0
+ * else
+ * x = 1
+ * end
+ * puts x
+ * ```
+ *
+ * a phi node for `x` is inserted just before the call `puts x`.
+ */
+ class PhiNode extends Definition, SsaImplCommon::PhiNode {
+ /**
+ * Gets an input of this phi node.
+ *
+ * Example:
+ *
+ * ```rb
+ * def m b
+ * i = 0 # defines i_0
+ * puts i
+ * puts i + 1
+ * if b
+ * i = 1 # defines i_1
+ * puts i
+ * puts i + 1
+ * else
+ * i = 2 # defines i_2
+ * puts i
+ * puts i + 1
+ * end
+ * # defines i_3 = phi(i_1, i_2); inputs are i_1 and i_2
+ * puts i
+ * end
+ * ```
+ */
+ final Definition getAnInput() { this.hasInputFromBlock(result, _) }
+
+ /** Holds if `inp` is an input to this phi node along the edge originating in `bb`. */
+ predicate hasInputFromBlock(Definition inp, BasicBlock bb) {
+ inp = SsaImpl::phiHasInputFromBlock(this, bb)
+ }
+
+ private string getSplitString() {
+ result = this.getBasicBlock().getFirstNode().(CfgNodes::AstCfgNode).getSplitsString()
+ }
+
+ override string toString() {
+ exists(string prefix |
+ prefix = "[" + this.getSplitString() + "] "
+ or
+ not exists(this.getSplitString()) and
+ prefix = ""
+ |
+ result = prefix + "phi"
+ )
+ }
+
+ /*
+ * The location of a phi node is the same as the location of the first node
+ * in the basic block in which it is defined.
+ *
+ * Strictly speaking, the node is *before* the first node, but such a location
+ * does not exist in the source program.
+ */
+
+ final override Location getLocation() {
+ result = this.getBasicBlock().getFirstNode().getLocation()
+ }
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll
new file mode 100644
index 00000000000..f6190bbf6b2
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll
@@ -0,0 +1,459 @@
+private import ruby
+private import codeql.ruby.CFG
+private import DataFlowPrivate
+private import codeql.ruby.typetracking.TypeTracker
+private import codeql.ruby.ast.internal.Module
+private import FlowSummaryImpl as FlowSummaryImpl
+private import codeql.ruby.dataflow.FlowSummary
+
+newtype TReturnKind =
+ TNormalReturnKind() or
+ TBreakReturnKind()
+
+/**
+ * Gets a node that can read the value returned from `call` with return kind
+ * `kind`.
+ */
+OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { call = result.getCall(kind) }
+
+/**
+ * A return kind. A return kind describes how a value can be returned
+ * from a callable.
+ */
+abstract class ReturnKind extends TReturnKind {
+ /** Gets a textual representation of this position. */
+ abstract string toString();
+}
+
+/**
+ * A value returned from a callable using a `return` statement or an expression
+ * body, that is, a "normal" return.
+ */
+class NormalReturnKind extends ReturnKind, TNormalReturnKind {
+ override string toString() { result = "return" }
+}
+
+/**
+ * A value returned from a callable using a `break` statement.
+ */
+class BreakReturnKind extends ReturnKind, TBreakReturnKind {
+ override string toString() { result = "break" }
+}
+
+/** A callable defined in library code, identified by a unique string. */
+abstract class LibraryCallable extends string {
+ bindingset[this]
+ LibraryCallable() { any() }
+
+ /** Gets a call to this library callable. */
+ abstract Call getACall();
+}
+
+/**
+ * A callable. This includes callables from source code, as well as callables
+ * defined in library code.
+ */
+class DataFlowCallable extends TDataFlowCallable {
+ /** Gets the underlying source code callable, if any. */
+ Callable asCallable() { this = TCfgScope(result) }
+
+ /** Gets the underlying library callable, if any. */
+ LibraryCallable asLibraryCallable() { this = TLibraryCallable(result) }
+
+ /** Gets a textual representation of this callable. */
+ string toString() { result = [this.asCallable().toString(), this.asLibraryCallable()] }
+
+ /** Gets the location of this callable. */
+ Location getLocation() { result = this.asCallable().getLocation() }
+}
+
+/**
+ * A call. This includes calls from source code, as well as call(back)s
+ * inside library callables with a flow summary.
+ */
+class DataFlowCall extends TDataFlowCall {
+ /** Gets the enclosing callable. */
+ DataFlowCallable getEnclosingCallable() { none() }
+
+ /** Gets the underlying source code call, if any. */
+ CfgNodes::ExprNodes::CallCfgNode asCall() { none() }
+
+ /** Gets a textual representation of this call. */
+ string toString() { none() }
+
+ /** Gets the location of this call. */
+ Location getLocation() { none() }
+
+ /**
+ * Holds if this element is at the specified location.
+ * The location spans column `startcolumn` of line `startline` to
+ * column `endcolumn` of line `endline` in file `filepath`.
+ * For more information, see
+ * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
+ */
+ predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
+ this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ }
+}
+
+/**
+ * A synthesized call inside a callable with a flow summary.
+ *
+ * For example, in
+ * ```rb
+ * ints.each do |i|
+ * puts i
+ * end
+ * ```
+ *
+ * there is a call to the block argument inside `each`.
+ */
+class SummaryCall extends DataFlowCall, TSummaryCall {
+ private FlowSummaryImpl::Public::SummarizedCallable c;
+ private DataFlow::Node receiver;
+
+ SummaryCall() { this = TSummaryCall(c, receiver) }
+
+ /** Gets the data flow node that this call targets. */
+ DataFlow::Node getReceiver() { result = receiver }
+
+ override DataFlowCallable getEnclosingCallable() { result = c }
+
+ override string toString() { result = "[summary] call to " + receiver + " in " + c }
+
+ override Location getLocation() { result = c.getLocation() }
+}
+
+private class NormalCall extends DataFlowCall, TNormalCall {
+ private CfgNodes::ExprNodes::CallCfgNode c;
+
+ NormalCall() { this = TNormalCall(c) }
+
+ override CfgNodes::ExprNodes::CallCfgNode asCall() { result = c }
+
+ override DataFlowCallable getEnclosingCallable() { result = TCfgScope(c.getScope()) }
+
+ override string toString() { result = c.toString() }
+
+ override Location getLocation() { result = c.getLocation() }
+}
+
+pragma[nomagic]
+private predicate methodCall(
+ CfgNodes::ExprNodes::CallCfgNode call, DataFlow::LocalSourceNode sourceNode, string method
+) {
+ exists(DataFlow::Node nodeTo |
+ method = call.getExpr().(MethodCall).getMethodName() and
+ nodeTo.asExpr() = call.getReceiver() and
+ sourceNode.flowsTo(nodeTo)
+ )
+}
+
+private Block yieldCall(CfgNodes::ExprNodes::CallCfgNode call) {
+ call.getExpr() instanceof YieldCall and
+ exists(BlockParameterNode node |
+ node = trackBlock(result) and
+ node.getMethod() = call.getExpr().getEnclosingMethod()
+ )
+}
+
+pragma[nomagic]
+private predicate superCall(CfgNodes::ExprNodes::CallCfgNode call, Module superClass, string method) {
+ call.getExpr() instanceof SuperCall and
+ exists(Module tp |
+ tp = call.getExpr().getEnclosingModule().getModule() and
+ superClass = tp.getSuperClass() and
+ method = call.getExpr().getEnclosingMethod().getName()
+ )
+}
+
+pragma[nomagic]
+private predicate instanceMethodCall(CfgNodes::ExprNodes::CallCfgNode call, Module tp, string method) {
+ exists(DataFlow::LocalSourceNode sourceNode |
+ methodCall(call, sourceNode, method) and
+ sourceNode = trackInstance(tp)
+ )
+}
+
+cached
+private module Cached {
+ cached
+ newtype TDataFlowCallable =
+ TCfgScope(CfgScope scope) or
+ TLibraryCallable(LibraryCallable callable)
+
+ cached
+ newtype TDataFlowCall =
+ TNormalCall(CfgNodes::ExprNodes::CallCfgNode c) or
+ TSummaryCall(FlowSummaryImpl::Public::SummarizedCallable c, DataFlow::Node receiver) {
+ FlowSummaryImpl::Private::summaryCallbackRange(c, receiver)
+ }
+
+ cached
+ CfgScope getTarget(CfgNodes::ExprNodes::CallCfgNode call) {
+ // Temporarily disable operation resolution (due to bad performance)
+ not call.getExpr() instanceof Operation and
+ (
+ exists(string method |
+ exists(Module tp |
+ instanceMethodCall(call, tp, method) and
+ result = lookupMethod(tp, method) and
+ if result.(Method).isPrivate()
+ then
+ exists(Self self |
+ self = call.getReceiver().getExpr() and
+ pragma[only_bind_out](self.getEnclosingModule().getModule().getSuperClass*()) =
+ pragma[only_bind_out](result.getEnclosingModule().getModule())
+ ) and
+ // For now, we restrict the scope of top-level declarations to their file.
+ // This may remove some plausible targets, but also removes a lot of
+ // implausible targets
+ if result.getEnclosingModule() instanceof Toplevel
+ then result.getFile() = call.getFile()
+ else any()
+ else any()
+ )
+ or
+ exists(DataFlow::LocalSourceNode sourceNode |
+ methodCall(call, sourceNode, method) and
+ sourceNode = trackSingletonMethod(result, method)
+ )
+ )
+ or
+ exists(Module superClass, string method |
+ superCall(call, superClass, method) and
+ result = lookupMethod(superClass, method)
+ )
+ or
+ result = yieldCall(call)
+ )
+ }
+}
+
+import Cached
+
+private DataFlow::LocalSourceNode trackInstance(Module tp, TypeTracker t) {
+ t.start() and
+ (
+ result.asExpr().getExpr() instanceof NilLiteral and tp = TResolved("NilClass")
+ or
+ result.asExpr().getExpr().(BooleanLiteral).isFalse() and tp = TResolved("FalseClass")
+ or
+ result.asExpr().getExpr().(BooleanLiteral).isTrue() and tp = TResolved("TrueClass")
+ or
+ result.asExpr().getExpr() instanceof IntegerLiteral and tp = TResolved("Integer")
+ or
+ result.asExpr().getExpr() instanceof FloatLiteral and tp = TResolved("Float")
+ or
+ result.asExpr().getExpr() instanceof RationalLiteral and tp = TResolved("Rational")
+ or
+ result.asExpr().getExpr() instanceof ComplexLiteral and tp = TResolved("Complex")
+ or
+ result.asExpr().getExpr() instanceof StringlikeLiteral and tp = TResolved("String")
+ or
+ exists(ConstantReadAccess array, MethodCall mc |
+ result.asExpr().getExpr() = mc and
+ mc.getMethodName() = "[]" and
+ mc.getReceiver() = array and
+ array.getName() = "Array" and
+ array.hasGlobalScope() and
+ tp = TResolved("Array")
+ )
+ or
+ result.asExpr().getExpr() instanceof HashLiteral and tp = TResolved("Hash")
+ or
+ result.asExpr().getExpr() instanceof MethodBase and tp = TResolved("Symbol")
+ or
+ result.asParameter() instanceof BlockParameter and tp = TResolved("Proc")
+ or
+ result.asExpr().getExpr() instanceof Lambda and tp = TResolved("Proc")
+ or
+ exists(CfgNodes::ExprNodes::CallCfgNode call, DataFlow::Node nodeTo |
+ call.getExpr().(MethodCall).getMethodName() = "new" and
+ nodeTo.asExpr() = call.getReceiver() and
+ trackModule(tp).flowsTo(nodeTo) and
+ result.asExpr() = call
+ )
+ or
+ // `self` in method
+ exists(Self self, Method enclosing |
+ self = result.asExpr().getExpr() and
+ enclosing = self.getEnclosingMethod() and
+ tp = enclosing.getEnclosingModule().getModule() and
+ not self.getEnclosingModule().getEnclosingMethod() = enclosing
+ )
+ or
+ // `self` in singleton method
+ exists(Self self, MethodBase enclosing |
+ self = result.asExpr().getExpr() and
+ flowsToSingletonMethodObject(trackInstance(tp), enclosing) and
+ enclosing = self.getEnclosingMethod() and
+ not self.getEnclosingModule().getEnclosingMethod() = enclosing
+ )
+ or
+ // `self` in top-level
+ exists(Self self, Toplevel enclosing |
+ self = result.asExpr().getExpr() and
+ enclosing = self.getEnclosingModule() and
+ tp = TResolved("Object") and
+ not self.getEnclosingMethod().getEnclosingModule() = enclosing
+ )
+ or
+ // a module or class
+ exists(Module m |
+ result = trackModule(m) and
+ if m.isClass() then tp = TResolved("Class") else tp = TResolved("Module")
+ )
+ )
+ or
+ exists(TypeTracker t2, StepSummary summary |
+ result = trackInstanceRec(tp, t2, summary) and t = t2.append(summary)
+ )
+}
+
+pragma[nomagic]
+private DataFlow::LocalSourceNode trackInstanceRec(Module tp, TypeTracker t, StepSummary summary) {
+ StepSummary::step(trackInstance(tp, t), result, summary)
+}
+
+private DataFlow::LocalSourceNode trackInstance(Module tp) {
+ result = trackInstance(tp, TypeTracker::end())
+}
+
+private DataFlow::LocalSourceNode trackBlock(Block block, TypeTracker t) {
+ t.start() and result.asExpr().getExpr() = block
+ or
+ exists(TypeTracker t2, StepSummary summary |
+ result = trackBlockRec(block, t2, summary) and t = t2.append(summary)
+ )
+}
+
+pragma[nomagic]
+private DataFlow::LocalSourceNode trackBlockRec(Block block, TypeTracker t, StepSummary summary) {
+ StepSummary::step(trackBlock(block, t), result, summary)
+}
+
+private DataFlow::LocalSourceNode trackBlock(Block block) {
+ result = trackBlock(block, TypeTracker::end())
+}
+
+private predicate singletonMethod(MethodBase method, Expr object) {
+ object = method.(SingletonMethod).getObject()
+ or
+ exists(SingletonClass cls |
+ object = cls.getValue() and method instanceof Method and method = cls.getAMethod()
+ )
+}
+
+pragma[nomagic]
+private predicate flowsToSingletonMethodObject(DataFlow::LocalSourceNode nodeFrom, MethodBase method) {
+ exists(DataFlow::LocalSourceNode nodeTo |
+ nodeFrom.flowsTo(nodeTo) and
+ singletonMethod(method, nodeTo.asExpr().getExpr())
+ )
+}
+
+pragma[nomagic]
+private predicate moduleFlowsToSingletonMethodObject(Module m, MethodBase method) {
+ flowsToSingletonMethodObject(trackModule(m), method)
+}
+
+pragma[nomagic]
+private DataFlow::LocalSourceNode trackSingletonMethod0(MethodBase method, TypeTracker t) {
+ t.start() and
+ (
+ flowsToSingletonMethodObject(result, method)
+ or
+ exists(Module m | result = trackModule(m) and moduleFlowsToSingletonMethodObject(m, method))
+ )
+ or
+ exists(TypeTracker t2, StepSummary summary |
+ result = trackSingletonMethod0Rec(method, t2, summary) and t = t2.append(summary)
+ )
+}
+
+pragma[nomagic]
+private DataFlow::LocalSourceNode trackSingletonMethod0Rec(
+ MethodBase method, TypeTracker t, StepSummary summary
+) {
+ StepSummary::step(trackSingletonMethod0(method, t), result, summary)
+}
+
+pragma[nomagic]
+private DataFlow::LocalSourceNode trackSingletonMethod(MethodBase m, string name) {
+ result = trackSingletonMethod0(m, TypeTracker::end()) and
+ name = m.getName()
+}
+
+private DataFlow::Node selfInModule(Module tp) {
+ exists(Self self, ModuleBase enclosing |
+ self = result.asExpr().getExpr() and
+ enclosing = self.getEnclosingModule() and
+ tp = enclosing.getModule() and
+ not self.getEnclosingMethod().getEnclosingModule() = enclosing
+ )
+}
+
+private DataFlow::LocalSourceNode trackModule(Module tp, TypeTracker t) {
+ t.start() and
+ (
+ // ConstantReadAccess to Module
+ resolveScopeExpr(result.asExpr().getExpr()) = tp
+ or
+ // `self` reference to Module
+ result = selfInModule(tp)
+ )
+ or
+ exists(TypeTracker t2, StepSummary summary |
+ result = trackModuleRec(tp, t2, summary) and t = t2.append(summary)
+ )
+}
+
+pragma[nomagic]
+private DataFlow::LocalSourceNode trackModuleRec(Module tp, TypeTracker t, StepSummary summary) {
+ StepSummary::step(trackModule(tp, t), result, summary)
+}
+
+private DataFlow::LocalSourceNode trackModule(Module tp) {
+ result = trackModule(tp, TypeTracker::end())
+}
+
+/** Gets a viable run-time target for the call `call`. */
+DataFlowCallable viableCallable(DataFlowCall call) {
+ result = TCfgScope(getTarget(call.asCall())) and
+ not call.asCall().getExpr() instanceof YieldCall // handled by `lambdaCreation`/`lambdaCall`
+ or
+ exists(LibraryCallable callable |
+ result = TLibraryCallable(callable) and
+ call.asCall().getExpr() = callable.getACall()
+ )
+}
+
+/**
+ * Holds if the set of viable implementations that can be called by `call`
+ * might be improved by knowing the call context. This is the case if the
+ * qualifier accesses a parameter of the enclosing callable `c` (including
+ * the implicit `self` parameter).
+ */
+predicate mayBenefitFromCallContext(DataFlowCall call, DataFlowCallable c) { none() }
+
+/**
+ * Gets a viable dispatch target of `call` in the context `ctx`. This is
+ * restricted to those `call`s for which a context might make a difference.
+ */
+DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) { none() }
+
+/**
+ * Holds if `e` is an `ExprNode` that may be returned by a call to `c`.
+ */
+predicate exprNodeReturnedFrom(DataFlow::ExprNode e, Callable c) {
+ exists(ReturningNode r |
+ nodeGetEnclosingCallable(r).asCallable() = c and
+ (
+ r.(ExplicitReturnNode).getReturningNode().getReturnedValueNode() = e.asExpr() or
+ r.(ExprReturnNode) = e
+ )
+ )
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl.qll
new file mode 100644
index 00000000000..08030e0b35b
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl.qll
@@ -0,0 +1,4667 @@
+/**
+ * Provides an implementation of global (interprocedural) data flow. This file
+ * re-exports the local (intraprocedural) data flow analysis from
+ * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed
+ * through the `Configuration` class. This file exists in several identical
+ * copies, allowing queries to use multiple `Configuration` classes that depend
+ * on each other without introducing mutual recursion among those configurations.
+ */
+
+private import DataFlowImplCommon
+private import DataFlowImplSpecific::Private
+import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
+
+/**
+ * A configuration of interprocedural data flow analysis. This defines
+ * sources, sinks, and any other configurable aspect of the analysis. Each
+ * use of the global data flow library must define its own unique extension
+ * of this abstract class. To create a configuration, extend this class with
+ * a subclass whose characteristic predicate is a unique singleton string.
+ * For example, write
+ *
+ * ```ql
+ * class MyAnalysisConfiguration extends DataFlow::Configuration {
+ * MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" }
+ * // Override `isSource` and `isSink`.
+ * // Optionally override `isBarrier`.
+ * // Optionally override `isAdditionalFlowStep`.
+ * }
+ * ```
+ * Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and
+ * the edges are those data-flow steps that preserve the value of the node
+ * along with any additional edges defined by `isAdditionalFlowStep`.
+ * Specifying nodes in `isBarrier` will remove those nodes from the graph, and
+ * specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going
+ * and/or out-going edges from those nodes, respectively.
+ *
+ * Then, to query whether there is flow between some `source` and `sink`,
+ * write
+ *
+ * ```ql
+ * exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink))
+ * ```
+ *
+ * Multiple configurations can coexist, but two classes extending
+ * `DataFlow::Configuration` should never depend on each other. One of them
+ * should instead depend on a `DataFlow2::Configuration`, a
+ * `DataFlow3::Configuration`, or a `DataFlow4::Configuration`.
+ */
+abstract class Configuration extends string {
+ bindingset[this]
+ Configuration() { any() }
+
+ /**
+ * Holds if `source` is a relevant data flow source.
+ */
+ abstract predicate isSource(Node source);
+
+ /**
+ * Holds if `sink` is a relevant data flow sink.
+ */
+ abstract predicate isSink(Node sink);
+
+ /**
+ * Holds if data flow through `node` is prohibited. This completely removes
+ * `node` from the data flow graph.
+ */
+ predicate isBarrier(Node node) { none() }
+
+ /** Holds if data flow into `node` is prohibited. */
+ predicate isBarrierIn(Node node) { none() }
+
+ /** Holds if data flow out of `node` is prohibited. */
+ predicate isBarrierOut(Node node) { none() }
+
+ /** Holds if data flow through nodes guarded by `guard` is prohibited. */
+ predicate isBarrierGuard(BarrierGuard guard) { none() }
+
+ /**
+ * Holds if the additional flow step from `node1` to `node2` must be taken
+ * into account in the analysis.
+ */
+ predicate isAdditionalFlowStep(Node node1, Node node2) { none() }
+
+ /**
+ * Holds if an arbitrary number of implicit read steps of content `c` may be
+ * taken at `node`.
+ */
+ predicate allowImplicitRead(Node node, Content c) { none() }
+
+ /**
+ * Gets the virtual dispatch branching limit when calculating field flow.
+ * This can be overridden to a smaller value to improve performance (a
+ * value of 0 disables field flow), or a larger value to get more results.
+ */
+ int fieldFlowBranchLimit() { result = 2 }
+
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
+ /**
+ * Holds if data may flow from `source` to `sink` for this configuration.
+ */
+ predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) }
+
+ /**
+ * Holds if data may flow from `source` to `sink` for this configuration.
+ *
+ * The corresponding paths are generated from the end-points and the graph
+ * included in the module `PathGraph`.
+ */
+ predicate hasFlowPath(PathNode source, PathNode sink) { flowsTo(source, sink, _, _, this) }
+
+ /**
+ * Holds if data may flow from some source to `sink` for this configuration.
+ */
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
+
+ /**
+ * Holds if data may flow from some source to `sink` for this configuration.
+ */
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
+
+ /**
+ * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
+ * measured in approximate number of interprocedural steps.
+ */
+ int explorationLimit() { none() }
+
+ /**
+ * Holds if there is a partial data flow path from `source` to `node`. The
+ * approximate distance between `node` and the closest source is `dist` and
+ * is restricted to be less than or equal to `explorationLimit()`. This
+ * predicate completely disregards sink definitions.
+ *
+ * This predicate is intended for data-flow exploration and debugging and may
+ * perform poorly if the number of sources is too big and/or the exploration
+ * limit is set too high without using barriers.
+ *
+ * This predicate is disabled (has no results) by default. Override
+ * `explorationLimit()` with a suitable number to enable this predicate.
+ *
+ * To use this in a `path-problem` query, import the module `PartialPathGraph`.
+ */
+ final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) {
+ partialFlow(source, node, this) and
+ dist = node.getSourceDistance()
+ }
+
+ /**
+ * Holds if there is a partial data flow path from `node` to `sink`. The
+ * approximate distance between `node` and the closest sink is `dist` and
+ * is restricted to be less than or equal to `explorationLimit()`. This
+ * predicate completely disregards source definitions.
+ *
+ * This predicate is intended for data-flow exploration and debugging and may
+ * perform poorly if the number of sinks is too big and/or the exploration
+ * limit is set too high without using barriers.
+ *
+ * This predicate is disabled (has no results) by default. Override
+ * `explorationLimit()` with a suitable number to enable this predicate.
+ *
+ * To use this in a `path-problem` query, import the module `PartialPathGraph`.
+ *
+ * Note that reverse flow has slightly lower precision than the corresponding
+ * forward flow, as reverse flow disregards type pruning among other features.
+ */
+ final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) {
+ revPartialFlow(node, sink, this) and
+ dist = node.getSinkDistance()
+ }
+}
+
+/**
+ * This class exists to prevent mutual recursion between the user-overridden
+ * member predicates of `Configuration` and the rest of the data-flow library.
+ * Good performance cannot be guaranteed in the presence of such recursion, so
+ * it should be replaced by using more than one copy of the data flow library.
+ */
+abstract private class ConfigurationRecursionPrevention extends Configuration {
+ bindingset[this]
+ ConfigurationRecursionPrevention() { any() }
+
+ override predicate hasFlow(Node source, Node sink) {
+ strictcount(Node n | this.isSource(n)) < 0
+ or
+ strictcount(Node n | this.isSink(n)) < 0
+ or
+ strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0
+ or
+ super.hasFlow(source, sink)
+ }
+}
+
+private newtype TNodeEx =
+ TNodeNormal(Node n) or
+ TNodeImplicitRead(Node n, boolean hasRead) {
+ any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true]
+ }
+
+private class NodeEx extends TNodeEx {
+ string toString() {
+ result = this.asNode().toString()
+ or
+ exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]")
+ }
+
+ Node asNode() { this = TNodeNormal(result) }
+
+ predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) }
+
+ Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) }
+
+ pragma[nomagic]
+ private DataFlowCallable getEnclosingCallable0() {
+ nodeEnclosingCallable(this.projectToNode(), result)
+ }
+
+ pragma[inline]
+ DataFlowCallable getEnclosingCallable() {
+ pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result)
+ }
+
+ pragma[nomagic]
+ private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) }
+
+ pragma[inline]
+ DataFlowType getDataFlowType() {
+ pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result)
+ }
+
+ predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
+ this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ }
+}
+
+private class ArgNodeEx extends NodeEx {
+ ArgNodeEx() { this.asNode() instanceof ArgNode }
+}
+
+private class ParamNodeEx extends NodeEx {
+ ParamNodeEx() { this.asNode() instanceof ParamNode }
+
+ predicate isParameterOf(DataFlowCallable c, int i) {
+ this.asNode().(ParamNode).isParameterOf(c, i)
+ }
+
+ int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
+}
+
+private class RetNodeEx extends NodeEx {
+ RetNodeEx() { this.asNode() instanceof ReturnNodeExt }
+
+ ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) }
+
+ ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() }
+}
+
+private predicate inBarrier(NodeEx node, Configuration config) {
+ exists(Node n |
+ node.asNode() = n and
+ config.isBarrierIn(n) and
+ config.isSource(n)
+ )
+}
+
+private predicate outBarrier(NodeEx node, Configuration config) {
+ exists(Node n |
+ node.asNode() = n and
+ config.isBarrierOut(n) and
+ config.isSink(n)
+ )
+}
+
+private predicate fullBarrier(NodeEx node, Configuration config) {
+ exists(Node n | node.asNode() = n |
+ config.isBarrier(n)
+ or
+ config.isBarrierIn(n) and
+ not config.isSource(n)
+ or
+ config.isBarrierOut(n) and
+ not config.isSink(n)
+ or
+ exists(BarrierGuard g |
+ config.isBarrierGuard(g) and
+ n = g.getAGuardedNode()
+ )
+ )
+}
+
+pragma[nomagic]
+private predicate sourceNode(NodeEx node, Configuration config) { config.isSource(node.asNode()) }
+
+pragma[nomagic]
+private predicate sinkNode(NodeEx node, Configuration config) { config.isSink(node.asNode()) }
+
+/**
+ * Holds if data can flow in one local step from `node1` to `node2`.
+ */
+private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) {
+ exists(Node n1, Node n2 |
+ node1.asNode() = n1 and
+ node2.asNode() = n2 and
+ simpleLocalFlowStepExt(n1, n2) and
+ not outBarrier(node1, config) and
+ not inBarrier(node2, config) and
+ not fullBarrier(node1, config) and
+ not fullBarrier(node2, config)
+ )
+ or
+ exists(Node n |
+ config.allowImplicitRead(n, _) and
+ node1.asNode() = n and
+ node2.isImplicitReadNode(n, false)
+ )
+}
+
+/**
+ * Holds if the additional step from `node1` to `node2` does not jump between callables.
+ */
+private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) {
+ exists(Node n1, Node n2 |
+ node1.asNode() = n1 and
+ node2.asNode() = n2 and
+ config.isAdditionalFlowStep(n1, n2) and
+ getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and
+ not outBarrier(node1, config) and
+ not inBarrier(node2, config) and
+ not fullBarrier(node1, config) and
+ not fullBarrier(node2, config)
+ )
+ or
+ exists(Node n |
+ config.allowImplicitRead(n, _) and
+ node1.isImplicitReadNode(n, true) and
+ node2.asNode() = n
+ )
+}
+
+/**
+ * Holds if data can flow from `node1` to `node2` in a way that discards call contexts.
+ */
+private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
+ exists(Node n1, Node n2 |
+ node1.asNode() = n1 and
+ node2.asNode() = n2 and
+ jumpStepCached(n1, n2) and
+ not outBarrier(node1, config) and
+ not inBarrier(node2, config) and
+ not fullBarrier(node1, config) and
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+/**
+ * Holds if the additional step from `node1` to `node2` jumps between callables.
+ */
+private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) {
+ exists(Node n1, Node n2 |
+ node1.asNode() = n1 and
+ node2.asNode() = n2 and
+ config.isAdditionalFlowStep(n1, n2) and
+ getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and
+ not outBarrier(node1, config) and
+ not inBarrier(node2, config) and
+ not fullBarrier(node1, config) and
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) {
+ read(node1.asNode(), c, node2.asNode())
+ or
+ exists(Node n |
+ node2.isImplicitReadNode(n, true) and
+ node1.isImplicitReadNode(n, _) and
+ config.allowImplicitRead(n, c)
+ )
+}
+
+private predicate store(
+ NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config
+) {
+ store(node1.asNode(), tc, node2.asNode(), contentType) and
+ read(_, tc.getContent(), _, config)
+}
+
+pragma[nomagic]
+private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) {
+ viableReturnPosOut(call, pos, out.asNode())
+}
+
+pragma[nomagic]
+private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) {
+ viableParamArg(call, p.asNode(), arg.asNode())
+}
+
+/**
+ * Holds if field flow should be used for the given configuration.
+ */
+private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private module Stage1 {
+ class ApApprox = Unit;
+
+ class Ap = Unit;
+
+ class ApOption = Unit;
+
+ class Cc = boolean;
+
+ /* Begin: Stage 1 logic. */
+ /**
+ * Holds if `node` is reachable from a source in the configuration `config`.
+ *
+ * The Boolean `cc` records whether the node is reached through an
+ * argument in a call.
+ */
+ predicate fwdFlow(NodeEx node, Cc cc, Configuration config) {
+ not fullBarrier(node, config) and
+ (
+ sourceNode(node, config) and
+ if hasSourceCallCtx(config) then cc = true else cc = false
+ or
+ exists(NodeEx mid |
+ fwdFlow(mid, cc, config) and
+ localFlowStep(mid, node, config)
+ )
+ or
+ exists(NodeEx mid |
+ fwdFlow(mid, cc, config) and
+ additionalLocalFlowStep(mid, node, config)
+ )
+ or
+ exists(NodeEx mid |
+ fwdFlow(mid, _, config) and
+ jumpStep(mid, node, config) and
+ cc = false
+ )
+ or
+ exists(NodeEx mid |
+ fwdFlow(mid, _, config) and
+ additionalJumpStep(mid, node, config) and
+ cc = false
+ )
+ or
+ // store
+ exists(NodeEx mid |
+ useFieldFlow(config) and
+ fwdFlow(mid, cc, config) and
+ store(mid, _, node, _, config) and
+ not outBarrier(mid, config)
+ )
+ or
+ // read
+ exists(Content c |
+ fwdFlowRead(c, node, cc, config) and
+ fwdFlowConsCand(c, config) and
+ not inBarrier(node, config)
+ )
+ or
+ // flow into a callable
+ exists(NodeEx arg |
+ fwdFlow(arg, _, config) and
+ viableParamArgEx(_, node, arg) and
+ cc = true
+ )
+ or
+ // flow out of a callable
+ exists(DataFlowCall call |
+ fwdFlowOut(call, node, false, config) and
+ cc = false
+ or
+ fwdFlowOutFromArg(call, node, config) and
+ fwdFlowIsEntered(call, cc, config)
+ )
+ )
+ }
+
+ private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) }
+
+ pragma[nomagic]
+ private predicate fwdFlowRead(Content c, NodeEx node, Cc cc, Configuration config) {
+ exists(NodeEx mid |
+ fwdFlow(mid, cc, config) and
+ read(mid, c, node, config)
+ )
+ }
+
+ /**
+ * Holds if `c` is the target of a store in the flow covered by `fwdFlow`.
+ */
+ pragma[nomagic]
+ private predicate fwdFlowConsCand(Content c, Configuration config) {
+ exists(NodeEx mid, NodeEx node, TypedContent tc |
+ not fullBarrier(node, config) and
+ useFieldFlow(config) and
+ fwdFlow(mid, _, config) and
+ store(mid, tc, node, _, config) and
+ c = tc.getContent()
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) {
+ exists(RetNodeEx ret |
+ fwdFlow(ret, cc, config) and
+ ret.getReturnPosition() = pos
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) {
+ exists(ReturnPosition pos |
+ fwdFlowReturnPosition(pos, cc, config) and
+ viableReturnPosOutEx(call, pos, out)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) {
+ fwdFlowOut(call, out, true, config)
+ }
+
+ /**
+ * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`.
+ */
+ pragma[nomagic]
+ private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) {
+ exists(ArgNodeEx arg |
+ fwdFlow(arg, cc, config) and
+ viableParamArgEx(call, _, arg)
+ )
+ }
+
+ /**
+ * Holds if `node` is part of a path from a source to a sink in the
+ * configuration `config`.
+ *
+ * The Boolean `toReturn` records whether the node must be returned from
+ * the enclosing callable in order to reach a sink.
+ */
+ pragma[nomagic]
+ predicate revFlow(NodeEx node, boolean toReturn, Configuration config) {
+ revFlow0(node, toReturn, config) and
+ fwdFlow(node, config)
+ }
+
+ pragma[nomagic]
+ private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
+ fwdFlow(node, config) and
+ sinkNode(node, config) and
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
+ or
+ exists(NodeEx mid |
+ localFlowStep(node, mid, config) and
+ revFlow(mid, toReturn, config)
+ )
+ or
+ exists(NodeEx mid |
+ additionalLocalFlowStep(node, mid, config) and
+ revFlow(mid, toReturn, config)
+ )
+ or
+ exists(NodeEx mid |
+ jumpStep(node, mid, config) and
+ revFlow(mid, _, config) and
+ toReturn = false
+ )
+ or
+ exists(NodeEx mid |
+ additionalJumpStep(node, mid, config) and
+ revFlow(mid, _, config) and
+ toReturn = false
+ )
+ or
+ // store
+ exists(Content c |
+ revFlowStore(c, node, toReturn, config) and
+ revFlowConsCand(c, config)
+ )
+ or
+ // read
+ exists(NodeEx mid, Content c |
+ read(node, c, mid, config) and
+ fwdFlowConsCand(c, pragma[only_bind_into](config)) and
+ revFlow(mid, toReturn, pragma[only_bind_into](config))
+ )
+ or
+ // flow into a callable
+ exists(DataFlowCall call |
+ revFlowIn(call, node, false, config) and
+ toReturn = false
+ or
+ revFlowInToReturn(call, node, config) and
+ revFlowIsReturned(call, toReturn, config)
+ )
+ or
+ // flow out of a callable
+ exists(ReturnPosition pos |
+ revFlowOut(pos, config) and
+ node.(RetNodeEx).getReturnPosition() = pos and
+ toReturn = true
+ )
+ }
+
+ /**
+ * Holds if `c` is the target of a read in the flow covered by `revFlow`.
+ */
+ pragma[nomagic]
+ private predicate revFlowConsCand(Content c, Configuration config) {
+ exists(NodeEx mid, NodeEx node |
+ fwdFlow(node, pragma[only_bind_into](config)) and
+ read(node, c, mid, config) and
+ fwdFlowConsCand(c, pragma[only_bind_into](config)) and
+ revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config))
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) {
+ exists(NodeEx mid, TypedContent tc |
+ revFlow(mid, toReturn, pragma[only_bind_into](config)) and
+ fwdFlowConsCand(c, pragma[only_bind_into](config)) and
+ store(node, tc, mid, _, config) and
+ c = tc.getContent()
+ )
+ }
+
+ /**
+ * Holds if `c` is the target of both a read and a store in the flow covered
+ * by `revFlow`.
+ */
+ private predicate revFlowIsReadAndStored(Content c, Configuration conf) {
+ revFlowConsCand(c, conf) and
+ revFlowStore(c, _, _, conf)
+ }
+
+ pragma[nomagic]
+ predicate viableReturnPosOutNodeCandFwd1(
+ DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
+ ) {
+ fwdFlowReturnPosition(pos, _, config) and
+ viableReturnPosOutEx(call, pos, out)
+ }
+
+ pragma[nomagic]
+ private predicate revFlowOut(ReturnPosition pos, Configuration config) {
+ exists(DataFlowCall call, NodeEx out |
+ revFlow(out, _, config) and
+ viableReturnPosOutNodeCandFwd1(call, pos, out, config)
+ )
+ }
+
+ pragma[nomagic]
+ predicate viableParamArgNodeCandFwd1(
+ DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
+ ) {
+ viableParamArgEx(call, p, arg) and
+ fwdFlow(arg, config)
+ }
+
+ pragma[nomagic]
+ private predicate revFlowIn(
+ DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config
+ ) {
+ exists(ParamNodeEx p |
+ revFlow(p, toReturn, config) and
+ viableParamArgNodeCandFwd1(call, p, arg, config)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) {
+ revFlowIn(call, arg, true, config)
+ }
+
+ /**
+ * Holds if an output from `call` is reached in the flow covered by `revFlow`
+ * and data might flow through the target callable resulting in reverse flow
+ * reaching an argument of `call`.
+ */
+ pragma[nomagic]
+ private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) {
+ exists(NodeEx out |
+ revFlow(out, toReturn, config) and
+ fwdFlowOutFromArg(call, out, config)
+ )
+ }
+
+ pragma[nomagic]
+ predicate storeStepCand(
+ NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType,
+ Configuration config
+ ) {
+ exists(Content c |
+ revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and
+ revFlow(node2, pragma[only_bind_into](config)) and
+ store(node1, tc, node2, contentType, config) and
+ c = tc.getContent() and
+ exists(ap1)
+ )
+ }
+
+ pragma[nomagic]
+ predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) {
+ revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and
+ revFlow(n2, pragma[only_bind_into](config)) and
+ read(n1, c, n2, pragma[only_bind_into](config))
+ }
+
+ pragma[nomagic]
+ predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) }
+
+ predicate revFlow(NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) {
+ revFlow(node, toReturn, config) and exists(returnAp) and exists(ap)
+ }
+
+ private predicate throughFlowNodeCand(NodeEx node, Configuration config) {
+ revFlow(node, true, config) and
+ fwdFlow(node, true, config) and
+ not inBarrier(node, config) and
+ not outBarrier(node, config)
+ }
+
+ /** Holds if flow may return from `callable`. */
+ pragma[nomagic]
+ private predicate returnFlowCallableNodeCand(
+ DataFlowCallable callable, ReturnKindExt kind, Configuration config
+ ) {
+ exists(RetNodeEx ret |
+ throughFlowNodeCand(ret, config) and
+ callable = ret.getEnclosingCallable() and
+ kind = ret.getKind()
+ )
+ }
+
+ /**
+ * Holds if flow may enter through `p` and reach a return node making `p` a
+ * candidate for the origin of a summary.
+ */
+ predicate parameterMayFlowThrough(ParamNodeEx p, DataFlowCallable c, Ap ap, Configuration config) {
+ exists(ReturnKindExt kind |
+ throughFlowNodeCand(p, config) and
+ returnFlowCallableNodeCand(c, kind, config) and
+ p.getEnclosingCallable() = c and
+ exists(ap) and
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
+ )
+ }
+
+ pragma[nomagic]
+ predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
+ exists(ArgNodeEx arg, boolean toReturn |
+ revFlow(arg, toReturn, config) and
+ revFlowInToReturn(call, arg, config) and
+ revFlowIsReturned(call, toReturn, config)
+ )
+ }
+
+ predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) {
+ fwd = true and
+ nodes = count(NodeEx node | fwdFlow(node, config)) and
+ fields = count(Content f0 | fwdFlowConsCand(f0, config)) and
+ conscand = -1 and
+ tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config))
+ or
+ fwd = false and
+ nodes = count(NodeEx node | revFlow(node, _, config)) and
+ fields = count(Content f0 | revFlowConsCand(f0, config)) and
+ conscand = -1 and
+ tuples = count(NodeEx n, boolean b | revFlow(n, b, config))
+ }
+ /* End: Stage 1 logic. */
+}
+
+pragma[noinline]
+private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) {
+ Stage1::revFlow(node2, config) and
+ localFlowStep(node1, node2, config)
+}
+
+pragma[noinline]
+private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) {
+ Stage1::revFlow(node2, config) and
+ additionalLocalFlowStep(node1, node2, config)
+}
+
+pragma[nomagic]
+private predicate viableReturnPosOutNodeCand1(
+ DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
+) {
+ Stage1::revFlow(out, config) and
+ Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config)
+}
+
+/**
+ * Holds if data can flow out of `call` from `ret` to `out`, either
+ * through a `ReturnNode` or through an argument that has been mutated, and
+ * that this step is part of a path from a source to a sink.
+ */
+pragma[nomagic]
+private predicate flowOutOfCallNodeCand1(
+ DataFlowCall call, RetNodeEx ret, NodeEx out, Configuration config
+) {
+ viableReturnPosOutNodeCand1(call, ret.getReturnPosition(), out, config) and
+ Stage1::revFlow(ret, config) and
+ not outBarrier(ret, config) and
+ not inBarrier(out, config)
+}
+
+pragma[nomagic]
+private predicate viableParamArgNodeCand1(
+ DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
+) {
+ Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and
+ Stage1::revFlow(arg, config)
+}
+
+/**
+ * Holds if data can flow into `call` and that this step is part of a
+ * path from a source to a sink.
+ */
+pragma[nomagic]
+private predicate flowIntoCallNodeCand1(
+ DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config
+) {
+ viableParamArgNodeCand1(call, p, arg, config) and
+ Stage1::revFlow(p, config) and
+ not outBarrier(arg, config) and
+ not inBarrier(p, config)
+}
+
+/**
+ * Gets the amount of forward branching on the origin of a cross-call path
+ * edge in the graph of paths between sources and sinks that ignores call
+ * contexts.
+ */
+private int branch(NodeEx n1, Configuration conf) {
+ result =
+ strictcount(NodeEx n |
+ flowOutOfCallNodeCand1(_, n1, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf)
+ )
+}
+
+/**
+ * Gets the amount of backward branching on the target of a cross-call path
+ * edge in the graph of paths between sources and sinks that ignores call
+ * contexts.
+ */
+private int join(NodeEx n2, Configuration conf) {
+ result =
+ strictcount(NodeEx n |
+ flowOutOfCallNodeCand1(_, n, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf)
+ )
+}
+
+/**
+ * Holds if data can flow out of `call` from `ret` to `out`, either
+ * through a `ReturnNode` or through an argument that has been mutated, and
+ * that this step is part of a path from a source to a sink. The
+ * `allowsFieldFlow` flag indicates whether the branching is within the limit
+ * specified by the configuration.
+ */
+pragma[nomagic]
+private predicate flowOutOfCallNodeCand1(
+ DataFlowCall call, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, Configuration config
+) {
+ flowOutOfCallNodeCand1(call, ret, out, config) and
+ exists(int b, int j |
+ b = branch(ret, config) and
+ j = join(out, config) and
+ if b.minimum(j) <= config.fieldFlowBranchLimit()
+ then allowsFieldFlow = true
+ else allowsFieldFlow = false
+ )
+}
+
+/**
+ * Holds if data can flow into `call` and that this step is part of a
+ * path from a source to a sink. The `allowsFieldFlow` flag indicates whether
+ * the branching is within the limit specified by the configuration.
+ */
+pragma[nomagic]
+private predicate flowIntoCallNodeCand1(
+ DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config
+) {
+ flowIntoCallNodeCand1(call, arg, p, config) and
+ exists(int b, int j |
+ b = branch(arg, config) and
+ j = join(p, config) and
+ if b.minimum(j) <= config.fieldFlowBranchLimit()
+ then allowsFieldFlow = true
+ else allowsFieldFlow = false
+ )
+}
+
+private module Stage2 {
+ module PrevStage = Stage1;
+
+ class ApApprox = PrevStage::Ap;
+
+ class Ap = boolean;
+
+ class ApNil extends Ap {
+ ApNil() { this = false }
+ }
+
+ bindingset[result, ap]
+ private ApApprox getApprox(Ap ap) { any() }
+
+ private ApNil getApNil(NodeEx node) { PrevStage::revFlow(node, _) and exists(result) }
+
+ bindingset[tc, tail]
+ private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) }
+
+ pragma[inline]
+ private Content getHeadContent(Ap ap) { exists(result) and ap = true }
+
+ class ApOption = BooleanOption;
+
+ ApOption apNone() { result = TBooleanNone() }
+
+ ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
+
+ class Cc = CallContext;
+
+ class CcCall = CallContextCall;
+
+ class CcNoCall = CallContextNoCall;
+
+ Cc ccNone() { result instanceof CallContextAny }
+
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
+ private class LocalCc = Unit;
+
+ bindingset[call, c, outercc]
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
+
+ bindingset[call, c, innercc]
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
+
+ bindingset[node, cc, config]
+ private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
+
+ private predicate localStep(
+ NodeEx node1, NodeEx node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc
+ ) {
+ (
+ preservesValue = true and
+ localFlowStepNodeCand1(node1, node2, config)
+ or
+ preservesValue = false and
+ additionalLocalFlowStepNodeCand1(node1, node2, config)
+ ) and
+ exists(ap) and
+ exists(lcc)
+ }
+
+ private predicate flowOutOfCall = flowOutOfCallNodeCand1/5;
+
+ private predicate flowIntoCall = flowIntoCallNodeCand1/5;
+
+ bindingset[ap, contentType]
+ private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() }
+
+ /* Begin: Stage 2 logic. */
+ private predicate flowCand(NodeEx node, ApApprox apa, Configuration config) {
+ PrevStage::revFlow(node, _, _, apa, config)
+ }
+
+ pragma[nomagic]
+ private predicate flowThroughOutOfCall(
+ DataFlowCall call, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, Configuration config
+ ) {
+ flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and
+ PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and
+ PrevStage::parameterMayFlowThrough(_, ret.getEnclosingCallable(), _,
+ pragma[only_bind_into](config))
+ }
+
+ /**
+ * Holds if `node` is reachable with access path `ap` from a source in the
+ * configuration `config`.
+ *
+ * The call context `cc` records whether the node is reached through an
+ * argument in a call, and if so, `argAp` records the access path of that
+ * argument.
+ */
+ pragma[nomagic]
+ predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
+ flowCand(node, _, config) and
+ sourceNode(node, config) and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
+ argAp = apNone() and
+ ap = getApNil(node)
+ or
+ exists(NodeEx mid, Ap ap0, LocalCc localCc |
+ fwdFlow(mid, cc, argAp, ap0, config) and
+ localCc = getLocalCc(mid, cc, config)
+ |
+ localStep(mid, node, true, _, config, localCc) and
+ ap = ap0
+ or
+ localStep(mid, node, false, ap, config, localCc) and
+ ap0 instanceof ApNil
+ )
+ or
+ exists(NodeEx mid |
+ fwdFlow(mid, _, _, ap, pragma[only_bind_into](config)) and
+ flowCand(node, _, pragma[only_bind_into](config)) and
+ jumpStep(mid, node, config) and
+ cc = ccNone() and
+ argAp = apNone()
+ )
+ or
+ exists(NodeEx mid, ApNil nil |
+ fwdFlow(mid, _, _, nil, pragma[only_bind_into](config)) and
+ flowCand(node, _, pragma[only_bind_into](config)) and
+ additionalJumpStep(mid, node, config) and
+ cc = ccNone() and
+ argAp = apNone() and
+ ap = getApNil(node)
+ )
+ or
+ // store
+ exists(TypedContent tc, Ap ap0 |
+ fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and
+ ap = apCons(tc, ap0)
+ )
+ or
+ // read
+ exists(Ap ap0, Content c |
+ fwdFlowRead(ap0, c, _, node, cc, argAp, config) and
+ fwdFlowConsCand(ap0, c, ap, config)
+ )
+ or
+ // flow into a callable
+ exists(ApApprox apa |
+ fwdFlowIn(_, node, _, cc, _, ap, config) and
+ apa = getApprox(ap) and
+ if PrevStage::parameterMayFlowThrough(node, _, apa, config)
+ then argAp = apSome(ap)
+ else argAp = apNone()
+ )
+ or
+ // flow out of a callable
+ fwdFlowOutNotFromArg(node, cc, argAp, ap, config)
+ or
+ exists(DataFlowCall call, Ap argAp0 |
+ fwdFlowOutFromArg(call, node, argAp0, ap, config) and
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowStore(
+ NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Cc cc, ApOption argAp, Configuration config
+ ) {
+ exists(DataFlowType contentType |
+ fwdFlow(node1, cc, argAp, ap1, config) and
+ PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and
+ typecheckStore(ap1, contentType)
+ )
+ }
+
+ /**
+ * Holds if forward flow with access path `tail` reaches a store of `c`
+ * resulting in access path `cons`.
+ */
+ pragma[nomagic]
+ private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) {
+ exists(TypedContent tc |
+ fwdFlowStore(_, tail, tc, _, _, _, config) and
+ tc.getContent() = c and
+ cons = apCons(tc, tail)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowRead(
+ Ap ap, Content c, NodeEx node1, NodeEx node2, Cc cc, ApOption argAp, Configuration config
+ ) {
+ fwdFlow(node1, cc, argAp, ap, config) and
+ PrevStage::readStepCand(node1, c, node2, config) and
+ getHeadContent(ap) = c
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowIn(
+ DataFlowCall call, ParamNodeEx p, Cc outercc, Cc innercc, ApOption argAp, Ap ap,
+ Configuration config
+ ) {
+ exists(ArgNodeEx arg, boolean allowsFieldFlow |
+ fwdFlow(arg, outercc, argAp, ap, config) and
+ flowIntoCall(call, arg, p, allowsFieldFlow, config) and
+ innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowOutNotFromArg(
+ NodeEx out, Cc ccOut, ApOption argAp, Ap ap, Configuration config
+ ) {
+ exists(
+ DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc,
+ DataFlowCallable inner
+ |
+ fwdFlow(ret, innercc, argAp, ap, config) and
+ flowOutOfCall(call, ret, out, allowsFieldFlow, config) and
+ inner = ret.getEnclosingCallable() and
+ ccOut = getCallContextReturn(inner, call, innercc)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowOutFromArg(
+ DataFlowCall call, NodeEx out, Ap argAp, Ap ap, Configuration config
+ ) {
+ exists(RetNodeEx ret, boolean allowsFieldFlow, CcCall ccc |
+ fwdFlow(ret, ccc, apSome(argAp), ap, config) and
+ flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and
+ ccc.matchesCall(call)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ /**
+ * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`
+ * and data might flow through the target callable and back out at `call`.
+ */
+ pragma[nomagic]
+ private predicate fwdFlowIsEntered(
+ DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config
+ ) {
+ exists(ParamNodeEx p |
+ fwdFlowIn(call, p, cc, _, argAp, ap, config) and
+ PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate storeStepFwd(
+ NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config
+ ) {
+ fwdFlowStore(node1, ap1, tc, node2, _, _, config) and
+ ap2 = apCons(tc, ap1) and
+ fwdFlowRead(ap2, tc.getContent(), _, _, _, _, config)
+ }
+
+ private predicate readStepFwd(
+ NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config
+ ) {
+ fwdFlowRead(ap1, c, n1, n2, _, _, config) and
+ fwdFlowConsCand(ap1, c, ap2, config)
+ }
+
+ pragma[nomagic]
+ private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
+ exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
+ fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
+ pragma[only_bind_into](config)) and
+ fwdFlowOutFromArg(call, out, argAp0, ap, config) and
+ fwdFlowIsEntered(pragma[only_bind_into](call), pragma[only_bind_into](cc),
+ pragma[only_bind_into](argAp), pragma[only_bind_into](argAp0),
+ pragma[only_bind_into](config))
+ )
+ }
+
+ pragma[nomagic]
+ private predicate flowThroughIntoCall(
+ DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config
+ ) {
+ flowIntoCall(call, arg, p, allowsFieldFlow, config) and
+ fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and
+ PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and
+ callMayFlowThroughFwd(call, pragma[only_bind_into](config))
+ }
+
+ /**
+ * Holds if `node` with access path `ap` is part of a path from a source to a
+ * sink in the configuration `config`.
+ *
+ * The Boolean `toReturn` records whether the node must be returned from the
+ * enclosing callable in order to reach a sink, and if so, `returnAp` records
+ * the access path of the returned value.
+ */
+ pragma[nomagic]
+ predicate revFlow(NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) {
+ revFlow0(node, toReturn, returnAp, ap, config) and
+ fwdFlow(node, _, _, ap, config)
+ }
+
+ pragma[nomagic]
+ private predicate revFlow0(
+ NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
+ ) {
+ fwdFlow(node, _, _, ap, config) and
+ sinkNode(node, config) and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
+ returnAp = apNone() and
+ ap instanceof ApNil
+ or
+ exists(NodeEx mid |
+ localStep(node, mid, true, _, config, _) and
+ revFlow(mid, toReturn, returnAp, ap, config)
+ )
+ or
+ exists(NodeEx mid, ApNil nil |
+ fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and
+ localStep(node, mid, false, _, config, _) and
+ revFlow(mid, toReturn, returnAp, nil, pragma[only_bind_into](config)) and
+ ap instanceof ApNil
+ )
+ or
+ exists(NodeEx mid |
+ jumpStep(node, mid, config) and
+ revFlow(mid, _, _, ap, config) and
+ toReturn = false and
+ returnAp = apNone()
+ )
+ or
+ exists(NodeEx mid, ApNil nil |
+ fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and
+ additionalJumpStep(node, mid, config) and
+ revFlow(pragma[only_bind_into](mid), _, _, nil, pragma[only_bind_into](config)) and
+ toReturn = false and
+ returnAp = apNone() and
+ ap instanceof ApNil
+ )
+ or
+ // store
+ exists(Ap ap0, Content c |
+ revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and
+ revFlowConsCand(ap0, c, ap, config)
+ )
+ or
+ // read
+ exists(NodeEx mid, Ap ap0 |
+ revFlow(mid, toReturn, returnAp, ap0, config) and
+ readStepFwd(node, ap, _, mid, ap0, config)
+ )
+ or
+ // flow into a callable
+ revFlowInNotToReturn(node, returnAp, ap, config) and
+ toReturn = false
+ or
+ exists(DataFlowCall call, Ap returnAp0 |
+ revFlowInToReturn(call, node, returnAp0, ap, config) and
+ revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
+ )
+ or
+ // flow out of a callable
+ revFlowOut(_, node, _, _, ap, config) and
+ toReturn = true and
+ if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config)
+ then returnAp = apSome(ap)
+ else returnAp = apNone()
+ }
+
+ pragma[nomagic]
+ private predicate revFlowStore(
+ Ap ap0, Content c, Ap ap, NodeEx node, TypedContent tc, NodeEx mid, boolean toReturn,
+ ApOption returnAp, Configuration config
+ ) {
+ revFlow(mid, toReturn, returnAp, ap0, config) and
+ storeStepFwd(node, ap, tc, mid, ap0, config) and
+ tc.getContent() = c
+ }
+
+ /**
+ * Holds if reverse flow with access path `tail` reaches a read of `c`
+ * resulting in access path `cons`.
+ */
+ pragma[nomagic]
+ private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) {
+ exists(NodeEx mid, Ap tail0 |
+ revFlow(mid, _, _, tail, config) and
+ tail = pragma[only_bind_into](tail0) and
+ readStepFwd(_, cons, c, mid, tail0, config)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revFlowOut(
+ DataFlowCall call, RetNodeEx ret, boolean toReturn, ApOption returnAp, Ap ap,
+ Configuration config
+ ) {
+ exists(NodeEx out, boolean allowsFieldFlow |
+ revFlow(out, toReturn, returnAp, ap, config) and
+ flowOutOfCall(call, ret, out, allowsFieldFlow, config)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revFlowInNotToReturn(
+ ArgNodeEx arg, ApOption returnAp, Ap ap, Configuration config
+ ) {
+ exists(ParamNodeEx p, boolean allowsFieldFlow |
+ revFlow(p, false, returnAp, ap, config) and
+ flowIntoCall(_, arg, p, allowsFieldFlow, config)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revFlowInToReturn(
+ DataFlowCall call, ArgNodeEx arg, Ap returnAp, Ap ap, Configuration config
+ ) {
+ exists(ParamNodeEx p, boolean allowsFieldFlow |
+ revFlow(p, true, apSome(returnAp), ap, config) and
+ flowThroughIntoCall(call, arg, p, allowsFieldFlow, config)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ /**
+ * Holds if an output from `call` is reached in the flow covered by `revFlow`
+ * and data might flow through the target callable resulting in reverse flow
+ * reaching an argument of `call`.
+ */
+ pragma[nomagic]
+ private predicate revFlowIsReturned(
+ DataFlowCall call, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
+ ) {
+ exists(RetNodeEx ret, CcCall ccc |
+ revFlowOut(call, ret, toReturn, returnAp, ap, config) and
+ fwdFlow(ret, ccc, apSome(_), ap, config) and
+ ccc.matchesCall(call)
+ )
+ }
+
+ pragma[nomagic]
+ predicate storeStepCand(
+ NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType,
+ Configuration config
+ ) {
+ exists(Ap ap2, Content c |
+ store(node1, tc, node2, contentType, config) and
+ revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and
+ revFlowConsCand(ap2, c, ap1, config)
+ )
+ }
+
+ predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) {
+ exists(Ap ap1, Ap ap2 |
+ revFlow(node2, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and
+ readStepFwd(node1, ap1, c, node2, ap2, config) and
+ revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _,
+ pragma[only_bind_into](config))
+ )
+ }
+
+ predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, config) }
+
+ private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) {
+ storeStepFwd(_, ap, tc, _, _, config)
+ }
+
+ predicate consCand(TypedContent tc, Ap ap, Configuration config) {
+ storeStepCand(_, ap, tc, _, _, config)
+ }
+
+ pragma[noinline]
+ private predicate parameterFlow(
+ ParamNodeEx p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config
+ ) {
+ revFlow(p, true, apSome(ap0), ap, config) and
+ c = p.getEnclosingCallable()
+ }
+
+ predicate parameterMayFlowThrough(ParamNodeEx p, DataFlowCallable c, Ap ap, Configuration config) {
+ exists(RetNodeEx ret, Ap ap0, ReturnKindExt kind, int pos |
+ parameterFlow(p, ap, ap0, c, config) and
+ c = ret.getEnclosingCallable() and
+ revFlow(pragma[only_bind_into](ret), true, apSome(_), pragma[only_bind_into](ap0),
+ pragma[only_bind_into](config)) and
+ fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
+ kind = ret.getKind() and
+ p.getPosition() = pos and
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
+ )
+ }
+
+ pragma[nomagic]
+ predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
+ exists(Ap returnAp0, ArgNodeEx arg, boolean toReturn, ApOption returnAp, Ap ap |
+ revFlow(arg, toReturn, returnAp, ap, config) and
+ revFlowInToReturn(call, arg, returnAp0, ap, config) and
+ revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
+ )
+ }
+
+ predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) {
+ fwd = true and
+ nodes = count(NodeEx node | fwdFlow(node, _, _, _, config)) and
+ fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and
+ conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and
+ tuples = count(NodeEx n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config))
+ or
+ fwd = false and
+ nodes = count(NodeEx node | revFlow(node, _, _, _, config)) and
+ fields = count(TypedContent f0 | consCand(f0, _, config)) and
+ conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and
+ tuples = count(NodeEx n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config))
+ }
+ /* End: Stage 2 logic. */
+}
+
+pragma[nomagic]
+private predicate flowOutOfCallNodeCand2(
+ DataFlowCall call, RetNodeEx node1, NodeEx node2, boolean allowsFieldFlow, Configuration config
+) {
+ flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and
+ Stage2::revFlow(node2, pragma[only_bind_into](config)) and
+ Stage2::revFlow(node1, pragma[only_bind_into](config))
+}
+
+pragma[nomagic]
+private predicate flowIntoCallNodeCand2(
+ DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow,
+ Configuration config
+) {
+ flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and
+ Stage2::revFlow(node2, pragma[only_bind_into](config)) and
+ Stage2::revFlow(node1, pragma[only_bind_into](config))
+}
+
+private module LocalFlowBigStep {
+ /**
+ * A node where some checking is required, and hence the big-step relation
+ * is not allowed to step over.
+ */
+ private class FlowCheckNode extends NodeEx {
+ FlowCheckNode() {
+ castNode(this.asNode()) or
+ clearsContentCached(this.asNode(), _)
+ }
+ }
+
+ /**
+ * Holds if `node` can be the first node in a maximal subsequence of local
+ * flow steps in a dataflow path.
+ */
+ predicate localFlowEntry(NodeEx node, Configuration config) {
+ Stage2::revFlow(node, config) and
+ (
+ sourceNode(node, config) or
+ jumpStep(_, node, config) or
+ additionalJumpStep(_, node, config) or
+ node instanceof ParamNodeEx or
+ node.asNode() instanceof OutNodeExt or
+ store(_, _, node, _, config) or
+ read(_, _, node, config) or
+ node instanceof FlowCheckNode
+ )
+ }
+
+ /**
+ * Holds if `node` can be the last node in a maximal subsequence of local
+ * flow steps in a dataflow path.
+ */
+ private predicate localFlowExit(NodeEx node, Configuration config) {
+ exists(NodeEx next | Stage2::revFlow(next, config) |
+ jumpStep(node, next, config) or
+ additionalJumpStep(node, next, config) or
+ flowIntoCallNodeCand1(_, node, next, config) or
+ flowOutOfCallNodeCand1(_, node, next, config) or
+ store(node, _, next, _, config) or
+ read(node, _, next, config)
+ )
+ or
+ node instanceof FlowCheckNode
+ or
+ sinkNode(node, config)
+ }
+
+ pragma[noinline]
+ private predicate additionalLocalFlowStepNodeCand2(
+ NodeEx node1, NodeEx node2, Configuration config
+ ) {
+ additionalLocalFlowStepNodeCand1(node1, node2, config) and
+ Stage2::revFlow(node1, _, _, false, pragma[only_bind_into](config)) and
+ Stage2::revFlow(node2, _, _, false, pragma[only_bind_into](config))
+ }
+
+ /**
+ * Holds if the local path from `node1` to `node2` is a prefix of a maximal
+ * subsequence of local flow steps in a dataflow path.
+ *
+ * This is the transitive closure of `[additional]localFlowStep` beginning
+ * at `localFlowEntry`.
+ */
+ pragma[nomagic]
+ private predicate localFlowStepPlus(
+ NodeEx node1, NodeEx node2, boolean preservesValue, DataFlowType t, Configuration config,
+ LocalCallContext cc
+ ) {
+ not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and
+ (
+ localFlowEntry(node1, pragma[only_bind_into](config)) and
+ (
+ localFlowStepNodeCand1(node1, node2, config) and
+ preservesValue = true and
+ t = node1.getDataFlowType() // irrelevant dummy value
+ or
+ additionalLocalFlowStepNodeCand2(node1, node2, config) and
+ preservesValue = false and
+ t = node2.getDataFlowType()
+ ) and
+ node1 != node2 and
+ cc.relevantFor(node1.getEnclosingCallable()) and
+ not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and
+ Stage2::revFlow(node2, pragma[only_bind_into](config))
+ or
+ exists(NodeEx mid |
+ localFlowStepPlus(node1, mid, preservesValue, t, pragma[only_bind_into](config), cc) and
+ localFlowStepNodeCand1(mid, node2, config) and
+ not mid instanceof FlowCheckNode and
+ Stage2::revFlow(node2, pragma[only_bind_into](config))
+ )
+ or
+ exists(NodeEx mid |
+ localFlowStepPlus(node1, mid, _, _, pragma[only_bind_into](config), cc) and
+ additionalLocalFlowStepNodeCand2(mid, node2, config) and
+ not mid instanceof FlowCheckNode and
+ preservesValue = false and
+ t = node2.getDataFlowType() and
+ Stage2::revFlow(node2, pragma[only_bind_into](config))
+ )
+ )
+ }
+
+ /**
+ * Holds if `node1` can step to `node2` in one or more local steps and this
+ * path can occur as a maximal subsequence of local steps in a dataflow path.
+ */
+ pragma[nomagic]
+ predicate localFlowBigStep(
+ NodeEx node1, NodeEx node2, boolean preservesValue, AccessPathFrontNil apf,
+ Configuration config, LocalCallContext callContext
+ ) {
+ localFlowStepPlus(node1, node2, preservesValue, apf.getType(), config, callContext) and
+ localFlowExit(node2, config)
+ }
+}
+
+private import LocalFlowBigStep
+
+private module Stage3 {
+ module PrevStage = Stage2;
+
+ class ApApprox = PrevStage::Ap;
+
+ class Ap = AccessPathFront;
+
+ class ApNil = AccessPathFrontNil;
+
+ private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() }
+
+ private ApNil getApNil(NodeEx node) {
+ PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType())
+ }
+
+ bindingset[tc, tail]
+ private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) }
+
+ pragma[noinline]
+ private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() }
+
+ class ApOption = AccessPathFrontOption;
+
+ ApOption apNone() { result = TAccessPathFrontNone() }
+
+ ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) }
+
+ class Cc = boolean;
+
+ class CcCall extends Cc {
+ CcCall() { this = true }
+
+ /** Holds if this call context may be `call`. */
+ predicate matchesCall(DataFlowCall call) { any() }
+ }
+
+ class CcNoCall extends Cc {
+ CcNoCall() { this = false }
+ }
+
+ Cc ccNone() { result = false }
+
+ CcCall ccSomeCall() { result = true }
+
+ private class LocalCc = Unit;
+
+ bindingset[call, c, outercc]
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+
+ bindingset[call, c, innercc]
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+
+ bindingset[node, cc, config]
+ private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
+
+ private predicate localStep(
+ NodeEx node1, NodeEx node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc
+ ) {
+ localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc)
+ }
+
+ private predicate flowOutOfCall = flowOutOfCallNodeCand2/5;
+
+ private predicate flowIntoCall = flowIntoCallNodeCand2/5;
+
+ pragma[nomagic]
+ private predicate clear(NodeEx node, Ap ap) { ap.isClearedAt(node.asNode()) }
+
+ pragma[nomagic]
+ private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode }
+
+ bindingset[node, ap]
+ private predicate filter(NodeEx node, Ap ap) {
+ not clear(node, ap) and
+ if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()
+ }
+
+ bindingset[ap, contentType]
+ private predicate typecheckStore(Ap ap, DataFlowType contentType) {
+ // We need to typecheck stores here, since reverse flow through a getter
+ // might have a different type here compared to inside the getter.
+ compatibleTypes(ap.getType(), contentType)
+ }
+
+ /* Begin: Stage 3 logic. */
+ private predicate flowCand(NodeEx node, ApApprox apa, Configuration config) {
+ PrevStage::revFlow(node, _, _, apa, config)
+ }
+
+ bindingset[result, apa]
+ private ApApprox unbindApa(ApApprox apa) {
+ exists(ApApprox apa0 |
+ apa = pragma[only_bind_into](apa0) and result = pragma[only_bind_into](apa0)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate flowThroughOutOfCall(
+ DataFlowCall call, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, Configuration config
+ ) {
+ flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and
+ PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and
+ PrevStage::parameterMayFlowThrough(_, ret.getEnclosingCallable(), _,
+ pragma[only_bind_into](config))
+ }
+
+ /**
+ * Holds if `node` is reachable with access path `ap` from a source in the
+ * configuration `config`.
+ *
+ * The call context `cc` records whether the node is reached through an
+ * argument in a call, and if so, `argAp` records the access path of that
+ * argument.
+ */
+ pragma[nomagic]
+ predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
+ fwdFlow0(node, cc, argAp, ap, config) and
+ flowCand(node, unbindApa(getApprox(ap)), config) and
+ filter(node, ap)
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
+ flowCand(node, _, config) and
+ sourceNode(node, config) and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
+ argAp = apNone() and
+ ap = getApNil(node)
+ or
+ exists(NodeEx mid, Ap ap0, LocalCc localCc |
+ fwdFlow(mid, cc, argAp, ap0, config) and
+ localCc = getLocalCc(mid, cc, config)
+ |
+ localStep(mid, node, true, _, config, localCc) and
+ ap = ap0
+ or
+ localStep(mid, node, false, ap, config, localCc) and
+ ap0 instanceof ApNil
+ )
+ or
+ exists(NodeEx mid |
+ fwdFlow(mid, _, _, ap, pragma[only_bind_into](config)) and
+ flowCand(node, _, pragma[only_bind_into](config)) and
+ jumpStep(mid, node, config) and
+ cc = ccNone() and
+ argAp = apNone()
+ )
+ or
+ exists(NodeEx mid, ApNil nil |
+ fwdFlow(mid, _, _, nil, pragma[only_bind_into](config)) and
+ flowCand(node, _, pragma[only_bind_into](config)) and
+ additionalJumpStep(mid, node, config) and
+ cc = ccNone() and
+ argAp = apNone() and
+ ap = getApNil(node)
+ )
+ or
+ // store
+ exists(TypedContent tc, Ap ap0 |
+ fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and
+ ap = apCons(tc, ap0)
+ )
+ or
+ // read
+ exists(Ap ap0, Content c |
+ fwdFlowRead(ap0, c, _, node, cc, argAp, config) and
+ fwdFlowConsCand(ap0, c, ap, config)
+ )
+ or
+ // flow into a callable
+ exists(ApApprox apa |
+ fwdFlowIn(_, node, _, cc, _, ap, config) and
+ apa = getApprox(ap) and
+ if PrevStage::parameterMayFlowThrough(node, _, apa, config)
+ then argAp = apSome(ap)
+ else argAp = apNone()
+ )
+ or
+ // flow out of a callable
+ fwdFlowOutNotFromArg(node, cc, argAp, ap, config)
+ or
+ exists(DataFlowCall call, Ap argAp0 |
+ fwdFlowOutFromArg(call, node, argAp0, ap, config) and
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowStore(
+ NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Cc cc, ApOption argAp, Configuration config
+ ) {
+ exists(DataFlowType contentType |
+ fwdFlow(node1, cc, argAp, ap1, config) and
+ PrevStage::storeStepCand(node1, unbindApa(getApprox(ap1)), tc, node2, contentType, config) and
+ typecheckStore(ap1, contentType)
+ )
+ }
+
+ /**
+ * Holds if forward flow with access path `tail` reaches a store of `c`
+ * resulting in access path `cons`.
+ */
+ pragma[nomagic]
+ private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) {
+ exists(TypedContent tc |
+ fwdFlowStore(_, tail, tc, _, _, _, config) and
+ tc.getContent() = c and
+ cons = apCons(tc, tail)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowRead(
+ Ap ap, Content c, NodeEx node1, NodeEx node2, Cc cc, ApOption argAp, Configuration config
+ ) {
+ fwdFlow(node1, cc, argAp, ap, config) and
+ PrevStage::readStepCand(node1, c, node2, config) and
+ getHeadContent(ap) = c
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowIn(
+ DataFlowCall call, ParamNodeEx p, Cc outercc, Cc innercc, ApOption argAp, Ap ap,
+ Configuration config
+ ) {
+ exists(ArgNodeEx arg, boolean allowsFieldFlow |
+ fwdFlow(arg, outercc, argAp, ap, config) and
+ flowIntoCall(call, arg, p, allowsFieldFlow, config) and
+ innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowOutNotFromArg(
+ NodeEx out, Cc ccOut, ApOption argAp, Ap ap, Configuration config
+ ) {
+ exists(
+ DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc,
+ DataFlowCallable inner
+ |
+ fwdFlow(ret, innercc, argAp, ap, config) and
+ flowOutOfCall(call, ret, out, allowsFieldFlow, config) and
+ inner = ret.getEnclosingCallable() and
+ ccOut = getCallContextReturn(inner, call, innercc)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowOutFromArg(
+ DataFlowCall call, NodeEx out, Ap argAp, Ap ap, Configuration config
+ ) {
+ exists(RetNodeEx ret, boolean allowsFieldFlow, CcCall ccc |
+ fwdFlow(ret, ccc, apSome(argAp), ap, config) and
+ flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and
+ ccc.matchesCall(call)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ /**
+ * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`
+ * and data might flow through the target callable and back out at `call`.
+ */
+ pragma[nomagic]
+ private predicate fwdFlowIsEntered(
+ DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config
+ ) {
+ exists(ParamNodeEx p |
+ fwdFlowIn(call, p, cc, _, argAp, ap, config) and
+ PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate storeStepFwd(
+ NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config
+ ) {
+ fwdFlowStore(node1, ap1, tc, node2, _, _, config) and
+ ap2 = apCons(tc, ap1) and
+ fwdFlowRead(ap2, tc.getContent(), _, _, _, _, config)
+ }
+
+ private predicate readStepFwd(
+ NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config
+ ) {
+ fwdFlowRead(ap1, c, n1, n2, _, _, config) and
+ fwdFlowConsCand(ap1, c, ap2, config)
+ }
+
+ pragma[nomagic]
+ private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
+ exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
+ fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
+ pragma[only_bind_into](config)) and
+ fwdFlowOutFromArg(call, out, argAp0, ap, config) and
+ fwdFlowIsEntered(pragma[only_bind_into](call), pragma[only_bind_into](cc),
+ pragma[only_bind_into](argAp), pragma[only_bind_into](argAp0),
+ pragma[only_bind_into](config))
+ )
+ }
+
+ pragma[nomagic]
+ private predicate flowThroughIntoCall(
+ DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config
+ ) {
+ flowIntoCall(call, arg, p, allowsFieldFlow, config) and
+ fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and
+ PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and
+ callMayFlowThroughFwd(call, pragma[only_bind_into](config))
+ }
+
+ /**
+ * Holds if `node` with access path `ap` is part of a path from a source to a
+ * sink in the configuration `config`.
+ *
+ * The Boolean `toReturn` records whether the node must be returned from the
+ * enclosing callable in order to reach a sink, and if so, `returnAp` records
+ * the access path of the returned value.
+ */
+ pragma[nomagic]
+ predicate revFlow(NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) {
+ revFlow0(node, toReturn, returnAp, ap, config) and
+ fwdFlow(node, _, _, ap, config)
+ }
+
+ pragma[nomagic]
+ private predicate revFlow0(
+ NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
+ ) {
+ fwdFlow(node, _, _, ap, config) and
+ sinkNode(node, config) and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
+ returnAp = apNone() and
+ ap instanceof ApNil
+ or
+ exists(NodeEx mid |
+ localStep(node, mid, true, _, config, _) and
+ revFlow(mid, toReturn, returnAp, ap, config)
+ )
+ or
+ exists(NodeEx mid, ApNil nil |
+ fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and
+ localStep(node, mid, false, _, config, _) and
+ revFlow(mid, toReturn, returnAp, nil, pragma[only_bind_into](config)) and
+ ap instanceof ApNil
+ )
+ or
+ exists(NodeEx mid |
+ jumpStep(node, mid, config) and
+ revFlow(mid, _, _, ap, config) and
+ toReturn = false and
+ returnAp = apNone()
+ )
+ or
+ exists(NodeEx mid, ApNil nil |
+ fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and
+ additionalJumpStep(node, mid, config) and
+ revFlow(pragma[only_bind_into](mid), _, _, nil, pragma[only_bind_into](config)) and
+ toReturn = false and
+ returnAp = apNone() and
+ ap instanceof ApNil
+ )
+ or
+ // store
+ exists(Ap ap0, Content c |
+ revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and
+ revFlowConsCand(ap0, c, ap, config)
+ )
+ or
+ // read
+ exists(NodeEx mid, Ap ap0 |
+ revFlow(mid, toReturn, returnAp, ap0, config) and
+ readStepFwd(node, ap, _, mid, ap0, config)
+ )
+ or
+ // flow into a callable
+ revFlowInNotToReturn(node, returnAp, ap, config) and
+ toReturn = false
+ or
+ exists(DataFlowCall call, Ap returnAp0 |
+ revFlowInToReturn(call, node, returnAp0, ap, config) and
+ revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
+ )
+ or
+ // flow out of a callable
+ revFlowOut(_, node, _, _, ap, config) and
+ toReturn = true and
+ if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config)
+ then returnAp = apSome(ap)
+ else returnAp = apNone()
+ }
+
+ pragma[nomagic]
+ private predicate revFlowStore(
+ Ap ap0, Content c, Ap ap, NodeEx node, TypedContent tc, NodeEx mid, boolean toReturn,
+ ApOption returnAp, Configuration config
+ ) {
+ revFlow(mid, toReturn, returnAp, ap0, config) and
+ storeStepFwd(node, ap, tc, mid, ap0, config) and
+ tc.getContent() = c
+ }
+
+ /**
+ * Holds if reverse flow with access path `tail` reaches a read of `c`
+ * resulting in access path `cons`.
+ */
+ pragma[nomagic]
+ private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) {
+ exists(NodeEx mid, Ap tail0 |
+ revFlow(mid, _, _, tail, config) and
+ tail = pragma[only_bind_into](tail0) and
+ readStepFwd(_, cons, c, mid, tail0, config)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revFlowOut(
+ DataFlowCall call, RetNodeEx ret, boolean toReturn, ApOption returnAp, Ap ap,
+ Configuration config
+ ) {
+ exists(NodeEx out, boolean allowsFieldFlow |
+ revFlow(out, toReturn, returnAp, ap, config) and
+ flowOutOfCall(call, ret, out, allowsFieldFlow, config)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revFlowInNotToReturn(
+ ArgNodeEx arg, ApOption returnAp, Ap ap, Configuration config
+ ) {
+ exists(ParamNodeEx p, boolean allowsFieldFlow |
+ revFlow(p, false, returnAp, ap, config) and
+ flowIntoCall(_, arg, p, allowsFieldFlow, config)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revFlowInToReturn(
+ DataFlowCall call, ArgNodeEx arg, Ap returnAp, Ap ap, Configuration config
+ ) {
+ exists(ParamNodeEx p, boolean allowsFieldFlow |
+ revFlow(p, true, apSome(returnAp), ap, config) and
+ flowThroughIntoCall(call, arg, p, allowsFieldFlow, config)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ /**
+ * Holds if an output from `call` is reached in the flow covered by `revFlow`
+ * and data might flow through the target callable resulting in reverse flow
+ * reaching an argument of `call`.
+ */
+ pragma[nomagic]
+ private predicate revFlowIsReturned(
+ DataFlowCall call, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
+ ) {
+ exists(RetNodeEx ret, CcCall ccc |
+ revFlowOut(call, ret, toReturn, returnAp, ap, config) and
+ fwdFlow(ret, ccc, apSome(_), ap, config) and
+ ccc.matchesCall(call)
+ )
+ }
+
+ pragma[nomagic]
+ predicate storeStepCand(
+ NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType,
+ Configuration config
+ ) {
+ exists(Ap ap2, Content c |
+ store(node1, tc, node2, contentType, config) and
+ revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and
+ revFlowConsCand(ap2, c, ap1, config)
+ )
+ }
+
+ predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) {
+ exists(Ap ap1, Ap ap2 |
+ revFlow(node2, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and
+ readStepFwd(node1, ap1, c, node2, ap2, config) and
+ revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _,
+ pragma[only_bind_into](config))
+ )
+ }
+
+ predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, config) }
+
+ private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) {
+ storeStepFwd(_, ap, tc, _, _, config)
+ }
+
+ predicate consCand(TypedContent tc, Ap ap, Configuration config) {
+ storeStepCand(_, ap, tc, _, _, config)
+ }
+
+ pragma[noinline]
+ private predicate parameterFlow(
+ ParamNodeEx p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config
+ ) {
+ revFlow(p, true, apSome(ap0), ap, config) and
+ c = p.getEnclosingCallable()
+ }
+
+ predicate parameterMayFlowThrough(ParamNodeEx p, DataFlowCallable c, Ap ap, Configuration config) {
+ exists(RetNodeEx ret, Ap ap0, ReturnKindExt kind, int pos |
+ parameterFlow(p, ap, ap0, c, config) and
+ c = ret.getEnclosingCallable() and
+ revFlow(pragma[only_bind_into](ret), true, apSome(_), pragma[only_bind_into](ap0),
+ pragma[only_bind_into](config)) and
+ fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
+ kind = ret.getKind() and
+ p.getPosition() = pos and
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
+ )
+ }
+
+ pragma[nomagic]
+ predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
+ exists(Ap returnAp0, ArgNodeEx arg, boolean toReturn, ApOption returnAp, Ap ap |
+ revFlow(arg, toReturn, returnAp, ap, config) and
+ revFlowInToReturn(call, arg, returnAp0, ap, config) and
+ revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
+ )
+ }
+
+ predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) {
+ fwd = true and
+ nodes = count(NodeEx node | fwdFlow(node, _, _, _, config)) and
+ fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and
+ conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and
+ tuples = count(NodeEx n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config))
+ or
+ fwd = false and
+ nodes = count(NodeEx node | revFlow(node, _, _, _, config)) and
+ fields = count(TypedContent f0 | consCand(f0, _, config)) and
+ conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and
+ tuples = count(NodeEx n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config))
+ }
+ /* End: Stage 3 logic. */
+}
+
+/**
+ * Holds if `argApf` is recorded as the summary context for flow reaching `node`
+ * and remains relevant for the following pruning stage.
+ */
+private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
+ exists(AccessPathFront apf |
+ Stage3::revFlow(node, true, _, apf, config) and
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
+ )
+}
+
+/**
+ * Holds if a length 2 access path approximation with the head `tc` is expected
+ * to be expensive.
+ */
+private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) {
+ exists(int tails, int nodes, int apLimit, int tupleLimit |
+ tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and
+ nodes =
+ strictcount(NodeEx n |
+ Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config)
+ or
+ flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config)
+ ) and
+ accessPathApproxCostLimits(apLimit, tupleLimit) and
+ apLimit < tails and
+ tupleLimit < (tails - 1) * nodes and
+ not tc.forceHighPrecision()
+ )
+}
+
+private newtype TAccessPathApprox =
+ TNil(DataFlowType t) or
+ TConsNil(TypedContent tc, DataFlowType t) {
+ Stage3::consCand(tc, TFrontNil(t), _) and
+ not expensiveLen2unfolding(tc, _)
+ } or
+ TConsCons(TypedContent tc1, TypedContent tc2, int len) {
+ Stage3::consCand(tc1, TFrontHead(tc2), _) and
+ len in [2 .. accessPathLimit()] and
+ not expensiveLen2unfolding(tc1, _)
+ } or
+ TCons1(TypedContent tc, int len) {
+ len in [1 .. accessPathLimit()] and
+ expensiveLen2unfolding(tc, _)
+ }
+
+/**
+ * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only
+ * the first two elements of the list and its length are tracked. If data flows
+ * from a source to a given node with a given `AccessPathApprox`, this indicates
+ * the sequence of dereference operations needed to get from the value in the node
+ * to the tracked object. The final type indicates the type of the tracked object.
+ */
+abstract private class AccessPathApprox extends TAccessPathApprox {
+ abstract string toString();
+
+ abstract TypedContent getHead();
+
+ abstract int len();
+
+ abstract DataFlowType getType();
+
+ abstract AccessPathFront getFront();
+
+ /** Gets the access path obtained by popping `head` from this path, if any. */
+ abstract AccessPathApprox pop(TypedContent head);
+}
+
+private class AccessPathApproxNil extends AccessPathApprox, TNil {
+ private DataFlowType t;
+
+ AccessPathApproxNil() { this = TNil(t) }
+
+ override string toString() { result = concat(": " + ppReprType(t)) }
+
+ override TypedContent getHead() { none() }
+
+ override int len() { result = 0 }
+
+ override DataFlowType getType() { result = t }
+
+ override AccessPathFront getFront() { result = TFrontNil(t) }
+
+ override AccessPathApprox pop(TypedContent head) { none() }
+}
+
+abstract private class AccessPathApproxCons extends AccessPathApprox { }
+
+private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil {
+ private TypedContent tc;
+ private DataFlowType t;
+
+ AccessPathApproxConsNil() { this = TConsNil(tc, t) }
+
+ override string toString() {
+ // The `concat` becomes "" if `ppReprType` has no result.
+ result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t))
+ }
+
+ override TypedContent getHead() { result = tc }
+
+ override int len() { result = 1 }
+
+ override DataFlowType getType() { result = tc.getContainerType() }
+
+ override AccessPathFront getFront() { result = TFrontHead(tc) }
+
+ override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) }
+}
+
+private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons {
+ private TypedContent tc1;
+ private TypedContent tc2;
+ private int len;
+
+ AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) }
+
+ override string toString() {
+ if len = 2
+ then result = "[" + tc1.toString() + ", " + tc2.toString() + "]"
+ else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]"
+ }
+
+ override TypedContent getHead() { result = tc1 }
+
+ override int len() { result = len }
+
+ override DataFlowType getType() { result = tc1.getContainerType() }
+
+ override AccessPathFront getFront() { result = TFrontHead(tc1) }
+
+ override AccessPathApprox pop(TypedContent head) {
+ head = tc1 and
+ (
+ result = TConsCons(tc2, _, len - 1)
+ or
+ len = 2 and
+ result = TConsNil(tc2, _)
+ or
+ result = TCons1(tc2, len - 1)
+ )
+ }
+}
+
+private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 {
+ private TypedContent tc;
+ private int len;
+
+ AccessPathApproxCons1() { this = TCons1(tc, len) }
+
+ override string toString() {
+ if len = 1
+ then result = "[" + tc.toString() + "]"
+ else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]"
+ }
+
+ override TypedContent getHead() { result = tc }
+
+ override int len() { result = len }
+
+ override DataFlowType getType() { result = tc.getContainerType() }
+
+ override AccessPathFront getFront() { result = TFrontHead(tc) }
+
+ override AccessPathApprox pop(TypedContent head) {
+ head = tc and
+ (
+ exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) |
+ result = TConsCons(tc2, _, len - 1)
+ or
+ len = 2 and
+ result = TConsNil(tc2, _)
+ or
+ result = TCons1(tc2, len - 1)
+ )
+ or
+ exists(DataFlowType t |
+ len = 1 and
+ Stage3::consCand(tc, TFrontNil(t), _) and
+ result = TNil(t)
+ )
+ )
+ }
+}
+
+/** Gets the access path obtained by popping `tc` from `ap`, if any. */
+private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) }
+
+/** Gets the access path obtained by pushing `tc` onto `ap`. */
+private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) }
+
+private newtype TAccessPathApproxOption =
+ TAccessPathApproxNone() or
+ TAccessPathApproxSome(AccessPathApprox apa)
+
+private class AccessPathApproxOption extends TAccessPathApproxOption {
+ string toString() {
+ this = TAccessPathApproxNone() and result = ""
+ or
+ this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString()))
+ }
+}
+
+private module Stage4 {
+ module PrevStage = Stage3;
+
+ class ApApprox = PrevStage::Ap;
+
+ class Ap = AccessPathApprox;
+
+ class ApNil = AccessPathApproxNil;
+
+ private ApApprox getApprox(Ap ap) { result = ap.getFront() }
+
+ private ApNil getApNil(NodeEx node) {
+ PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType())
+ }
+
+ bindingset[tc, tail]
+ private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) }
+
+ pragma[noinline]
+ private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() }
+
+ class ApOption = AccessPathApproxOption;
+
+ ApOption apNone() { result = TAccessPathApproxNone() }
+
+ ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) }
+
+ class Cc = CallContext;
+
+ class CcCall = CallContextCall;
+
+ class CcNoCall = CallContextNoCall;
+
+ Cc ccNone() { result instanceof CallContextAny }
+
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
+ private class LocalCc = LocalCallContext;
+
+ bindingset[call, c, outercc]
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall()
+ }
+
+ bindingset[call, c, innercc]
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
+
+ bindingset[node, cc, config]
+ private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result =
+ getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)),
+ node.getEnclosingCallable())
+ }
+
+ private predicate localStep(
+ NodeEx node1, NodeEx node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc
+ ) {
+ localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc)
+ }
+
+ pragma[nomagic]
+ private predicate flowOutOfCall(
+ DataFlowCall call, RetNodeEx node1, NodeEx node2, boolean allowsFieldFlow, Configuration config
+ ) {
+ flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and
+ PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and
+ PrevStage::revFlow(node1, _, _, _, pragma[only_bind_into](config))
+ }
+
+ pragma[nomagic]
+ private predicate flowIntoCall(
+ DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow,
+ Configuration config
+ ) {
+ flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and
+ PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and
+ PrevStage::revFlow(node1, _, _, _, pragma[only_bind_into](config))
+ }
+
+ bindingset[node, ap]
+ private predicate filter(NodeEx node, Ap ap) { any() }
+
+ // Type checking is not necessary here as it has already been done in stage 3.
+ bindingset[ap, contentType]
+ private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() }
+
+ /* Begin: Stage 4 logic. */
+ private predicate flowCand(NodeEx node, ApApprox apa, Configuration config) {
+ PrevStage::revFlow(node, _, _, apa, config)
+ }
+
+ bindingset[result, apa]
+ private ApApprox unbindApa(ApApprox apa) {
+ exists(ApApprox apa0 |
+ apa = pragma[only_bind_into](apa0) and result = pragma[only_bind_into](apa0)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate flowThroughOutOfCall(
+ DataFlowCall call, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, Configuration config
+ ) {
+ flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and
+ PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and
+ PrevStage::parameterMayFlowThrough(_, ret.getEnclosingCallable(), _,
+ pragma[only_bind_into](config))
+ }
+
+ /**
+ * Holds if `node` is reachable with access path `ap` from a source in the
+ * configuration `config`.
+ *
+ * The call context `cc` records whether the node is reached through an
+ * argument in a call, and if so, `argAp` records the access path of that
+ * argument.
+ */
+ pragma[nomagic]
+ predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
+ fwdFlow0(node, cc, argAp, ap, config) and
+ flowCand(node, unbindApa(getApprox(ap)), config) and
+ filter(node, ap)
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
+ flowCand(node, _, config) and
+ sourceNode(node, config) and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
+ argAp = apNone() and
+ ap = getApNil(node)
+ or
+ exists(NodeEx mid, Ap ap0, LocalCc localCc |
+ fwdFlow(mid, cc, argAp, ap0, config) and
+ localCc = getLocalCc(mid, cc, config)
+ |
+ localStep(mid, node, true, _, config, localCc) and
+ ap = ap0
+ or
+ localStep(mid, node, false, ap, config, localCc) and
+ ap0 instanceof ApNil
+ )
+ or
+ exists(NodeEx mid |
+ fwdFlow(mid, _, _, ap, pragma[only_bind_into](config)) and
+ flowCand(node, _, pragma[only_bind_into](config)) and
+ jumpStep(mid, node, config) and
+ cc = ccNone() and
+ argAp = apNone()
+ )
+ or
+ exists(NodeEx mid, ApNil nil |
+ fwdFlow(mid, _, _, nil, pragma[only_bind_into](config)) and
+ flowCand(node, _, pragma[only_bind_into](config)) and
+ additionalJumpStep(mid, node, config) and
+ cc = ccNone() and
+ argAp = apNone() and
+ ap = getApNil(node)
+ )
+ or
+ // store
+ exists(TypedContent tc, Ap ap0 |
+ fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and
+ ap = apCons(tc, ap0)
+ )
+ or
+ // read
+ exists(Ap ap0, Content c |
+ fwdFlowRead(ap0, c, _, node, cc, argAp, config) and
+ fwdFlowConsCand(ap0, c, ap, config)
+ )
+ or
+ // flow into a callable
+ exists(ApApprox apa |
+ fwdFlowIn(_, node, _, cc, _, ap, config) and
+ apa = getApprox(ap) and
+ if PrevStage::parameterMayFlowThrough(node, _, apa, config)
+ then argAp = apSome(ap)
+ else argAp = apNone()
+ )
+ or
+ // flow out of a callable
+ fwdFlowOutNotFromArg(node, cc, argAp, ap, config)
+ or
+ exists(DataFlowCall call, Ap argAp0 |
+ fwdFlowOutFromArg(call, node, argAp0, ap, config) and
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowStore(
+ NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Cc cc, ApOption argAp, Configuration config
+ ) {
+ exists(DataFlowType contentType |
+ fwdFlow(node1, cc, argAp, ap1, config) and
+ PrevStage::storeStepCand(node1, unbindApa(getApprox(ap1)), tc, node2, contentType, config) and
+ typecheckStore(ap1, contentType)
+ )
+ }
+
+ /**
+ * Holds if forward flow with access path `tail` reaches a store of `c`
+ * resulting in access path `cons`.
+ */
+ pragma[nomagic]
+ private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) {
+ exists(TypedContent tc |
+ fwdFlowStore(_, tail, tc, _, _, _, config) and
+ tc.getContent() = c and
+ cons = apCons(tc, tail)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowRead(
+ Ap ap, Content c, NodeEx node1, NodeEx node2, Cc cc, ApOption argAp, Configuration config
+ ) {
+ fwdFlow(node1, cc, argAp, ap, config) and
+ PrevStage::readStepCand(node1, c, node2, config) and
+ getHeadContent(ap) = c
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowIn(
+ DataFlowCall call, ParamNodeEx p, Cc outercc, Cc innercc, ApOption argAp, Ap ap,
+ Configuration config
+ ) {
+ exists(ArgNodeEx arg, boolean allowsFieldFlow |
+ fwdFlow(arg, outercc, argAp, ap, config) and
+ flowIntoCall(call, arg, p, allowsFieldFlow, config) and
+ innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowOutNotFromArg(
+ NodeEx out, Cc ccOut, ApOption argAp, Ap ap, Configuration config
+ ) {
+ exists(
+ DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc,
+ DataFlowCallable inner
+ |
+ fwdFlow(ret, innercc, argAp, ap, config) and
+ flowOutOfCall(call, ret, out, allowsFieldFlow, config) and
+ inner = ret.getEnclosingCallable() and
+ ccOut = getCallContextReturn(inner, call, innercc)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowOutFromArg(
+ DataFlowCall call, NodeEx out, Ap argAp, Ap ap, Configuration config
+ ) {
+ exists(RetNodeEx ret, boolean allowsFieldFlow, CcCall ccc |
+ fwdFlow(ret, ccc, apSome(argAp), ap, config) and
+ flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and
+ ccc.matchesCall(call)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ /**
+ * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`
+ * and data might flow through the target callable and back out at `call`.
+ */
+ pragma[nomagic]
+ private predicate fwdFlowIsEntered(
+ DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config
+ ) {
+ exists(ParamNodeEx p |
+ fwdFlowIn(call, p, cc, _, argAp, ap, config) and
+ PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate storeStepFwd(
+ NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config
+ ) {
+ fwdFlowStore(node1, ap1, tc, node2, _, _, config) and
+ ap2 = apCons(tc, ap1) and
+ fwdFlowRead(ap2, tc.getContent(), _, _, _, _, config)
+ }
+
+ private predicate readStepFwd(
+ NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config
+ ) {
+ fwdFlowRead(ap1, c, n1, n2, _, _, config) and
+ fwdFlowConsCand(ap1, c, ap2, config)
+ }
+
+ pragma[nomagic]
+ private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
+ exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
+ fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
+ pragma[only_bind_into](config)) and
+ fwdFlowOutFromArg(call, out, argAp0, ap, config) and
+ fwdFlowIsEntered(pragma[only_bind_into](call), pragma[only_bind_into](cc),
+ pragma[only_bind_into](argAp), pragma[only_bind_into](argAp0),
+ pragma[only_bind_into](config))
+ )
+ }
+
+ pragma[nomagic]
+ private predicate flowThroughIntoCall(
+ DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config
+ ) {
+ flowIntoCall(call, arg, p, allowsFieldFlow, config) and
+ fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and
+ PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and
+ callMayFlowThroughFwd(call, pragma[only_bind_into](config))
+ }
+
+ /**
+ * Holds if `node` with access path `ap` is part of a path from a source to a
+ * sink in the configuration `config`.
+ *
+ * The Boolean `toReturn` records whether the node must be returned from the
+ * enclosing callable in order to reach a sink, and if so, `returnAp` records
+ * the access path of the returned value.
+ */
+ pragma[nomagic]
+ predicate revFlow(NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) {
+ revFlow0(node, toReturn, returnAp, ap, config) and
+ fwdFlow(node, _, _, ap, config)
+ }
+
+ pragma[nomagic]
+ private predicate revFlow0(
+ NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
+ ) {
+ fwdFlow(node, _, _, ap, config) and
+ sinkNode(node, config) and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
+ returnAp = apNone() and
+ ap instanceof ApNil
+ or
+ exists(NodeEx mid |
+ localStep(node, mid, true, _, config, _) and
+ revFlow(mid, toReturn, returnAp, ap, config)
+ )
+ or
+ exists(NodeEx mid, ApNil nil |
+ fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and
+ localStep(node, mid, false, _, config, _) and
+ revFlow(mid, toReturn, returnAp, nil, pragma[only_bind_into](config)) and
+ ap instanceof ApNil
+ )
+ or
+ exists(NodeEx mid |
+ jumpStep(node, mid, config) and
+ revFlow(mid, _, _, ap, config) and
+ toReturn = false and
+ returnAp = apNone()
+ )
+ or
+ exists(NodeEx mid, ApNil nil |
+ fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and
+ additionalJumpStep(node, mid, config) and
+ revFlow(pragma[only_bind_into](mid), _, _, nil, pragma[only_bind_into](config)) and
+ toReturn = false and
+ returnAp = apNone() and
+ ap instanceof ApNil
+ )
+ or
+ // store
+ exists(Ap ap0, Content c |
+ revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and
+ revFlowConsCand(ap0, c, ap, config)
+ )
+ or
+ // read
+ exists(NodeEx mid, Ap ap0 |
+ revFlow(mid, toReturn, returnAp, ap0, config) and
+ readStepFwd(node, ap, _, mid, ap0, config)
+ )
+ or
+ // flow into a callable
+ revFlowInNotToReturn(node, returnAp, ap, config) and
+ toReturn = false
+ or
+ exists(DataFlowCall call, Ap returnAp0 |
+ revFlowInToReturn(call, node, returnAp0, ap, config) and
+ revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
+ )
+ or
+ // flow out of a callable
+ revFlowOut(_, node, _, _, ap, config) and
+ toReturn = true and
+ if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config)
+ then returnAp = apSome(ap)
+ else returnAp = apNone()
+ }
+
+ pragma[nomagic]
+ private predicate revFlowStore(
+ Ap ap0, Content c, Ap ap, NodeEx node, TypedContent tc, NodeEx mid, boolean toReturn,
+ ApOption returnAp, Configuration config
+ ) {
+ revFlow(mid, toReturn, returnAp, ap0, config) and
+ storeStepFwd(node, ap, tc, mid, ap0, config) and
+ tc.getContent() = c
+ }
+
+ /**
+ * Holds if reverse flow with access path `tail` reaches a read of `c`
+ * resulting in access path `cons`.
+ */
+ pragma[nomagic]
+ private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) {
+ exists(NodeEx mid, Ap tail0 |
+ revFlow(mid, _, _, tail, config) and
+ tail = pragma[only_bind_into](tail0) and
+ readStepFwd(_, cons, c, mid, tail0, config)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revFlowOut(
+ DataFlowCall call, RetNodeEx ret, boolean toReturn, ApOption returnAp, Ap ap,
+ Configuration config
+ ) {
+ exists(NodeEx out, boolean allowsFieldFlow |
+ revFlow(out, toReturn, returnAp, ap, config) and
+ flowOutOfCall(call, ret, out, allowsFieldFlow, config)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revFlowInNotToReturn(
+ ArgNodeEx arg, ApOption returnAp, Ap ap, Configuration config
+ ) {
+ exists(ParamNodeEx p, boolean allowsFieldFlow |
+ revFlow(p, false, returnAp, ap, config) and
+ flowIntoCall(_, arg, p, allowsFieldFlow, config)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revFlowInToReturn(
+ DataFlowCall call, ArgNodeEx arg, Ap returnAp, Ap ap, Configuration config
+ ) {
+ exists(ParamNodeEx p, boolean allowsFieldFlow |
+ revFlow(p, true, apSome(returnAp), ap, config) and
+ flowThroughIntoCall(call, arg, p, allowsFieldFlow, config)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ /**
+ * Holds if an output from `call` is reached in the flow covered by `revFlow`
+ * and data might flow through the target callable resulting in reverse flow
+ * reaching an argument of `call`.
+ */
+ pragma[nomagic]
+ private predicate revFlowIsReturned(
+ DataFlowCall call, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
+ ) {
+ exists(RetNodeEx ret, CcCall ccc |
+ revFlowOut(call, ret, toReturn, returnAp, ap, config) and
+ fwdFlow(ret, ccc, apSome(_), ap, config) and
+ ccc.matchesCall(call)
+ )
+ }
+
+ pragma[nomagic]
+ predicate storeStepCand(
+ NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType,
+ Configuration config
+ ) {
+ exists(Ap ap2, Content c |
+ store(node1, tc, node2, contentType, config) and
+ revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and
+ revFlowConsCand(ap2, c, ap1, config)
+ )
+ }
+
+ predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) {
+ exists(Ap ap1, Ap ap2 |
+ revFlow(node2, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and
+ readStepFwd(node1, ap1, c, node2, ap2, config) and
+ revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _,
+ pragma[only_bind_into](config))
+ )
+ }
+
+ predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, config) }
+
+ private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) {
+ storeStepFwd(_, ap, tc, _, _, config)
+ }
+
+ predicate consCand(TypedContent tc, Ap ap, Configuration config) {
+ storeStepCand(_, ap, tc, _, _, config)
+ }
+
+ pragma[noinline]
+ private predicate parameterFlow(
+ ParamNodeEx p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config
+ ) {
+ revFlow(p, true, apSome(ap0), ap, config) and
+ c = p.getEnclosingCallable()
+ }
+
+ predicate parameterMayFlowThrough(ParamNodeEx p, DataFlowCallable c, Ap ap, Configuration config) {
+ exists(RetNodeEx ret, Ap ap0, ReturnKindExt kind, int pos |
+ parameterFlow(p, ap, ap0, c, config) and
+ c = ret.getEnclosingCallable() and
+ revFlow(pragma[only_bind_into](ret), true, apSome(_), pragma[only_bind_into](ap0),
+ pragma[only_bind_into](config)) and
+ fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
+ kind = ret.getKind() and
+ p.getPosition() = pos and
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
+ )
+ }
+
+ pragma[nomagic]
+ predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
+ exists(Ap returnAp0, ArgNodeEx arg, boolean toReturn, ApOption returnAp, Ap ap |
+ revFlow(arg, toReturn, returnAp, ap, config) and
+ revFlowInToReturn(call, arg, returnAp0, ap, config) and
+ revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
+ )
+ }
+
+ predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) {
+ fwd = true and
+ nodes = count(NodeEx node | fwdFlow(node, _, _, _, config)) and
+ fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and
+ conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and
+ tuples = count(NodeEx n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config))
+ or
+ fwd = false and
+ nodes = count(NodeEx node | revFlow(node, _, _, _, config)) and
+ fields = count(TypedContent f0 | consCand(f0, _, config)) and
+ conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and
+ tuples = count(NodeEx n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config))
+ }
+ /* End: Stage 4 logic. */
+}
+
+bindingset[conf, result]
+private Configuration unbindConf(Configuration conf) {
+ exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c))
+}
+
+private predicate nodeMayUseSummary(NodeEx n, AccessPathApprox apa, Configuration config) {
+ exists(DataFlowCallable c, AccessPathApprox apa0 |
+ Stage4::parameterMayFlowThrough(_, c, apa, _) and
+ Stage4::revFlow(n, true, _, apa0, config) and
+ Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and
+ n.getEnclosingCallable() = c
+ )
+}
+
+private newtype TSummaryCtx =
+ TSummaryCtxNone() or
+ TSummaryCtxSome(ParamNodeEx p, AccessPath ap) {
+ Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _)
+ }
+
+/**
+ * A context for generating flow summaries. This represents flow entry through
+ * a specific parameter with an access path of a specific shape.
+ *
+ * Summaries are only created for parameters that may flow through.
+ */
+abstract private class SummaryCtx extends TSummaryCtx {
+ abstract string toString();
+}
+
+/** A summary context from which no flow summary can be generated. */
+private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone {
+ override string toString() { result = "" }
+}
+
+/** A summary context from which a flow summary can be generated. */
+private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
+ private ParamNodeEx p;
+ private AccessPath ap;
+
+ SummaryCtxSome() { this = TSummaryCtxSome(p, ap) }
+
+ int getParameterPos() { p.isParameterOf(_, result) }
+
+ ParamNodeEx getParamNode() { result = p }
+
+ override string toString() { result = p + ": " + ap }
+
+ predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
+ p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ }
+}
+
+/**
+ * Gets the number of length 2 access path approximations that correspond to `apa`.
+ */
+private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) {
+ exists(TypedContent tc, int len |
+ tc = apa.getHead() and
+ len = apa.len() and
+ result =
+ strictcount(AccessPathFront apf |
+ Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1),
+ config)
+ )
+ )
+}
+
+private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) {
+ result =
+ strictcount(NodeEx n |
+ Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)
+ )
+}
+
+/**
+ * Holds if a length 2 access path approximation matching `apa` is expected
+ * to be expensive.
+ */
+private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) {
+ exists(int aps, int nodes, int apLimit, int tupleLimit |
+ aps = count1to2unfold(apa, config) and
+ nodes = countNodesUsingAccessPath(apa, config) and
+ accessPathCostLimits(apLimit, tupleLimit) and
+ apLimit < aps and
+ tupleLimit < (aps - 1) * nodes
+ )
+}
+
+private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) {
+ exists(TypedContent head |
+ apa.pop(head) = result and
+ Stage4::consCand(head, result, config)
+ )
+}
+
+/**
+ * Holds with `unfold = false` if a precise head-tail representation of `apa` is
+ * expected to be expensive. Holds with `unfold = true` otherwise.
+ */
+private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) {
+ if apa.getHead().forceHighPrecision()
+ then unfold = true
+ else
+ exists(int aps, int nodes, int apLimit, int tupleLimit |
+ aps = countPotentialAps(apa, config) and
+ nodes = countNodesUsingAccessPath(apa, config) and
+ accessPathCostLimits(apLimit, tupleLimit) and
+ if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true
+ )
+}
+
+/**
+ * Gets the number of `AccessPath`s that correspond to `apa`.
+ */
+private int countAps(AccessPathApprox apa, Configuration config) {
+ evalUnfold(apa, false, config) and
+ result = 1 and
+ (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config))
+ or
+ evalUnfold(apa, false, config) and
+ result = count1to2unfold(apa, config) and
+ not expensiveLen1to2unfolding(apa, config)
+ or
+ evalUnfold(apa, true, config) and
+ result = countPotentialAps(apa, config)
+}
+
+/**
+ * Gets the number of `AccessPath`s that would correspond to `apa` assuming
+ * that it is expanded to a precise head-tail representation.
+ */
+language[monotonicAggregates]
+private int countPotentialAps(AccessPathApprox apa, Configuration config) {
+ apa instanceof AccessPathApproxNil and result = 1
+ or
+ result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config))
+}
+
+private newtype TAccessPath =
+ TAccessPathNil(DataFlowType t) or
+ TAccessPathCons(TypedContent head, AccessPath tail) {
+ exists(AccessPathApproxCons apa |
+ not evalUnfold(apa, false, _) and
+ head = apa.getHead() and
+ tail.getApprox() = getATail(apa, _)
+ )
+ } or
+ TAccessPathCons2(TypedContent head1, TypedContent head2, int len) {
+ exists(AccessPathApproxCons apa |
+ evalUnfold(apa, false, _) and
+ not expensiveLen1to2unfolding(apa, _) and
+ apa.len() = len and
+ head1 = apa.getHead() and
+ head2 = getATail(apa, _).getHead()
+ )
+ } or
+ TAccessPathCons1(TypedContent head, int len) {
+ exists(AccessPathApproxCons apa |
+ evalUnfold(apa, false, _) and
+ expensiveLen1to2unfolding(apa, _) and
+ apa.len() = len and
+ head = apa.getHead()
+ )
+ }
+
+private newtype TPathNode =
+ TPathNodeMid(NodeEx node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) {
+ // A PathNode is introduced by a source ...
+ Stage4::revFlow(node, config) and
+ sourceNode(node, config) and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
+ sc instanceof SummaryCtxNone and
+ ap = TAccessPathNil(node.getDataFlowType())
+ or
+ // ... or a step from an existing PathNode to another node.
+ exists(PathNodeMid mid |
+ pathStep(mid, node, cc, sc, ap) and
+ pragma[only_bind_into](config) = mid.getConfiguration() and
+ Stage4::revFlow(node, _, _, ap.getApprox(), pragma[only_bind_into](config))
+ )
+ } or
+ TPathNodeSink(NodeEx node, Configuration config) {
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
+ )
+ }
+
+/**
+ * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a
+ * source to a given node with a given `AccessPath`, this indicates the sequence
+ * of dereference operations needed to get from the value in the node to the
+ * tracked object. The final type indicates the type of the tracked object.
+ */
+abstract private class AccessPath extends TAccessPath {
+ /** Gets the head of this access path, if any. */
+ abstract TypedContent getHead();
+
+ /** Gets the tail of this access path, if any. */
+ abstract AccessPath getTail();
+
+ /** Gets the front of this access path. */
+ abstract AccessPathFront getFront();
+
+ /** Gets the approximation of this access path. */
+ abstract AccessPathApprox getApprox();
+
+ /** Gets the length of this access path. */
+ abstract int length();
+
+ /** Gets a textual representation of this access path. */
+ abstract string toString();
+
+ /** Gets the access path obtained by popping `tc` from this access path, if any. */
+ final AccessPath pop(TypedContent tc) {
+ result = this.getTail() and
+ tc = this.getHead()
+ }
+
+ /** Gets the access path obtained by pushing `tc` onto this access path. */
+ final AccessPath push(TypedContent tc) { this = result.pop(tc) }
+}
+
+private class AccessPathNil extends AccessPath, TAccessPathNil {
+ private DataFlowType t;
+
+ AccessPathNil() { this = TAccessPathNil(t) }
+
+ DataFlowType getType() { result = t }
+
+ override TypedContent getHead() { none() }
+
+ override AccessPath getTail() { none() }
+
+ override AccessPathFrontNil getFront() { result = TFrontNil(t) }
+
+ override AccessPathApproxNil getApprox() { result = TNil(t) }
+
+ override int length() { result = 0 }
+
+ override string toString() { result = concat(": " + ppReprType(t)) }
+}
+
+private class AccessPathCons extends AccessPath, TAccessPathCons {
+ private TypedContent head;
+ private AccessPath tail;
+
+ AccessPathCons() { this = TAccessPathCons(head, tail) }
+
+ override TypedContent getHead() { result = head }
+
+ override AccessPath getTail() { result = tail }
+
+ override AccessPathFrontHead getFront() { result = TFrontHead(head) }
+
+ override AccessPathApproxCons getApprox() {
+ result = TConsNil(head, tail.(AccessPathNil).getType())
+ or
+ result = TConsCons(head, tail.getHead(), this.length())
+ or
+ result = TCons1(head, this.length())
+ }
+
+ override int length() { result = 1 + tail.length() }
+
+ private string toStringImpl(boolean needsSuffix) {
+ exists(DataFlowType t |
+ tail = TAccessPathNil(t) and
+ needsSuffix = false and
+ result = head.toString() + "]" + concat(" : " + ppReprType(t))
+ )
+ or
+ result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix)
+ or
+ exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) |
+ result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true
+ or
+ result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false
+ )
+ or
+ exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) |
+ result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true
+ or
+ result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false
+ )
+ }
+
+ override string toString() {
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
+ or
+ result = "[" + this.toStringImpl(false)
+ }
+}
+
+private class AccessPathCons2 extends AccessPath, TAccessPathCons2 {
+ private TypedContent head1;
+ private TypedContent head2;
+ private int len;
+
+ AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) }
+
+ override TypedContent getHead() { result = head1 }
+
+ override AccessPath getTail() {
+ Stage4::consCand(head1, result.getApprox(), _) and
+ result.getHead() = head2 and
+ result.length() = len - 1
+ }
+
+ override AccessPathFrontHead getFront() { result = TFrontHead(head1) }
+
+ override AccessPathApproxCons getApprox() {
+ result = TConsCons(head1, head2, len) or
+ result = TCons1(head1, len)
+ }
+
+ override int length() { result = len }
+
+ override string toString() {
+ if len = 2
+ then result = "[" + head1.toString() + ", " + head2.toString() + "]"
+ else
+ result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]"
+ }
+}
+
+private class AccessPathCons1 extends AccessPath, TAccessPathCons1 {
+ private TypedContent head;
+ private int len;
+
+ AccessPathCons1() { this = TAccessPathCons1(head, len) }
+
+ override TypedContent getHead() { result = head }
+
+ override AccessPath getTail() {
+ Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1
+ }
+
+ override AccessPathFrontHead getFront() { result = TFrontHead(head) }
+
+ override AccessPathApproxCons getApprox() { result = TCons1(head, len) }
+
+ override int length() { result = len }
+
+ override string toString() {
+ if len = 1
+ then result = "[" + head.toString() + "]"
+ else result = "[" + head.toString() + ", ... (" + len.toString() + ")]"
+ }
+}
+
+/**
+ * A `Node` augmented with a call context (except for sinks), an access path, and a configuration.
+ * Only those `PathNode`s that are reachable from a source are generated.
+ */
+class PathNode extends TPathNode {
+ /** Gets a textual representation of this element. */
+ string toString() { none() }
+
+ /**
+ * Gets a textual representation of this element, including a textual
+ * representation of the call context.
+ */
+ string toStringWithContext() { none() }
+
+ /**
+ * Holds if this element is at the specified location.
+ * The location spans column `startcolumn` of line `startline` to
+ * column `endcolumn` of line `endline` in file `filepath`.
+ * For more information, see
+ * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
+ */
+ predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
+ none()
+ }
+
+ /** Gets the underlying `Node`. */
+ final Node getNode() { this.(PathNodeImpl).getNodeEx().projectToNode() = result }
+
+ /** Gets the associated configuration. */
+ Configuration getConfiguration() { none() }
+
+ private PathNode getASuccessorIfHidden() {
+ this.(PathNodeImpl).isHidden() and
+ result = this.(PathNodeImpl).getASuccessorImpl()
+ }
+
+ /** Gets a successor of this node, if any. */
+ final PathNode getASuccessor() {
+ result = this.(PathNodeImpl).getASuccessorImpl().getASuccessorIfHidden*() and
+ not this.(PathNodeImpl).isHidden() and
+ not result.(PathNodeImpl).isHidden()
+ }
+
+ /** Holds if this node is a source. */
+ predicate isSource() { none() }
+}
+
+abstract private class PathNodeImpl extends PathNode {
+ abstract PathNode getASuccessorImpl();
+
+ abstract NodeEx getNodeEx();
+
+ predicate isHidden() {
+ hiddenNode(this.getNodeEx().asNode()) and
+ not this.isSource() and
+ not this instanceof PathNodeSink
+ or
+ this.getNodeEx() instanceof TNodeImplicitRead
+ }
+
+ private string ppAp() {
+ this instanceof PathNodeSink and result = ""
+ or
+ exists(string s | s = this.(PathNodeMid).getAp().toString() |
+ if s = "" then result = "" else result = " " + s
+ )
+ }
+
+ private string ppCtx() {
+ this instanceof PathNodeSink and result = ""
+ or
+ result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
+ }
+
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
+
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
+
+ override predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
+ this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ }
+}
+
+/** Holds if `n` can reach a sink. */
+private predicate directReach(PathNode n) {
+ n instanceof PathNodeSink or directReach(n.getASuccessor())
+}
+
+/** Holds if `n` can reach a sink or is used in a subpath. */
+private predicate reach(PathNode n) { directReach(n) or Subpaths::retReach(n) }
+
+/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */
+private predicate pathSucc(PathNode n1, PathNode n2) { n1.getASuccessor() = n2 and directReach(n2) }
+
+private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1, n2)
+
+/**
+ * Provides the query predicates needed to include a graph in a path-problem query.
+ */
+module PathGraph {
+ /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
+ query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b and reach(b) }
+
+ /** Holds if `n` is a node in the graph of data flow path explanations. */
+ query predicate nodes(PathNode n, string key, string val) {
+ reach(n) and key = "semmle.label" and val = n.toString()
+ }
+
+ query predicate subpaths = Subpaths::subpaths/4;
+}
+
+/**
+ * An intermediate flow graph node. This is a triple consisting of a `Node`,
+ * a `CallContext`, and a `Configuration`.
+ */
+private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
+ NodeEx node;
+ CallContext cc;
+ SummaryCtx sc;
+ AccessPath ap;
+ Configuration config;
+
+ PathNodeMid() { this = TPathNodeMid(node, cc, sc, ap, config) }
+
+ override NodeEx getNodeEx() { result = node }
+
+ CallContext getCallContext() { result = cc }
+
+ SummaryCtx getSummaryCtx() { result = sc }
+
+ AccessPath getAp() { result = ap }
+
+ override Configuration getConfiguration() { result = config }
+
+ private PathNodeMid getSuccMid() {
+ pathStep(this, result.getNodeEx(), result.getCallContext(), result.getSummaryCtx(),
+ result.getAp()) and
+ result.getConfiguration() = unbindConf(this.getConfiguration())
+ }
+
+ override PathNodeImpl getASuccessorImpl() {
+ // an intermediate step to another intermediate node
+ result = this.getSuccMid()
+ or
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
+ }
+
+ override predicate isSource() {
+ sourceNode(node, config) and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
+ sc instanceof SummaryCtxNone and
+ ap instanceof AccessPathNil
+ }
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
+}
+
+/**
+ * A flow graph node corresponding to a sink. This is disjoint from the
+ * intermediate nodes in order to uniquely correspond to a given sink by
+ * excluding the `CallContext`.
+ */
+private class PathNodeSink extends PathNodeImpl, TPathNodeSink {
+ NodeEx node;
+ Configuration config;
+
+ PathNodeSink() { this = TPathNodeSink(node, config) }
+
+ override NodeEx getNodeEx() { result = node }
+
+ override Configuration getConfiguration() { result = config }
+
+ override PathNode getASuccessorImpl() { none() }
+
+ override predicate isSource() { sourceNode(node, config) }
+}
+
+/**
+ * Holds if data may flow from `mid` to `node`. The last step in or out of
+ * a callable is recorded by `cc`.
+ */
+private predicate pathStep(
+ PathNodeMid mid, NodeEx node, CallContext cc, SummaryCtx sc, AccessPath ap
+) {
+ exists(AccessPath ap0, NodeEx midnode, Configuration conf, LocalCallContext localCC |
+ midnode = mid.getNodeEx() and
+ conf = mid.getConfiguration() and
+ cc = mid.getCallContext() and
+ sc = mid.getSummaryCtx() and
+ localCC =
+ getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)),
+ midnode.getEnclosingCallable()) and
+ ap0 = mid.getAp()
+ |
+ localFlowBigStep(midnode, node, true, _, conf, localCC) and
+ ap = ap0
+ or
+ localFlowBigStep(midnode, node, false, ap.getFront(), conf, localCC) and
+ ap0 instanceof AccessPathNil
+ )
+ or
+ jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and
+ cc instanceof CallContextAny and
+ sc instanceof SummaryCtxNone and
+ ap = mid.getAp()
+ or
+ additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and
+ cc instanceof CallContextAny and
+ sc instanceof SummaryCtxNone and
+ mid.getAp() instanceof AccessPathNil and
+ ap = TAccessPathNil(node.getDataFlowType())
+ or
+ exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and
+ sc = mid.getSummaryCtx()
+ or
+ exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
+ sc = mid.getSummaryCtx()
+ or
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
+ or
+ pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
+ or
+ pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx()
+}
+
+pragma[nomagic]
+private predicate pathReadStep(
+ PathNodeMid mid, NodeEx node, AccessPath ap0, TypedContent tc, CallContext cc
+) {
+ ap0 = mid.getAp() and
+ tc = ap0.getHead() and
+ Stage4::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and
+ cc = mid.getCallContext()
+}
+
+pragma[nomagic]
+private predicate pathStoreStep(
+ PathNodeMid mid, NodeEx node, AccessPath ap0, TypedContent tc, CallContext cc
+) {
+ ap0 = mid.getAp() and
+ Stage4::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and
+ cc = mid.getCallContext()
+}
+
+private predicate pathOutOfCallable0(
+ PathNodeMid mid, ReturnPosition pos, CallContext innercc, AccessPathApprox apa,
+ Configuration config
+) {
+ pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and
+ innercc = mid.getCallContext() and
+ innercc instanceof CallContextNoCall and
+ apa = mid.getAp().getApprox() and
+ config = mid.getConfiguration()
+}
+
+pragma[nomagic]
+private predicate pathOutOfCallable1(
+ PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, AccessPathApprox apa,
+ Configuration config
+) {
+ exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc |
+ pathOutOfCallable0(mid, pos, innercc, apa, config) and
+ c = pos.getCallable() and
+ kind = pos.getKind() and
+ resolveReturn(innercc, c, call)
+ |
+ if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext()
+ )
+}
+
+pragma[noinline]
+private NodeEx getAnOutNodeFlow(
+ ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config
+) {
+ result.asNode() = kind.getAnOutNode(call) and
+ Stage4::revFlow(result, _, _, apa, config)
+}
+
+/**
+ * Holds if data may flow from `mid` to `out`. The last step of this path
+ * is a return from a callable and is recorded by `cc`, if needed.
+ */
+pragma[noinline]
+private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc) {
+ exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config |
+ pathOutOfCallable1(mid, call, kind, cc, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
+ )
+}
+
+/**
+ * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`.
+ */
+pragma[noinline]
+private predicate pathIntoArg(
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
+) {
+ exists(ArgNode arg |
+ arg = mid.getNodeEx().asNode() and
+ cc = mid.getCallContext() and
+ arg.argumentOf(call, i) and
+ ap = mid.getAp() and
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
+ )
+}
+
+pragma[nomagic]
+private predicate parameterCand(
+ DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
+) {
+ exists(ParamNodeEx p |
+ Stage4::revFlow(p, _, _, apa, config) and
+ p.isParameterOf(callable, i)
+ )
+}
+
+pragma[nomagic]
+private predicate pathIntoCallable0(
+ PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
+ AccessPath ap, Configuration config
+) {
+ exists(AccessPathApprox apa |
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
+ callable = resolveCall(call, outercc) and
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
+ )
+}
+
+/**
+ * Holds if data may flow from `mid` to `p` through `call`. The contexts
+ * before and after entering the callable are `outercc` and `innercc`,
+ * respectively.
+ */
+pragma[nomagic]
+private predicate pathIntoCallable(
+ PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
+ DataFlowCall call, Configuration config
+) {
+ exists(int i, DataFlowCallable callable, AccessPath ap |
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
+ p.isParameterOf(callable, i) and
+ (
+ sc = TSummaryCtxSome(p, ap)
+ or
+ not exists(TSummaryCtxSome(p, ap)) and
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
+ )
+ |
+ if recordDataFlowCallSite(call, callable)
+ then innercc = TSpecificCall(call)
+ else innercc = TSomeCall()
+ )
+}
+
+/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */
+pragma[nomagic]
+private predicate paramFlowsThrough(
+ ReturnKindExt kind, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, AccessPathApprox apa,
+ Configuration config
+) {
+ exists(PathNodeMid mid, RetNodeEx ret, int pos |
+ mid.getNodeEx() = ret and
+ kind = ret.getKind() and
+ cc = mid.getCallContext() and
+ sc = mid.getSummaryCtx() and
+ config = mid.getConfiguration() and
+ ap = mid.getAp() and
+ apa = ap.getApprox() and
+ pos = sc.getParameterPos() and
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
+ )
+}
+
+pragma[nomagic]
+private predicate pathThroughCallable0(
+ DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
+ AccessPathApprox apa, Configuration config
+) {
+ exists(CallContext innercc, SummaryCtx sc |
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
+ )
+}
+
+/**
+ * Holds if data may flow from `mid` through a callable to the node `out`.
+ * The context `cc` is restored to its value prior to entering the callable.
+ */
+pragma[noinline]
+private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
+ )
+}
+
+private module Subpaths {
+ /**
+ * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by
+ * `kind`, `sc`, `apout`, and `innercc`.
+ */
+ pragma[nomagic]
+ private predicate subpaths01(
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ NodeEx out, AccessPath apout
+ ) {
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
+ }
+
+ /**
+ * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by
+ * `kind`, `sc`, `apout`, and `innercc`.
+ */
+ pragma[nomagic]
+ private predicate subpaths02(
+ PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ NodeEx out, AccessPath apout
+ ) {
+ subpaths01(arg, par, sc, innercc, kind, out, apout) and
+ out.asNode() = kind.getAnOutNode(_)
+ }
+
+ pragma[nomagic]
+ private Configuration getPathNodeConf(PathNode n) { result = n.getConfiguration() }
+
+ /**
+ * Holds if `(arg, par, ret, out)` forms a subpath-tuple.
+ */
+ pragma[nomagic]
+ private predicate subpaths03(
+ PathNode arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, AccessPath apout
+ ) {
+ exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode |
+ subpaths02(arg, par, sc, innercc, kind, out, apout) and
+ ret.getNodeEx() = retnode and
+ kind = retnode.getKind() and
+ innercc = ret.getCallContext() and
+ sc = ret.getSummaryCtx() and
+ ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
+ )
+ }
+
+ /**
+ * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through
+ * a subpath between `par` and `ret` with the connecting edges `arg -> par` and
+ * `ret -> out` is summarized as the edge `arg -> out`.
+ */
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
+ exists(ParamNodeEx p, NodeEx o, AccessPath apout |
+ pragma[only_bind_into](arg).getASuccessor() = par and
+ pragma[only_bind_into](arg).getASuccessor() = out and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
+ par.getNodeEx() = p and
+ out.getNodeEx() = o and
+ out.getAp() = apout
+ )
+ }
+
+ /**
+ * Holds if `n` can reach a return node in a summarized subpath.
+ */
+ predicate retReach(PathNode n) {
+ subpaths(_, _, n, _)
+ or
+ exists(PathNode mid |
+ retReach(mid) and
+ n.getASuccessor() = mid and
+ not subpaths(_, mid, _, _)
+ )
+ }
+}
+
+/**
+ * Holds if data can flow (inter-procedurally) from `source` to `sink`.
+ *
+ * Will only have results if `configuration` has non-empty sources and
+ * sinks.
+ */
+private predicate flowsTo(
+ PathNode flowsource, PathNodeSink flowsink, Node source, Node sink, Configuration configuration
+) {
+ flowsource.isSource() and
+ flowsource.getConfiguration() = configuration and
+ flowsource.(PathNodeImpl).getNodeEx().asNode() = source and
+ (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and
+ flowsink.getNodeEx().asNode() = sink
+}
+
+/**
+ * Holds if data can flow (inter-procedurally) from `source` to `sink`.
+ *
+ * Will only have results if `configuration` has non-empty sources and
+ * sinks.
+ */
+predicate flowsTo(Node source, Node sink, Configuration configuration) {
+ flowsTo(_, _, source, sink, configuration)
+}
+
+private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) {
+ fwd = true and
+ nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and
+ fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and
+ conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and
+ tuples = count(PathNode pn)
+ or
+ fwd = false and
+ nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and
+ fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and
+ conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and
+ tuples = count(PathNode pn | reach(pn))
+}
+
+/**
+ * INTERNAL: Only for debugging.
+ *
+ * Calculates per-stage metrics for data flow.
+ */
+predicate stageStats(
+ int n, string stage, int nodes, int fields, int conscand, int tuples, Configuration config
+) {
+ stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config)
+ or
+ stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config)
+ or
+ stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config)
+ or
+ stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config)
+ or
+ stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config)
+ or
+ stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config)
+ or
+ stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config)
+ or
+ stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config)
+ or
+ stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples)
+ or
+ stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples)
+}
+
+private module FlowExploration {
+ private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) {
+ exists(NodeEx node1, NodeEx node2 |
+ jumpStep(node1, node2, config)
+ or
+ additionalJumpStep(node1, node2, config)
+ or
+ // flow into callable
+ viableParamArgEx(_, node2, node1)
+ or
+ // flow out of a callable
+ viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2)
+ |
+ c1 = node1.getEnclosingCallable() and
+ c2 = node2.getEnclosingCallable() and
+ c1 != c2
+ )
+ }
+
+ private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) {
+ exists(Node n | config.isSource(n) and c = getNodeEnclosingCallable(n))
+ or
+ exists(DataFlowCallable mid |
+ interestingCallableSrc(mid, config) and callableStep(mid, c, config)
+ )
+ }
+
+ private predicate interestingCallableSink(DataFlowCallable c, Configuration config) {
+ exists(Node n | config.isSink(n) and c = getNodeEnclosingCallable(n))
+ or
+ exists(DataFlowCallable mid |
+ interestingCallableSink(mid, config) and callableStep(c, mid, config)
+ )
+ }
+
+ private newtype TCallableExt =
+ TCallable(DataFlowCallable c, Configuration config) {
+ interestingCallableSrc(c, config) or
+ interestingCallableSink(c, config)
+ } or
+ TCallableSrc() or
+ TCallableSink()
+
+ private predicate callableExtSrc(TCallableSrc src) { any() }
+
+ private predicate callableExtSink(TCallableSink sink) { any() }
+
+ private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) {
+ exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config |
+ callableStep(c1, c2, config) and
+ ce1 = TCallable(c1, pragma[only_bind_into](config)) and
+ ce2 = TCallable(c2, pragma[only_bind_into](config))
+ )
+ or
+ exists(Node n, Configuration config |
+ ce1 = TCallableSrc() and
+ config.isSource(n) and
+ ce2 = TCallable(getNodeEnclosingCallable(n), config)
+ )
+ or
+ exists(Node n, Configuration config |
+ ce2 = TCallableSink() and
+ config.isSink(n) and
+ ce1 = TCallable(getNodeEnclosingCallable(n), config)
+ )
+ }
+
+ private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) {
+ callableExtStepFwd(ce2, ce1)
+ }
+
+ private int distSrcExt(TCallableExt c) =
+ shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result)
+
+ private int distSinkExt(TCallableExt c) =
+ shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result)
+
+ private int distSrc(DataFlowCallable c, Configuration config) {
+ result = distSrcExt(TCallable(c, config)) - 1
+ }
+
+ private int distSink(DataFlowCallable c, Configuration config) {
+ result = distSinkExt(TCallable(c, config)) - 1
+ }
+
+ private newtype TPartialAccessPath =
+ TPartialNil(DataFlowType t) or
+ TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] }
+
+ /**
+ * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first
+ * element of the list and its length are tracked. If data flows from a source to
+ * a given node with a given `AccessPath`, this indicates the sequence of
+ * dereference operations needed to get from the value in the node to the
+ * tracked object. The final type indicates the type of the tracked object.
+ */
+ private class PartialAccessPath extends TPartialAccessPath {
+ abstract string toString();
+
+ TypedContent getHead() { this = TPartialCons(result, _) }
+
+ int len() {
+ this = TPartialNil(_) and result = 0
+ or
+ this = TPartialCons(_, result)
+ }
+
+ DataFlowType getType() {
+ this = TPartialNil(result)
+ or
+ exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType())
+ }
+ }
+
+ private class PartialAccessPathNil extends PartialAccessPath, TPartialNil {
+ override string toString() {
+ exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t)))
+ }
+ }
+
+ private class PartialAccessPathCons extends PartialAccessPath, TPartialCons {
+ override string toString() {
+ exists(TypedContent tc, int len | this = TPartialCons(tc, len) |
+ if len = 1
+ then result = "[" + tc.toString() + "]"
+ else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]"
+ )
+ }
+ }
+
+ private newtype TRevPartialAccessPath =
+ TRevPartialNil() or
+ TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] }
+
+ /**
+ * Conceptually a list of `Content`s, but only the first
+ * element of the list and its length are tracked.
+ */
+ private class RevPartialAccessPath extends TRevPartialAccessPath {
+ abstract string toString();
+
+ Content getHead() { this = TRevPartialCons(result, _) }
+
+ int len() {
+ this = TRevPartialNil() and result = 0
+ or
+ this = TRevPartialCons(_, result)
+ }
+ }
+
+ private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil {
+ override string toString() { result = "" }
+ }
+
+ private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons {
+ override string toString() {
+ exists(Content c, int len | this = TRevPartialCons(c, len) |
+ if len = 1
+ then result = "[" + c.toString() + "]"
+ else result = "[" + c.toString() + ", ... (" + len.toString() + ")]"
+ )
+ }
+ }
+
+ private newtype TSummaryCtx1 =
+ TSummaryCtx1None() or
+ TSummaryCtx1Param(ParamNodeEx p)
+
+ private newtype TSummaryCtx2 =
+ TSummaryCtx2None() or
+ TSummaryCtx2Some(PartialAccessPath ap)
+
+ private newtype TRevSummaryCtx1 =
+ TRevSummaryCtx1None() or
+ TRevSummaryCtx1Some(ReturnPosition pos)
+
+ private newtype TRevSummaryCtx2 =
+ TRevSummaryCtx2None() or
+ TRevSummaryCtx2Some(RevPartialAccessPath ap)
+
+ private newtype TPartialPathNode =
+ TPartialPathNodeFwd(
+ NodeEx node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap,
+ Configuration config
+ ) {
+ sourceNode(node, config) and
+ cc instanceof CallContextAny and
+ sc1 = TSummaryCtx1None() and
+ sc2 = TSummaryCtx2None() and
+ ap = TPartialNil(node.getDataFlowType()) and
+ not fullBarrier(node, config) and
+ exists(config.explorationLimit())
+ or
+ partialPathNodeMk0(node, cc, sc1, sc2, ap, config) and
+ distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit()
+ } or
+ TPartialPathNodeRev(
+ NodeEx node, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, RevPartialAccessPath ap,
+ Configuration config
+ ) {
+ sinkNode(node, config) and
+ sc1 = TRevSummaryCtx1None() and
+ sc2 = TRevSummaryCtx2None() and
+ ap = TRevPartialNil() and
+ not fullBarrier(node, config) and
+ exists(config.explorationLimit())
+ or
+ exists(PartialPathNodeRev mid |
+ revPartialPathStep(mid, node, sc1, sc2, ap, config) and
+ not clearsContentCached(node.asNode(), ap.getHead()) and
+ not fullBarrier(node, config) and
+ distSink(node.getEnclosingCallable(), config) <= config.explorationLimit()
+ )
+ }
+
+ pragma[nomagic]
+ private predicate partialPathNodeMk0(
+ NodeEx node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap,
+ Configuration config
+ ) {
+ exists(PartialPathNodeFwd mid |
+ partialPathStep(mid, node, cc, sc1, sc2, ap, config) and
+ not fullBarrier(node, config) and
+ not clearsContentCached(node.asNode(), ap.getHead().getContent()) and
+ if node.asNode() instanceof CastingNode
+ then compatibleTypes(node.getDataFlowType(), ap.getType())
+ else any()
+ )
+ }
+
+ /**
+ * A `Node` augmented with a call context, an access path, and a configuration.
+ */
+ class PartialPathNode extends TPartialPathNode {
+ /** Gets a textual representation of this element. */
+ string toString() { result = this.getNodeEx().toString() + this.ppAp() }
+
+ /**
+ * Gets a textual representation of this element, including a textual
+ * representation of the call context.
+ */
+ string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
+
+ /**
+ * Holds if this element is at the specified location.
+ * The location spans column `startcolumn` of line `startline` to
+ * column `endcolumn` of line `endline` in file `filepath`.
+ * For more information, see
+ * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
+ */
+ predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
+ this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ }
+
+ /** Gets the underlying `Node`. */
+ final Node getNode() { this.getNodeEx().projectToNode() = result }
+
+ private NodeEx getNodeEx() {
+ result = this.(PartialPathNodeFwd).getNodeEx() or
+ result = this.(PartialPathNodeRev).getNodeEx()
+ }
+
+ /** Gets the associated configuration. */
+ Configuration getConfiguration() { none() }
+
+ /** Gets a successor of this node, if any. */
+ PartialPathNode getASuccessor() { none() }
+
+ /**
+ * Gets the approximate distance to the nearest source measured in number
+ * of interprocedural steps.
+ */
+ int getSourceDistance() {
+ result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration())
+ }
+
+ /**
+ * Gets the approximate distance to the nearest sink measured in number
+ * of interprocedural steps.
+ */
+ int getSinkDistance() {
+ result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration())
+ }
+
+ private string ppAp() {
+ exists(string s |
+ s = this.(PartialPathNodeFwd).getAp().toString() or
+ s = this.(PartialPathNodeRev).getAp().toString()
+ |
+ if s = "" then result = "" else result = " " + s
+ )
+ }
+
+ private string ppCtx() {
+ result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">"
+ }
+
+ /** Holds if this is a source in a forward-flow path. */
+ predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() }
+
+ /** Holds if this is a sink in a reverse-flow path. */
+ predicate isRevSink() { this.(PartialPathNodeRev).isSink() }
+ }
+
+ /**
+ * Provides the query predicates needed to include a graph in a path-problem query.
+ */
+ module PartialPathGraph {
+ /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
+ query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b }
+ }
+
+ private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd {
+ NodeEx node;
+ CallContext cc;
+ TSummaryCtx1 sc1;
+ TSummaryCtx2 sc2;
+ PartialAccessPath ap;
+ Configuration config;
+
+ PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, cc, sc1, sc2, ap, config) }
+
+ NodeEx getNodeEx() { result = node }
+
+ CallContext getCallContext() { result = cc }
+
+ TSummaryCtx1 getSummaryCtx1() { result = sc1 }
+
+ TSummaryCtx2 getSummaryCtx2() { result = sc2 }
+
+ PartialAccessPath getAp() { result = ap }
+
+ override Configuration getConfiguration() { result = config }
+
+ override PartialPathNodeFwd getASuccessor() {
+ partialPathStep(this, result.getNodeEx(), result.getCallContext(), result.getSummaryCtx1(),
+ result.getSummaryCtx2(), result.getAp(), result.getConfiguration())
+ }
+
+ predicate isSource() {
+ sourceNode(node, config) and
+ cc instanceof CallContextAny and
+ sc1 = TSummaryCtx1None() and
+ sc2 = TSummaryCtx2None() and
+ ap instanceof TPartialNil
+ }
+ }
+
+ private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev {
+ NodeEx node;
+ TRevSummaryCtx1 sc1;
+ TRevSummaryCtx2 sc2;
+ RevPartialAccessPath ap;
+ Configuration config;
+
+ PartialPathNodeRev() { this = TPartialPathNodeRev(node, sc1, sc2, ap, config) }
+
+ NodeEx getNodeEx() { result = node }
+
+ TRevSummaryCtx1 getSummaryCtx1() { result = sc1 }
+
+ TRevSummaryCtx2 getSummaryCtx2() { result = sc2 }
+
+ RevPartialAccessPath getAp() { result = ap }
+
+ override Configuration getConfiguration() { result = config }
+
+ override PartialPathNodeRev getASuccessor() {
+ revPartialPathStep(result, this.getNodeEx(), this.getSummaryCtx1(), this.getSummaryCtx2(),
+ this.getAp(), this.getConfiguration())
+ }
+
+ predicate isSink() {
+ sinkNode(node, config) and
+ sc1 = TRevSummaryCtx1None() and
+ sc2 = TRevSummaryCtx2None() and
+ ap = TRevPartialNil()
+ }
+ }
+
+ private predicate partialPathStep(
+ PartialPathNodeFwd mid, NodeEx node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2,
+ PartialAccessPath ap, Configuration config
+ ) {
+ not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and
+ (
+ localFlowStep(mid.getNodeEx(), node, config) and
+ cc = mid.getCallContext() and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2() and
+ ap = mid.getAp() and
+ config = mid.getConfiguration()
+ or
+ additionalLocalFlowStep(mid.getNodeEx(), node, config) and
+ cc = mid.getCallContext() and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2() and
+ mid.getAp() instanceof PartialAccessPathNil and
+ ap = TPartialNil(node.getDataFlowType()) and
+ config = mid.getConfiguration()
+ )
+ or
+ jumpStep(mid.getNodeEx(), node, config) and
+ cc instanceof CallContextAny and
+ sc1 = TSummaryCtx1None() and
+ sc2 = TSummaryCtx2None() and
+ ap = mid.getAp() and
+ config = mid.getConfiguration()
+ or
+ additionalJumpStep(mid.getNodeEx(), node, config) and
+ cc instanceof CallContextAny and
+ sc1 = TSummaryCtx1None() and
+ sc2 = TSummaryCtx2None() and
+ mid.getAp() instanceof PartialAccessPathNil and
+ ap = TPartialNil(node.getDataFlowType()) and
+ config = mid.getConfiguration()
+ or
+ partialPathStoreStep(mid, _, _, node, ap) and
+ cc = mid.getCallContext() and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2() and
+ config = mid.getConfiguration()
+ or
+ exists(PartialAccessPath ap0, TypedContent tc |
+ partialPathReadStep(mid, ap0, tc, node, cc, config) and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2() and
+ apConsFwd(ap, tc, ap0, config)
+ )
+ or
+ partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config)
+ or
+ partialPathOutOfCallable(mid, node, cc, ap, config) and
+ sc1 = TSummaryCtx1None() and
+ sc2 = TSummaryCtx2None()
+ or
+ partialPathThroughCallable(mid, node, cc, ap, config) and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2()
+ }
+
+ bindingset[result, i]
+ private int unbindInt(int i) { i <= result and i >= result }
+
+ pragma[inline]
+ private predicate partialPathStoreStep(
+ PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node,
+ PartialAccessPath ap2
+ ) {
+ exists(NodeEx midNode, DataFlowType contentType |
+ midNode = mid.getNodeEx() and
+ ap1 = mid.getAp() and
+ store(midNode, tc, node, contentType, mid.getConfiguration()) and
+ ap2.getHead() = tc and
+ ap2.len() = unbindInt(ap1.len() + 1) and
+ compatibleTypes(ap1.getType(), contentType)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate apConsFwd(
+ PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config
+ ) {
+ exists(PartialPathNodeFwd mid |
+ partialPathStoreStep(mid, ap1, tc, _, ap2) and
+ config = mid.getConfiguration()
+ )
+ }
+
+ pragma[nomagic]
+ private predicate partialPathReadStep(
+ PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc,
+ Configuration config
+ ) {
+ exists(NodeEx midNode |
+ midNode = mid.getNodeEx() and
+ ap = mid.getAp() and
+ read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and
+ ap.getHead() = tc and
+ pragma[only_bind_into](config) = mid.getConfiguration() and
+ cc = mid.getCallContext()
+ )
+ }
+
+ private predicate partialPathOutOfCallable0(
+ PartialPathNodeFwd mid, ReturnPosition pos, CallContext innercc, PartialAccessPath ap,
+ Configuration config
+ ) {
+ pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and
+ innercc = mid.getCallContext() and
+ innercc instanceof CallContextNoCall and
+ ap = mid.getAp() and
+ config = mid.getConfiguration()
+ }
+
+ pragma[nomagic]
+ private predicate partialPathOutOfCallable1(
+ PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, CallContext cc,
+ PartialAccessPath ap, Configuration config
+ ) {
+ exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc |
+ partialPathOutOfCallable0(mid, pos, innercc, ap, config) and
+ c = pos.getCallable() and
+ kind = pos.getKind() and
+ resolveReturn(innercc, c, call)
+ |
+ if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext()
+ )
+ }
+
+ private predicate partialPathOutOfCallable(
+ PartialPathNodeFwd mid, NodeEx out, CallContext cc, PartialAccessPath ap, Configuration config
+ ) {
+ exists(ReturnKindExt kind, DataFlowCall call |
+ partialPathOutOfCallable1(mid, call, kind, cc, ap, config)
+ |
+ out.asNode() = kind.getAnOutNode(call)
+ )
+ }
+
+ pragma[noinline]
+ private predicate partialPathIntoArg(
+ PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap,
+ Configuration config
+ ) {
+ exists(ArgNode arg |
+ arg = mid.getNodeEx().asNode() and
+ cc = mid.getCallContext() and
+ arg.argumentOf(call, i) and
+ ap = mid.getAp() and
+ config = mid.getConfiguration()
+ )
+ }
+
+ pragma[nomagic]
+ private predicate partialPathIntoCallable0(
+ PartialPathNodeFwd mid, DataFlowCallable callable, int i, CallContext outercc,
+ DataFlowCall call, PartialAccessPath ap, Configuration config
+ ) {
+ partialPathIntoArg(mid, i, outercc, call, ap, config) and
+ callable = resolveCall(call, outercc)
+ }
+
+ private predicate partialPathIntoCallable(
+ PartialPathNodeFwd mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc,
+ TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap,
+ Configuration config
+ ) {
+ exists(int i, DataFlowCallable callable |
+ partialPathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
+ p.isParameterOf(callable, i) and
+ sc1 = TSummaryCtx1Param(p) and
+ sc2 = TSummaryCtx2Some(ap)
+ |
+ if recordDataFlowCallSite(call, callable)
+ then innercc = TSpecificCall(call)
+ else innercc = TSomeCall()
+ )
+ }
+
+ pragma[nomagic]
+ private predicate paramFlowsThroughInPartialPath(
+ ReturnKindExt kind, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2,
+ PartialAccessPath ap, Configuration config
+ ) {
+ exists(PartialPathNodeFwd mid, RetNodeEx ret |
+ mid.getNodeEx() = ret and
+ kind = ret.getKind() and
+ cc = mid.getCallContext() and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2() and
+ config = mid.getConfiguration() and
+ ap = mid.getAp()
+ )
+ }
+
+ pragma[noinline]
+ private predicate partialPathThroughCallable0(
+ DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc,
+ PartialAccessPath ap, Configuration config
+ ) {
+ exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 |
+ partialPathIntoCallable(mid, _, cc, innercc, sc1, sc2, call, _, config) and
+ paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config)
+ )
+ }
+
+ private predicate partialPathThroughCallable(
+ PartialPathNodeFwd mid, NodeEx out, CallContext cc, PartialAccessPath ap, Configuration config
+ ) {
+ exists(DataFlowCall call, ReturnKindExt kind |
+ partialPathThroughCallable0(call, mid, kind, cc, ap, config) and
+ out.asNode() = kind.getAnOutNode(call)
+ )
+ }
+
+ private predicate revPartialPathStep(
+ PartialPathNodeRev mid, NodeEx node, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2,
+ RevPartialAccessPath ap, Configuration config
+ ) {
+ localFlowStep(node, mid.getNodeEx(), config) and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2() and
+ ap = mid.getAp() and
+ config = mid.getConfiguration()
+ or
+ additionalLocalFlowStep(node, mid.getNodeEx(), config) and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2() and
+ mid.getAp() instanceof RevPartialAccessPathNil and
+ ap = TRevPartialNil() and
+ config = mid.getConfiguration()
+ or
+ jumpStep(node, mid.getNodeEx(), config) and
+ sc1 = TRevSummaryCtx1None() and
+ sc2 = TRevSummaryCtx2None() and
+ ap = mid.getAp() and
+ config = mid.getConfiguration()
+ or
+ additionalJumpStep(node, mid.getNodeEx(), config) and
+ sc1 = TRevSummaryCtx1None() and
+ sc2 = TRevSummaryCtx2None() and
+ mid.getAp() instanceof RevPartialAccessPathNil and
+ ap = TRevPartialNil() and
+ config = mid.getConfiguration()
+ or
+ revPartialPathReadStep(mid, _, _, node, ap) and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2() and
+ config = mid.getConfiguration()
+ or
+ exists(RevPartialAccessPath ap0, Content c |
+ revPartialPathStoreStep(mid, ap0, c, node, config) and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2() and
+ apConsRev(ap, c, ap0, config)
+ )
+ or
+ exists(ParamNodeEx p |
+ mid.getNodeEx() = p and
+ viableParamArgEx(_, p, node) and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2() and
+ sc1 = TRevSummaryCtx1None() and
+ sc2 = TRevSummaryCtx2None() and
+ ap = mid.getAp() and
+ config = mid.getConfiguration()
+ )
+ or
+ exists(ReturnPosition pos |
+ revPartialPathIntoReturn(mid, pos, sc1, sc2, _, ap, config) and
+ pos = getReturnPosition(node.asNode())
+ )
+ or
+ revPartialPathThroughCallable(mid, node, ap, config) and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2()
+ }
+
+ pragma[inline]
+ private predicate revPartialPathReadStep(
+ PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node,
+ RevPartialAccessPath ap2
+ ) {
+ exists(NodeEx midNode |
+ midNode = mid.getNodeEx() and
+ ap1 = mid.getAp() and
+ read(node, c, midNode, mid.getConfiguration()) and
+ ap2.getHead() = c and
+ ap2.len() = unbindInt(ap1.len() + 1)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate apConsRev(
+ RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config
+ ) {
+ exists(PartialPathNodeRev mid |
+ revPartialPathReadStep(mid, ap1, c, _, ap2) and
+ config = mid.getConfiguration()
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revPartialPathStoreStep(
+ PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config
+ ) {
+ exists(NodeEx midNode, TypedContent tc |
+ midNode = mid.getNodeEx() and
+ ap = mid.getAp() and
+ store(node, tc, midNode, _, config) and
+ ap.getHead() = c and
+ config = mid.getConfiguration() and
+ tc.getContent() = c
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revPartialPathIntoReturn(
+ PartialPathNodeRev mid, ReturnPosition pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2,
+ DataFlowCall call, RevPartialAccessPath ap, Configuration config
+ ) {
+ exists(NodeEx out |
+ mid.getNodeEx() = out and
+ viableReturnPosOutEx(call, pos, out) and
+ sc1 = TRevSummaryCtx1Some(pos) and
+ sc2 = TRevSummaryCtx2Some(ap) and
+ ap = mid.getAp() and
+ config = mid.getConfiguration()
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revPartialPathFlowsThrough(
+ int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap,
+ Configuration config
+ ) {
+ exists(PartialPathNodeRev mid, ParamNodeEx p |
+ mid.getNodeEx() = p and
+ p.getPosition() = pos and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2() and
+ ap = mid.getAp() and
+ config = mid.getConfiguration()
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revPartialPathThroughCallable0(
+ DataFlowCall call, PartialPathNodeRev mid, int pos, RevPartialAccessPath ap,
+ Configuration config
+ ) {
+ exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2 |
+ revPartialPathIntoReturn(mid, _, sc1, sc2, call, _, config) and
+ revPartialPathFlowsThrough(pos, sc1, sc2, ap, config)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revPartialPathThroughCallable(
+ PartialPathNodeRev mid, ArgNodeEx node, RevPartialAccessPath ap, Configuration config
+ ) {
+ exists(DataFlowCall call, int pos |
+ revPartialPathThroughCallable0(call, mid, pos, ap, config) and
+ node.asNode().(ArgNode).argumentOf(call, pos)
+ )
+ }
+}
+
+import FlowExploration
+
+private predicate partialFlow(
+ PartialPathNode source, PartialPathNode node, Configuration configuration
+) {
+ source.getConfiguration() = configuration and
+ source.isFwdSource() and
+ node = source.getASuccessor+()
+}
+
+private predicate revPartialFlow(
+ PartialPathNode node, PartialPathNode sink, Configuration configuration
+) {
+ sink.getConfiguration() = configuration and
+ sink.isRevSink() and
+ node.getASuccessor+() = sink
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl2.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl2.qll
new file mode 100644
index 00000000000..08030e0b35b
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl2.qll
@@ -0,0 +1,4667 @@
+/**
+ * Provides an implementation of global (interprocedural) data flow. This file
+ * re-exports the local (intraprocedural) data flow analysis from
+ * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed
+ * through the `Configuration` class. This file exists in several identical
+ * copies, allowing queries to use multiple `Configuration` classes that depend
+ * on each other without introducing mutual recursion among those configurations.
+ */
+
+private import DataFlowImplCommon
+private import DataFlowImplSpecific::Private
+import DataFlowImplSpecific::Public
+import DataFlowImplCommonPublic
+
+/**
+ * A configuration of interprocedural data flow analysis. This defines
+ * sources, sinks, and any other configurable aspect of the analysis. Each
+ * use of the global data flow library must define its own unique extension
+ * of this abstract class. To create a configuration, extend this class with
+ * a subclass whose characteristic predicate is a unique singleton string.
+ * For example, write
+ *
+ * ```ql
+ * class MyAnalysisConfiguration extends DataFlow::Configuration {
+ * MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" }
+ * // Override `isSource` and `isSink`.
+ * // Optionally override `isBarrier`.
+ * // Optionally override `isAdditionalFlowStep`.
+ * }
+ * ```
+ * Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and
+ * the edges are those data-flow steps that preserve the value of the node
+ * along with any additional edges defined by `isAdditionalFlowStep`.
+ * Specifying nodes in `isBarrier` will remove those nodes from the graph, and
+ * specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going
+ * and/or out-going edges from those nodes, respectively.
+ *
+ * Then, to query whether there is flow between some `source` and `sink`,
+ * write
+ *
+ * ```ql
+ * exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink))
+ * ```
+ *
+ * Multiple configurations can coexist, but two classes extending
+ * `DataFlow::Configuration` should never depend on each other. One of them
+ * should instead depend on a `DataFlow2::Configuration`, a
+ * `DataFlow3::Configuration`, or a `DataFlow4::Configuration`.
+ */
+abstract class Configuration extends string {
+ bindingset[this]
+ Configuration() { any() }
+
+ /**
+ * Holds if `source` is a relevant data flow source.
+ */
+ abstract predicate isSource(Node source);
+
+ /**
+ * Holds if `sink` is a relevant data flow sink.
+ */
+ abstract predicate isSink(Node sink);
+
+ /**
+ * Holds if data flow through `node` is prohibited. This completely removes
+ * `node` from the data flow graph.
+ */
+ predicate isBarrier(Node node) { none() }
+
+ /** Holds if data flow into `node` is prohibited. */
+ predicate isBarrierIn(Node node) { none() }
+
+ /** Holds if data flow out of `node` is prohibited. */
+ predicate isBarrierOut(Node node) { none() }
+
+ /** Holds if data flow through nodes guarded by `guard` is prohibited. */
+ predicate isBarrierGuard(BarrierGuard guard) { none() }
+
+ /**
+ * Holds if the additional flow step from `node1` to `node2` must be taken
+ * into account in the analysis.
+ */
+ predicate isAdditionalFlowStep(Node node1, Node node2) { none() }
+
+ /**
+ * Holds if an arbitrary number of implicit read steps of content `c` may be
+ * taken at `node`.
+ */
+ predicate allowImplicitRead(Node node, Content c) { none() }
+
+ /**
+ * Gets the virtual dispatch branching limit when calculating field flow.
+ * This can be overridden to a smaller value to improve performance (a
+ * value of 0 disables field flow), or a larger value to get more results.
+ */
+ int fieldFlowBranchLimit() { result = 2 }
+
+ /**
+ * Gets a data flow configuration feature to add restrictions to the set of
+ * valid flow paths.
+ *
+ * - `FeatureHasSourceCallContext`:
+ * Assume that sources have some existing call context to disallow
+ * conflicting return-flow directly following the source.
+ * - `FeatureHasSinkCallContext`:
+ * Assume that sinks have some existing call context to disallow
+ * conflicting argument-to-parameter flow directly preceding the sink.
+ * - `FeatureEqualSourceSinkCallContext`:
+ * Implies both of the above and additionally ensures that the entire flow
+ * path preserves the call context.
+ */
+ FlowFeature getAFeature() { none() }
+
+ /**
+ * Holds if data may flow from `source` to `sink` for this configuration.
+ */
+ predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) }
+
+ /**
+ * Holds if data may flow from `source` to `sink` for this configuration.
+ *
+ * The corresponding paths are generated from the end-points and the graph
+ * included in the module `PathGraph`.
+ */
+ predicate hasFlowPath(PathNode source, PathNode sink) { flowsTo(source, sink, _, _, this) }
+
+ /**
+ * Holds if data may flow from some source to `sink` for this configuration.
+ */
+ predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
+
+ /**
+ * Holds if data may flow from some source to `sink` for this configuration.
+ */
+ predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
+
+ /**
+ * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
+ * measured in approximate number of interprocedural steps.
+ */
+ int explorationLimit() { none() }
+
+ /**
+ * Holds if there is a partial data flow path from `source` to `node`. The
+ * approximate distance between `node` and the closest source is `dist` and
+ * is restricted to be less than or equal to `explorationLimit()`. This
+ * predicate completely disregards sink definitions.
+ *
+ * This predicate is intended for data-flow exploration and debugging and may
+ * perform poorly if the number of sources is too big and/or the exploration
+ * limit is set too high without using barriers.
+ *
+ * This predicate is disabled (has no results) by default. Override
+ * `explorationLimit()` with a suitable number to enable this predicate.
+ *
+ * To use this in a `path-problem` query, import the module `PartialPathGraph`.
+ */
+ final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) {
+ partialFlow(source, node, this) and
+ dist = node.getSourceDistance()
+ }
+
+ /**
+ * Holds if there is a partial data flow path from `node` to `sink`. The
+ * approximate distance between `node` and the closest sink is `dist` and
+ * is restricted to be less than or equal to `explorationLimit()`. This
+ * predicate completely disregards source definitions.
+ *
+ * This predicate is intended for data-flow exploration and debugging and may
+ * perform poorly if the number of sinks is too big and/or the exploration
+ * limit is set too high without using barriers.
+ *
+ * This predicate is disabled (has no results) by default. Override
+ * `explorationLimit()` with a suitable number to enable this predicate.
+ *
+ * To use this in a `path-problem` query, import the module `PartialPathGraph`.
+ *
+ * Note that reverse flow has slightly lower precision than the corresponding
+ * forward flow, as reverse flow disregards type pruning among other features.
+ */
+ final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) {
+ revPartialFlow(node, sink, this) and
+ dist = node.getSinkDistance()
+ }
+}
+
+/**
+ * This class exists to prevent mutual recursion between the user-overridden
+ * member predicates of `Configuration` and the rest of the data-flow library.
+ * Good performance cannot be guaranteed in the presence of such recursion, so
+ * it should be replaced by using more than one copy of the data flow library.
+ */
+abstract private class ConfigurationRecursionPrevention extends Configuration {
+ bindingset[this]
+ ConfigurationRecursionPrevention() { any() }
+
+ override predicate hasFlow(Node source, Node sink) {
+ strictcount(Node n | this.isSource(n)) < 0
+ or
+ strictcount(Node n | this.isSink(n)) < 0
+ or
+ strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0
+ or
+ super.hasFlow(source, sink)
+ }
+}
+
+private newtype TNodeEx =
+ TNodeNormal(Node n) or
+ TNodeImplicitRead(Node n, boolean hasRead) {
+ any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true]
+ }
+
+private class NodeEx extends TNodeEx {
+ string toString() {
+ result = this.asNode().toString()
+ or
+ exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]")
+ }
+
+ Node asNode() { this = TNodeNormal(result) }
+
+ predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) }
+
+ Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) }
+
+ pragma[nomagic]
+ private DataFlowCallable getEnclosingCallable0() {
+ nodeEnclosingCallable(this.projectToNode(), result)
+ }
+
+ pragma[inline]
+ DataFlowCallable getEnclosingCallable() {
+ pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result)
+ }
+
+ pragma[nomagic]
+ private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) }
+
+ pragma[inline]
+ DataFlowType getDataFlowType() {
+ pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result)
+ }
+
+ predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
+ this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ }
+}
+
+private class ArgNodeEx extends NodeEx {
+ ArgNodeEx() { this.asNode() instanceof ArgNode }
+}
+
+private class ParamNodeEx extends NodeEx {
+ ParamNodeEx() { this.asNode() instanceof ParamNode }
+
+ predicate isParameterOf(DataFlowCallable c, int i) {
+ this.asNode().(ParamNode).isParameterOf(c, i)
+ }
+
+ int getPosition() { this.isParameterOf(_, result) }
+
+ predicate allowParameterReturnInSelf() { allowParameterReturnInSelfCached(this.asNode()) }
+}
+
+private class RetNodeEx extends NodeEx {
+ RetNodeEx() { this.asNode() instanceof ReturnNodeExt }
+
+ ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) }
+
+ ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() }
+}
+
+private predicate inBarrier(NodeEx node, Configuration config) {
+ exists(Node n |
+ node.asNode() = n and
+ config.isBarrierIn(n) and
+ config.isSource(n)
+ )
+}
+
+private predicate outBarrier(NodeEx node, Configuration config) {
+ exists(Node n |
+ node.asNode() = n and
+ config.isBarrierOut(n) and
+ config.isSink(n)
+ )
+}
+
+private predicate fullBarrier(NodeEx node, Configuration config) {
+ exists(Node n | node.asNode() = n |
+ config.isBarrier(n)
+ or
+ config.isBarrierIn(n) and
+ not config.isSource(n)
+ or
+ config.isBarrierOut(n) and
+ not config.isSink(n)
+ or
+ exists(BarrierGuard g |
+ config.isBarrierGuard(g) and
+ n = g.getAGuardedNode()
+ )
+ )
+}
+
+pragma[nomagic]
+private predicate sourceNode(NodeEx node, Configuration config) { config.isSource(node.asNode()) }
+
+pragma[nomagic]
+private predicate sinkNode(NodeEx node, Configuration config) { config.isSink(node.asNode()) }
+
+/**
+ * Holds if data can flow in one local step from `node1` to `node2`.
+ */
+private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) {
+ exists(Node n1, Node n2 |
+ node1.asNode() = n1 and
+ node2.asNode() = n2 and
+ simpleLocalFlowStepExt(n1, n2) and
+ not outBarrier(node1, config) and
+ not inBarrier(node2, config) and
+ not fullBarrier(node1, config) and
+ not fullBarrier(node2, config)
+ )
+ or
+ exists(Node n |
+ config.allowImplicitRead(n, _) and
+ node1.asNode() = n and
+ node2.isImplicitReadNode(n, false)
+ )
+}
+
+/**
+ * Holds if the additional step from `node1` to `node2` does not jump between callables.
+ */
+private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) {
+ exists(Node n1, Node n2 |
+ node1.asNode() = n1 and
+ node2.asNode() = n2 and
+ config.isAdditionalFlowStep(n1, n2) and
+ getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and
+ not outBarrier(node1, config) and
+ not inBarrier(node2, config) and
+ not fullBarrier(node1, config) and
+ not fullBarrier(node2, config)
+ )
+ or
+ exists(Node n |
+ config.allowImplicitRead(n, _) and
+ node1.isImplicitReadNode(n, true) and
+ node2.asNode() = n
+ )
+}
+
+/**
+ * Holds if data can flow from `node1` to `node2` in a way that discards call contexts.
+ */
+private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
+ exists(Node n1, Node n2 |
+ node1.asNode() = n1 and
+ node2.asNode() = n2 and
+ jumpStepCached(n1, n2) and
+ not outBarrier(node1, config) and
+ not inBarrier(node2, config) and
+ not fullBarrier(node1, config) and
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+/**
+ * Holds if the additional step from `node1` to `node2` jumps between callables.
+ */
+private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) {
+ exists(Node n1, Node n2 |
+ node1.asNode() = n1 and
+ node2.asNode() = n2 and
+ config.isAdditionalFlowStep(n1, n2) and
+ getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and
+ not outBarrier(node1, config) and
+ not inBarrier(node2, config) and
+ not fullBarrier(node1, config) and
+ not fullBarrier(node2, config) and
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) {
+ read(node1.asNode(), c, node2.asNode())
+ or
+ exists(Node n |
+ node2.isImplicitReadNode(n, true) and
+ node1.isImplicitReadNode(n, _) and
+ config.allowImplicitRead(n, c)
+ )
+}
+
+private predicate store(
+ NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config
+) {
+ store(node1.asNode(), tc, node2.asNode(), contentType) and
+ read(_, tc.getContent(), _, config)
+}
+
+pragma[nomagic]
+private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) {
+ viableReturnPosOut(call, pos, out.asNode())
+}
+
+pragma[nomagic]
+private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) {
+ viableParamArg(call, p.asNode(), arg.asNode())
+}
+
+/**
+ * Holds if field flow should be used for the given configuration.
+ */
+private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
+
+private predicate hasSourceCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSourceCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private predicate hasSinkCallCtx(Configuration config) {
+ exists(FlowFeature feature | feature = config.getAFeature() |
+ feature instanceof FeatureHasSinkCallContext or
+ feature instanceof FeatureEqualSourceSinkCallContext
+ )
+}
+
+private module Stage1 {
+ class ApApprox = Unit;
+
+ class Ap = Unit;
+
+ class ApOption = Unit;
+
+ class Cc = boolean;
+
+ /* Begin: Stage 1 logic. */
+ /**
+ * Holds if `node` is reachable from a source in the configuration `config`.
+ *
+ * The Boolean `cc` records whether the node is reached through an
+ * argument in a call.
+ */
+ predicate fwdFlow(NodeEx node, Cc cc, Configuration config) {
+ not fullBarrier(node, config) and
+ (
+ sourceNode(node, config) and
+ if hasSourceCallCtx(config) then cc = true else cc = false
+ or
+ exists(NodeEx mid |
+ fwdFlow(mid, cc, config) and
+ localFlowStep(mid, node, config)
+ )
+ or
+ exists(NodeEx mid |
+ fwdFlow(mid, cc, config) and
+ additionalLocalFlowStep(mid, node, config)
+ )
+ or
+ exists(NodeEx mid |
+ fwdFlow(mid, _, config) and
+ jumpStep(mid, node, config) and
+ cc = false
+ )
+ or
+ exists(NodeEx mid |
+ fwdFlow(mid, _, config) and
+ additionalJumpStep(mid, node, config) and
+ cc = false
+ )
+ or
+ // store
+ exists(NodeEx mid |
+ useFieldFlow(config) and
+ fwdFlow(mid, cc, config) and
+ store(mid, _, node, _, config) and
+ not outBarrier(mid, config)
+ )
+ or
+ // read
+ exists(Content c |
+ fwdFlowRead(c, node, cc, config) and
+ fwdFlowConsCand(c, config) and
+ not inBarrier(node, config)
+ )
+ or
+ // flow into a callable
+ exists(NodeEx arg |
+ fwdFlow(arg, _, config) and
+ viableParamArgEx(_, node, arg) and
+ cc = true
+ )
+ or
+ // flow out of a callable
+ exists(DataFlowCall call |
+ fwdFlowOut(call, node, false, config) and
+ cc = false
+ or
+ fwdFlowOutFromArg(call, node, config) and
+ fwdFlowIsEntered(call, cc, config)
+ )
+ )
+ }
+
+ private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) }
+
+ pragma[nomagic]
+ private predicate fwdFlowRead(Content c, NodeEx node, Cc cc, Configuration config) {
+ exists(NodeEx mid |
+ fwdFlow(mid, cc, config) and
+ read(mid, c, node, config)
+ )
+ }
+
+ /**
+ * Holds if `c` is the target of a store in the flow covered by `fwdFlow`.
+ */
+ pragma[nomagic]
+ private predicate fwdFlowConsCand(Content c, Configuration config) {
+ exists(NodeEx mid, NodeEx node, TypedContent tc |
+ not fullBarrier(node, config) and
+ useFieldFlow(config) and
+ fwdFlow(mid, _, config) and
+ store(mid, tc, node, _, config) and
+ c = tc.getContent()
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) {
+ exists(RetNodeEx ret |
+ fwdFlow(ret, cc, config) and
+ ret.getReturnPosition() = pos
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) {
+ exists(ReturnPosition pos |
+ fwdFlowReturnPosition(pos, cc, config) and
+ viableReturnPosOutEx(call, pos, out)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) {
+ fwdFlowOut(call, out, true, config)
+ }
+
+ /**
+ * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`.
+ */
+ pragma[nomagic]
+ private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) {
+ exists(ArgNodeEx arg |
+ fwdFlow(arg, cc, config) and
+ viableParamArgEx(call, _, arg)
+ )
+ }
+
+ /**
+ * Holds if `node` is part of a path from a source to a sink in the
+ * configuration `config`.
+ *
+ * The Boolean `toReturn` records whether the node must be returned from
+ * the enclosing callable in order to reach a sink.
+ */
+ pragma[nomagic]
+ predicate revFlow(NodeEx node, boolean toReturn, Configuration config) {
+ revFlow0(node, toReturn, config) and
+ fwdFlow(node, config)
+ }
+
+ pragma[nomagic]
+ private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
+ fwdFlow(node, config) and
+ sinkNode(node, config) and
+ if hasSinkCallCtx(config) then toReturn = true else toReturn = false
+ or
+ exists(NodeEx mid |
+ localFlowStep(node, mid, config) and
+ revFlow(mid, toReturn, config)
+ )
+ or
+ exists(NodeEx mid |
+ additionalLocalFlowStep(node, mid, config) and
+ revFlow(mid, toReturn, config)
+ )
+ or
+ exists(NodeEx mid |
+ jumpStep(node, mid, config) and
+ revFlow(mid, _, config) and
+ toReturn = false
+ )
+ or
+ exists(NodeEx mid |
+ additionalJumpStep(node, mid, config) and
+ revFlow(mid, _, config) and
+ toReturn = false
+ )
+ or
+ // store
+ exists(Content c |
+ revFlowStore(c, node, toReturn, config) and
+ revFlowConsCand(c, config)
+ )
+ or
+ // read
+ exists(NodeEx mid, Content c |
+ read(node, c, mid, config) and
+ fwdFlowConsCand(c, pragma[only_bind_into](config)) and
+ revFlow(mid, toReturn, pragma[only_bind_into](config))
+ )
+ or
+ // flow into a callable
+ exists(DataFlowCall call |
+ revFlowIn(call, node, false, config) and
+ toReturn = false
+ or
+ revFlowInToReturn(call, node, config) and
+ revFlowIsReturned(call, toReturn, config)
+ )
+ or
+ // flow out of a callable
+ exists(ReturnPosition pos |
+ revFlowOut(pos, config) and
+ node.(RetNodeEx).getReturnPosition() = pos and
+ toReturn = true
+ )
+ }
+
+ /**
+ * Holds if `c` is the target of a read in the flow covered by `revFlow`.
+ */
+ pragma[nomagic]
+ private predicate revFlowConsCand(Content c, Configuration config) {
+ exists(NodeEx mid, NodeEx node |
+ fwdFlow(node, pragma[only_bind_into](config)) and
+ read(node, c, mid, config) and
+ fwdFlowConsCand(c, pragma[only_bind_into](config)) and
+ revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config))
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) {
+ exists(NodeEx mid, TypedContent tc |
+ revFlow(mid, toReturn, pragma[only_bind_into](config)) and
+ fwdFlowConsCand(c, pragma[only_bind_into](config)) and
+ store(node, tc, mid, _, config) and
+ c = tc.getContent()
+ )
+ }
+
+ /**
+ * Holds if `c` is the target of both a read and a store in the flow covered
+ * by `revFlow`.
+ */
+ private predicate revFlowIsReadAndStored(Content c, Configuration conf) {
+ revFlowConsCand(c, conf) and
+ revFlowStore(c, _, _, conf)
+ }
+
+ pragma[nomagic]
+ predicate viableReturnPosOutNodeCandFwd1(
+ DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
+ ) {
+ fwdFlowReturnPosition(pos, _, config) and
+ viableReturnPosOutEx(call, pos, out)
+ }
+
+ pragma[nomagic]
+ private predicate revFlowOut(ReturnPosition pos, Configuration config) {
+ exists(DataFlowCall call, NodeEx out |
+ revFlow(out, _, config) and
+ viableReturnPosOutNodeCandFwd1(call, pos, out, config)
+ )
+ }
+
+ pragma[nomagic]
+ predicate viableParamArgNodeCandFwd1(
+ DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
+ ) {
+ viableParamArgEx(call, p, arg) and
+ fwdFlow(arg, config)
+ }
+
+ pragma[nomagic]
+ private predicate revFlowIn(
+ DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config
+ ) {
+ exists(ParamNodeEx p |
+ revFlow(p, toReturn, config) and
+ viableParamArgNodeCandFwd1(call, p, arg, config)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) {
+ revFlowIn(call, arg, true, config)
+ }
+
+ /**
+ * Holds if an output from `call` is reached in the flow covered by `revFlow`
+ * and data might flow through the target callable resulting in reverse flow
+ * reaching an argument of `call`.
+ */
+ pragma[nomagic]
+ private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) {
+ exists(NodeEx out |
+ revFlow(out, toReturn, config) and
+ fwdFlowOutFromArg(call, out, config)
+ )
+ }
+
+ pragma[nomagic]
+ predicate storeStepCand(
+ NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType,
+ Configuration config
+ ) {
+ exists(Content c |
+ revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and
+ revFlow(node2, pragma[only_bind_into](config)) and
+ store(node1, tc, node2, contentType, config) and
+ c = tc.getContent() and
+ exists(ap1)
+ )
+ }
+
+ pragma[nomagic]
+ predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) {
+ revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and
+ revFlow(n2, pragma[only_bind_into](config)) and
+ read(n1, c, n2, pragma[only_bind_into](config))
+ }
+
+ pragma[nomagic]
+ predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) }
+
+ predicate revFlow(NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) {
+ revFlow(node, toReturn, config) and exists(returnAp) and exists(ap)
+ }
+
+ private predicate throughFlowNodeCand(NodeEx node, Configuration config) {
+ revFlow(node, true, config) and
+ fwdFlow(node, true, config) and
+ not inBarrier(node, config) and
+ not outBarrier(node, config)
+ }
+
+ /** Holds if flow may return from `callable`. */
+ pragma[nomagic]
+ private predicate returnFlowCallableNodeCand(
+ DataFlowCallable callable, ReturnKindExt kind, Configuration config
+ ) {
+ exists(RetNodeEx ret |
+ throughFlowNodeCand(ret, config) and
+ callable = ret.getEnclosingCallable() and
+ kind = ret.getKind()
+ )
+ }
+
+ /**
+ * Holds if flow may enter through `p` and reach a return node making `p` a
+ * candidate for the origin of a summary.
+ */
+ predicate parameterMayFlowThrough(ParamNodeEx p, DataFlowCallable c, Ap ap, Configuration config) {
+ exists(ReturnKindExt kind |
+ throughFlowNodeCand(p, config) and
+ returnFlowCallableNodeCand(c, kind, config) and
+ p.getEnclosingCallable() = c and
+ exists(ap) and
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition()
+ or
+ p.allowParameterReturnInSelf()
+ )
+ )
+ }
+
+ pragma[nomagic]
+ predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
+ exists(ArgNodeEx arg, boolean toReturn |
+ revFlow(arg, toReturn, config) and
+ revFlowInToReturn(call, arg, config) and
+ revFlowIsReturned(call, toReturn, config)
+ )
+ }
+
+ predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) {
+ fwd = true and
+ nodes = count(NodeEx node | fwdFlow(node, config)) and
+ fields = count(Content f0 | fwdFlowConsCand(f0, config)) and
+ conscand = -1 and
+ tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config))
+ or
+ fwd = false and
+ nodes = count(NodeEx node | revFlow(node, _, config)) and
+ fields = count(Content f0 | revFlowConsCand(f0, config)) and
+ conscand = -1 and
+ tuples = count(NodeEx n, boolean b | revFlow(n, b, config))
+ }
+ /* End: Stage 1 logic. */
+}
+
+pragma[noinline]
+private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) {
+ Stage1::revFlow(node2, config) and
+ localFlowStep(node1, node2, config)
+}
+
+pragma[noinline]
+private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) {
+ Stage1::revFlow(node2, config) and
+ additionalLocalFlowStep(node1, node2, config)
+}
+
+pragma[nomagic]
+private predicate viableReturnPosOutNodeCand1(
+ DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
+) {
+ Stage1::revFlow(out, config) and
+ Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config)
+}
+
+/**
+ * Holds if data can flow out of `call` from `ret` to `out`, either
+ * through a `ReturnNode` or through an argument that has been mutated, and
+ * that this step is part of a path from a source to a sink.
+ */
+pragma[nomagic]
+private predicate flowOutOfCallNodeCand1(
+ DataFlowCall call, RetNodeEx ret, NodeEx out, Configuration config
+) {
+ viableReturnPosOutNodeCand1(call, ret.getReturnPosition(), out, config) and
+ Stage1::revFlow(ret, config) and
+ not outBarrier(ret, config) and
+ not inBarrier(out, config)
+}
+
+pragma[nomagic]
+private predicate viableParamArgNodeCand1(
+ DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
+) {
+ Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and
+ Stage1::revFlow(arg, config)
+}
+
+/**
+ * Holds if data can flow into `call` and that this step is part of a
+ * path from a source to a sink.
+ */
+pragma[nomagic]
+private predicate flowIntoCallNodeCand1(
+ DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config
+) {
+ viableParamArgNodeCand1(call, p, arg, config) and
+ Stage1::revFlow(p, config) and
+ not outBarrier(arg, config) and
+ not inBarrier(p, config)
+}
+
+/**
+ * Gets the amount of forward branching on the origin of a cross-call path
+ * edge in the graph of paths between sources and sinks that ignores call
+ * contexts.
+ */
+private int branch(NodeEx n1, Configuration conf) {
+ result =
+ strictcount(NodeEx n |
+ flowOutOfCallNodeCand1(_, n1, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf)
+ )
+}
+
+/**
+ * Gets the amount of backward branching on the target of a cross-call path
+ * edge in the graph of paths between sources and sinks that ignores call
+ * contexts.
+ */
+private int join(NodeEx n2, Configuration conf) {
+ result =
+ strictcount(NodeEx n |
+ flowOutOfCallNodeCand1(_, n, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf)
+ )
+}
+
+/**
+ * Holds if data can flow out of `call` from `ret` to `out`, either
+ * through a `ReturnNode` or through an argument that has been mutated, and
+ * that this step is part of a path from a source to a sink. The
+ * `allowsFieldFlow` flag indicates whether the branching is within the limit
+ * specified by the configuration.
+ */
+pragma[nomagic]
+private predicate flowOutOfCallNodeCand1(
+ DataFlowCall call, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, Configuration config
+) {
+ flowOutOfCallNodeCand1(call, ret, out, config) and
+ exists(int b, int j |
+ b = branch(ret, config) and
+ j = join(out, config) and
+ if b.minimum(j) <= config.fieldFlowBranchLimit()
+ then allowsFieldFlow = true
+ else allowsFieldFlow = false
+ )
+}
+
+/**
+ * Holds if data can flow into `call` and that this step is part of a
+ * path from a source to a sink. The `allowsFieldFlow` flag indicates whether
+ * the branching is within the limit specified by the configuration.
+ */
+pragma[nomagic]
+private predicate flowIntoCallNodeCand1(
+ DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config
+) {
+ flowIntoCallNodeCand1(call, arg, p, config) and
+ exists(int b, int j |
+ b = branch(arg, config) and
+ j = join(p, config) and
+ if b.minimum(j) <= config.fieldFlowBranchLimit()
+ then allowsFieldFlow = true
+ else allowsFieldFlow = false
+ )
+}
+
+private module Stage2 {
+ module PrevStage = Stage1;
+
+ class ApApprox = PrevStage::Ap;
+
+ class Ap = boolean;
+
+ class ApNil extends Ap {
+ ApNil() { this = false }
+ }
+
+ bindingset[result, ap]
+ private ApApprox getApprox(Ap ap) { any() }
+
+ private ApNil getApNil(NodeEx node) { PrevStage::revFlow(node, _) and exists(result) }
+
+ bindingset[tc, tail]
+ private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) }
+
+ pragma[inline]
+ private Content getHeadContent(Ap ap) { exists(result) and ap = true }
+
+ class ApOption = BooleanOption;
+
+ ApOption apNone() { result = TBooleanNone() }
+
+ ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
+
+ class Cc = CallContext;
+
+ class CcCall = CallContextCall;
+
+ class CcNoCall = CallContextNoCall;
+
+ Cc ccNone() { result instanceof CallContextAny }
+
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
+ private class LocalCc = Unit;
+
+ bindingset[call, c, outercc]
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
+
+ bindingset[call, c, innercc]
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
+
+ bindingset[node, cc, config]
+ private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
+
+ private predicate localStep(
+ NodeEx node1, NodeEx node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc
+ ) {
+ (
+ preservesValue = true and
+ localFlowStepNodeCand1(node1, node2, config)
+ or
+ preservesValue = false and
+ additionalLocalFlowStepNodeCand1(node1, node2, config)
+ ) and
+ exists(ap) and
+ exists(lcc)
+ }
+
+ private predicate flowOutOfCall = flowOutOfCallNodeCand1/5;
+
+ private predicate flowIntoCall = flowIntoCallNodeCand1/5;
+
+ bindingset[ap, contentType]
+ private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() }
+
+ /* Begin: Stage 2 logic. */
+ private predicate flowCand(NodeEx node, ApApprox apa, Configuration config) {
+ PrevStage::revFlow(node, _, _, apa, config)
+ }
+
+ pragma[nomagic]
+ private predicate flowThroughOutOfCall(
+ DataFlowCall call, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, Configuration config
+ ) {
+ flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and
+ PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and
+ PrevStage::parameterMayFlowThrough(_, ret.getEnclosingCallable(), _,
+ pragma[only_bind_into](config))
+ }
+
+ /**
+ * Holds if `node` is reachable with access path `ap` from a source in the
+ * configuration `config`.
+ *
+ * The call context `cc` records whether the node is reached through an
+ * argument in a call, and if so, `argAp` records the access path of that
+ * argument.
+ */
+ pragma[nomagic]
+ predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
+ flowCand(node, _, config) and
+ sourceNode(node, config) and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
+ argAp = apNone() and
+ ap = getApNil(node)
+ or
+ exists(NodeEx mid, Ap ap0, LocalCc localCc |
+ fwdFlow(mid, cc, argAp, ap0, config) and
+ localCc = getLocalCc(mid, cc, config)
+ |
+ localStep(mid, node, true, _, config, localCc) and
+ ap = ap0
+ or
+ localStep(mid, node, false, ap, config, localCc) and
+ ap0 instanceof ApNil
+ )
+ or
+ exists(NodeEx mid |
+ fwdFlow(mid, _, _, ap, pragma[only_bind_into](config)) and
+ flowCand(node, _, pragma[only_bind_into](config)) and
+ jumpStep(mid, node, config) and
+ cc = ccNone() and
+ argAp = apNone()
+ )
+ or
+ exists(NodeEx mid, ApNil nil |
+ fwdFlow(mid, _, _, nil, pragma[only_bind_into](config)) and
+ flowCand(node, _, pragma[only_bind_into](config)) and
+ additionalJumpStep(mid, node, config) and
+ cc = ccNone() and
+ argAp = apNone() and
+ ap = getApNil(node)
+ )
+ or
+ // store
+ exists(TypedContent tc, Ap ap0 |
+ fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and
+ ap = apCons(tc, ap0)
+ )
+ or
+ // read
+ exists(Ap ap0, Content c |
+ fwdFlowRead(ap0, c, _, node, cc, argAp, config) and
+ fwdFlowConsCand(ap0, c, ap, config)
+ )
+ or
+ // flow into a callable
+ exists(ApApprox apa |
+ fwdFlowIn(_, node, _, cc, _, ap, config) and
+ apa = getApprox(ap) and
+ if PrevStage::parameterMayFlowThrough(node, _, apa, config)
+ then argAp = apSome(ap)
+ else argAp = apNone()
+ )
+ or
+ // flow out of a callable
+ fwdFlowOutNotFromArg(node, cc, argAp, ap, config)
+ or
+ exists(DataFlowCall call, Ap argAp0 |
+ fwdFlowOutFromArg(call, node, argAp0, ap, config) and
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowStore(
+ NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Cc cc, ApOption argAp, Configuration config
+ ) {
+ exists(DataFlowType contentType |
+ fwdFlow(node1, cc, argAp, ap1, config) and
+ PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and
+ typecheckStore(ap1, contentType)
+ )
+ }
+
+ /**
+ * Holds if forward flow with access path `tail` reaches a store of `c`
+ * resulting in access path `cons`.
+ */
+ pragma[nomagic]
+ private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) {
+ exists(TypedContent tc |
+ fwdFlowStore(_, tail, tc, _, _, _, config) and
+ tc.getContent() = c and
+ cons = apCons(tc, tail)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowRead(
+ Ap ap, Content c, NodeEx node1, NodeEx node2, Cc cc, ApOption argAp, Configuration config
+ ) {
+ fwdFlow(node1, cc, argAp, ap, config) and
+ PrevStage::readStepCand(node1, c, node2, config) and
+ getHeadContent(ap) = c
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowIn(
+ DataFlowCall call, ParamNodeEx p, Cc outercc, Cc innercc, ApOption argAp, Ap ap,
+ Configuration config
+ ) {
+ exists(ArgNodeEx arg, boolean allowsFieldFlow |
+ fwdFlow(arg, outercc, argAp, ap, config) and
+ flowIntoCall(call, arg, p, allowsFieldFlow, config) and
+ innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowOutNotFromArg(
+ NodeEx out, Cc ccOut, ApOption argAp, Ap ap, Configuration config
+ ) {
+ exists(
+ DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc,
+ DataFlowCallable inner
+ |
+ fwdFlow(ret, innercc, argAp, ap, config) and
+ flowOutOfCall(call, ret, out, allowsFieldFlow, config) and
+ inner = ret.getEnclosingCallable() and
+ ccOut = getCallContextReturn(inner, call, innercc)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowOutFromArg(
+ DataFlowCall call, NodeEx out, Ap argAp, Ap ap, Configuration config
+ ) {
+ exists(RetNodeEx ret, boolean allowsFieldFlow, CcCall ccc |
+ fwdFlow(ret, ccc, apSome(argAp), ap, config) and
+ flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and
+ ccc.matchesCall(call)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ /**
+ * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`
+ * and data might flow through the target callable and back out at `call`.
+ */
+ pragma[nomagic]
+ private predicate fwdFlowIsEntered(
+ DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config
+ ) {
+ exists(ParamNodeEx p |
+ fwdFlowIn(call, p, cc, _, argAp, ap, config) and
+ PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate storeStepFwd(
+ NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config
+ ) {
+ fwdFlowStore(node1, ap1, tc, node2, _, _, config) and
+ ap2 = apCons(tc, ap1) and
+ fwdFlowRead(ap2, tc.getContent(), _, _, _, _, config)
+ }
+
+ private predicate readStepFwd(
+ NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config
+ ) {
+ fwdFlowRead(ap1, c, n1, n2, _, _, config) and
+ fwdFlowConsCand(ap1, c, ap2, config)
+ }
+
+ pragma[nomagic]
+ private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
+ exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
+ fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
+ pragma[only_bind_into](config)) and
+ fwdFlowOutFromArg(call, out, argAp0, ap, config) and
+ fwdFlowIsEntered(pragma[only_bind_into](call), pragma[only_bind_into](cc),
+ pragma[only_bind_into](argAp), pragma[only_bind_into](argAp0),
+ pragma[only_bind_into](config))
+ )
+ }
+
+ pragma[nomagic]
+ private predicate flowThroughIntoCall(
+ DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config
+ ) {
+ flowIntoCall(call, arg, p, allowsFieldFlow, config) and
+ fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and
+ PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and
+ callMayFlowThroughFwd(call, pragma[only_bind_into](config))
+ }
+
+ /**
+ * Holds if `node` with access path `ap` is part of a path from a source to a
+ * sink in the configuration `config`.
+ *
+ * The Boolean `toReturn` records whether the node must be returned from the
+ * enclosing callable in order to reach a sink, and if so, `returnAp` records
+ * the access path of the returned value.
+ */
+ pragma[nomagic]
+ predicate revFlow(NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) {
+ revFlow0(node, toReturn, returnAp, ap, config) and
+ fwdFlow(node, _, _, ap, config)
+ }
+
+ pragma[nomagic]
+ private predicate revFlow0(
+ NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
+ ) {
+ fwdFlow(node, _, _, ap, config) and
+ sinkNode(node, config) and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
+ returnAp = apNone() and
+ ap instanceof ApNil
+ or
+ exists(NodeEx mid |
+ localStep(node, mid, true, _, config, _) and
+ revFlow(mid, toReturn, returnAp, ap, config)
+ )
+ or
+ exists(NodeEx mid, ApNil nil |
+ fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and
+ localStep(node, mid, false, _, config, _) and
+ revFlow(mid, toReturn, returnAp, nil, pragma[only_bind_into](config)) and
+ ap instanceof ApNil
+ )
+ or
+ exists(NodeEx mid |
+ jumpStep(node, mid, config) and
+ revFlow(mid, _, _, ap, config) and
+ toReturn = false and
+ returnAp = apNone()
+ )
+ or
+ exists(NodeEx mid, ApNil nil |
+ fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and
+ additionalJumpStep(node, mid, config) and
+ revFlow(pragma[only_bind_into](mid), _, _, nil, pragma[only_bind_into](config)) and
+ toReturn = false and
+ returnAp = apNone() and
+ ap instanceof ApNil
+ )
+ or
+ // store
+ exists(Ap ap0, Content c |
+ revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and
+ revFlowConsCand(ap0, c, ap, config)
+ )
+ or
+ // read
+ exists(NodeEx mid, Ap ap0 |
+ revFlow(mid, toReturn, returnAp, ap0, config) and
+ readStepFwd(node, ap, _, mid, ap0, config)
+ )
+ or
+ // flow into a callable
+ revFlowInNotToReturn(node, returnAp, ap, config) and
+ toReturn = false
+ or
+ exists(DataFlowCall call, Ap returnAp0 |
+ revFlowInToReturn(call, node, returnAp0, ap, config) and
+ revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
+ )
+ or
+ // flow out of a callable
+ revFlowOut(_, node, _, _, ap, config) and
+ toReturn = true and
+ if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config)
+ then returnAp = apSome(ap)
+ else returnAp = apNone()
+ }
+
+ pragma[nomagic]
+ private predicate revFlowStore(
+ Ap ap0, Content c, Ap ap, NodeEx node, TypedContent tc, NodeEx mid, boolean toReturn,
+ ApOption returnAp, Configuration config
+ ) {
+ revFlow(mid, toReturn, returnAp, ap0, config) and
+ storeStepFwd(node, ap, tc, mid, ap0, config) and
+ tc.getContent() = c
+ }
+
+ /**
+ * Holds if reverse flow with access path `tail` reaches a read of `c`
+ * resulting in access path `cons`.
+ */
+ pragma[nomagic]
+ private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) {
+ exists(NodeEx mid, Ap tail0 |
+ revFlow(mid, _, _, tail, config) and
+ tail = pragma[only_bind_into](tail0) and
+ readStepFwd(_, cons, c, mid, tail0, config)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revFlowOut(
+ DataFlowCall call, RetNodeEx ret, boolean toReturn, ApOption returnAp, Ap ap,
+ Configuration config
+ ) {
+ exists(NodeEx out, boolean allowsFieldFlow |
+ revFlow(out, toReturn, returnAp, ap, config) and
+ flowOutOfCall(call, ret, out, allowsFieldFlow, config)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revFlowInNotToReturn(
+ ArgNodeEx arg, ApOption returnAp, Ap ap, Configuration config
+ ) {
+ exists(ParamNodeEx p, boolean allowsFieldFlow |
+ revFlow(p, false, returnAp, ap, config) and
+ flowIntoCall(_, arg, p, allowsFieldFlow, config)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revFlowInToReturn(
+ DataFlowCall call, ArgNodeEx arg, Ap returnAp, Ap ap, Configuration config
+ ) {
+ exists(ParamNodeEx p, boolean allowsFieldFlow |
+ revFlow(p, true, apSome(returnAp), ap, config) and
+ flowThroughIntoCall(call, arg, p, allowsFieldFlow, config)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ /**
+ * Holds if an output from `call` is reached in the flow covered by `revFlow`
+ * and data might flow through the target callable resulting in reverse flow
+ * reaching an argument of `call`.
+ */
+ pragma[nomagic]
+ private predicate revFlowIsReturned(
+ DataFlowCall call, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
+ ) {
+ exists(RetNodeEx ret, CcCall ccc |
+ revFlowOut(call, ret, toReturn, returnAp, ap, config) and
+ fwdFlow(ret, ccc, apSome(_), ap, config) and
+ ccc.matchesCall(call)
+ )
+ }
+
+ pragma[nomagic]
+ predicate storeStepCand(
+ NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType,
+ Configuration config
+ ) {
+ exists(Ap ap2, Content c |
+ store(node1, tc, node2, contentType, config) and
+ revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and
+ revFlowConsCand(ap2, c, ap1, config)
+ )
+ }
+
+ predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) {
+ exists(Ap ap1, Ap ap2 |
+ revFlow(node2, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and
+ readStepFwd(node1, ap1, c, node2, ap2, config) and
+ revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _,
+ pragma[only_bind_into](config))
+ )
+ }
+
+ predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, config) }
+
+ private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) {
+ storeStepFwd(_, ap, tc, _, _, config)
+ }
+
+ predicate consCand(TypedContent tc, Ap ap, Configuration config) {
+ storeStepCand(_, ap, tc, _, _, config)
+ }
+
+ pragma[noinline]
+ private predicate parameterFlow(
+ ParamNodeEx p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config
+ ) {
+ revFlow(p, true, apSome(ap0), ap, config) and
+ c = p.getEnclosingCallable()
+ }
+
+ predicate parameterMayFlowThrough(ParamNodeEx p, DataFlowCallable c, Ap ap, Configuration config) {
+ exists(RetNodeEx ret, Ap ap0, ReturnKindExt kind, int pos |
+ parameterFlow(p, ap, ap0, c, config) and
+ c = ret.getEnclosingCallable() and
+ revFlow(pragma[only_bind_into](ret), true, apSome(_), pragma[only_bind_into](ap0),
+ pragma[only_bind_into](config)) and
+ fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
+ kind = ret.getKind() and
+ p.getPosition() = pos and
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
+ )
+ }
+
+ pragma[nomagic]
+ predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
+ exists(Ap returnAp0, ArgNodeEx arg, boolean toReturn, ApOption returnAp, Ap ap |
+ revFlow(arg, toReturn, returnAp, ap, config) and
+ revFlowInToReturn(call, arg, returnAp0, ap, config) and
+ revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
+ )
+ }
+
+ predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) {
+ fwd = true and
+ nodes = count(NodeEx node | fwdFlow(node, _, _, _, config)) and
+ fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and
+ conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and
+ tuples = count(NodeEx n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config))
+ or
+ fwd = false and
+ nodes = count(NodeEx node | revFlow(node, _, _, _, config)) and
+ fields = count(TypedContent f0 | consCand(f0, _, config)) and
+ conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and
+ tuples = count(NodeEx n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config))
+ }
+ /* End: Stage 2 logic. */
+}
+
+pragma[nomagic]
+private predicate flowOutOfCallNodeCand2(
+ DataFlowCall call, RetNodeEx node1, NodeEx node2, boolean allowsFieldFlow, Configuration config
+) {
+ flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and
+ Stage2::revFlow(node2, pragma[only_bind_into](config)) and
+ Stage2::revFlow(node1, pragma[only_bind_into](config))
+}
+
+pragma[nomagic]
+private predicate flowIntoCallNodeCand2(
+ DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow,
+ Configuration config
+) {
+ flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and
+ Stage2::revFlow(node2, pragma[only_bind_into](config)) and
+ Stage2::revFlow(node1, pragma[only_bind_into](config))
+}
+
+private module LocalFlowBigStep {
+ /**
+ * A node where some checking is required, and hence the big-step relation
+ * is not allowed to step over.
+ */
+ private class FlowCheckNode extends NodeEx {
+ FlowCheckNode() {
+ castNode(this.asNode()) or
+ clearsContentCached(this.asNode(), _)
+ }
+ }
+
+ /**
+ * Holds if `node` can be the first node in a maximal subsequence of local
+ * flow steps in a dataflow path.
+ */
+ predicate localFlowEntry(NodeEx node, Configuration config) {
+ Stage2::revFlow(node, config) and
+ (
+ sourceNode(node, config) or
+ jumpStep(_, node, config) or
+ additionalJumpStep(_, node, config) or
+ node instanceof ParamNodeEx or
+ node.asNode() instanceof OutNodeExt or
+ store(_, _, node, _, config) or
+ read(_, _, node, config) or
+ node instanceof FlowCheckNode
+ )
+ }
+
+ /**
+ * Holds if `node` can be the last node in a maximal subsequence of local
+ * flow steps in a dataflow path.
+ */
+ private predicate localFlowExit(NodeEx node, Configuration config) {
+ exists(NodeEx next | Stage2::revFlow(next, config) |
+ jumpStep(node, next, config) or
+ additionalJumpStep(node, next, config) or
+ flowIntoCallNodeCand1(_, node, next, config) or
+ flowOutOfCallNodeCand1(_, node, next, config) or
+ store(node, _, next, _, config) or
+ read(node, _, next, config)
+ )
+ or
+ node instanceof FlowCheckNode
+ or
+ sinkNode(node, config)
+ }
+
+ pragma[noinline]
+ private predicate additionalLocalFlowStepNodeCand2(
+ NodeEx node1, NodeEx node2, Configuration config
+ ) {
+ additionalLocalFlowStepNodeCand1(node1, node2, config) and
+ Stage2::revFlow(node1, _, _, false, pragma[only_bind_into](config)) and
+ Stage2::revFlow(node2, _, _, false, pragma[only_bind_into](config))
+ }
+
+ /**
+ * Holds if the local path from `node1` to `node2` is a prefix of a maximal
+ * subsequence of local flow steps in a dataflow path.
+ *
+ * This is the transitive closure of `[additional]localFlowStep` beginning
+ * at `localFlowEntry`.
+ */
+ pragma[nomagic]
+ private predicate localFlowStepPlus(
+ NodeEx node1, NodeEx node2, boolean preservesValue, DataFlowType t, Configuration config,
+ LocalCallContext cc
+ ) {
+ not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and
+ (
+ localFlowEntry(node1, pragma[only_bind_into](config)) and
+ (
+ localFlowStepNodeCand1(node1, node2, config) and
+ preservesValue = true and
+ t = node1.getDataFlowType() // irrelevant dummy value
+ or
+ additionalLocalFlowStepNodeCand2(node1, node2, config) and
+ preservesValue = false and
+ t = node2.getDataFlowType()
+ ) and
+ node1 != node2 and
+ cc.relevantFor(node1.getEnclosingCallable()) and
+ not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and
+ Stage2::revFlow(node2, pragma[only_bind_into](config))
+ or
+ exists(NodeEx mid |
+ localFlowStepPlus(node1, mid, preservesValue, t, pragma[only_bind_into](config), cc) and
+ localFlowStepNodeCand1(mid, node2, config) and
+ not mid instanceof FlowCheckNode and
+ Stage2::revFlow(node2, pragma[only_bind_into](config))
+ )
+ or
+ exists(NodeEx mid |
+ localFlowStepPlus(node1, mid, _, _, pragma[only_bind_into](config), cc) and
+ additionalLocalFlowStepNodeCand2(mid, node2, config) and
+ not mid instanceof FlowCheckNode and
+ preservesValue = false and
+ t = node2.getDataFlowType() and
+ Stage2::revFlow(node2, pragma[only_bind_into](config))
+ )
+ )
+ }
+
+ /**
+ * Holds if `node1` can step to `node2` in one or more local steps and this
+ * path can occur as a maximal subsequence of local steps in a dataflow path.
+ */
+ pragma[nomagic]
+ predicate localFlowBigStep(
+ NodeEx node1, NodeEx node2, boolean preservesValue, AccessPathFrontNil apf,
+ Configuration config, LocalCallContext callContext
+ ) {
+ localFlowStepPlus(node1, node2, preservesValue, apf.getType(), config, callContext) and
+ localFlowExit(node2, config)
+ }
+}
+
+private import LocalFlowBigStep
+
+private module Stage3 {
+ module PrevStage = Stage2;
+
+ class ApApprox = PrevStage::Ap;
+
+ class Ap = AccessPathFront;
+
+ class ApNil = AccessPathFrontNil;
+
+ private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() }
+
+ private ApNil getApNil(NodeEx node) {
+ PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType())
+ }
+
+ bindingset[tc, tail]
+ private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) }
+
+ pragma[noinline]
+ private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() }
+
+ class ApOption = AccessPathFrontOption;
+
+ ApOption apNone() { result = TAccessPathFrontNone() }
+
+ ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) }
+
+ class Cc = boolean;
+
+ class CcCall extends Cc {
+ CcCall() { this = true }
+
+ /** Holds if this call context may be `call`. */
+ predicate matchesCall(DataFlowCall call) { any() }
+ }
+
+ class CcNoCall extends Cc {
+ CcNoCall() { this = false }
+ }
+
+ Cc ccNone() { result = false }
+
+ CcCall ccSomeCall() { result = true }
+
+ private class LocalCc = Unit;
+
+ bindingset[call, c, outercc]
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+
+ bindingset[call, c, innercc]
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+
+ bindingset[node, cc, config]
+ private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
+
+ private predicate localStep(
+ NodeEx node1, NodeEx node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc
+ ) {
+ localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc)
+ }
+
+ private predicate flowOutOfCall = flowOutOfCallNodeCand2/5;
+
+ private predicate flowIntoCall = flowIntoCallNodeCand2/5;
+
+ pragma[nomagic]
+ private predicate clear(NodeEx node, Ap ap) { ap.isClearedAt(node.asNode()) }
+
+ pragma[nomagic]
+ private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode }
+
+ bindingset[node, ap]
+ private predicate filter(NodeEx node, Ap ap) {
+ not clear(node, ap) and
+ if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any()
+ }
+
+ bindingset[ap, contentType]
+ private predicate typecheckStore(Ap ap, DataFlowType contentType) {
+ // We need to typecheck stores here, since reverse flow through a getter
+ // might have a different type here compared to inside the getter.
+ compatibleTypes(ap.getType(), contentType)
+ }
+
+ /* Begin: Stage 3 logic. */
+ private predicate flowCand(NodeEx node, ApApprox apa, Configuration config) {
+ PrevStage::revFlow(node, _, _, apa, config)
+ }
+
+ bindingset[result, apa]
+ private ApApprox unbindApa(ApApprox apa) {
+ exists(ApApprox apa0 |
+ apa = pragma[only_bind_into](apa0) and result = pragma[only_bind_into](apa0)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate flowThroughOutOfCall(
+ DataFlowCall call, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, Configuration config
+ ) {
+ flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and
+ PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and
+ PrevStage::parameterMayFlowThrough(_, ret.getEnclosingCallable(), _,
+ pragma[only_bind_into](config))
+ }
+
+ /**
+ * Holds if `node` is reachable with access path `ap` from a source in the
+ * configuration `config`.
+ *
+ * The call context `cc` records whether the node is reached through an
+ * argument in a call, and if so, `argAp` records the access path of that
+ * argument.
+ */
+ pragma[nomagic]
+ predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
+ fwdFlow0(node, cc, argAp, ap, config) and
+ flowCand(node, unbindApa(getApprox(ap)), config) and
+ filter(node, ap)
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
+ flowCand(node, _, config) and
+ sourceNode(node, config) and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
+ argAp = apNone() and
+ ap = getApNil(node)
+ or
+ exists(NodeEx mid, Ap ap0, LocalCc localCc |
+ fwdFlow(mid, cc, argAp, ap0, config) and
+ localCc = getLocalCc(mid, cc, config)
+ |
+ localStep(mid, node, true, _, config, localCc) and
+ ap = ap0
+ or
+ localStep(mid, node, false, ap, config, localCc) and
+ ap0 instanceof ApNil
+ )
+ or
+ exists(NodeEx mid |
+ fwdFlow(mid, _, _, ap, pragma[only_bind_into](config)) and
+ flowCand(node, _, pragma[only_bind_into](config)) and
+ jumpStep(mid, node, config) and
+ cc = ccNone() and
+ argAp = apNone()
+ )
+ or
+ exists(NodeEx mid, ApNil nil |
+ fwdFlow(mid, _, _, nil, pragma[only_bind_into](config)) and
+ flowCand(node, _, pragma[only_bind_into](config)) and
+ additionalJumpStep(mid, node, config) and
+ cc = ccNone() and
+ argAp = apNone() and
+ ap = getApNil(node)
+ )
+ or
+ // store
+ exists(TypedContent tc, Ap ap0 |
+ fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and
+ ap = apCons(tc, ap0)
+ )
+ or
+ // read
+ exists(Ap ap0, Content c |
+ fwdFlowRead(ap0, c, _, node, cc, argAp, config) and
+ fwdFlowConsCand(ap0, c, ap, config)
+ )
+ or
+ // flow into a callable
+ exists(ApApprox apa |
+ fwdFlowIn(_, node, _, cc, _, ap, config) and
+ apa = getApprox(ap) and
+ if PrevStage::parameterMayFlowThrough(node, _, apa, config)
+ then argAp = apSome(ap)
+ else argAp = apNone()
+ )
+ or
+ // flow out of a callable
+ fwdFlowOutNotFromArg(node, cc, argAp, ap, config)
+ or
+ exists(DataFlowCall call, Ap argAp0 |
+ fwdFlowOutFromArg(call, node, argAp0, ap, config) and
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowStore(
+ NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Cc cc, ApOption argAp, Configuration config
+ ) {
+ exists(DataFlowType contentType |
+ fwdFlow(node1, cc, argAp, ap1, config) and
+ PrevStage::storeStepCand(node1, unbindApa(getApprox(ap1)), tc, node2, contentType, config) and
+ typecheckStore(ap1, contentType)
+ )
+ }
+
+ /**
+ * Holds if forward flow with access path `tail` reaches a store of `c`
+ * resulting in access path `cons`.
+ */
+ pragma[nomagic]
+ private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) {
+ exists(TypedContent tc |
+ fwdFlowStore(_, tail, tc, _, _, _, config) and
+ tc.getContent() = c and
+ cons = apCons(tc, tail)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowRead(
+ Ap ap, Content c, NodeEx node1, NodeEx node2, Cc cc, ApOption argAp, Configuration config
+ ) {
+ fwdFlow(node1, cc, argAp, ap, config) and
+ PrevStage::readStepCand(node1, c, node2, config) and
+ getHeadContent(ap) = c
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowIn(
+ DataFlowCall call, ParamNodeEx p, Cc outercc, Cc innercc, ApOption argAp, Ap ap,
+ Configuration config
+ ) {
+ exists(ArgNodeEx arg, boolean allowsFieldFlow |
+ fwdFlow(arg, outercc, argAp, ap, config) and
+ flowIntoCall(call, arg, p, allowsFieldFlow, config) and
+ innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowOutNotFromArg(
+ NodeEx out, Cc ccOut, ApOption argAp, Ap ap, Configuration config
+ ) {
+ exists(
+ DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc,
+ DataFlowCallable inner
+ |
+ fwdFlow(ret, innercc, argAp, ap, config) and
+ flowOutOfCall(call, ret, out, allowsFieldFlow, config) and
+ inner = ret.getEnclosingCallable() and
+ ccOut = getCallContextReturn(inner, call, innercc)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowOutFromArg(
+ DataFlowCall call, NodeEx out, Ap argAp, Ap ap, Configuration config
+ ) {
+ exists(RetNodeEx ret, boolean allowsFieldFlow, CcCall ccc |
+ fwdFlow(ret, ccc, apSome(argAp), ap, config) and
+ flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and
+ ccc.matchesCall(call)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ /**
+ * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`
+ * and data might flow through the target callable and back out at `call`.
+ */
+ pragma[nomagic]
+ private predicate fwdFlowIsEntered(
+ DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config
+ ) {
+ exists(ParamNodeEx p |
+ fwdFlowIn(call, p, cc, _, argAp, ap, config) and
+ PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate storeStepFwd(
+ NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config
+ ) {
+ fwdFlowStore(node1, ap1, tc, node2, _, _, config) and
+ ap2 = apCons(tc, ap1) and
+ fwdFlowRead(ap2, tc.getContent(), _, _, _, _, config)
+ }
+
+ private predicate readStepFwd(
+ NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config
+ ) {
+ fwdFlowRead(ap1, c, n1, n2, _, _, config) and
+ fwdFlowConsCand(ap1, c, ap2, config)
+ }
+
+ pragma[nomagic]
+ private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
+ exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
+ fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
+ pragma[only_bind_into](config)) and
+ fwdFlowOutFromArg(call, out, argAp0, ap, config) and
+ fwdFlowIsEntered(pragma[only_bind_into](call), pragma[only_bind_into](cc),
+ pragma[only_bind_into](argAp), pragma[only_bind_into](argAp0),
+ pragma[only_bind_into](config))
+ )
+ }
+
+ pragma[nomagic]
+ private predicate flowThroughIntoCall(
+ DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config
+ ) {
+ flowIntoCall(call, arg, p, allowsFieldFlow, config) and
+ fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and
+ PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and
+ callMayFlowThroughFwd(call, pragma[only_bind_into](config))
+ }
+
+ /**
+ * Holds if `node` with access path `ap` is part of a path from a source to a
+ * sink in the configuration `config`.
+ *
+ * The Boolean `toReturn` records whether the node must be returned from the
+ * enclosing callable in order to reach a sink, and if so, `returnAp` records
+ * the access path of the returned value.
+ */
+ pragma[nomagic]
+ predicate revFlow(NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) {
+ revFlow0(node, toReturn, returnAp, ap, config) and
+ fwdFlow(node, _, _, ap, config)
+ }
+
+ pragma[nomagic]
+ private predicate revFlow0(
+ NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
+ ) {
+ fwdFlow(node, _, _, ap, config) and
+ sinkNode(node, config) and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
+ returnAp = apNone() and
+ ap instanceof ApNil
+ or
+ exists(NodeEx mid |
+ localStep(node, mid, true, _, config, _) and
+ revFlow(mid, toReturn, returnAp, ap, config)
+ )
+ or
+ exists(NodeEx mid, ApNil nil |
+ fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and
+ localStep(node, mid, false, _, config, _) and
+ revFlow(mid, toReturn, returnAp, nil, pragma[only_bind_into](config)) and
+ ap instanceof ApNil
+ )
+ or
+ exists(NodeEx mid |
+ jumpStep(node, mid, config) and
+ revFlow(mid, _, _, ap, config) and
+ toReturn = false and
+ returnAp = apNone()
+ )
+ or
+ exists(NodeEx mid, ApNil nil |
+ fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and
+ additionalJumpStep(node, mid, config) and
+ revFlow(pragma[only_bind_into](mid), _, _, nil, pragma[only_bind_into](config)) and
+ toReturn = false and
+ returnAp = apNone() and
+ ap instanceof ApNil
+ )
+ or
+ // store
+ exists(Ap ap0, Content c |
+ revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and
+ revFlowConsCand(ap0, c, ap, config)
+ )
+ or
+ // read
+ exists(NodeEx mid, Ap ap0 |
+ revFlow(mid, toReturn, returnAp, ap0, config) and
+ readStepFwd(node, ap, _, mid, ap0, config)
+ )
+ or
+ // flow into a callable
+ revFlowInNotToReturn(node, returnAp, ap, config) and
+ toReturn = false
+ or
+ exists(DataFlowCall call, Ap returnAp0 |
+ revFlowInToReturn(call, node, returnAp0, ap, config) and
+ revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
+ )
+ or
+ // flow out of a callable
+ revFlowOut(_, node, _, _, ap, config) and
+ toReturn = true and
+ if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config)
+ then returnAp = apSome(ap)
+ else returnAp = apNone()
+ }
+
+ pragma[nomagic]
+ private predicate revFlowStore(
+ Ap ap0, Content c, Ap ap, NodeEx node, TypedContent tc, NodeEx mid, boolean toReturn,
+ ApOption returnAp, Configuration config
+ ) {
+ revFlow(mid, toReturn, returnAp, ap0, config) and
+ storeStepFwd(node, ap, tc, mid, ap0, config) and
+ tc.getContent() = c
+ }
+
+ /**
+ * Holds if reverse flow with access path `tail` reaches a read of `c`
+ * resulting in access path `cons`.
+ */
+ pragma[nomagic]
+ private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) {
+ exists(NodeEx mid, Ap tail0 |
+ revFlow(mid, _, _, tail, config) and
+ tail = pragma[only_bind_into](tail0) and
+ readStepFwd(_, cons, c, mid, tail0, config)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revFlowOut(
+ DataFlowCall call, RetNodeEx ret, boolean toReturn, ApOption returnAp, Ap ap,
+ Configuration config
+ ) {
+ exists(NodeEx out, boolean allowsFieldFlow |
+ revFlow(out, toReturn, returnAp, ap, config) and
+ flowOutOfCall(call, ret, out, allowsFieldFlow, config)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revFlowInNotToReturn(
+ ArgNodeEx arg, ApOption returnAp, Ap ap, Configuration config
+ ) {
+ exists(ParamNodeEx p, boolean allowsFieldFlow |
+ revFlow(p, false, returnAp, ap, config) and
+ flowIntoCall(_, arg, p, allowsFieldFlow, config)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revFlowInToReturn(
+ DataFlowCall call, ArgNodeEx arg, Ap returnAp, Ap ap, Configuration config
+ ) {
+ exists(ParamNodeEx p, boolean allowsFieldFlow |
+ revFlow(p, true, apSome(returnAp), ap, config) and
+ flowThroughIntoCall(call, arg, p, allowsFieldFlow, config)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ /**
+ * Holds if an output from `call` is reached in the flow covered by `revFlow`
+ * and data might flow through the target callable resulting in reverse flow
+ * reaching an argument of `call`.
+ */
+ pragma[nomagic]
+ private predicate revFlowIsReturned(
+ DataFlowCall call, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
+ ) {
+ exists(RetNodeEx ret, CcCall ccc |
+ revFlowOut(call, ret, toReturn, returnAp, ap, config) and
+ fwdFlow(ret, ccc, apSome(_), ap, config) and
+ ccc.matchesCall(call)
+ )
+ }
+
+ pragma[nomagic]
+ predicate storeStepCand(
+ NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType,
+ Configuration config
+ ) {
+ exists(Ap ap2, Content c |
+ store(node1, tc, node2, contentType, config) and
+ revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and
+ revFlowConsCand(ap2, c, ap1, config)
+ )
+ }
+
+ predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) {
+ exists(Ap ap1, Ap ap2 |
+ revFlow(node2, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and
+ readStepFwd(node1, ap1, c, node2, ap2, config) and
+ revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _,
+ pragma[only_bind_into](config))
+ )
+ }
+
+ predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, config) }
+
+ private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) {
+ storeStepFwd(_, ap, tc, _, _, config)
+ }
+
+ predicate consCand(TypedContent tc, Ap ap, Configuration config) {
+ storeStepCand(_, ap, tc, _, _, config)
+ }
+
+ pragma[noinline]
+ private predicate parameterFlow(
+ ParamNodeEx p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config
+ ) {
+ revFlow(p, true, apSome(ap0), ap, config) and
+ c = p.getEnclosingCallable()
+ }
+
+ predicate parameterMayFlowThrough(ParamNodeEx p, DataFlowCallable c, Ap ap, Configuration config) {
+ exists(RetNodeEx ret, Ap ap0, ReturnKindExt kind, int pos |
+ parameterFlow(p, ap, ap0, c, config) and
+ c = ret.getEnclosingCallable() and
+ revFlow(pragma[only_bind_into](ret), true, apSome(_), pragma[only_bind_into](ap0),
+ pragma[only_bind_into](config)) and
+ fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
+ kind = ret.getKind() and
+ p.getPosition() = pos and
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
+ )
+ }
+
+ pragma[nomagic]
+ predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
+ exists(Ap returnAp0, ArgNodeEx arg, boolean toReturn, ApOption returnAp, Ap ap |
+ revFlow(arg, toReturn, returnAp, ap, config) and
+ revFlowInToReturn(call, arg, returnAp0, ap, config) and
+ revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
+ )
+ }
+
+ predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) {
+ fwd = true and
+ nodes = count(NodeEx node | fwdFlow(node, _, _, _, config)) and
+ fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and
+ conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and
+ tuples = count(NodeEx n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config))
+ or
+ fwd = false and
+ nodes = count(NodeEx node | revFlow(node, _, _, _, config)) and
+ fields = count(TypedContent f0 | consCand(f0, _, config)) and
+ conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and
+ tuples = count(NodeEx n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config))
+ }
+ /* End: Stage 3 logic. */
+}
+
+/**
+ * Holds if `argApf` is recorded as the summary context for flow reaching `node`
+ * and remains relevant for the following pruning stage.
+ */
+private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
+ exists(AccessPathFront apf |
+ Stage3::revFlow(node, true, _, apf, config) and
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
+ )
+}
+
+/**
+ * Holds if a length 2 access path approximation with the head `tc` is expected
+ * to be expensive.
+ */
+private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) {
+ exists(int tails, int nodes, int apLimit, int tupleLimit |
+ tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and
+ nodes =
+ strictcount(NodeEx n |
+ Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config)
+ or
+ flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config)
+ ) and
+ accessPathApproxCostLimits(apLimit, tupleLimit) and
+ apLimit < tails and
+ tupleLimit < (tails - 1) * nodes and
+ not tc.forceHighPrecision()
+ )
+}
+
+private newtype TAccessPathApprox =
+ TNil(DataFlowType t) or
+ TConsNil(TypedContent tc, DataFlowType t) {
+ Stage3::consCand(tc, TFrontNil(t), _) and
+ not expensiveLen2unfolding(tc, _)
+ } or
+ TConsCons(TypedContent tc1, TypedContent tc2, int len) {
+ Stage3::consCand(tc1, TFrontHead(tc2), _) and
+ len in [2 .. accessPathLimit()] and
+ not expensiveLen2unfolding(tc1, _)
+ } or
+ TCons1(TypedContent tc, int len) {
+ len in [1 .. accessPathLimit()] and
+ expensiveLen2unfolding(tc, _)
+ }
+
+/**
+ * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only
+ * the first two elements of the list and its length are tracked. If data flows
+ * from a source to a given node with a given `AccessPathApprox`, this indicates
+ * the sequence of dereference operations needed to get from the value in the node
+ * to the tracked object. The final type indicates the type of the tracked object.
+ */
+abstract private class AccessPathApprox extends TAccessPathApprox {
+ abstract string toString();
+
+ abstract TypedContent getHead();
+
+ abstract int len();
+
+ abstract DataFlowType getType();
+
+ abstract AccessPathFront getFront();
+
+ /** Gets the access path obtained by popping `head` from this path, if any. */
+ abstract AccessPathApprox pop(TypedContent head);
+}
+
+private class AccessPathApproxNil extends AccessPathApprox, TNil {
+ private DataFlowType t;
+
+ AccessPathApproxNil() { this = TNil(t) }
+
+ override string toString() { result = concat(": " + ppReprType(t)) }
+
+ override TypedContent getHead() { none() }
+
+ override int len() { result = 0 }
+
+ override DataFlowType getType() { result = t }
+
+ override AccessPathFront getFront() { result = TFrontNil(t) }
+
+ override AccessPathApprox pop(TypedContent head) { none() }
+}
+
+abstract private class AccessPathApproxCons extends AccessPathApprox { }
+
+private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil {
+ private TypedContent tc;
+ private DataFlowType t;
+
+ AccessPathApproxConsNil() { this = TConsNil(tc, t) }
+
+ override string toString() {
+ // The `concat` becomes "" if `ppReprType` has no result.
+ result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t))
+ }
+
+ override TypedContent getHead() { result = tc }
+
+ override int len() { result = 1 }
+
+ override DataFlowType getType() { result = tc.getContainerType() }
+
+ override AccessPathFront getFront() { result = TFrontHead(tc) }
+
+ override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) }
+}
+
+private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons {
+ private TypedContent tc1;
+ private TypedContent tc2;
+ private int len;
+
+ AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) }
+
+ override string toString() {
+ if len = 2
+ then result = "[" + tc1.toString() + ", " + tc2.toString() + "]"
+ else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]"
+ }
+
+ override TypedContent getHead() { result = tc1 }
+
+ override int len() { result = len }
+
+ override DataFlowType getType() { result = tc1.getContainerType() }
+
+ override AccessPathFront getFront() { result = TFrontHead(tc1) }
+
+ override AccessPathApprox pop(TypedContent head) {
+ head = tc1 and
+ (
+ result = TConsCons(tc2, _, len - 1)
+ or
+ len = 2 and
+ result = TConsNil(tc2, _)
+ or
+ result = TCons1(tc2, len - 1)
+ )
+ }
+}
+
+private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 {
+ private TypedContent tc;
+ private int len;
+
+ AccessPathApproxCons1() { this = TCons1(tc, len) }
+
+ override string toString() {
+ if len = 1
+ then result = "[" + tc.toString() + "]"
+ else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]"
+ }
+
+ override TypedContent getHead() { result = tc }
+
+ override int len() { result = len }
+
+ override DataFlowType getType() { result = tc.getContainerType() }
+
+ override AccessPathFront getFront() { result = TFrontHead(tc) }
+
+ override AccessPathApprox pop(TypedContent head) {
+ head = tc and
+ (
+ exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) |
+ result = TConsCons(tc2, _, len - 1)
+ or
+ len = 2 and
+ result = TConsNil(tc2, _)
+ or
+ result = TCons1(tc2, len - 1)
+ )
+ or
+ exists(DataFlowType t |
+ len = 1 and
+ Stage3::consCand(tc, TFrontNil(t), _) and
+ result = TNil(t)
+ )
+ )
+ }
+}
+
+/** Gets the access path obtained by popping `tc` from `ap`, if any. */
+private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) }
+
+/** Gets the access path obtained by pushing `tc` onto `ap`. */
+private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) }
+
+private newtype TAccessPathApproxOption =
+ TAccessPathApproxNone() or
+ TAccessPathApproxSome(AccessPathApprox apa)
+
+private class AccessPathApproxOption extends TAccessPathApproxOption {
+ string toString() {
+ this = TAccessPathApproxNone() and result = ""
+ or
+ this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString()))
+ }
+}
+
+private module Stage4 {
+ module PrevStage = Stage3;
+
+ class ApApprox = PrevStage::Ap;
+
+ class Ap = AccessPathApprox;
+
+ class ApNil = AccessPathApproxNil;
+
+ private ApApprox getApprox(Ap ap) { result = ap.getFront() }
+
+ private ApNil getApNil(NodeEx node) {
+ PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType())
+ }
+
+ bindingset[tc, tail]
+ private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) }
+
+ pragma[noinline]
+ private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() }
+
+ class ApOption = AccessPathApproxOption;
+
+ ApOption apNone() { result = TAccessPathApproxNone() }
+
+ ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) }
+
+ class Cc = CallContext;
+
+ class CcCall = CallContextCall;
+
+ class CcNoCall = CallContextNoCall;
+
+ Cc ccNone() { result instanceof CallContextAny }
+
+ CcCall ccSomeCall() { result instanceof CallContextSomeCall }
+
+ private class LocalCc = LocalCallContext;
+
+ bindingset[call, c, outercc]
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall()
+ }
+
+ bindingset[call, c, innercc]
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
+
+ bindingset[node, cc, config]
+ private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result =
+ getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)),
+ node.getEnclosingCallable())
+ }
+
+ private predicate localStep(
+ NodeEx node1, NodeEx node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc
+ ) {
+ localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc)
+ }
+
+ pragma[nomagic]
+ private predicate flowOutOfCall(
+ DataFlowCall call, RetNodeEx node1, NodeEx node2, boolean allowsFieldFlow, Configuration config
+ ) {
+ flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and
+ PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and
+ PrevStage::revFlow(node1, _, _, _, pragma[only_bind_into](config))
+ }
+
+ pragma[nomagic]
+ private predicate flowIntoCall(
+ DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow,
+ Configuration config
+ ) {
+ flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and
+ PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and
+ PrevStage::revFlow(node1, _, _, _, pragma[only_bind_into](config))
+ }
+
+ bindingset[node, ap]
+ private predicate filter(NodeEx node, Ap ap) { any() }
+
+ // Type checking is not necessary here as it has already been done in stage 3.
+ bindingset[ap, contentType]
+ private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() }
+
+ /* Begin: Stage 4 logic. */
+ private predicate flowCand(NodeEx node, ApApprox apa, Configuration config) {
+ PrevStage::revFlow(node, _, _, apa, config)
+ }
+
+ bindingset[result, apa]
+ private ApApprox unbindApa(ApApprox apa) {
+ exists(ApApprox apa0 |
+ apa = pragma[only_bind_into](apa0) and result = pragma[only_bind_into](apa0)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate flowThroughOutOfCall(
+ DataFlowCall call, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, Configuration config
+ ) {
+ flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and
+ PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and
+ PrevStage::parameterMayFlowThrough(_, ret.getEnclosingCallable(), _,
+ pragma[only_bind_into](config))
+ }
+
+ /**
+ * Holds if `node` is reachable with access path `ap` from a source in the
+ * configuration `config`.
+ *
+ * The call context `cc` records whether the node is reached through an
+ * argument in a call, and if so, `argAp` records the access path of that
+ * argument.
+ */
+ pragma[nomagic]
+ predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
+ fwdFlow0(node, cc, argAp, ap, config) and
+ flowCand(node, unbindApa(getApprox(ap)), config) and
+ filter(node, ap)
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
+ flowCand(node, _, config) and
+ sourceNode(node, config) and
+ (if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
+ argAp = apNone() and
+ ap = getApNil(node)
+ or
+ exists(NodeEx mid, Ap ap0, LocalCc localCc |
+ fwdFlow(mid, cc, argAp, ap0, config) and
+ localCc = getLocalCc(mid, cc, config)
+ |
+ localStep(mid, node, true, _, config, localCc) and
+ ap = ap0
+ or
+ localStep(mid, node, false, ap, config, localCc) and
+ ap0 instanceof ApNil
+ )
+ or
+ exists(NodeEx mid |
+ fwdFlow(mid, _, _, ap, pragma[only_bind_into](config)) and
+ flowCand(node, _, pragma[only_bind_into](config)) and
+ jumpStep(mid, node, config) and
+ cc = ccNone() and
+ argAp = apNone()
+ )
+ or
+ exists(NodeEx mid, ApNil nil |
+ fwdFlow(mid, _, _, nil, pragma[only_bind_into](config)) and
+ flowCand(node, _, pragma[only_bind_into](config)) and
+ additionalJumpStep(mid, node, config) and
+ cc = ccNone() and
+ argAp = apNone() and
+ ap = getApNil(node)
+ )
+ or
+ // store
+ exists(TypedContent tc, Ap ap0 |
+ fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and
+ ap = apCons(tc, ap0)
+ )
+ or
+ // read
+ exists(Ap ap0, Content c |
+ fwdFlowRead(ap0, c, _, node, cc, argAp, config) and
+ fwdFlowConsCand(ap0, c, ap, config)
+ )
+ or
+ // flow into a callable
+ exists(ApApprox apa |
+ fwdFlowIn(_, node, _, cc, _, ap, config) and
+ apa = getApprox(ap) and
+ if PrevStage::parameterMayFlowThrough(node, _, apa, config)
+ then argAp = apSome(ap)
+ else argAp = apNone()
+ )
+ or
+ // flow out of a callable
+ fwdFlowOutNotFromArg(node, cc, argAp, ap, config)
+ or
+ exists(DataFlowCall call, Ap argAp0 |
+ fwdFlowOutFromArg(call, node, argAp0, ap, config) and
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowStore(
+ NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Cc cc, ApOption argAp, Configuration config
+ ) {
+ exists(DataFlowType contentType |
+ fwdFlow(node1, cc, argAp, ap1, config) and
+ PrevStage::storeStepCand(node1, unbindApa(getApprox(ap1)), tc, node2, contentType, config) and
+ typecheckStore(ap1, contentType)
+ )
+ }
+
+ /**
+ * Holds if forward flow with access path `tail` reaches a store of `c`
+ * resulting in access path `cons`.
+ */
+ pragma[nomagic]
+ private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) {
+ exists(TypedContent tc |
+ fwdFlowStore(_, tail, tc, _, _, _, config) and
+ tc.getContent() = c and
+ cons = apCons(tc, tail)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowRead(
+ Ap ap, Content c, NodeEx node1, NodeEx node2, Cc cc, ApOption argAp, Configuration config
+ ) {
+ fwdFlow(node1, cc, argAp, ap, config) and
+ PrevStage::readStepCand(node1, c, node2, config) and
+ getHeadContent(ap) = c
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowIn(
+ DataFlowCall call, ParamNodeEx p, Cc outercc, Cc innercc, ApOption argAp, Ap ap,
+ Configuration config
+ ) {
+ exists(ArgNodeEx arg, boolean allowsFieldFlow |
+ fwdFlow(arg, outercc, argAp, ap, config) and
+ flowIntoCall(call, arg, p, allowsFieldFlow, config) and
+ innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowOutNotFromArg(
+ NodeEx out, Cc ccOut, ApOption argAp, Ap ap, Configuration config
+ ) {
+ exists(
+ DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc,
+ DataFlowCallable inner
+ |
+ fwdFlow(ret, innercc, argAp, ap, config) and
+ flowOutOfCall(call, ret, out, allowsFieldFlow, config) and
+ inner = ret.getEnclosingCallable() and
+ ccOut = getCallContextReturn(inner, call, innercc)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ pragma[nomagic]
+ private predicate fwdFlowOutFromArg(
+ DataFlowCall call, NodeEx out, Ap argAp, Ap ap, Configuration config
+ ) {
+ exists(RetNodeEx ret, boolean allowsFieldFlow, CcCall ccc |
+ fwdFlow(ret, ccc, apSome(argAp), ap, config) and
+ flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and
+ ccc.matchesCall(call)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ /**
+ * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`
+ * and data might flow through the target callable and back out at `call`.
+ */
+ pragma[nomagic]
+ private predicate fwdFlowIsEntered(
+ DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config
+ ) {
+ exists(ParamNodeEx p |
+ fwdFlowIn(call, p, cc, _, argAp, ap, config) and
+ PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate storeStepFwd(
+ NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config
+ ) {
+ fwdFlowStore(node1, ap1, tc, node2, _, _, config) and
+ ap2 = apCons(tc, ap1) and
+ fwdFlowRead(ap2, tc.getContent(), _, _, _, _, config)
+ }
+
+ private predicate readStepFwd(
+ NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config
+ ) {
+ fwdFlowRead(ap1, c, n1, n2, _, _, config) and
+ fwdFlowConsCand(ap1, c, ap2, config)
+ }
+
+ pragma[nomagic]
+ private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
+ exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
+ fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
+ pragma[only_bind_into](config)) and
+ fwdFlowOutFromArg(call, out, argAp0, ap, config) and
+ fwdFlowIsEntered(pragma[only_bind_into](call), pragma[only_bind_into](cc),
+ pragma[only_bind_into](argAp), pragma[only_bind_into](argAp0),
+ pragma[only_bind_into](config))
+ )
+ }
+
+ pragma[nomagic]
+ private predicate flowThroughIntoCall(
+ DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config
+ ) {
+ flowIntoCall(call, arg, p, allowsFieldFlow, config) and
+ fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and
+ PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and
+ callMayFlowThroughFwd(call, pragma[only_bind_into](config))
+ }
+
+ /**
+ * Holds if `node` with access path `ap` is part of a path from a source to a
+ * sink in the configuration `config`.
+ *
+ * The Boolean `toReturn` records whether the node must be returned from the
+ * enclosing callable in order to reach a sink, and if so, `returnAp` records
+ * the access path of the returned value.
+ */
+ pragma[nomagic]
+ predicate revFlow(NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) {
+ revFlow0(node, toReturn, returnAp, ap, config) and
+ fwdFlow(node, _, _, ap, config)
+ }
+
+ pragma[nomagic]
+ private predicate revFlow0(
+ NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
+ ) {
+ fwdFlow(node, _, _, ap, config) and
+ sinkNode(node, config) and
+ (if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
+ returnAp = apNone() and
+ ap instanceof ApNil
+ or
+ exists(NodeEx mid |
+ localStep(node, mid, true, _, config, _) and
+ revFlow(mid, toReturn, returnAp, ap, config)
+ )
+ or
+ exists(NodeEx mid, ApNil nil |
+ fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and
+ localStep(node, mid, false, _, config, _) and
+ revFlow(mid, toReturn, returnAp, nil, pragma[only_bind_into](config)) and
+ ap instanceof ApNil
+ )
+ or
+ exists(NodeEx mid |
+ jumpStep(node, mid, config) and
+ revFlow(mid, _, _, ap, config) and
+ toReturn = false and
+ returnAp = apNone()
+ )
+ or
+ exists(NodeEx mid, ApNil nil |
+ fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and
+ additionalJumpStep(node, mid, config) and
+ revFlow(pragma[only_bind_into](mid), _, _, nil, pragma[only_bind_into](config)) and
+ toReturn = false and
+ returnAp = apNone() and
+ ap instanceof ApNil
+ )
+ or
+ // store
+ exists(Ap ap0, Content c |
+ revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and
+ revFlowConsCand(ap0, c, ap, config)
+ )
+ or
+ // read
+ exists(NodeEx mid, Ap ap0 |
+ revFlow(mid, toReturn, returnAp, ap0, config) and
+ readStepFwd(node, ap, _, mid, ap0, config)
+ )
+ or
+ // flow into a callable
+ revFlowInNotToReturn(node, returnAp, ap, config) and
+ toReturn = false
+ or
+ exists(DataFlowCall call, Ap returnAp0 |
+ revFlowInToReturn(call, node, returnAp0, ap, config) and
+ revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
+ )
+ or
+ // flow out of a callable
+ revFlowOut(_, node, _, _, ap, config) and
+ toReturn = true and
+ if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config)
+ then returnAp = apSome(ap)
+ else returnAp = apNone()
+ }
+
+ pragma[nomagic]
+ private predicate revFlowStore(
+ Ap ap0, Content c, Ap ap, NodeEx node, TypedContent tc, NodeEx mid, boolean toReturn,
+ ApOption returnAp, Configuration config
+ ) {
+ revFlow(mid, toReturn, returnAp, ap0, config) and
+ storeStepFwd(node, ap, tc, mid, ap0, config) and
+ tc.getContent() = c
+ }
+
+ /**
+ * Holds if reverse flow with access path `tail` reaches a read of `c`
+ * resulting in access path `cons`.
+ */
+ pragma[nomagic]
+ private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) {
+ exists(NodeEx mid, Ap tail0 |
+ revFlow(mid, _, _, tail, config) and
+ tail = pragma[only_bind_into](tail0) and
+ readStepFwd(_, cons, c, mid, tail0, config)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revFlowOut(
+ DataFlowCall call, RetNodeEx ret, boolean toReturn, ApOption returnAp, Ap ap,
+ Configuration config
+ ) {
+ exists(NodeEx out, boolean allowsFieldFlow |
+ revFlow(out, toReturn, returnAp, ap, config) and
+ flowOutOfCall(call, ret, out, allowsFieldFlow, config)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revFlowInNotToReturn(
+ ArgNodeEx arg, ApOption returnAp, Ap ap, Configuration config
+ ) {
+ exists(ParamNodeEx p, boolean allowsFieldFlow |
+ revFlow(p, false, returnAp, ap, config) and
+ flowIntoCall(_, arg, p, allowsFieldFlow, config)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revFlowInToReturn(
+ DataFlowCall call, ArgNodeEx arg, Ap returnAp, Ap ap, Configuration config
+ ) {
+ exists(ParamNodeEx p, boolean allowsFieldFlow |
+ revFlow(p, true, apSome(returnAp), ap, config) and
+ flowThroughIntoCall(call, arg, p, allowsFieldFlow, config)
+ |
+ ap instanceof ApNil or allowsFieldFlow = true
+ )
+ }
+
+ /**
+ * Holds if an output from `call` is reached in the flow covered by `revFlow`
+ * and data might flow through the target callable resulting in reverse flow
+ * reaching an argument of `call`.
+ */
+ pragma[nomagic]
+ private predicate revFlowIsReturned(
+ DataFlowCall call, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
+ ) {
+ exists(RetNodeEx ret, CcCall ccc |
+ revFlowOut(call, ret, toReturn, returnAp, ap, config) and
+ fwdFlow(ret, ccc, apSome(_), ap, config) and
+ ccc.matchesCall(call)
+ )
+ }
+
+ pragma[nomagic]
+ predicate storeStepCand(
+ NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType,
+ Configuration config
+ ) {
+ exists(Ap ap2, Content c |
+ store(node1, tc, node2, contentType, config) and
+ revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and
+ revFlowConsCand(ap2, c, ap1, config)
+ )
+ }
+
+ predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) {
+ exists(Ap ap1, Ap ap2 |
+ revFlow(node2, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and
+ readStepFwd(node1, ap1, c, node2, ap2, config) and
+ revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _,
+ pragma[only_bind_into](config))
+ )
+ }
+
+ predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, config) }
+
+ private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) {
+ storeStepFwd(_, ap, tc, _, _, config)
+ }
+
+ predicate consCand(TypedContent tc, Ap ap, Configuration config) {
+ storeStepCand(_, ap, tc, _, _, config)
+ }
+
+ pragma[noinline]
+ private predicate parameterFlow(
+ ParamNodeEx p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config
+ ) {
+ revFlow(p, true, apSome(ap0), ap, config) and
+ c = p.getEnclosingCallable()
+ }
+
+ predicate parameterMayFlowThrough(ParamNodeEx p, DataFlowCallable c, Ap ap, Configuration config) {
+ exists(RetNodeEx ret, Ap ap0, ReturnKindExt kind, int pos |
+ parameterFlow(p, ap, ap0, c, config) and
+ c = ret.getEnclosingCallable() and
+ revFlow(pragma[only_bind_into](ret), true, apSome(_), pragma[only_bind_into](ap0),
+ pragma[only_bind_into](config)) and
+ fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and
+ kind = ret.getKind() and
+ p.getPosition() = pos and
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ p.allowParameterReturnInSelf()
+ )
+ )
+ }
+
+ pragma[nomagic]
+ predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
+ exists(Ap returnAp0, ArgNodeEx arg, boolean toReturn, ApOption returnAp, Ap ap |
+ revFlow(arg, toReturn, returnAp, ap, config) and
+ revFlowInToReturn(call, arg, returnAp0, ap, config) and
+ revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
+ )
+ }
+
+ predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) {
+ fwd = true and
+ nodes = count(NodeEx node | fwdFlow(node, _, _, _, config)) and
+ fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and
+ conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and
+ tuples = count(NodeEx n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config))
+ or
+ fwd = false and
+ nodes = count(NodeEx node | revFlow(node, _, _, _, config)) and
+ fields = count(TypedContent f0 | consCand(f0, _, config)) and
+ conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and
+ tuples = count(NodeEx n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config))
+ }
+ /* End: Stage 4 logic. */
+}
+
+bindingset[conf, result]
+private Configuration unbindConf(Configuration conf) {
+ exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c))
+}
+
+private predicate nodeMayUseSummary(NodeEx n, AccessPathApprox apa, Configuration config) {
+ exists(DataFlowCallable c, AccessPathApprox apa0 |
+ Stage4::parameterMayFlowThrough(_, c, apa, _) and
+ Stage4::revFlow(n, true, _, apa0, config) and
+ Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and
+ n.getEnclosingCallable() = c
+ )
+}
+
+private newtype TSummaryCtx =
+ TSummaryCtxNone() or
+ TSummaryCtxSome(ParamNodeEx p, AccessPath ap) {
+ Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _)
+ }
+
+/**
+ * A context for generating flow summaries. This represents flow entry through
+ * a specific parameter with an access path of a specific shape.
+ *
+ * Summaries are only created for parameters that may flow through.
+ */
+abstract private class SummaryCtx extends TSummaryCtx {
+ abstract string toString();
+}
+
+/** A summary context from which no flow summary can be generated. */
+private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone {
+ override string toString() { result = "" }
+}
+
+/** A summary context from which a flow summary can be generated. */
+private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
+ private ParamNodeEx p;
+ private AccessPath ap;
+
+ SummaryCtxSome() { this = TSummaryCtxSome(p, ap) }
+
+ int getParameterPos() { p.isParameterOf(_, result) }
+
+ ParamNodeEx getParamNode() { result = p }
+
+ override string toString() { result = p + ": " + ap }
+
+ predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
+ p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ }
+}
+
+/**
+ * Gets the number of length 2 access path approximations that correspond to `apa`.
+ */
+private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) {
+ exists(TypedContent tc, int len |
+ tc = apa.getHead() and
+ len = apa.len() and
+ result =
+ strictcount(AccessPathFront apf |
+ Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1),
+ config)
+ )
+ )
+}
+
+private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) {
+ result =
+ strictcount(NodeEx n |
+ Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)
+ )
+}
+
+/**
+ * Holds if a length 2 access path approximation matching `apa` is expected
+ * to be expensive.
+ */
+private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) {
+ exists(int aps, int nodes, int apLimit, int tupleLimit |
+ aps = count1to2unfold(apa, config) and
+ nodes = countNodesUsingAccessPath(apa, config) and
+ accessPathCostLimits(apLimit, tupleLimit) and
+ apLimit < aps and
+ tupleLimit < (aps - 1) * nodes
+ )
+}
+
+private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) {
+ exists(TypedContent head |
+ apa.pop(head) = result and
+ Stage4::consCand(head, result, config)
+ )
+}
+
+/**
+ * Holds with `unfold = false` if a precise head-tail representation of `apa` is
+ * expected to be expensive. Holds with `unfold = true` otherwise.
+ */
+private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) {
+ if apa.getHead().forceHighPrecision()
+ then unfold = true
+ else
+ exists(int aps, int nodes, int apLimit, int tupleLimit |
+ aps = countPotentialAps(apa, config) and
+ nodes = countNodesUsingAccessPath(apa, config) and
+ accessPathCostLimits(apLimit, tupleLimit) and
+ if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true
+ )
+}
+
+/**
+ * Gets the number of `AccessPath`s that correspond to `apa`.
+ */
+private int countAps(AccessPathApprox apa, Configuration config) {
+ evalUnfold(apa, false, config) and
+ result = 1 and
+ (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config))
+ or
+ evalUnfold(apa, false, config) and
+ result = count1to2unfold(apa, config) and
+ not expensiveLen1to2unfolding(apa, config)
+ or
+ evalUnfold(apa, true, config) and
+ result = countPotentialAps(apa, config)
+}
+
+/**
+ * Gets the number of `AccessPath`s that would correspond to `apa` assuming
+ * that it is expanded to a precise head-tail representation.
+ */
+language[monotonicAggregates]
+private int countPotentialAps(AccessPathApprox apa, Configuration config) {
+ apa instanceof AccessPathApproxNil and result = 1
+ or
+ result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config))
+}
+
+private newtype TAccessPath =
+ TAccessPathNil(DataFlowType t) or
+ TAccessPathCons(TypedContent head, AccessPath tail) {
+ exists(AccessPathApproxCons apa |
+ not evalUnfold(apa, false, _) and
+ head = apa.getHead() and
+ tail.getApprox() = getATail(apa, _)
+ )
+ } or
+ TAccessPathCons2(TypedContent head1, TypedContent head2, int len) {
+ exists(AccessPathApproxCons apa |
+ evalUnfold(apa, false, _) and
+ not expensiveLen1to2unfolding(apa, _) and
+ apa.len() = len and
+ head1 = apa.getHead() and
+ head2 = getATail(apa, _).getHead()
+ )
+ } or
+ TAccessPathCons1(TypedContent head, int len) {
+ exists(AccessPathApproxCons apa |
+ evalUnfold(apa, false, _) and
+ expensiveLen1to2unfolding(apa, _) and
+ apa.len() = len and
+ head = apa.getHead()
+ )
+ }
+
+private newtype TPathNode =
+ TPathNodeMid(NodeEx node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) {
+ // A PathNode is introduced by a source ...
+ Stage4::revFlow(node, config) and
+ sourceNode(node, config) and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
+ sc instanceof SummaryCtxNone and
+ ap = TAccessPathNil(node.getDataFlowType())
+ or
+ // ... or a step from an existing PathNode to another node.
+ exists(PathNodeMid mid |
+ pathStep(mid, node, cc, sc, ap) and
+ pragma[only_bind_into](config) = mid.getConfiguration() and
+ Stage4::revFlow(node, _, _, ap.getApprox(), pragma[only_bind_into](config))
+ )
+ } or
+ TPathNodeSink(NodeEx node, Configuration config) {
+ exists(PathNodeMid sink |
+ sink.isAtSink() and
+ node = sink.getNodeEx() and
+ config = sink.getConfiguration()
+ )
+ }
+
+/**
+ * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a
+ * source to a given node with a given `AccessPath`, this indicates the sequence
+ * of dereference operations needed to get from the value in the node to the
+ * tracked object. The final type indicates the type of the tracked object.
+ */
+abstract private class AccessPath extends TAccessPath {
+ /** Gets the head of this access path, if any. */
+ abstract TypedContent getHead();
+
+ /** Gets the tail of this access path, if any. */
+ abstract AccessPath getTail();
+
+ /** Gets the front of this access path. */
+ abstract AccessPathFront getFront();
+
+ /** Gets the approximation of this access path. */
+ abstract AccessPathApprox getApprox();
+
+ /** Gets the length of this access path. */
+ abstract int length();
+
+ /** Gets a textual representation of this access path. */
+ abstract string toString();
+
+ /** Gets the access path obtained by popping `tc` from this access path, if any. */
+ final AccessPath pop(TypedContent tc) {
+ result = this.getTail() and
+ tc = this.getHead()
+ }
+
+ /** Gets the access path obtained by pushing `tc` onto this access path. */
+ final AccessPath push(TypedContent tc) { this = result.pop(tc) }
+}
+
+private class AccessPathNil extends AccessPath, TAccessPathNil {
+ private DataFlowType t;
+
+ AccessPathNil() { this = TAccessPathNil(t) }
+
+ DataFlowType getType() { result = t }
+
+ override TypedContent getHead() { none() }
+
+ override AccessPath getTail() { none() }
+
+ override AccessPathFrontNil getFront() { result = TFrontNil(t) }
+
+ override AccessPathApproxNil getApprox() { result = TNil(t) }
+
+ override int length() { result = 0 }
+
+ override string toString() { result = concat(": " + ppReprType(t)) }
+}
+
+private class AccessPathCons extends AccessPath, TAccessPathCons {
+ private TypedContent head;
+ private AccessPath tail;
+
+ AccessPathCons() { this = TAccessPathCons(head, tail) }
+
+ override TypedContent getHead() { result = head }
+
+ override AccessPath getTail() { result = tail }
+
+ override AccessPathFrontHead getFront() { result = TFrontHead(head) }
+
+ override AccessPathApproxCons getApprox() {
+ result = TConsNil(head, tail.(AccessPathNil).getType())
+ or
+ result = TConsCons(head, tail.getHead(), this.length())
+ or
+ result = TCons1(head, this.length())
+ }
+
+ override int length() { result = 1 + tail.length() }
+
+ private string toStringImpl(boolean needsSuffix) {
+ exists(DataFlowType t |
+ tail = TAccessPathNil(t) and
+ needsSuffix = false and
+ result = head.toString() + "]" + concat(" : " + ppReprType(t))
+ )
+ or
+ result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix)
+ or
+ exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) |
+ result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true
+ or
+ result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false
+ )
+ or
+ exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) |
+ result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true
+ or
+ result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false
+ )
+ }
+
+ override string toString() {
+ result = "[" + this.toStringImpl(true) + this.length().toString() + ")]"
+ or
+ result = "[" + this.toStringImpl(false)
+ }
+}
+
+private class AccessPathCons2 extends AccessPath, TAccessPathCons2 {
+ private TypedContent head1;
+ private TypedContent head2;
+ private int len;
+
+ AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) }
+
+ override TypedContent getHead() { result = head1 }
+
+ override AccessPath getTail() {
+ Stage4::consCand(head1, result.getApprox(), _) and
+ result.getHead() = head2 and
+ result.length() = len - 1
+ }
+
+ override AccessPathFrontHead getFront() { result = TFrontHead(head1) }
+
+ override AccessPathApproxCons getApprox() {
+ result = TConsCons(head1, head2, len) or
+ result = TCons1(head1, len)
+ }
+
+ override int length() { result = len }
+
+ override string toString() {
+ if len = 2
+ then result = "[" + head1.toString() + ", " + head2.toString() + "]"
+ else
+ result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]"
+ }
+}
+
+private class AccessPathCons1 extends AccessPath, TAccessPathCons1 {
+ private TypedContent head;
+ private int len;
+
+ AccessPathCons1() { this = TAccessPathCons1(head, len) }
+
+ override TypedContent getHead() { result = head }
+
+ override AccessPath getTail() {
+ Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1
+ }
+
+ override AccessPathFrontHead getFront() { result = TFrontHead(head) }
+
+ override AccessPathApproxCons getApprox() { result = TCons1(head, len) }
+
+ override int length() { result = len }
+
+ override string toString() {
+ if len = 1
+ then result = "[" + head.toString() + "]"
+ else result = "[" + head.toString() + ", ... (" + len.toString() + ")]"
+ }
+}
+
+/**
+ * A `Node` augmented with a call context (except for sinks), an access path, and a configuration.
+ * Only those `PathNode`s that are reachable from a source are generated.
+ */
+class PathNode extends TPathNode {
+ /** Gets a textual representation of this element. */
+ string toString() { none() }
+
+ /**
+ * Gets a textual representation of this element, including a textual
+ * representation of the call context.
+ */
+ string toStringWithContext() { none() }
+
+ /**
+ * Holds if this element is at the specified location.
+ * The location spans column `startcolumn` of line `startline` to
+ * column `endcolumn` of line `endline` in file `filepath`.
+ * For more information, see
+ * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
+ */
+ predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
+ none()
+ }
+
+ /** Gets the underlying `Node`. */
+ final Node getNode() { this.(PathNodeImpl).getNodeEx().projectToNode() = result }
+
+ /** Gets the associated configuration. */
+ Configuration getConfiguration() { none() }
+
+ private PathNode getASuccessorIfHidden() {
+ this.(PathNodeImpl).isHidden() and
+ result = this.(PathNodeImpl).getASuccessorImpl()
+ }
+
+ /** Gets a successor of this node, if any. */
+ final PathNode getASuccessor() {
+ result = this.(PathNodeImpl).getASuccessorImpl().getASuccessorIfHidden*() and
+ not this.(PathNodeImpl).isHidden() and
+ not result.(PathNodeImpl).isHidden()
+ }
+
+ /** Holds if this node is a source. */
+ predicate isSource() { none() }
+}
+
+abstract private class PathNodeImpl extends PathNode {
+ abstract PathNode getASuccessorImpl();
+
+ abstract NodeEx getNodeEx();
+
+ predicate isHidden() {
+ hiddenNode(this.getNodeEx().asNode()) and
+ not this.isSource() and
+ not this instanceof PathNodeSink
+ or
+ this.getNodeEx() instanceof TNodeImplicitRead
+ }
+
+ private string ppAp() {
+ this instanceof PathNodeSink and result = ""
+ or
+ exists(string s | s = this.(PathNodeMid).getAp().toString() |
+ if s = "" then result = "" else result = " " + s
+ )
+ }
+
+ private string ppCtx() {
+ this instanceof PathNodeSink and result = ""
+ or
+ result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
+ }
+
+ override string toString() { result = this.getNodeEx().toString() + this.ppAp() }
+
+ override string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
+
+ override predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
+ this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ }
+}
+
+/** Holds if `n` can reach a sink. */
+private predicate directReach(PathNode n) {
+ n instanceof PathNodeSink or directReach(n.getASuccessor())
+}
+
+/** Holds if `n` can reach a sink or is used in a subpath. */
+private predicate reach(PathNode n) { directReach(n) or Subpaths::retReach(n) }
+
+/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */
+private predicate pathSucc(PathNode n1, PathNode n2) { n1.getASuccessor() = n2 and directReach(n2) }
+
+private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1, n2)
+
+/**
+ * Provides the query predicates needed to include a graph in a path-problem query.
+ */
+module PathGraph {
+ /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
+ query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b and reach(b) }
+
+ /** Holds if `n` is a node in the graph of data flow path explanations. */
+ query predicate nodes(PathNode n, string key, string val) {
+ reach(n) and key = "semmle.label" and val = n.toString()
+ }
+
+ query predicate subpaths = Subpaths::subpaths/4;
+}
+
+/**
+ * An intermediate flow graph node. This is a triple consisting of a `Node`,
+ * a `CallContext`, and a `Configuration`.
+ */
+private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
+ NodeEx node;
+ CallContext cc;
+ SummaryCtx sc;
+ AccessPath ap;
+ Configuration config;
+
+ PathNodeMid() { this = TPathNodeMid(node, cc, sc, ap, config) }
+
+ override NodeEx getNodeEx() { result = node }
+
+ CallContext getCallContext() { result = cc }
+
+ SummaryCtx getSummaryCtx() { result = sc }
+
+ AccessPath getAp() { result = ap }
+
+ override Configuration getConfiguration() { result = config }
+
+ private PathNodeMid getSuccMid() {
+ pathStep(this, result.getNodeEx(), result.getCallContext(), result.getSummaryCtx(),
+ result.getAp()) and
+ result.getConfiguration() = unbindConf(this.getConfiguration())
+ }
+
+ override PathNodeImpl getASuccessorImpl() {
+ // an intermediate step to another intermediate node
+ result = this.getSuccMid()
+ or
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
+ }
+
+ override predicate isSource() {
+ sourceNode(node, config) and
+ (
+ if hasSourceCallCtx(config)
+ then cc instanceof CallContextSomeCall
+ else cc instanceof CallContextAny
+ ) and
+ sc instanceof SummaryCtxNone and
+ ap instanceof AccessPathNil
+ }
+
+ predicate isAtSink() {
+ sinkNode(node, config) and
+ ap instanceof AccessPathNil and
+ if hasSinkCallCtx(config)
+ then
+ // For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
+ // is exactly what we need to check. This also implies
+ // `sc instanceof SummaryCtxNone`.
+ // For `FeatureEqualSourceSinkCallContext` the initial call context was
+ // set to `CallContextSomeCall` and jumps are disallowed, so
+ // `cc instanceof CallContextNoCall` never holds. On the other hand,
+ // in this case there's never any need to enter a call except to identify
+ // a summary, so the condition in `pathIntoCallable` enforces this, which
+ // means that `sc instanceof SummaryCtxNone` holds if and only if we are
+ // in the call context of the source.
+ sc instanceof SummaryCtxNone or
+ cc instanceof CallContextNoCall
+ else any()
+ }
+
+ PathNodeSink projectToSink() {
+ this.isAtSink() and
+ result.getNodeEx() = node and
+ result.getConfiguration() = unbindConf(config)
+ }
+}
+
+/**
+ * A flow graph node corresponding to a sink. This is disjoint from the
+ * intermediate nodes in order to uniquely correspond to a given sink by
+ * excluding the `CallContext`.
+ */
+private class PathNodeSink extends PathNodeImpl, TPathNodeSink {
+ NodeEx node;
+ Configuration config;
+
+ PathNodeSink() { this = TPathNodeSink(node, config) }
+
+ override NodeEx getNodeEx() { result = node }
+
+ override Configuration getConfiguration() { result = config }
+
+ override PathNode getASuccessorImpl() { none() }
+
+ override predicate isSource() { sourceNode(node, config) }
+}
+
+/**
+ * Holds if data may flow from `mid` to `node`. The last step in or out of
+ * a callable is recorded by `cc`.
+ */
+private predicate pathStep(
+ PathNodeMid mid, NodeEx node, CallContext cc, SummaryCtx sc, AccessPath ap
+) {
+ exists(AccessPath ap0, NodeEx midnode, Configuration conf, LocalCallContext localCC |
+ midnode = mid.getNodeEx() and
+ conf = mid.getConfiguration() and
+ cc = mid.getCallContext() and
+ sc = mid.getSummaryCtx() and
+ localCC =
+ getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)),
+ midnode.getEnclosingCallable()) and
+ ap0 = mid.getAp()
+ |
+ localFlowBigStep(midnode, node, true, _, conf, localCC) and
+ ap = ap0
+ or
+ localFlowBigStep(midnode, node, false, ap.getFront(), conf, localCC) and
+ ap0 instanceof AccessPathNil
+ )
+ or
+ jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and
+ cc instanceof CallContextAny and
+ sc instanceof SummaryCtxNone and
+ ap = mid.getAp()
+ or
+ additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and
+ cc instanceof CallContextAny and
+ sc instanceof SummaryCtxNone and
+ mid.getAp() instanceof AccessPathNil and
+ ap = TAccessPathNil(node.getDataFlowType())
+ or
+ exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and
+ sc = mid.getSummaryCtx()
+ or
+ exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and
+ sc = mid.getSummaryCtx()
+ or
+ pathIntoCallable(mid, node, _, cc, sc, _, _) and ap = mid.getAp()
+ or
+ pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone
+ or
+ pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx()
+}
+
+pragma[nomagic]
+private predicate pathReadStep(
+ PathNodeMid mid, NodeEx node, AccessPath ap0, TypedContent tc, CallContext cc
+) {
+ ap0 = mid.getAp() and
+ tc = ap0.getHead() and
+ Stage4::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and
+ cc = mid.getCallContext()
+}
+
+pragma[nomagic]
+private predicate pathStoreStep(
+ PathNodeMid mid, NodeEx node, AccessPath ap0, TypedContent tc, CallContext cc
+) {
+ ap0 = mid.getAp() and
+ Stage4::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and
+ cc = mid.getCallContext()
+}
+
+private predicate pathOutOfCallable0(
+ PathNodeMid mid, ReturnPosition pos, CallContext innercc, AccessPathApprox apa,
+ Configuration config
+) {
+ pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and
+ innercc = mid.getCallContext() and
+ innercc instanceof CallContextNoCall and
+ apa = mid.getAp().getApprox() and
+ config = mid.getConfiguration()
+}
+
+pragma[nomagic]
+private predicate pathOutOfCallable1(
+ PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, AccessPathApprox apa,
+ Configuration config
+) {
+ exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc |
+ pathOutOfCallable0(mid, pos, innercc, apa, config) and
+ c = pos.getCallable() and
+ kind = pos.getKind() and
+ resolveReturn(innercc, c, call)
+ |
+ if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext()
+ )
+}
+
+pragma[noinline]
+private NodeEx getAnOutNodeFlow(
+ ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config
+) {
+ result.asNode() = kind.getAnOutNode(call) and
+ Stage4::revFlow(result, _, _, apa, config)
+}
+
+/**
+ * Holds if data may flow from `mid` to `out`. The last step of this path
+ * is a return from a callable and is recorded by `cc`, if needed.
+ */
+pragma[noinline]
+private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc) {
+ exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config |
+ pathOutOfCallable1(mid, call, kind, cc, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
+ )
+}
+
+/**
+ * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`.
+ */
+pragma[noinline]
+private predicate pathIntoArg(
+ PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa,
+ Configuration config
+) {
+ exists(ArgNode arg |
+ arg = mid.getNodeEx().asNode() and
+ cc = mid.getCallContext() and
+ arg.argumentOf(call, i) and
+ ap = mid.getAp() and
+ apa = ap.getApprox() and
+ config = mid.getConfiguration()
+ )
+}
+
+pragma[nomagic]
+private predicate parameterCand(
+ DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
+) {
+ exists(ParamNodeEx p |
+ Stage4::revFlow(p, _, _, apa, config) and
+ p.isParameterOf(callable, i)
+ )
+}
+
+pragma[nomagic]
+private predicate pathIntoCallable0(
+ PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call,
+ AccessPath ap, Configuration config
+) {
+ exists(AccessPathApprox apa |
+ pathIntoArg(mid, pragma[only_bind_into](i), outercc, call, ap, pragma[only_bind_into](apa),
+ pragma[only_bind_into](config)) and
+ callable = resolveCall(call, outercc) and
+ parameterCand(callable, pragma[only_bind_into](i), pragma[only_bind_into](apa),
+ pragma[only_bind_into](config))
+ )
+}
+
+/**
+ * Holds if data may flow from `mid` to `p` through `call`. The contexts
+ * before and after entering the callable are `outercc` and `innercc`,
+ * respectively.
+ */
+pragma[nomagic]
+private predicate pathIntoCallable(
+ PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc,
+ DataFlowCall call, Configuration config
+) {
+ exists(int i, DataFlowCallable callable, AccessPath ap |
+ pathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
+ p.isParameterOf(callable, i) and
+ (
+ sc = TSummaryCtxSome(p, ap)
+ or
+ not exists(TSummaryCtxSome(p, ap)) and
+ sc = TSummaryCtxNone() and
+ // When the call contexts of source and sink needs to match then there's
+ // never any reason to enter a callable except to find a summary. See also
+ // the comment in `PathNodeMid::isAtSink`.
+ not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
+ )
+ |
+ if recordDataFlowCallSite(call, callable)
+ then innercc = TSpecificCall(call)
+ else innercc = TSomeCall()
+ )
+}
+
+/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */
+pragma[nomagic]
+private predicate paramFlowsThrough(
+ ReturnKindExt kind, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, AccessPathApprox apa,
+ Configuration config
+) {
+ exists(PathNodeMid mid, RetNodeEx ret, int pos |
+ mid.getNodeEx() = ret and
+ kind = ret.getKind() and
+ cc = mid.getCallContext() and
+ sc = mid.getSummaryCtx() and
+ config = mid.getConfiguration() and
+ ap = mid.getAp() and
+ apa = ap.getApprox() and
+ pos = sc.getParameterPos() and
+ // we don't expect a parameter to return stored in itself, unless explicitly allowed
+ (
+ not kind.(ParamUpdateReturnKind).getPosition() = pos
+ or
+ sc.getParamNode().allowParameterReturnInSelf()
+ )
+ )
+}
+
+pragma[nomagic]
+private predicate pathThroughCallable0(
+ DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap,
+ AccessPathApprox apa, Configuration config
+) {
+ exists(CallContext innercc, SummaryCtx sc |
+ pathIntoCallable(mid, _, cc, innercc, sc, call, config) and
+ paramFlowsThrough(kind, innercc, sc, ap, apa, config)
+ )
+}
+
+/**
+ * Holds if data may flow from `mid` through a callable to the node `out`.
+ * The context `cc` is restored to its value prior to entering the callable.
+ */
+pragma[noinline]
+private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) {
+ exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa, Configuration config |
+ pathThroughCallable0(call, mid, kind, cc, ap, apa, config) and
+ out = getAnOutNodeFlow(kind, call, apa, config)
+ )
+}
+
+private module Subpaths {
+ /**
+ * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by
+ * `kind`, `sc`, `apout`, and `innercc`.
+ */
+ pragma[nomagic]
+ private predicate subpaths01(
+ PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ NodeEx out, AccessPath apout
+ ) {
+ exists(Configuration config |
+ pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
+ pathIntoCallable(arg, par, _, innercc, sc, _, config) and
+ paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
+ not arg.isHidden()
+ )
+ }
+
+ /**
+ * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by
+ * `kind`, `sc`, `apout`, and `innercc`.
+ */
+ pragma[nomagic]
+ private predicate subpaths02(
+ PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
+ NodeEx out, AccessPath apout
+ ) {
+ subpaths01(arg, par, sc, innercc, kind, out, apout) and
+ out.asNode() = kind.getAnOutNode(_)
+ }
+
+ pragma[nomagic]
+ private Configuration getPathNodeConf(PathNode n) { result = n.getConfiguration() }
+
+ /**
+ * Holds if `(arg, par, ret, out)` forms a subpath-tuple.
+ */
+ pragma[nomagic]
+ private predicate subpaths03(
+ PathNode arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, AccessPath apout
+ ) {
+ exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode |
+ subpaths02(arg, par, sc, innercc, kind, out, apout) and
+ ret.getNodeEx() = retnode and
+ kind = retnode.getKind() and
+ innercc = ret.getCallContext() and
+ sc = ret.getSummaryCtx() and
+ ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
+ apout = ret.getAp()
+ )
+ }
+
+ private PathNodeImpl localStepToHidden(PathNodeImpl n) {
+ n.getASuccessorImpl() = result and
+ result.isHidden() and
+ exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
+ localFlowBigStep(n1, n2, _, _, _, _) or
+ store(n1, _, n2, _, _) or
+ read(n1, _, n2, _)
+ )
+ }
+
+ /**
+ * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through
+ * a subpath between `par` and `ret` with the connecting edges `arg -> par` and
+ * `ret -> out` is summarized as the edge `arg -> out`.
+ */
+ predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
+ exists(ParamNodeEx p, NodeEx o, AccessPath apout |
+ pragma[only_bind_into](arg).getASuccessor() = par and
+ pragma[only_bind_into](arg).getASuccessor() = out and
+ subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
+ not ret.isHidden() and
+ par.getNodeEx() = p and
+ out.getNodeEx() = o and
+ out.getAp() = apout
+ )
+ }
+
+ /**
+ * Holds if `n` can reach a return node in a summarized subpath.
+ */
+ predicate retReach(PathNode n) {
+ subpaths(_, _, n, _)
+ or
+ exists(PathNode mid |
+ retReach(mid) and
+ n.getASuccessor() = mid and
+ not subpaths(_, mid, _, _)
+ )
+ }
+}
+
+/**
+ * Holds if data can flow (inter-procedurally) from `source` to `sink`.
+ *
+ * Will only have results if `configuration` has non-empty sources and
+ * sinks.
+ */
+private predicate flowsTo(
+ PathNode flowsource, PathNodeSink flowsink, Node source, Node sink, Configuration configuration
+) {
+ flowsource.isSource() and
+ flowsource.getConfiguration() = configuration and
+ flowsource.(PathNodeImpl).getNodeEx().asNode() = source and
+ (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and
+ flowsink.getNodeEx().asNode() = sink
+}
+
+/**
+ * Holds if data can flow (inter-procedurally) from `source` to `sink`.
+ *
+ * Will only have results if `configuration` has non-empty sources and
+ * sinks.
+ */
+predicate flowsTo(Node source, Node sink, Configuration configuration) {
+ flowsTo(_, _, source, sink, configuration)
+}
+
+private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) {
+ fwd = true and
+ nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and
+ fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and
+ conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and
+ tuples = count(PathNode pn)
+ or
+ fwd = false and
+ nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and
+ fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and
+ conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and
+ tuples = count(PathNode pn | reach(pn))
+}
+
+/**
+ * INTERNAL: Only for debugging.
+ *
+ * Calculates per-stage metrics for data flow.
+ */
+predicate stageStats(
+ int n, string stage, int nodes, int fields, int conscand, int tuples, Configuration config
+) {
+ stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config)
+ or
+ stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config)
+ or
+ stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config)
+ or
+ stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config)
+ or
+ stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config)
+ or
+ stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config)
+ or
+ stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config)
+ or
+ stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config)
+ or
+ stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples)
+ or
+ stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples)
+}
+
+private module FlowExploration {
+ private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) {
+ exists(NodeEx node1, NodeEx node2 |
+ jumpStep(node1, node2, config)
+ or
+ additionalJumpStep(node1, node2, config)
+ or
+ // flow into callable
+ viableParamArgEx(_, node2, node1)
+ or
+ // flow out of a callable
+ viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2)
+ |
+ c1 = node1.getEnclosingCallable() and
+ c2 = node2.getEnclosingCallable() and
+ c1 != c2
+ )
+ }
+
+ private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) {
+ exists(Node n | config.isSource(n) and c = getNodeEnclosingCallable(n))
+ or
+ exists(DataFlowCallable mid |
+ interestingCallableSrc(mid, config) and callableStep(mid, c, config)
+ )
+ }
+
+ private predicate interestingCallableSink(DataFlowCallable c, Configuration config) {
+ exists(Node n | config.isSink(n) and c = getNodeEnclosingCallable(n))
+ or
+ exists(DataFlowCallable mid |
+ interestingCallableSink(mid, config) and callableStep(c, mid, config)
+ )
+ }
+
+ private newtype TCallableExt =
+ TCallable(DataFlowCallable c, Configuration config) {
+ interestingCallableSrc(c, config) or
+ interestingCallableSink(c, config)
+ } or
+ TCallableSrc() or
+ TCallableSink()
+
+ private predicate callableExtSrc(TCallableSrc src) { any() }
+
+ private predicate callableExtSink(TCallableSink sink) { any() }
+
+ private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) {
+ exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config |
+ callableStep(c1, c2, config) and
+ ce1 = TCallable(c1, pragma[only_bind_into](config)) and
+ ce2 = TCallable(c2, pragma[only_bind_into](config))
+ )
+ or
+ exists(Node n, Configuration config |
+ ce1 = TCallableSrc() and
+ config.isSource(n) and
+ ce2 = TCallable(getNodeEnclosingCallable(n), config)
+ )
+ or
+ exists(Node n, Configuration config |
+ ce2 = TCallableSink() and
+ config.isSink(n) and
+ ce1 = TCallable(getNodeEnclosingCallable(n), config)
+ )
+ }
+
+ private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) {
+ callableExtStepFwd(ce2, ce1)
+ }
+
+ private int distSrcExt(TCallableExt c) =
+ shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result)
+
+ private int distSinkExt(TCallableExt c) =
+ shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result)
+
+ private int distSrc(DataFlowCallable c, Configuration config) {
+ result = distSrcExt(TCallable(c, config)) - 1
+ }
+
+ private int distSink(DataFlowCallable c, Configuration config) {
+ result = distSinkExt(TCallable(c, config)) - 1
+ }
+
+ private newtype TPartialAccessPath =
+ TPartialNil(DataFlowType t) or
+ TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] }
+
+ /**
+ * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first
+ * element of the list and its length are tracked. If data flows from a source to
+ * a given node with a given `AccessPath`, this indicates the sequence of
+ * dereference operations needed to get from the value in the node to the
+ * tracked object. The final type indicates the type of the tracked object.
+ */
+ private class PartialAccessPath extends TPartialAccessPath {
+ abstract string toString();
+
+ TypedContent getHead() { this = TPartialCons(result, _) }
+
+ int len() {
+ this = TPartialNil(_) and result = 0
+ or
+ this = TPartialCons(_, result)
+ }
+
+ DataFlowType getType() {
+ this = TPartialNil(result)
+ or
+ exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType())
+ }
+ }
+
+ private class PartialAccessPathNil extends PartialAccessPath, TPartialNil {
+ override string toString() {
+ exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t)))
+ }
+ }
+
+ private class PartialAccessPathCons extends PartialAccessPath, TPartialCons {
+ override string toString() {
+ exists(TypedContent tc, int len | this = TPartialCons(tc, len) |
+ if len = 1
+ then result = "[" + tc.toString() + "]"
+ else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]"
+ )
+ }
+ }
+
+ private newtype TRevPartialAccessPath =
+ TRevPartialNil() or
+ TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] }
+
+ /**
+ * Conceptually a list of `Content`s, but only the first
+ * element of the list and its length are tracked.
+ */
+ private class RevPartialAccessPath extends TRevPartialAccessPath {
+ abstract string toString();
+
+ Content getHead() { this = TRevPartialCons(result, _) }
+
+ int len() {
+ this = TRevPartialNil() and result = 0
+ or
+ this = TRevPartialCons(_, result)
+ }
+ }
+
+ private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil {
+ override string toString() { result = "" }
+ }
+
+ private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons {
+ override string toString() {
+ exists(Content c, int len | this = TRevPartialCons(c, len) |
+ if len = 1
+ then result = "[" + c.toString() + "]"
+ else result = "[" + c.toString() + ", ... (" + len.toString() + ")]"
+ )
+ }
+ }
+
+ private newtype TSummaryCtx1 =
+ TSummaryCtx1None() or
+ TSummaryCtx1Param(ParamNodeEx p)
+
+ private newtype TSummaryCtx2 =
+ TSummaryCtx2None() or
+ TSummaryCtx2Some(PartialAccessPath ap)
+
+ private newtype TRevSummaryCtx1 =
+ TRevSummaryCtx1None() or
+ TRevSummaryCtx1Some(ReturnPosition pos)
+
+ private newtype TRevSummaryCtx2 =
+ TRevSummaryCtx2None() or
+ TRevSummaryCtx2Some(RevPartialAccessPath ap)
+
+ private newtype TPartialPathNode =
+ TPartialPathNodeFwd(
+ NodeEx node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap,
+ Configuration config
+ ) {
+ sourceNode(node, config) and
+ cc instanceof CallContextAny and
+ sc1 = TSummaryCtx1None() and
+ sc2 = TSummaryCtx2None() and
+ ap = TPartialNil(node.getDataFlowType()) and
+ not fullBarrier(node, config) and
+ exists(config.explorationLimit())
+ or
+ partialPathNodeMk0(node, cc, sc1, sc2, ap, config) and
+ distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit()
+ } or
+ TPartialPathNodeRev(
+ NodeEx node, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, RevPartialAccessPath ap,
+ Configuration config
+ ) {
+ sinkNode(node, config) and
+ sc1 = TRevSummaryCtx1None() and
+ sc2 = TRevSummaryCtx2None() and
+ ap = TRevPartialNil() and
+ not fullBarrier(node, config) and
+ exists(config.explorationLimit())
+ or
+ exists(PartialPathNodeRev mid |
+ revPartialPathStep(mid, node, sc1, sc2, ap, config) and
+ not clearsContentCached(node.asNode(), ap.getHead()) and
+ not fullBarrier(node, config) and
+ distSink(node.getEnclosingCallable(), config) <= config.explorationLimit()
+ )
+ }
+
+ pragma[nomagic]
+ private predicate partialPathNodeMk0(
+ NodeEx node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap,
+ Configuration config
+ ) {
+ exists(PartialPathNodeFwd mid |
+ partialPathStep(mid, node, cc, sc1, sc2, ap, config) and
+ not fullBarrier(node, config) and
+ not clearsContentCached(node.asNode(), ap.getHead().getContent()) and
+ if node.asNode() instanceof CastingNode
+ then compatibleTypes(node.getDataFlowType(), ap.getType())
+ else any()
+ )
+ }
+
+ /**
+ * A `Node` augmented with a call context, an access path, and a configuration.
+ */
+ class PartialPathNode extends TPartialPathNode {
+ /** Gets a textual representation of this element. */
+ string toString() { result = this.getNodeEx().toString() + this.ppAp() }
+
+ /**
+ * Gets a textual representation of this element, including a textual
+ * representation of the call context.
+ */
+ string toStringWithContext() {
+ result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx()
+ }
+
+ /**
+ * Holds if this element is at the specified location.
+ * The location spans column `startcolumn` of line `startline` to
+ * column `endcolumn` of line `endline` in file `filepath`.
+ * For more information, see
+ * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
+ */
+ predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
+ this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ }
+
+ /** Gets the underlying `Node`. */
+ final Node getNode() { this.getNodeEx().projectToNode() = result }
+
+ private NodeEx getNodeEx() {
+ result = this.(PartialPathNodeFwd).getNodeEx() or
+ result = this.(PartialPathNodeRev).getNodeEx()
+ }
+
+ /** Gets the associated configuration. */
+ Configuration getConfiguration() { none() }
+
+ /** Gets a successor of this node, if any. */
+ PartialPathNode getASuccessor() { none() }
+
+ /**
+ * Gets the approximate distance to the nearest source measured in number
+ * of interprocedural steps.
+ */
+ int getSourceDistance() {
+ result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration())
+ }
+
+ /**
+ * Gets the approximate distance to the nearest sink measured in number
+ * of interprocedural steps.
+ */
+ int getSinkDistance() {
+ result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration())
+ }
+
+ private string ppAp() {
+ exists(string s |
+ s = this.(PartialPathNodeFwd).getAp().toString() or
+ s = this.(PartialPathNodeRev).getAp().toString()
+ |
+ if s = "" then result = "" else result = " " + s
+ )
+ }
+
+ private string ppCtx() {
+ result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">"
+ }
+
+ /** Holds if this is a source in a forward-flow path. */
+ predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() }
+
+ /** Holds if this is a sink in a reverse-flow path. */
+ predicate isRevSink() { this.(PartialPathNodeRev).isSink() }
+ }
+
+ /**
+ * Provides the query predicates needed to include a graph in a path-problem query.
+ */
+ module PartialPathGraph {
+ /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
+ query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b }
+ }
+
+ private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd {
+ NodeEx node;
+ CallContext cc;
+ TSummaryCtx1 sc1;
+ TSummaryCtx2 sc2;
+ PartialAccessPath ap;
+ Configuration config;
+
+ PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, cc, sc1, sc2, ap, config) }
+
+ NodeEx getNodeEx() { result = node }
+
+ CallContext getCallContext() { result = cc }
+
+ TSummaryCtx1 getSummaryCtx1() { result = sc1 }
+
+ TSummaryCtx2 getSummaryCtx2() { result = sc2 }
+
+ PartialAccessPath getAp() { result = ap }
+
+ override Configuration getConfiguration() { result = config }
+
+ override PartialPathNodeFwd getASuccessor() {
+ partialPathStep(this, result.getNodeEx(), result.getCallContext(), result.getSummaryCtx1(),
+ result.getSummaryCtx2(), result.getAp(), result.getConfiguration())
+ }
+
+ predicate isSource() {
+ sourceNode(node, config) and
+ cc instanceof CallContextAny and
+ sc1 = TSummaryCtx1None() and
+ sc2 = TSummaryCtx2None() and
+ ap instanceof TPartialNil
+ }
+ }
+
+ private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev {
+ NodeEx node;
+ TRevSummaryCtx1 sc1;
+ TRevSummaryCtx2 sc2;
+ RevPartialAccessPath ap;
+ Configuration config;
+
+ PartialPathNodeRev() { this = TPartialPathNodeRev(node, sc1, sc2, ap, config) }
+
+ NodeEx getNodeEx() { result = node }
+
+ TRevSummaryCtx1 getSummaryCtx1() { result = sc1 }
+
+ TRevSummaryCtx2 getSummaryCtx2() { result = sc2 }
+
+ RevPartialAccessPath getAp() { result = ap }
+
+ override Configuration getConfiguration() { result = config }
+
+ override PartialPathNodeRev getASuccessor() {
+ revPartialPathStep(result, this.getNodeEx(), this.getSummaryCtx1(), this.getSummaryCtx2(),
+ this.getAp(), this.getConfiguration())
+ }
+
+ predicate isSink() {
+ sinkNode(node, config) and
+ sc1 = TRevSummaryCtx1None() and
+ sc2 = TRevSummaryCtx2None() and
+ ap = TRevPartialNil()
+ }
+ }
+
+ private predicate partialPathStep(
+ PartialPathNodeFwd mid, NodeEx node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2,
+ PartialAccessPath ap, Configuration config
+ ) {
+ not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and
+ (
+ localFlowStep(mid.getNodeEx(), node, config) and
+ cc = mid.getCallContext() and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2() and
+ ap = mid.getAp() and
+ config = mid.getConfiguration()
+ or
+ additionalLocalFlowStep(mid.getNodeEx(), node, config) and
+ cc = mid.getCallContext() and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2() and
+ mid.getAp() instanceof PartialAccessPathNil and
+ ap = TPartialNil(node.getDataFlowType()) and
+ config = mid.getConfiguration()
+ )
+ or
+ jumpStep(mid.getNodeEx(), node, config) and
+ cc instanceof CallContextAny and
+ sc1 = TSummaryCtx1None() and
+ sc2 = TSummaryCtx2None() and
+ ap = mid.getAp() and
+ config = mid.getConfiguration()
+ or
+ additionalJumpStep(mid.getNodeEx(), node, config) and
+ cc instanceof CallContextAny and
+ sc1 = TSummaryCtx1None() and
+ sc2 = TSummaryCtx2None() and
+ mid.getAp() instanceof PartialAccessPathNil and
+ ap = TPartialNil(node.getDataFlowType()) and
+ config = mid.getConfiguration()
+ or
+ partialPathStoreStep(mid, _, _, node, ap) and
+ cc = mid.getCallContext() and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2() and
+ config = mid.getConfiguration()
+ or
+ exists(PartialAccessPath ap0, TypedContent tc |
+ partialPathReadStep(mid, ap0, tc, node, cc, config) and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2() and
+ apConsFwd(ap, tc, ap0, config)
+ )
+ or
+ partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config)
+ or
+ partialPathOutOfCallable(mid, node, cc, ap, config) and
+ sc1 = TSummaryCtx1None() and
+ sc2 = TSummaryCtx2None()
+ or
+ partialPathThroughCallable(mid, node, cc, ap, config) and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2()
+ }
+
+ bindingset[result, i]
+ private int unbindInt(int i) { i <= result and i >= result }
+
+ pragma[inline]
+ private predicate partialPathStoreStep(
+ PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node,
+ PartialAccessPath ap2
+ ) {
+ exists(NodeEx midNode, DataFlowType contentType |
+ midNode = mid.getNodeEx() and
+ ap1 = mid.getAp() and
+ store(midNode, tc, node, contentType, mid.getConfiguration()) and
+ ap2.getHead() = tc and
+ ap2.len() = unbindInt(ap1.len() + 1) and
+ compatibleTypes(ap1.getType(), contentType)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate apConsFwd(
+ PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config
+ ) {
+ exists(PartialPathNodeFwd mid |
+ partialPathStoreStep(mid, ap1, tc, _, ap2) and
+ config = mid.getConfiguration()
+ )
+ }
+
+ pragma[nomagic]
+ private predicate partialPathReadStep(
+ PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc,
+ Configuration config
+ ) {
+ exists(NodeEx midNode |
+ midNode = mid.getNodeEx() and
+ ap = mid.getAp() and
+ read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and
+ ap.getHead() = tc and
+ pragma[only_bind_into](config) = mid.getConfiguration() and
+ cc = mid.getCallContext()
+ )
+ }
+
+ private predicate partialPathOutOfCallable0(
+ PartialPathNodeFwd mid, ReturnPosition pos, CallContext innercc, PartialAccessPath ap,
+ Configuration config
+ ) {
+ pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and
+ innercc = mid.getCallContext() and
+ innercc instanceof CallContextNoCall and
+ ap = mid.getAp() and
+ config = mid.getConfiguration()
+ }
+
+ pragma[nomagic]
+ private predicate partialPathOutOfCallable1(
+ PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, CallContext cc,
+ PartialAccessPath ap, Configuration config
+ ) {
+ exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc |
+ partialPathOutOfCallable0(mid, pos, innercc, ap, config) and
+ c = pos.getCallable() and
+ kind = pos.getKind() and
+ resolveReturn(innercc, c, call)
+ |
+ if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext()
+ )
+ }
+
+ private predicate partialPathOutOfCallable(
+ PartialPathNodeFwd mid, NodeEx out, CallContext cc, PartialAccessPath ap, Configuration config
+ ) {
+ exists(ReturnKindExt kind, DataFlowCall call |
+ partialPathOutOfCallable1(mid, call, kind, cc, ap, config)
+ |
+ out.asNode() = kind.getAnOutNode(call)
+ )
+ }
+
+ pragma[noinline]
+ private predicate partialPathIntoArg(
+ PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap,
+ Configuration config
+ ) {
+ exists(ArgNode arg |
+ arg = mid.getNodeEx().asNode() and
+ cc = mid.getCallContext() and
+ arg.argumentOf(call, i) and
+ ap = mid.getAp() and
+ config = mid.getConfiguration()
+ )
+ }
+
+ pragma[nomagic]
+ private predicate partialPathIntoCallable0(
+ PartialPathNodeFwd mid, DataFlowCallable callable, int i, CallContext outercc,
+ DataFlowCall call, PartialAccessPath ap, Configuration config
+ ) {
+ partialPathIntoArg(mid, i, outercc, call, ap, config) and
+ callable = resolveCall(call, outercc)
+ }
+
+ private predicate partialPathIntoCallable(
+ PartialPathNodeFwd mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc,
+ TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap,
+ Configuration config
+ ) {
+ exists(int i, DataFlowCallable callable |
+ partialPathIntoCallable0(mid, callable, i, outercc, call, ap, config) and
+ p.isParameterOf(callable, i) and
+ sc1 = TSummaryCtx1Param(p) and
+ sc2 = TSummaryCtx2Some(ap)
+ |
+ if recordDataFlowCallSite(call, callable)
+ then innercc = TSpecificCall(call)
+ else innercc = TSomeCall()
+ )
+ }
+
+ pragma[nomagic]
+ private predicate paramFlowsThroughInPartialPath(
+ ReturnKindExt kind, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2,
+ PartialAccessPath ap, Configuration config
+ ) {
+ exists(PartialPathNodeFwd mid, RetNodeEx ret |
+ mid.getNodeEx() = ret and
+ kind = ret.getKind() and
+ cc = mid.getCallContext() and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2() and
+ config = mid.getConfiguration() and
+ ap = mid.getAp()
+ )
+ }
+
+ pragma[noinline]
+ private predicate partialPathThroughCallable0(
+ DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc,
+ PartialAccessPath ap, Configuration config
+ ) {
+ exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 |
+ partialPathIntoCallable(mid, _, cc, innercc, sc1, sc2, call, _, config) and
+ paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config)
+ )
+ }
+
+ private predicate partialPathThroughCallable(
+ PartialPathNodeFwd mid, NodeEx out, CallContext cc, PartialAccessPath ap, Configuration config
+ ) {
+ exists(DataFlowCall call, ReturnKindExt kind |
+ partialPathThroughCallable0(call, mid, kind, cc, ap, config) and
+ out.asNode() = kind.getAnOutNode(call)
+ )
+ }
+
+ private predicate revPartialPathStep(
+ PartialPathNodeRev mid, NodeEx node, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2,
+ RevPartialAccessPath ap, Configuration config
+ ) {
+ localFlowStep(node, mid.getNodeEx(), config) and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2() and
+ ap = mid.getAp() and
+ config = mid.getConfiguration()
+ or
+ additionalLocalFlowStep(node, mid.getNodeEx(), config) and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2() and
+ mid.getAp() instanceof RevPartialAccessPathNil and
+ ap = TRevPartialNil() and
+ config = mid.getConfiguration()
+ or
+ jumpStep(node, mid.getNodeEx(), config) and
+ sc1 = TRevSummaryCtx1None() and
+ sc2 = TRevSummaryCtx2None() and
+ ap = mid.getAp() and
+ config = mid.getConfiguration()
+ or
+ additionalJumpStep(node, mid.getNodeEx(), config) and
+ sc1 = TRevSummaryCtx1None() and
+ sc2 = TRevSummaryCtx2None() and
+ mid.getAp() instanceof RevPartialAccessPathNil and
+ ap = TRevPartialNil() and
+ config = mid.getConfiguration()
+ or
+ revPartialPathReadStep(mid, _, _, node, ap) and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2() and
+ config = mid.getConfiguration()
+ or
+ exists(RevPartialAccessPath ap0, Content c |
+ revPartialPathStoreStep(mid, ap0, c, node, config) and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2() and
+ apConsRev(ap, c, ap0, config)
+ )
+ or
+ exists(ParamNodeEx p |
+ mid.getNodeEx() = p and
+ viableParamArgEx(_, p, node) and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2() and
+ sc1 = TRevSummaryCtx1None() and
+ sc2 = TRevSummaryCtx2None() and
+ ap = mid.getAp() and
+ config = mid.getConfiguration()
+ )
+ or
+ exists(ReturnPosition pos |
+ revPartialPathIntoReturn(mid, pos, sc1, sc2, _, ap, config) and
+ pos = getReturnPosition(node.asNode())
+ )
+ or
+ revPartialPathThroughCallable(mid, node, ap, config) and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2()
+ }
+
+ pragma[inline]
+ private predicate revPartialPathReadStep(
+ PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node,
+ RevPartialAccessPath ap2
+ ) {
+ exists(NodeEx midNode |
+ midNode = mid.getNodeEx() and
+ ap1 = mid.getAp() and
+ read(node, c, midNode, mid.getConfiguration()) and
+ ap2.getHead() = c and
+ ap2.len() = unbindInt(ap1.len() + 1)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate apConsRev(
+ RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config
+ ) {
+ exists(PartialPathNodeRev mid |
+ revPartialPathReadStep(mid, ap1, c, _, ap2) and
+ config = mid.getConfiguration()
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revPartialPathStoreStep(
+ PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config
+ ) {
+ exists(NodeEx midNode, TypedContent tc |
+ midNode = mid.getNodeEx() and
+ ap = mid.getAp() and
+ store(node, tc, midNode, _, config) and
+ ap.getHead() = c and
+ config = mid.getConfiguration() and
+ tc.getContent() = c
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revPartialPathIntoReturn(
+ PartialPathNodeRev mid, ReturnPosition pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2,
+ DataFlowCall call, RevPartialAccessPath ap, Configuration config
+ ) {
+ exists(NodeEx out |
+ mid.getNodeEx() = out and
+ viableReturnPosOutEx(call, pos, out) and
+ sc1 = TRevSummaryCtx1Some(pos) and
+ sc2 = TRevSummaryCtx2Some(ap) and
+ ap = mid.getAp() and
+ config = mid.getConfiguration()
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revPartialPathFlowsThrough(
+ int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap,
+ Configuration config
+ ) {
+ exists(PartialPathNodeRev mid, ParamNodeEx p |
+ mid.getNodeEx() = p and
+ p.getPosition() = pos and
+ sc1 = mid.getSummaryCtx1() and
+ sc2 = mid.getSummaryCtx2() and
+ ap = mid.getAp() and
+ config = mid.getConfiguration()
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revPartialPathThroughCallable0(
+ DataFlowCall call, PartialPathNodeRev mid, int pos, RevPartialAccessPath ap,
+ Configuration config
+ ) {
+ exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2 |
+ revPartialPathIntoReturn(mid, _, sc1, sc2, call, _, config) and
+ revPartialPathFlowsThrough(pos, sc1, sc2, ap, config)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate revPartialPathThroughCallable(
+ PartialPathNodeRev mid, ArgNodeEx node, RevPartialAccessPath ap, Configuration config
+ ) {
+ exists(DataFlowCall call, int pos |
+ revPartialPathThroughCallable0(call, mid, pos, ap, config) and
+ node.asNode().(ArgNode).argumentOf(call, pos)
+ )
+ }
+}
+
+import FlowExploration
+
+private predicate partialFlow(
+ PartialPathNode source, PartialPathNode node, Configuration configuration
+) {
+ source.getConfiguration() = configuration and
+ source.isFwdSource() and
+ node = source.getASuccessor+()
+}
+
+private predicate revPartialFlow(
+ PartialPathNode node, PartialPathNode sink, Configuration configuration
+) {
+ sink.getConfiguration() = configuration and
+ sink.isRevSink() and
+ node.getASuccessor+() = sink
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplCommon.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplCommon.qll
new file mode 100644
index 00000000000..c28ceabb438
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplCommon.qll
@@ -0,0 +1,1331 @@
+private import DataFlowImplSpecific::Private
+private import DataFlowImplSpecific::Public
+import Cached
+
+module DataFlowImplCommonPublic {
+ private newtype TFlowFeature =
+ TFeatureHasSourceCallContext() or
+ TFeatureHasSinkCallContext() or
+ TFeatureEqualSourceSinkCallContext()
+
+ /** A flow configuration feature for use in `Configuration::getAFeature()`. */
+ class FlowFeature extends TFlowFeature {
+ string toString() { none() }
+ }
+
+ /**
+ * A flow configuration feature that implies that sources have some existing
+ * call context.
+ */
+ class FeatureHasSourceCallContext extends FlowFeature, TFeatureHasSourceCallContext {
+ override string toString() { result = "FeatureHasSourceCallContext" }
+ }
+
+ /**
+ * A flow configuration feature that implies that sinks have some existing
+ * call context.
+ */
+ class FeatureHasSinkCallContext extends FlowFeature, TFeatureHasSinkCallContext {
+ override string toString() { result = "FeatureHasSinkCallContext" }
+ }
+
+ /**
+ * A flow configuration feature that implies that source-sink pairs have some
+ * shared existing call context.
+ */
+ class FeatureEqualSourceSinkCallContext extends FlowFeature, TFeatureEqualSourceSinkCallContext {
+ override string toString() { result = "FeatureEqualSourceSinkCallContext" }
+ }
+}
+
+/**
+ * The cost limits for the `AccessPathFront` to `AccessPathApprox` expansion.
+ *
+ * `apLimit` bounds the acceptable fan-out, and `tupleLimit` bounds the
+ * estimated per-`AccessPathFront` tuple cost. Access paths exceeding both of
+ * these limits are represented with lower precision during pruning.
+ */
+predicate accessPathApproxCostLimits(int apLimit, int tupleLimit) {
+ apLimit = 10 and
+ tupleLimit = 10000
+}
+
+/**
+ * The cost limits for the `AccessPathApprox` to `AccessPath` expansion.
+ *
+ * `apLimit` bounds the acceptable fan-out, and `tupleLimit` bounds the
+ * estimated per-`AccessPathApprox` tuple cost. Access paths exceeding both of
+ * these limits are represented with lower precision.
+ */
+predicate accessPathCostLimits(int apLimit, int tupleLimit) {
+ apLimit = 5 and
+ tupleLimit = 1000
+}
+
+/**
+ * Provides a simple data-flow analysis for resolving lambda calls. The analysis
+ * currently excludes read-steps, store-steps, and flow-through.
+ *
+ * The analysis uses non-linear recursion: When computing a flow path in or out
+ * of a call, we use the results of the analysis recursively to resolve lambda
+ * calls. For this reason, we cannot reuse the code from `DataFlowImpl.qll` directly.
+ */
+private module LambdaFlow {
+ private predicate viableParamNonLambda(DataFlowCall call, int i, ParamNode p) {
+ p.isParameterOf(viableCallable(call), i)
+ }
+
+ private predicate viableParamLambda(DataFlowCall call, int i, ParamNode p) {
+ p.isParameterOf(viableCallableLambda(call, _), i)
+ }
+
+ private predicate viableParamArgNonLambda(DataFlowCall call, ParamNode p, ArgNode arg) {
+ exists(int i |
+ viableParamNonLambda(call, i, p) and
+ arg.argumentOf(call, i)
+ )
+ }
+
+ private predicate viableParamArgLambda(DataFlowCall call, ParamNode p, ArgNode arg) {
+ exists(int i |
+ viableParamLambda(call, i, p) and
+ arg.argumentOf(call, i)
+ )
+ }
+
+ private newtype TReturnPositionSimple =
+ TReturnPositionSimple0(DataFlowCallable c, ReturnKind kind) {
+ exists(ReturnNode ret |
+ c = getNodeEnclosingCallable(ret) and
+ kind = ret.getKind()
+ )
+ }
+
+ pragma[noinline]
+ private TReturnPositionSimple getReturnPositionSimple(ReturnNode ret, ReturnKind kind) {
+ result = TReturnPositionSimple0(getNodeEnclosingCallable(ret), kind)
+ }
+
+ pragma[nomagic]
+ private TReturnPositionSimple viableReturnPosNonLambda(DataFlowCall call, ReturnKind kind) {
+ result = TReturnPositionSimple0(viableCallable(call), kind)
+ }
+
+ pragma[nomagic]
+ private TReturnPositionSimple viableReturnPosLambda(
+ DataFlowCall call, DataFlowCallOption lastCall, ReturnKind kind
+ ) {
+ result = TReturnPositionSimple0(viableCallableLambda(call, lastCall), kind)
+ }
+
+ private predicate viableReturnPosOutNonLambda(
+ DataFlowCall call, TReturnPositionSimple pos, OutNode out
+ ) {
+ exists(ReturnKind kind |
+ pos = viableReturnPosNonLambda(call, kind) and
+ out = getAnOutNode(call, kind)
+ )
+ }
+
+ private predicate viableReturnPosOutLambda(
+ DataFlowCall call, DataFlowCallOption lastCall, TReturnPositionSimple pos, OutNode out
+ ) {
+ exists(ReturnKind kind |
+ pos = viableReturnPosLambda(call, lastCall, kind) and
+ out = getAnOutNode(call, kind)
+ )
+ }
+
+ /**
+ * Holds if data can flow (inter-procedurally) from `node` (of type `t`) to
+ * the lambda call `lambdaCall`.
+ *
+ * The parameter `toReturn` indicates whether the path from `node` to
+ * `lambdaCall` goes through a return, and `toJump` whether the path goes
+ * through a jump step.
+ *
+ * The call context `lastCall` records the last call on the path from `node`
+ * to `lambdaCall`, if any. That is, `lastCall` is able to target the enclosing
+ * callable of `lambdaCall`.
+ */
+ pragma[nomagic]
+ predicate revLambdaFlow(
+ DataFlowCall lambdaCall, LambdaCallKind kind, Node node, DataFlowType t, boolean toReturn,
+ boolean toJump, DataFlowCallOption lastCall
+ ) {
+ revLambdaFlow0(lambdaCall, kind, node, t, toReturn, toJump, lastCall) and
+ if castNode(node) or node instanceof ArgNode or node instanceof ReturnNode
+ then compatibleTypes(t, getNodeDataFlowType(node))
+ else any()
+ }
+
+ pragma[nomagic]
+ predicate revLambdaFlow0(
+ DataFlowCall lambdaCall, LambdaCallKind kind, Node node, DataFlowType t, boolean toReturn,
+ boolean toJump, DataFlowCallOption lastCall
+ ) {
+ lambdaCall(lambdaCall, kind, node) and
+ t = getNodeDataFlowType(node) and
+ toReturn = false and
+ toJump = false and
+ lastCall = TDataFlowCallNone()
+ or
+ // local flow
+ exists(Node mid, DataFlowType t0 |
+ revLambdaFlow(lambdaCall, kind, mid, t0, toReturn, toJump, lastCall)
+ |
+ simpleLocalFlowStep(node, mid) and
+ t = t0
+ or
+ exists(boolean preservesValue |
+ additionalLambdaFlowStep(node, mid, preservesValue) and
+ getNodeEnclosingCallable(node) = getNodeEnclosingCallable(mid)
+ |
+ preservesValue = false and
+ t = getNodeDataFlowType(node)
+ or
+ preservesValue = true and
+ t = t0
+ )
+ )
+ or
+ // jump step
+ exists(Node mid, DataFlowType t0 |
+ revLambdaFlow(lambdaCall, kind, mid, t0, _, _, _) and
+ toReturn = false and
+ toJump = true and
+ lastCall = TDataFlowCallNone()
+ |
+ jumpStepCached(node, mid) and
+ t = t0
+ or
+ exists(boolean preservesValue |
+ additionalLambdaFlowStep(node, mid, preservesValue) and
+ getNodeEnclosingCallable(node) != getNodeEnclosingCallable(mid)
+ |
+ preservesValue = false and
+ t = getNodeDataFlowType(node)
+ or
+ preservesValue = true and
+ t = t0
+ )
+ )
+ or
+ // flow into a callable
+ exists(ParamNode p, DataFlowCallOption lastCall0, DataFlowCall call |
+ revLambdaFlowIn(lambdaCall, kind, p, t, toJump, lastCall0) and
+ (
+ if lastCall0 = TDataFlowCallNone() and toJump = false
+ then lastCall = TDataFlowCallSome(call)
+ else lastCall = lastCall0
+ ) and
+ toReturn = false
+ |
+ viableParamArgNonLambda(call, p, node)
+ or
+ viableParamArgLambda(call, p, node) // non-linear recursion
+ )
+ or
+ // flow out of a callable
+ exists(TReturnPositionSimple pos |
+ revLambdaFlowOut(lambdaCall, kind, pos, t, toJump, lastCall) and
+ getReturnPositionSimple(node, node.(ReturnNode).getKind()) = pos and
+ toReturn = true
+ )
+ }
+
+ pragma[nomagic]
+ predicate revLambdaFlowOutLambdaCall(
+ DataFlowCall lambdaCall, LambdaCallKind kind, OutNode out, DataFlowType t, boolean toJump,
+ DataFlowCall call, DataFlowCallOption lastCall
+ ) {
+ revLambdaFlow(lambdaCall, kind, out, t, _, toJump, lastCall) and
+ exists(ReturnKindExt rk |
+ out = rk.getAnOutNode(call) and
+ lambdaCall(call, _, _)
+ )
+ }
+
+ pragma[nomagic]
+ predicate revLambdaFlowOut(
+ DataFlowCall lambdaCall, LambdaCallKind kind, TReturnPositionSimple pos, DataFlowType t,
+ boolean toJump, DataFlowCallOption lastCall
+ ) {
+ exists(DataFlowCall call, OutNode out |
+ revLambdaFlow(lambdaCall, kind, out, t, _, toJump, lastCall) and
+ viableReturnPosOutNonLambda(call, pos, out)
+ or
+ // non-linear recursion
+ revLambdaFlowOutLambdaCall(lambdaCall, kind, out, t, toJump, call, lastCall) and
+ viableReturnPosOutLambda(call, _, pos, out)
+ )
+ }
+
+ pragma[nomagic]
+ predicate revLambdaFlowIn(
+ DataFlowCall lambdaCall, LambdaCallKind kind, ParamNode p, DataFlowType t, boolean toJump,
+ DataFlowCallOption lastCall
+ ) {
+ revLambdaFlow(lambdaCall, kind, p, t, false, toJump, lastCall)
+ }
+}
+
+private DataFlowCallable viableCallableExt(DataFlowCall call) {
+ result = viableCallable(call)
+ or
+ result = viableCallableLambda(call, _)
+}
+
+cached
+private module Cached {
+ /**
+ * If needed, call this predicate from `DataFlowImplSpecific.qll` in order to
+ * force a stage-dependency on the `DataFlowImplCommon.qll` stage and therby
+ * collapsing the two stages.
+ */
+ cached
+ predicate forceCachingInSameStage() { any() }
+
+ cached
+ predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = nodeGetEnclosingCallable(n) }
+
+ cached
+ predicate callEnclosingCallable(DataFlowCall call, DataFlowCallable c) {
+ c = call.getEnclosingCallable()
+ }
+
+ cached
+ predicate nodeDataFlowType(Node n, DataFlowType t) { t = getNodeType(n) }
+
+ cached
+ predicate jumpStepCached(Node node1, Node node2) { jumpStep(node1, node2) }
+
+ cached
+ predicate clearsContentCached(Node n, Content c) { clearsContent(n, c) }
+
+ cached
+ predicate isUnreachableInCallCached(Node n, DataFlowCall call) { isUnreachableInCall(n, call) }
+
+ cached
+ predicate outNodeExt(Node n) {
+ n instanceof OutNode
+ or
+ n.(PostUpdateNode).getPreUpdateNode() instanceof ArgNode
+ }
+
+ cached
+ predicate hiddenNode(Node n) { nodeIsHidden(n) }
+
+ cached
+ OutNodeExt getAnOutNodeExt(DataFlowCall call, ReturnKindExt k) {
+ result = getAnOutNode(call, k.(ValueReturnKind).getKind())
+ or
+ exists(ArgNode arg |
+ result.(PostUpdateNode).getPreUpdateNode() = arg and
+ arg.argumentOf(call, k.(ParamUpdateReturnKind).getPosition())
+ )
+ }
+
+ cached
+ predicate returnNodeExt(Node n, ReturnKindExt k) {
+ k = TValueReturn(n.(ReturnNode).getKind())
+ or
+ exists(ParamNode p, int pos |
+ parameterValueFlowsToPreUpdate(p, n) and
+ p.isParameterOf(_, pos) and
+ k = TParamUpdate(pos)
+ )
+ }
+
+ cached
+ predicate castNode(Node n) { n instanceof CastNode }
+
+ cached
+ predicate castingNode(Node n) {
+ castNode(n) or
+ n instanceof ParamNode or
+ n instanceof OutNodeExt or
+ // For reads, `x.f`, we want to check that the tracked type after the read (which
+ // is obtained by popping the head of the access path stack) is compatible with
+ // the type of `x.f`.
+ read(_, _, n)
+ }
+
+ cached
+ predicate parameterNode(Node p, DataFlowCallable c, int pos) { isParameterNode(p, c, pos) }
+
+ cached
+ predicate argumentNode(Node n, DataFlowCall call, int pos) {
+ n.(ArgumentNode).argumentOf(call, pos)
+ }
+
+ /**
+ * Gets a viable target for the lambda call `call`.
+ *
+ * `lastCall` records the call required to reach `call` in order for the result
+ * to be a viable target, if any.
+ */
+ cached
+ DataFlowCallable viableCallableLambda(DataFlowCall call, DataFlowCallOption lastCall) {
+ exists(Node creation, LambdaCallKind kind |
+ LambdaFlow::revLambdaFlow(call, kind, creation, _, _, _, lastCall) and
+ lambdaCreation(creation, kind, result)
+ )
+ }
+
+ /**
+ * Holds if `p` is the `i`th parameter of a viable dispatch target of `call`.
+ * The instance parameter is considered to have index `-1`.
+ */
+ pragma[nomagic]
+ private predicate viableParam(DataFlowCall call, int i, ParamNode p) {
+ p.isParameterOf(viableCallableExt(call), i)
+ }
+
+ /**
+ * Holds if `arg` is a possible argument to `p` in `call`, taking virtual
+ * dispatch into account.
+ */
+ cached
+ predicate viableParamArg(DataFlowCall call, ParamNode p, ArgNode arg) {
+ exists(int i |
+ viableParam(call, i, p) and
+ arg.argumentOf(call, i) and
+ compatibleTypes(getNodeDataFlowType(arg), getNodeDataFlowType(p))
+ )
+ }
+
+ pragma[nomagic]
+ private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) {
+ viableCallableExt(call) = result.getCallable() and
+ kind = result.getKind()
+ }
+
+ /**
+ * Holds if a value at return position `pos` can be returned to `out` via `call`,
+ * taking virtual dispatch into account.
+ */
+ cached
+ predicate viableReturnPosOut(DataFlowCall call, ReturnPosition pos, Node out) {
+ exists(ReturnKindExt kind |
+ pos = viableReturnPos(call, kind) and
+ out = kind.getAnOutNode(call)
+ )
+ }
+
+ /** Provides predicates for calculating flow-through summaries. */
+ private module FlowThrough {
+ /**
+ * The first flow-through approximation:
+ *
+ * - Input access paths are abstracted with a Boolean parameter
+ * that indicates (non-)emptiness.
+ */
+ private module Cand {
+ /**
+ * Holds if `p` can flow to `node` in the same callable using only
+ * value-preserving steps.
+ *
+ * `read` indicates whether it is contents of `p` that can flow to `node`.
+ */
+ pragma[nomagic]
+ private predicate parameterValueFlowCand(ParamNode p, Node node, boolean read) {
+ p = node and
+ read = false
+ or
+ // local flow
+ exists(Node mid |
+ parameterValueFlowCand(p, mid, read) and
+ simpleLocalFlowStep(mid, node)
+ )
+ or
+ // read
+ exists(Node mid |
+ parameterValueFlowCand(p, mid, false) and
+ read(mid, _, node) and
+ read = true
+ )
+ or
+ // flow through: no prior read
+ exists(ArgNode arg |
+ parameterValueFlowArgCand(p, arg, false) and
+ argumentValueFlowsThroughCand(arg, node, read)
+ )
+ or
+ // flow through: no read inside method
+ exists(ArgNode arg |
+ parameterValueFlowArgCand(p, arg, read) and
+ argumentValueFlowsThroughCand(arg, node, false)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate parameterValueFlowArgCand(ParamNode p, ArgNode arg, boolean read) {
+ parameterValueFlowCand(p, arg, read)
+ }
+
+ pragma[nomagic]
+ predicate parameterValueFlowsToPreUpdateCand(ParamNode p, PostUpdateNode n) {
+ parameterValueFlowCand(p, n.getPreUpdateNode(), false)
+ }
+
+ /**
+ * Holds if `p` can flow to a return node of kind `kind` in the same
+ * callable using only value-preserving steps, not taking call contexts
+ * into account.
+ *
+ * `read` indicates whether it is contents of `p` that can flow to the return
+ * node.
+ */
+ predicate parameterValueFlowReturnCand(ParamNode p, ReturnKind kind, boolean read) {
+ exists(ReturnNode ret |
+ parameterValueFlowCand(p, ret, read) and
+ kind = ret.getKind()
+ )
+ }
+
+ pragma[nomagic]
+ private predicate argumentValueFlowsThroughCand0(
+ DataFlowCall call, ArgNode arg, ReturnKind kind, boolean read
+ ) {
+ exists(ParamNode param | viableParamArg(call, param, arg) |
+ parameterValueFlowReturnCand(param, kind, read)
+ )
+ }
+
+ /**
+ * Holds if `arg` flows to `out` through a call using only value-preserving steps,
+ * not taking call contexts into account.
+ *
+ * `read` indicates whether it is contents of `arg` that can flow to `out`.
+ */
+ predicate argumentValueFlowsThroughCand(ArgNode arg, Node out, boolean read) {
+ exists(DataFlowCall call, ReturnKind kind |
+ argumentValueFlowsThroughCand0(call, arg, kind, read) and
+ out = getAnOutNode(call, kind)
+ )
+ }
+
+ predicate cand(ParamNode p, Node n) {
+ parameterValueFlowCand(p, n, _) and
+ (
+ parameterValueFlowReturnCand(p, _, _)
+ or
+ parameterValueFlowsToPreUpdateCand(p, _)
+ )
+ }
+ }
+
+ /**
+ * The final flow-through calculation:
+ *
+ * - Calculated flow is either value-preserving (`read = TReadStepTypesNone()`)
+ * or summarized as a single read step with before and after types recorded
+ * in the `ReadStepTypesOption` parameter.
+ * - Types are checked using the `compatibleTypes()` relation.
+ */
+ private module Final {
+ /**
+ * Holds if `p` can flow to `node` in the same callable using only
+ * value-preserving steps and possibly a single read step, not taking
+ * call contexts into account.
+ *
+ * If a read step was taken, then `read` captures the `Content`, the
+ * container type, and the content type.
+ */
+ predicate parameterValueFlow(ParamNode p, Node node, ReadStepTypesOption read) {
+ parameterValueFlow0(p, node, read) and
+ if node instanceof CastingNode
+ then
+ // normal flow through
+ read = TReadStepTypesNone() and
+ compatibleTypes(getNodeDataFlowType(p), getNodeDataFlowType(node))
+ or
+ // getter
+ compatibleTypes(read.getContentType(), getNodeDataFlowType(node))
+ else any()
+ }
+
+ pragma[nomagic]
+ private predicate parameterValueFlow0(ParamNode p, Node node, ReadStepTypesOption read) {
+ p = node and
+ Cand::cand(p, _) and
+ read = TReadStepTypesNone()
+ or
+ // local flow
+ exists(Node mid |
+ parameterValueFlow(p, mid, read) and
+ simpleLocalFlowStep(mid, node)
+ )
+ or
+ // read
+ exists(Node mid |
+ parameterValueFlow(p, mid, TReadStepTypesNone()) and
+ readStepWithTypes(mid, read.getContainerType(), read.getContent(), node,
+ read.getContentType()) and
+ Cand::parameterValueFlowReturnCand(p, _, true) and
+ compatibleTypes(getNodeDataFlowType(p), read.getContainerType())
+ )
+ or
+ parameterValueFlow0_0(TReadStepTypesNone(), p, node, read)
+ }
+
+ pragma[nomagic]
+ private predicate parameterValueFlow0_0(
+ ReadStepTypesOption mustBeNone, ParamNode p, Node node, ReadStepTypesOption read
+ ) {
+ // flow through: no prior read
+ exists(ArgNode arg |
+ parameterValueFlowArg(p, arg, mustBeNone) and
+ argumentValueFlowsThrough(arg, read, node)
+ )
+ or
+ // flow through: no read inside method
+ exists(ArgNode arg |
+ parameterValueFlowArg(p, arg, read) and
+ argumentValueFlowsThrough(arg, mustBeNone, node)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate parameterValueFlowArg(ParamNode p, ArgNode arg, ReadStepTypesOption read) {
+ parameterValueFlow(p, arg, read) and
+ Cand::argumentValueFlowsThroughCand(arg, _, _)
+ }
+
+ pragma[nomagic]
+ private predicate argumentValueFlowsThrough0(
+ DataFlowCall call, ArgNode arg, ReturnKind kind, ReadStepTypesOption read
+ ) {
+ exists(ParamNode param | viableParamArg(call, param, arg) |
+ parameterValueFlowReturn(param, kind, read)
+ )
+ }
+
+ /**
+ * Holds if `arg` flows to `out` through a call using only
+ * value-preserving steps and possibly a single read step, not taking
+ * call contexts into account.
+ *
+ * If a read step was taken, then `read` captures the `Content`, the
+ * container type, and the content type.
+ */
+ pragma[nomagic]
+ predicate argumentValueFlowsThrough(ArgNode arg, ReadStepTypesOption read, Node out) {
+ exists(DataFlowCall call, ReturnKind kind |
+ argumentValueFlowsThrough0(call, arg, kind, read) and
+ out = getAnOutNode(call, kind)
+ |
+ // normal flow through
+ read = TReadStepTypesNone() and
+ compatibleTypes(getNodeDataFlowType(arg), getNodeDataFlowType(out))
+ or
+ // getter
+ compatibleTypes(getNodeDataFlowType(arg), read.getContainerType()) and
+ compatibleTypes(read.getContentType(), getNodeDataFlowType(out))
+ )
+ }
+
+ /**
+ * Holds if `arg` flows to `out` through a call using only
+ * value-preserving steps and a single read step, not taking call
+ * contexts into account, thus representing a getter-step.
+ */
+ predicate getterStep(ArgNode arg, Content c, Node out) {
+ argumentValueFlowsThrough(arg, TReadStepTypesSome(_, c, _), out)
+ }
+
+ /**
+ * Holds if `p` can flow to a return node of kind `kind` in the same
+ * callable using only value-preserving steps and possibly a single read
+ * step.
+ *
+ * If a read step was taken, then `read` captures the `Content`, the
+ * container type, and the content type.
+ */
+ private predicate parameterValueFlowReturn(
+ ParamNode p, ReturnKind kind, ReadStepTypesOption read
+ ) {
+ exists(ReturnNode ret |
+ parameterValueFlow(p, ret, read) and
+ kind = ret.getKind()
+ )
+ }
+ }
+
+ import Final
+ }
+
+ import FlowThrough
+
+ cached
+ private module DispatchWithCallContext {
+ /**
+ * Holds if the set of viable implementations that can be called by `call`
+ * might be improved by knowing the call context.
+ */
+ pragma[nomagic]
+ private predicate mayBenefitFromCallContextExt(DataFlowCall call, DataFlowCallable callable) {
+ mayBenefitFromCallContext(call, callable)
+ or
+ callEnclosingCallable(call, callable) and
+ exists(viableCallableLambda(call, TDataFlowCallSome(_)))
+ }
+
+ /**
+ * Gets a viable dispatch target of `call` in the context `ctx`. This is
+ * restricted to those `call`s for which a context might make a difference.
+ */
+ pragma[nomagic]
+ private DataFlowCallable viableImplInCallContextExt(DataFlowCall call, DataFlowCall ctx) {
+ result = viableImplInCallContext(call, ctx)
+ or
+ result = viableCallableLambda(call, TDataFlowCallSome(ctx))
+ or
+ exists(DataFlowCallable enclosing |
+ mayBenefitFromCallContextExt(call, enclosing) and
+ enclosing = viableCallableExt(ctx) and
+ result = viableCallableLambda(call, TDataFlowCallNone())
+ )
+ }
+
+ /**
+ * Holds if the call context `ctx` reduces the set of viable run-time
+ * dispatch targets of call `call` in `c`.
+ */
+ cached
+ predicate reducedViableImplInCallContext(DataFlowCall call, DataFlowCallable c, DataFlowCall ctx) {
+ exists(int tgts, int ctxtgts |
+ mayBenefitFromCallContextExt(call, c) and
+ c = viableCallableExt(ctx) and
+ ctxtgts = count(viableImplInCallContextExt(call, ctx)) and
+ tgts = strictcount(viableCallableExt(call)) and
+ ctxtgts < tgts
+ )
+ }
+
+ /**
+ * Gets a viable run-time dispatch target for the call `call` in the
+ * context `ctx`. This is restricted to those calls for which a context
+ * makes a difference.
+ */
+ cached
+ DataFlowCallable prunedViableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
+ result = viableImplInCallContextExt(call, ctx) and
+ reducedViableImplInCallContext(call, _, ctx)
+ }
+
+ /**
+ * Holds if flow returning from callable `c` to call `call` might return
+ * further and if this path restricts the set of call sites that can be
+ * returned to.
+ */
+ cached
+ predicate reducedViableImplInReturn(DataFlowCallable c, DataFlowCall call) {
+ exists(int tgts, int ctxtgts |
+ mayBenefitFromCallContextExt(call, _) and
+ c = viableCallableExt(call) and
+ ctxtgts = count(DataFlowCall ctx | c = viableImplInCallContextExt(call, ctx)) and
+ tgts = strictcount(DataFlowCall ctx | callEnclosingCallable(call, viableCallableExt(ctx))) and
+ ctxtgts < tgts
+ )
+ }
+
+ /**
+ * Gets a viable run-time dispatch target for the call `call` in the
+ * context `ctx`. This is restricted to those calls and results for which
+ * the return flow from the result to `call` restricts the possible context
+ * `ctx`.
+ */
+ cached
+ DataFlowCallable prunedViableImplInCallContextReverse(DataFlowCall call, DataFlowCall ctx) {
+ result = viableImplInCallContextExt(call, ctx) and
+ reducedViableImplInReturn(result, call)
+ }
+ }
+
+ import DispatchWithCallContext
+
+ /**
+ * Holds if `p` can flow to the pre-update node associated with post-update
+ * node `n`, in the same callable, using only value-preserving steps.
+ */
+ private predicate parameterValueFlowsToPreUpdate(ParamNode p, PostUpdateNode n) {
+ parameterValueFlow(p, n.getPreUpdateNode(), TReadStepTypesNone())
+ }
+
+ private predicate store(
+ Node node1, Content c, Node node2, DataFlowType contentType, DataFlowType containerType
+ ) {
+ storeStep(node1, c, node2) and
+ contentType = getNodeDataFlowType(node1) and
+ containerType = getNodeDataFlowType(node2)
+ or
+ exists(Node n1, Node n2 |
+ n1 = node1.(PostUpdateNode).getPreUpdateNode() and
+ n2 = node2.(PostUpdateNode).getPreUpdateNode()
+ |
+ argumentValueFlowsThrough(n2, TReadStepTypesSome(containerType, c, contentType), n1)
+ or
+ read(n2, c, n1) and
+ contentType = getNodeDataFlowType(n1) and
+ containerType = getNodeDataFlowType(n2)
+ )
+ }
+
+ cached
+ predicate read(Node node1, Content c, Node node2) { readStep(node1, c, node2) }
+
+ /**
+ * Holds if data can flow from `node1` to `node2` via a direct assignment to
+ * `f`.
+ *
+ * This includes reverse steps through reads when the result of the read has
+ * been stored into, in order to handle cases like `x.f1.f2 = y`.
+ */
+ cached
+ predicate store(Node node1, TypedContent tc, Node node2, DataFlowType contentType) {
+ store(node1, tc.getContent(), node2, contentType, tc.getContainerType())
+ }
+
+ /**
+ * Holds if data can flow from `fromNode` to `toNode` because they are the post-update
+ * nodes of some function output and input respectively, where the output and input
+ * are aliases. A typical example is a function returning `this`, implementing a fluent
+ * interface.
+ */
+ private predicate reverseStepThroughInputOutputAlias(
+ PostUpdateNode fromNode, PostUpdateNode toNode
+ ) {
+ exists(Node fromPre, Node toPre |
+ fromPre = fromNode.getPreUpdateNode() and
+ toPre = toNode.getPreUpdateNode()
+ |
+ exists(DataFlowCall c |
+ // Does the language-specific simpleLocalFlowStep already model flow
+ // from function input to output?
+ fromPre = getAnOutNode(c, _) and
+ toPre.(ArgNode).argumentOf(c, _) and
+ simpleLocalFlowStep(toPre.(ArgNode), fromPre)
+ )
+ or
+ argumentValueFlowsThrough(toPre, TReadStepTypesNone(), fromPre)
+ )
+ }
+
+ cached
+ predicate simpleLocalFlowStepExt(Node node1, Node node2) {
+ simpleLocalFlowStep(node1, node2) or
+ reverseStepThroughInputOutputAlias(node1, node2)
+ }
+
+ /**
+ * Holds if the call context `call` improves virtual dispatch in `callable`.
+ */
+ cached
+ predicate recordDataFlowCallSiteDispatch(DataFlowCall call, DataFlowCallable callable) {
+ reducedViableImplInCallContext(_, callable, call)
+ }
+
+ /**
+ * Holds if the call context `call` allows us to prune unreachable nodes in `callable`.
+ */
+ cached
+ predicate recordDataFlowCallSiteUnreachable(DataFlowCall call, DataFlowCallable callable) {
+ exists(Node n | getNodeEnclosingCallable(n) = callable | isUnreachableInCallCached(n, call))
+ }
+
+ cached
+ predicate allowParameterReturnInSelfCached(ParamNode p) { allowParameterReturnInSelf(p) }
+
+ cached
+ newtype TCallContext =
+ TAnyCallContext() or
+ TSpecificCall(DataFlowCall call) { recordDataFlowCallSite(call, _) } or
+ TSomeCall() or
+ TReturn(DataFlowCallable c, DataFlowCall call) { reducedViableImplInReturn(c, call) }
+
+ cached
+ newtype TReturnPosition =
+ TReturnPosition0(DataFlowCallable c, ReturnKindExt kind) {
+ exists(ReturnNodeExt ret |
+ c = returnNodeGetEnclosingCallable(ret) and
+ kind = ret.getKind()
+ )
+ }
+
+ cached
+ newtype TLocalFlowCallContext =
+ TAnyLocalCall() or
+ TSpecificLocalCall(DataFlowCall call) { isUnreachableInCallCached(_, call) }
+
+ cached
+ newtype TReturnKindExt =
+ TValueReturn(ReturnKind kind) or
+ TParamUpdate(int pos) { exists(ParamNode p | p.isParameterOf(_, pos)) }
+
+ cached
+ newtype TBooleanOption =
+ TBooleanNone() or
+ TBooleanSome(boolean b) { b = true or b = false }
+
+ cached
+ newtype TDataFlowCallOption =
+ TDataFlowCallNone() or
+ TDataFlowCallSome(DataFlowCall call)
+
+ cached
+ newtype TTypedContent = MkTypedContent(Content c, DataFlowType t) { store(_, c, _, _, t) }
+
+ cached
+ newtype TAccessPathFront =
+ TFrontNil(DataFlowType t) or
+ TFrontHead(TypedContent tc)
+
+ cached
+ newtype TAccessPathFrontOption =
+ TAccessPathFrontNone() or
+ TAccessPathFrontSome(AccessPathFront apf)
+}
+
+/**
+ * Holds if the call context `call` either improves virtual dispatch in
+ * `callable` or if it allows us to prune unreachable nodes in `callable`.
+ */
+predicate recordDataFlowCallSite(DataFlowCall call, DataFlowCallable callable) {
+ recordDataFlowCallSiteDispatch(call, callable) or
+ recordDataFlowCallSiteUnreachable(call, callable)
+}
+
+/**
+ * A `Node` at which a cast can occur such that the type should be checked.
+ */
+class CastingNode extends Node {
+ CastingNode() { castingNode(this) }
+}
+
+private predicate readStepWithTypes(
+ Node n1, DataFlowType container, Content c, Node n2, DataFlowType content
+) {
+ read(n1, c, n2) and
+ container = getNodeDataFlowType(n1) and
+ content = getNodeDataFlowType(n2)
+}
+
+private newtype TReadStepTypesOption =
+ TReadStepTypesNone() or
+ TReadStepTypesSome(DataFlowType container, Content c, DataFlowType content) {
+ readStepWithTypes(_, container, c, _, content)
+ }
+
+private class ReadStepTypesOption extends TReadStepTypesOption {
+ predicate isSome() { this instanceof TReadStepTypesSome }
+
+ DataFlowType getContainerType() { this = TReadStepTypesSome(result, _, _) }
+
+ Content getContent() { this = TReadStepTypesSome(_, result, _) }
+
+ DataFlowType getContentType() { this = TReadStepTypesSome(_, _, result) }
+
+ string toString() { if this.isSome() then result = "Some(..)" else result = "None()" }
+}
+
+/**
+ * A call context to restrict the targets of virtual dispatch, prune local flow,
+ * and match the call sites of flow into a method with flow out of a method.
+ *
+ * There are four cases:
+ * - `TAnyCallContext()` : No restrictions on method flow.
+ * - `TSpecificCall(DataFlowCall call)` : Flow entered through the
+ * given `call`. This call improves the set of viable
+ * dispatch targets for at least one method call in the current callable
+ * or helps prune unreachable nodes in the current callable.
+ * - `TSomeCall()` : Flow entered through a parameter. The
+ * originating call does not improve the set of dispatch targets for any
+ * method call in the current callable and was therefore not recorded.
+ * - `TReturn(Callable c, DataFlowCall call)` : Flow reached `call` from `c` and
+ * this dispatch target of `call` implies a reduced set of dispatch origins
+ * to which data may flow if it should reach a `return` statement.
+ */
+abstract class CallContext extends TCallContext {
+ abstract string toString();
+
+ /** Holds if this call context is relevant for `callable`. */
+ abstract predicate relevantFor(DataFlowCallable callable);
+}
+
+abstract class CallContextNoCall extends CallContext { }
+
+class CallContextAny extends CallContextNoCall, TAnyCallContext {
+ override string toString() { result = "CcAny" }
+
+ override predicate relevantFor(DataFlowCallable callable) { any() }
+}
+
+abstract class CallContextCall extends CallContext {
+ /** Holds if this call context may be `call`. */
+ bindingset[call]
+ abstract predicate matchesCall(DataFlowCall call);
+}
+
+class CallContextSpecificCall extends CallContextCall, TSpecificCall {
+ override string toString() {
+ exists(DataFlowCall call | this = TSpecificCall(call) | result = "CcCall(" + call + ")")
+ }
+
+ override predicate relevantFor(DataFlowCallable callable) {
+ recordDataFlowCallSite(this.getCall(), callable)
+ }
+
+ override predicate matchesCall(DataFlowCall call) { call = this.getCall() }
+
+ DataFlowCall getCall() { this = TSpecificCall(result) }
+}
+
+class CallContextSomeCall extends CallContextCall, TSomeCall {
+ override string toString() { result = "CcSomeCall" }
+
+ override predicate relevantFor(DataFlowCallable callable) {
+ exists(ParamNode p | getNodeEnclosingCallable(p) = callable)
+ }
+
+ override predicate matchesCall(DataFlowCall call) { any() }
+}
+
+class CallContextReturn extends CallContextNoCall, TReturn {
+ override string toString() {
+ exists(DataFlowCall call | this = TReturn(_, call) | result = "CcReturn(" + call + ")")
+ }
+
+ override predicate relevantFor(DataFlowCallable callable) {
+ exists(DataFlowCall call | this = TReturn(_, call) and callEnclosingCallable(call, callable))
+ }
+}
+
+/**
+ * A call context that is relevant for pruning local flow.
+ */
+abstract class LocalCallContext extends TLocalFlowCallContext {
+ abstract string toString();
+
+ /** Holds if this call context is relevant for `callable`. */
+ abstract predicate relevantFor(DataFlowCallable callable);
+}
+
+class LocalCallContextAny extends LocalCallContext, TAnyLocalCall {
+ override string toString() { result = "LocalCcAny" }
+
+ override predicate relevantFor(DataFlowCallable callable) { any() }
+}
+
+class LocalCallContextSpecificCall extends LocalCallContext, TSpecificLocalCall {
+ LocalCallContextSpecificCall() { this = TSpecificLocalCall(call) }
+
+ DataFlowCall call;
+
+ DataFlowCall getCall() { result = call }
+
+ override string toString() { result = "LocalCcCall(" + call + ")" }
+
+ override predicate relevantFor(DataFlowCallable callable) { relevantLocalCCtx(call, callable) }
+}
+
+private predicate relevantLocalCCtx(DataFlowCall call, DataFlowCallable callable) {
+ exists(Node n | getNodeEnclosingCallable(n) = callable and isUnreachableInCallCached(n, call))
+}
+
+/**
+ * Gets the local call context given the call context and the callable that
+ * the contexts apply to.
+ */
+LocalCallContext getLocalCallContext(CallContext ctx, DataFlowCallable callable) {
+ ctx.relevantFor(callable) and
+ if relevantLocalCCtx(ctx.(CallContextSpecificCall).getCall(), callable)
+ then result.(LocalCallContextSpecificCall).getCall() = ctx.(CallContextSpecificCall).getCall()
+ else result instanceof LocalCallContextAny
+}
+
+/**
+ * The value of a parameter at function entry, viewed as a node in a data
+ * flow graph.
+ */
+class ParamNode extends Node {
+ ParamNode() { parameterNode(this, _, _) }
+
+ /**
+ * Holds if this node is the parameter of callable `c` at the specified
+ * (zero-based) position.
+ */
+ predicate isParameterOf(DataFlowCallable c, int i) { parameterNode(this, c, i) }
+}
+
+/** A data-flow node that represents a call argument. */
+class ArgNode extends Node {
+ ArgNode() { argumentNode(this, _, _) }
+
+ /** Holds if this argument occurs at the given position in the given call. */
+ final predicate argumentOf(DataFlowCall call, int pos) { argumentNode(this, call, pos) }
+}
+
+/**
+ * A node from which flow can return to the caller. This is either a regular
+ * `ReturnNode` or a `PostUpdateNode` corresponding to the value of a parameter.
+ */
+class ReturnNodeExt extends Node {
+ ReturnNodeExt() { returnNodeExt(this, _) }
+
+ /** Gets the kind of this returned value. */
+ ReturnKindExt getKind() { returnNodeExt(this, result) }
+}
+
+/**
+ * A node to which data can flow from a call. Either an ordinary out node
+ * or a post-update node associated with a call argument.
+ */
+class OutNodeExt extends Node {
+ OutNodeExt() { outNodeExt(this) }
+}
+
+/**
+ * An extended return kind. A return kind describes how data can be returned
+ * from a callable. This can either be through a returned value or an updated
+ * parameter.
+ */
+abstract class ReturnKindExt extends TReturnKindExt {
+ /** Gets a textual representation of this return kind. */
+ abstract string toString();
+
+ /** Gets a node corresponding to data flow out of `call`. */
+ final OutNodeExt getAnOutNode(DataFlowCall call) { result = getAnOutNodeExt(call, this) }
+}
+
+class ValueReturnKind extends ReturnKindExt, TValueReturn {
+ private ReturnKind kind;
+
+ ValueReturnKind() { this = TValueReturn(kind) }
+
+ ReturnKind getKind() { result = kind }
+
+ override string toString() { result = kind.toString() }
+}
+
+class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate {
+ private int pos;
+
+ ParamUpdateReturnKind() { this = TParamUpdate(pos) }
+
+ int getPosition() { result = pos }
+
+ override string toString() { result = "param update " + pos }
+}
+
+/** A callable tagged with a relevant return kind. */
+class ReturnPosition extends TReturnPosition0 {
+ private DataFlowCallable c;
+ private ReturnKindExt kind;
+
+ ReturnPosition() { this = TReturnPosition0(c, kind) }
+
+ /** Gets the callable. */
+ DataFlowCallable getCallable() { result = c }
+
+ /** Gets the return kind. */
+ ReturnKindExt getKind() { result = kind }
+
+ /** Gets a textual representation of this return position. */
+ string toString() { result = "[" + kind + "] " + c }
+}
+
+/**
+ * Gets the enclosing callable of `n`. Unlike `n.getEnclosingCallable()`, this
+ * predicate ensures that joins go from `n` to the result instead of the other
+ * way around.
+ */
+pragma[inline]
+DataFlowCallable getNodeEnclosingCallable(Node n) {
+ nodeEnclosingCallable(pragma[only_bind_out](n), pragma[only_bind_into](result))
+}
+
+/** Gets the type of `n` used for type pruning. */
+pragma[inline]
+DataFlowType getNodeDataFlowType(Node n) {
+ nodeDataFlowType(pragma[only_bind_out](n), pragma[only_bind_into](result))
+}
+
+pragma[noinline]
+private DataFlowCallable returnNodeGetEnclosingCallable(ReturnNodeExt ret) {
+ result = getNodeEnclosingCallable(ret)
+}
+
+pragma[noinline]
+private ReturnPosition getReturnPosition0(ReturnNodeExt ret, ReturnKindExt kind) {
+ result.getCallable() = returnNodeGetEnclosingCallable(ret) and
+ kind = result.getKind()
+}
+
+pragma[noinline]
+ReturnPosition getReturnPosition(ReturnNodeExt ret) {
+ result = getReturnPosition0(ret, ret.getKind())
+}
+
+/**
+ * Checks whether `inner` can return to `call` in the call context `innercc`.
+ * Assumes a context of `inner = viableCallableExt(call)`.
+ */
+bindingset[innercc, inner, call]
+predicate checkCallContextReturn(CallContext innercc, DataFlowCallable inner, DataFlowCall call) {
+ innercc instanceof CallContextAny
+ or
+ exists(DataFlowCallable c0, DataFlowCall call0 |
+ callEnclosingCallable(call0, inner) and
+ innercc = TReturn(c0, call0) and
+ c0 = prunedViableImplInCallContextReverse(call0, call)
+ )
+}
+
+/**
+ * Checks whether `call` can resolve to `calltarget` in the call context `cc`.
+ * Assumes a context of `calltarget = viableCallableExt(call)`.
+ */
+bindingset[cc, call, calltarget]
+predicate checkCallContextCall(CallContext cc, DataFlowCall call, DataFlowCallable calltarget) {
+ exists(DataFlowCall ctx | cc = TSpecificCall(ctx) |
+ if reducedViableImplInCallContext(call, _, ctx)
+ then calltarget = prunedViableImplInCallContext(call, ctx)
+ else any()
+ )
+ or
+ cc instanceof CallContextSomeCall
+ or
+ cc instanceof CallContextAny
+ or
+ cc instanceof CallContextReturn
+}
+
+/**
+ * Resolves a return from `callable` in `cc` to `call`. This is equivalent to
+ * `callable = viableCallableExt(call) and checkCallContextReturn(cc, callable, call)`.
+ */
+bindingset[cc, callable]
+predicate resolveReturn(CallContext cc, DataFlowCallable callable, DataFlowCall call) {
+ cc instanceof CallContextAny and callable = viableCallableExt(call)
+ or
+ exists(DataFlowCallable c0, DataFlowCall call0 |
+ callEnclosingCallable(call0, callable) and
+ cc = TReturn(c0, call0) and
+ c0 = prunedViableImplInCallContextReverse(call0, call)
+ )
+}
+
+/**
+ * Resolves a call from `call` in `cc` to `result`. This is equivalent to
+ * `result = viableCallableExt(call) and checkCallContextCall(cc, call, result)`.
+ */
+bindingset[call, cc]
+DataFlowCallable resolveCall(DataFlowCall call, CallContext cc) {
+ exists(DataFlowCall ctx | cc = TSpecificCall(ctx) |
+ if reducedViableImplInCallContext(call, _, ctx)
+ then result = prunedViableImplInCallContext(call, ctx)
+ else result = viableCallableExt(call)
+ )
+ or
+ result = viableCallableExt(call) and cc instanceof CallContextSomeCall
+ or
+ result = viableCallableExt(call) and cc instanceof CallContextAny
+ or
+ result = viableCallableExt(call) and cc instanceof CallContextReturn
+}
+
+/** An optional Boolean value. */
+class BooleanOption extends TBooleanOption {
+ string toString() {
+ this = TBooleanNone() and result = ""
+ or
+ this = TBooleanSome(any(boolean b | result = b.toString()))
+ }
+}
+
+/** An optional `DataFlowCall`. */
+class DataFlowCallOption extends TDataFlowCallOption {
+ string toString() {
+ this = TDataFlowCallNone() and
+ result = "(none)"
+ or
+ exists(DataFlowCall call |
+ this = TDataFlowCallSome(call) and
+ result = call.toString()
+ )
+ }
+}
+
+/** Content tagged with the type of a containing object. */
+class TypedContent extends MkTypedContent {
+ private Content c;
+ private DataFlowType t;
+
+ TypedContent() { this = MkTypedContent(c, t) }
+
+ /** Gets the content. */
+ Content getContent() { result = c }
+
+ /** Gets the container type. */
+ DataFlowType getContainerType() { result = t }
+
+ /** Gets a textual representation of this content. */
+ string toString() { result = c.toString() }
+
+ /**
+ * Holds if access paths with this `TypedContent` at their head always should
+ * be tracked at high precision. This disables adaptive access path precision
+ * for such access paths.
+ */
+ predicate forceHighPrecision() { forceHighPrecision(c) }
+}
+
+/**
+ * The front of an access path. This is either a head or a nil.
+ */
+abstract class AccessPathFront extends TAccessPathFront {
+ abstract string toString();
+
+ abstract DataFlowType getType();
+
+ abstract boolean toBoolNonEmpty();
+
+ TypedContent getHead() { this = TFrontHead(result) }
+
+ predicate isClearedAt(Node n) { clearsContentCached(n, this.getHead().getContent()) }
+}
+
+class AccessPathFrontNil extends AccessPathFront, TFrontNil {
+ private DataFlowType t;
+
+ AccessPathFrontNil() { this = TFrontNil(t) }
+
+ override string toString() { result = ppReprType(t) }
+
+ override DataFlowType getType() { result = t }
+
+ override boolean toBoolNonEmpty() { result = false }
+}
+
+class AccessPathFrontHead extends AccessPathFront, TFrontHead {
+ private TypedContent tc;
+
+ AccessPathFrontHead() { this = TFrontHead(tc) }
+
+ override string toString() { result = tc.toString() }
+
+ override DataFlowType getType() { result = tc.getContainerType() }
+
+ override boolean toBoolNonEmpty() { result = true }
+}
+
+/** An optional access path front. */
+class AccessPathFrontOption extends TAccessPathFrontOption {
+ string toString() {
+ this = TAccessPathFrontNone() and result = ""
+ or
+ this = TAccessPathFrontSome(any(AccessPathFront apf | result = apf.toString()))
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplConsistency.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplConsistency.qll
new file mode 100644
index 00000000000..acf31338f9a
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplConsistency.qll
@@ -0,0 +1,182 @@
+/**
+ * Provides consistency queries for checking invariants in the language-specific
+ * data-flow classes and predicates.
+ */
+
+private import DataFlowImplSpecific::Private
+private import DataFlowImplSpecific::Public
+private import tainttracking1.TaintTrackingParameter::Private
+private import tainttracking1.TaintTrackingParameter::Public
+
+module Consistency {
+ private class RelevantNode extends Node {
+ RelevantNode() {
+ this instanceof ArgumentNode or
+ this instanceof ParameterNode or
+ this instanceof ReturnNode or
+ this = getAnOutNode(_, _) or
+ simpleLocalFlowStep(this, _) or
+ simpleLocalFlowStep(_, this) or
+ jumpStep(this, _) or
+ jumpStep(_, this) or
+ storeStep(this, _, _) or
+ storeStep(_, _, this) or
+ readStep(this, _, _) or
+ readStep(_, _, this) or
+ defaultAdditionalTaintStep(this, _) or
+ defaultAdditionalTaintStep(_, this)
+ }
+ }
+
+ query predicate uniqueEnclosingCallable(Node n, string msg) {
+ exists(int c |
+ n instanceof RelevantNode and
+ c = count(nodeGetEnclosingCallable(n)) and
+ c != 1 and
+ msg = "Node should have one enclosing callable but has " + c + "."
+ )
+ }
+
+ query predicate uniqueType(Node n, string msg) {
+ exists(int c |
+ n instanceof RelevantNode and
+ c = count(getNodeType(n)) and
+ c != 1 and
+ msg = "Node should have one type but has " + c + "."
+ )
+ }
+
+ query predicate uniqueNodeLocation(Node n, string msg) {
+ exists(int c |
+ c =
+ count(string filepath, int startline, int startcolumn, int endline, int endcolumn |
+ n.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ ) and
+ c != 1 and
+ msg = "Node should have one location but has " + c + "."
+ )
+ }
+
+ query predicate missingLocation(string msg) {
+ exists(int c |
+ c =
+ strictcount(Node n |
+ not exists(string filepath, int startline, int startcolumn, int endline, int endcolumn |
+ n.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ )
+ ) and
+ msg = "Nodes without location: " + c
+ )
+ }
+
+ query predicate uniqueNodeToString(Node n, string msg) {
+ exists(int c |
+ c = count(n.toString()) and
+ c != 1 and
+ msg = "Node should have one toString but has " + c + "."
+ )
+ }
+
+ query predicate missingToString(string msg) {
+ exists(int c |
+ c = strictcount(Node n | not exists(n.toString())) and
+ msg = "Nodes without toString: " + c
+ )
+ }
+
+ query predicate parameterCallable(ParameterNode p, string msg) {
+ exists(DataFlowCallable c | isParameterNode(p, c, _) and c != nodeGetEnclosingCallable(p)) and
+ msg = "Callable mismatch for parameter."
+ }
+
+ query predicate localFlowIsLocal(Node n1, Node n2, string msg) {
+ simpleLocalFlowStep(n1, n2) and
+ nodeGetEnclosingCallable(n1) != nodeGetEnclosingCallable(n2) and
+ msg = "Local flow step does not preserve enclosing callable."
+ }
+
+ private DataFlowType typeRepr() { result = getNodeType(_) }
+
+ query predicate compatibleTypesReflexive(DataFlowType t, string msg) {
+ t = typeRepr() and
+ not compatibleTypes(t, t) and
+ msg = "Type compatibility predicate is not reflexive."
+ }
+
+ query predicate unreachableNodeCCtx(Node n, DataFlowCall call, string msg) {
+ isUnreachableInCall(n, call) and
+ exists(DataFlowCallable c |
+ c = nodeGetEnclosingCallable(n) and
+ not viableCallable(call) = c
+ ) and
+ msg = "Call context for isUnreachableInCall is inconsistent with call graph."
+ }
+
+ query predicate localCallNodes(DataFlowCall call, Node n, string msg) {
+ (
+ n = getAnOutNode(call, _) and
+ msg = "OutNode and call does not share enclosing callable."
+ or
+ n.(ArgumentNode).argumentOf(call, _) and
+ msg = "ArgumentNode and call does not share enclosing callable."
+ ) and
+ nodeGetEnclosingCallable(n) != call.getEnclosingCallable()
+ }
+
+ // This predicate helps the compiler forget that in some languages
+ // it is impossible for a result of `getPreUpdateNode` to be an
+ // instance of `PostUpdateNode`.
+ private Node getPre(PostUpdateNode n) {
+ result = n.getPreUpdateNode()
+ or
+ none()
+ }
+
+ query predicate postIsNotPre(PostUpdateNode n, string msg) {
+ getPre(n) = n and
+ msg = "PostUpdateNode should not equal its pre-update node."
+ }
+
+ query predicate postHasUniquePre(PostUpdateNode n, string msg) {
+ exists(int c |
+ c = count(n.getPreUpdateNode()) and
+ c != 1 and
+ msg = "PostUpdateNode should have one pre-update node but has " + c + "."
+ )
+ }
+
+ query predicate uniquePostUpdate(Node n, string msg) {
+ 1 < strictcount(PostUpdateNode post | post.getPreUpdateNode() = n) and
+ msg = "Node has multiple PostUpdateNodes."
+ }
+
+ query predicate postIsInSameCallable(PostUpdateNode n, string msg) {
+ nodeGetEnclosingCallable(n) != nodeGetEnclosingCallable(n.getPreUpdateNode()) and
+ msg = "PostUpdateNode does not share callable with its pre-update node."
+ }
+
+ private predicate hasPost(Node n) { exists(PostUpdateNode post | post.getPreUpdateNode() = n) }
+
+ query predicate reverseRead(Node n, string msg) {
+ exists(Node n2 | readStep(n, _, n2) and hasPost(n2) and not hasPost(n)) and
+ msg = "Origin of readStep is missing a PostUpdateNode."
+ }
+
+ query predicate argHasPostUpdate(ArgumentNode n, string msg) {
+ not hasPost(n) and
+ not isImmutableOrUnobservable(n) and
+ msg = "ArgumentNode is missing PostUpdateNode."
+ }
+
+ // This predicate helps the compiler forget that in some languages
+ // it is impossible for a `PostUpdateNode` to be the target of
+ // `simpleLocalFlowStep`.
+ private predicate isPostUpdateNode(Node n) { n instanceof PostUpdateNode or none() }
+
+ query predicate postWithInFlow(Node n, string msg) {
+ isPostUpdateNode(n) and
+ not clearsContent(n, _) and
+ simpleLocalFlowStep(_, n) and
+ msg = "PostUpdateNode should not be the target of local flow."
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplSpecific.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplSpecific.qll
new file mode 100644
index 00000000000..e78a0814a14
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplSpecific.qll
@@ -0,0 +1,11 @@
+/**
+ * Provides Ruby-specific definitions for use in the data flow library.
+ */
+module Private {
+ import DataFlowPrivate
+ import DataFlowDispatch
+}
+
+module Public {
+ import DataFlowPublic
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll
new file mode 100644
index 00000000000..e295907c6ad
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll
@@ -0,0 +1,824 @@
+private import ruby
+private import codeql.ruby.CFG
+private import codeql.ruby.dataflow.SSA
+private import DataFlowPublic
+private import DataFlowDispatch
+private import SsaImpl as SsaImpl
+private import FlowSummaryImpl as FlowSummaryImpl
+
+/** Gets the callable in which this node occurs. */
+DataFlowCallable nodeGetEnclosingCallable(NodeImpl n) { result = n.getEnclosingCallable() }
+
+/** Holds if `p` is a `ParameterNode` of `c` with position `pos`. */
+predicate isParameterNode(ParameterNodeImpl p, DataFlowCallable c, int pos) {
+ p.isParameterOf(c, pos)
+}
+
+abstract class NodeImpl extends Node {
+ DataFlowCallable getEnclosingCallable() { result = TCfgScope(this.getCfgScope()) }
+
+ /** Do not call: use `getEnclosingCallable()` instead. */
+ abstract CfgScope getCfgScope();
+
+ /** Do not call: use `getLocation()` instead. */
+ abstract Location getLocationImpl();
+
+ /** Do not call: use `toString()` instead. */
+ abstract string toStringImpl();
+}
+
+private class ExprNodeImpl extends ExprNode, NodeImpl {
+ override CfgScope getCfgScope() { result = this.getExprNode().getExpr().getCfgScope() }
+
+ override Location getLocationImpl() { result = this.getExprNode().getLocation() }
+
+ override string toStringImpl() { result = this.getExprNode().toString() }
+}
+
+/** Provides predicates related to local data flow. */
+module LocalFlow {
+ private import codeql.ruby.dataflow.internal.SsaImpl
+
+ /**
+ * Holds if `nodeFrom` is a last node referencing SSA definition `def`, which
+ * can reach `next`.
+ */
+ private predicate localFlowSsaInput(Node nodeFrom, Ssa::Definition def, Ssa::Definition next) {
+ exists(BasicBlock bb, int i | lastRefBeforeRedef(def, bb, i, next) |
+ def = nodeFrom.(SsaDefinitionNode).getDefinition() and
+ def.definesAt(_, bb, i)
+ or
+ exists(CfgNodes::ExprCfgNode e |
+ e = nodeFrom.asExpr() and
+ e = bb.getNode(i) and
+ e.getExpr() instanceof VariableReadAccess
+ )
+ )
+ }
+
+ /** Gets the SSA definition node corresponding to parameter `p`. */
+ SsaDefinitionNode getParameterDefNode(NamedParameter p) {
+ exists(BasicBlock bb, int i |
+ bb.getNode(i).getNode() = p.getDefiningAccess() and
+ result.getDefinition().definesAt(_, bb, i)
+ )
+ }
+
+ /**
+ * Holds if there is a local flow step from `nodeFrom` to `nodeTo` involving
+ * SSA definition `def`.
+ */
+ predicate localSsaFlowStep(Ssa::Definition def, Node nodeFrom, Node nodeTo) {
+ // Flow from assignment into SSA definition
+ def.(Ssa::WriteDefinition).assigns(nodeFrom.asExpr()) and
+ nodeTo.(SsaDefinitionNode).getDefinition() = def
+ or
+ // Flow from SSA definition to first read
+ def = nodeFrom.(SsaDefinitionNode).getDefinition() and
+ nodeTo.asExpr() = def.getAFirstRead()
+ or
+ // Flow from read to next read
+ exists(
+ CfgNodes::ExprNodes::VariableReadAccessCfgNode read1,
+ CfgNodes::ExprNodes::VariableReadAccessCfgNode read2
+ |
+ def.hasAdjacentReads(read1, read2) and
+ nodeTo.asExpr() = read2
+ |
+ nodeFrom.asExpr() = read1
+ or
+ read1 = nodeFrom.(PostUpdateNode).getPreUpdateNode().asExpr()
+ )
+ or
+ // Flow into phi node
+ exists(Ssa::PhiNode phi |
+ localFlowSsaInput(nodeFrom, def, phi) and
+ phi = nodeTo.(SsaDefinitionNode).getDefinition() and
+ def = phi.getAnInput()
+ )
+ // TODO
+ // or
+ // // Flow into uncertain SSA definition
+ // exists(LocalFlow::UncertainExplicitSsaDefinition uncertain |
+ // localFlowSsaInput(nodeFrom, def, uncertain) and
+ // uncertain = nodeTo.(SsaDefinitionNode).getDefinition() and
+ // def = uncertain.getPriorDefinition()
+ // )
+ }
+}
+
+/** An argument of a call (including qualifier arguments, excluding block arguments). */
+private class Argument extends CfgNodes::ExprCfgNode {
+ private CfgNodes::ExprNodes::CallCfgNode call;
+ private int arg;
+
+ Argument() {
+ this = call.getArgument(arg) and
+ not this.getExpr() instanceof BlockArgument
+ or
+ this = call.getReceiver() and arg = -1
+ }
+
+ /** Holds if this expression is the `i`th argument of `c`. */
+ predicate isArgumentOf(CfgNodes::ExprNodes::CallCfgNode c, int i) { c = call and i = arg }
+}
+
+/** A collection of cached types and predicates to be evaluated in the same stage. */
+cached
+private module Cached {
+ cached
+ newtype TNode =
+ TExprNode(CfgNodes::ExprCfgNode n) or
+ TReturningNode(CfgNodes::ReturningCfgNode n) or
+ TSynthReturnNode(CfgScope scope, ReturnKind kind) {
+ exists(ReturningNode ret |
+ ret.(NodeImpl).getCfgScope() = scope and
+ ret.getKind() = kind
+ )
+ } or
+ TSsaDefinitionNode(Ssa::Definition def) or
+ TNormalParameterNode(Parameter p) { not p instanceof BlockParameter } or
+ TSelfParameterNode(MethodBase m) or
+ TBlockParameterNode(MethodBase m) or
+ TExprPostUpdateNode(CfgNodes::ExprCfgNode n) { n instanceof Argument } or
+ TSummaryNode(
+ FlowSummaryImpl::Public::SummarizedCallable c,
+ FlowSummaryImpl::Private::SummaryNodeState state
+ ) {
+ FlowSummaryImpl::Private::summaryNodeRange(c, state)
+ } or
+ TSummaryParameterNode(FlowSummaryImpl::Public::SummarizedCallable c, int i) {
+ FlowSummaryImpl::Private::summaryParameterNodeRange(c, i)
+ }
+
+ class TParameterNode =
+ TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or TSummaryParameterNode;
+
+ private predicate defaultValueFlow(NamedParameter p, ExprNode e) {
+ p.(OptionalParameter).getDefaultValue() = e.getExprNode().getExpr()
+ or
+ p.(KeywordParameter).getDefaultValue() = e.getExprNode().getExpr()
+ }
+
+ private predicate localFlowStepCommon(Node nodeFrom, Node nodeTo) {
+ LocalFlow::localSsaFlowStep(_, nodeFrom, nodeTo)
+ or
+ nodeFrom.(SelfParameterNode).getMethod() = nodeTo.asExpr().getExpr().getEnclosingCallable() and
+ nodeTo.asExpr().getExpr() instanceof Self
+ or
+ nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::AssignExprCfgNode).getRhs()
+ or
+ nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::BlockArgumentCfgNode).getValue()
+ or
+ nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::StmtSequenceCfgNode).getLastStmt()
+ or
+ nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::ConditionalExprCfgNode).getBranch(_)
+ or
+ nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::CaseExprCfgNode).getBranch(_)
+ or
+ exists(CfgNodes::ExprCfgNode exprTo, ReturningStatementNode n |
+ nodeFrom = n and
+ exprTo = nodeTo.asExpr() and
+ n.getReturningNode().getNode() instanceof BreakStmt and
+ exprTo.getNode() instanceof Loop and
+ nodeTo.asExpr().getAPredecessor(any(SuccessorTypes::BreakSuccessor s)) = n.getReturningNode()
+ )
+ or
+ nodeFrom.asExpr() = nodeTo.(ReturningStatementNode).getReturningNode().getReturnedValueNode()
+ or
+ nodeTo.asExpr() =
+ any(CfgNodes::ExprNodes::ForExprCfgNode for |
+ exists(SuccessorType s |
+ not s instanceof SuccessorTypes::BreakSuccessor and
+ exists(for.getAPredecessor(s))
+ ) and
+ nodeFrom.asExpr() = for.getValue()
+ )
+ }
+
+ /**
+ * This is the local flow predicate that is used as a building block in global
+ * data flow.
+ */
+ cached
+ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
+ localFlowStepCommon(nodeFrom, nodeTo)
+ or
+ defaultValueFlow(nodeTo.(ParameterNode).getParameter(), nodeFrom)
+ or
+ nodeTo = LocalFlow::getParameterDefNode(nodeFrom.(ParameterNode).getParameter())
+ or
+ nodeTo.(SynthReturnNode).getAnInput() = nodeFrom
+ or
+ FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, true)
+ }
+
+ /** This is the local flow predicate that is exposed. */
+ cached
+ predicate localFlowStepImpl(Node nodeFrom, Node nodeTo) {
+ localFlowStepCommon(nodeFrom, nodeTo)
+ or
+ defaultValueFlow(nodeTo.(ParameterNode).getParameter(), nodeFrom)
+ or
+ nodeTo = LocalFlow::getParameterDefNode(nodeFrom.(ParameterNode).getParameter())
+ or
+ // Simple flow through library code is included in the exposed local
+ // step relation, even though flow is technically inter-procedural
+ FlowSummaryImpl::Private::Steps::summaryThroughStep(nodeFrom, nodeTo, true)
+ }
+
+ /** This is the local flow predicate that is used in type tracking. */
+ cached
+ predicate localFlowStepTypeTracker(Node nodeFrom, Node nodeTo) {
+ localFlowStepCommon(nodeFrom, nodeTo)
+ or
+ exists(NamedParameter p |
+ defaultValueFlow(p, nodeFrom) and
+ nodeTo = LocalFlow::getParameterDefNode(p)
+ )
+ }
+
+ cached
+ predicate isLocalSourceNode(Node n) {
+ n instanceof ParameterNode
+ or
+ // This case should not be needed once we have proper use-use flow
+ // for `self`. At that point, the `self`s returned by `trackInstance`
+ // in `DataFlowDispatch.qll` should refer to the post-update node,
+ // and we can remove this case.
+ n.asExpr().getExpr() instanceof Self
+ or
+ not localFlowStepTypeTracker+(any(Node e |
+ e instanceof ExprNode
+ or
+ e instanceof ParameterNode
+ ), n)
+ }
+
+ cached
+ newtype TContent = TTodoContent() // stub
+}
+
+import Cached
+
+/** Holds if `n` should be hidden from path explanations. */
+predicate nodeIsHidden(Node n) {
+ exists(Ssa::Definition def | def = n.(SsaDefinitionNode).getDefinition() |
+ def instanceof Ssa::PhiNode
+ )
+ or
+ n instanceof SummaryNode
+ or
+ n instanceof SummaryParameterNode
+ or
+ n instanceof SynthReturnNode
+}
+
+/** An SSA definition, viewed as a node in a data flow graph. */
+class SsaDefinitionNode extends NodeImpl, TSsaDefinitionNode {
+ Ssa::Definition def;
+
+ SsaDefinitionNode() { this = TSsaDefinitionNode(def) }
+
+ /** Gets the underlying SSA definition. */
+ Ssa::Definition getDefinition() { result = def }
+
+ override CfgScope getCfgScope() { result = def.getBasicBlock().getScope() }
+
+ override Location getLocationImpl() { result = def.getLocation() }
+
+ override string toStringImpl() { result = def.toString() }
+}
+
+/**
+ * A value returning statement, viewed as a node in a data flow graph.
+ *
+ * Note that because of control-flow splitting, one `ReturningStmt` may correspond
+ * to multiple `ReturningStatementNode`s, just like it may correspond to multiple
+ * `ControlFlow::Node`s.
+ */
+class ReturningStatementNode extends NodeImpl, TReturningNode {
+ CfgNodes::ReturningCfgNode n;
+
+ ReturningStatementNode() { this = TReturningNode(n) }
+
+ /** Gets the expression corresponding to this node. */
+ CfgNodes::ReturningCfgNode getReturningNode() { result = n }
+
+ override CfgScope getCfgScope() { result = n.getScope() }
+
+ override Location getLocationImpl() { result = n.getLocation() }
+
+ override string toStringImpl() { result = n.toString() }
+}
+
+private module ParameterNodes {
+ abstract class ParameterNodeImpl extends ParameterNode, NodeImpl {
+ abstract predicate isSourceParameterOf(Callable c, int i);
+
+ predicate isParameterOf(DataFlowCallable c, int i) {
+ this.isSourceParameterOf(c.asCallable(), i)
+ }
+ }
+
+ /**
+ * The value of a normal parameter at function entry, viewed as a node in a data
+ * flow graph.
+ */
+ class NormalParameterNode extends ParameterNodeImpl, TNormalParameterNode {
+ private Parameter parameter;
+
+ NormalParameterNode() { this = TNormalParameterNode(parameter) }
+
+ override Parameter getParameter() { result = parameter }
+
+ override predicate isSourceParameterOf(Callable c, int i) { c.getParameter(i) = parameter }
+
+ override CfgScope getCfgScope() { result = parameter.getCallable() }
+
+ override Location getLocationImpl() { result = parameter.getLocation() }
+
+ override string toStringImpl() { result = parameter.toString() }
+ }
+
+ /**
+ * The value of the `self` parameter at function entry, viewed as a node in a data
+ * flow graph.
+ */
+ class SelfParameterNode extends ParameterNodeImpl, TSelfParameterNode {
+ private MethodBase method;
+
+ SelfParameterNode() { this = TSelfParameterNode(method) }
+
+ final MethodBase getMethod() { result = method }
+
+ override predicate isSourceParameterOf(Callable c, int i) { method = c and i = -1 }
+
+ override CfgScope getCfgScope() { result = method }
+
+ override Location getLocationImpl() { result = method.getLocation() }
+
+ override string toStringImpl() { result = "self in " + method.toString() }
+ }
+
+ /**
+ * The value of a block parameter at function entry, viewed as a node in a data
+ * flow graph.
+ */
+ class BlockParameterNode extends ParameterNodeImpl, TBlockParameterNode {
+ private MethodBase method;
+
+ BlockParameterNode() { this = TBlockParameterNode(method) }
+
+ final MethodBase getMethod() { result = method }
+
+ override Parameter getParameter() {
+ result = method.getAParameter() and result instanceof BlockParameter
+ }
+
+ override predicate isSourceParameterOf(Callable c, int i) { c = method and i = -2 }
+
+ override CfgScope getCfgScope() { result = method }
+
+ override Location getLocationImpl() {
+ result = this.getParameter().getLocation()
+ or
+ not exists(this.getParameter()) and result = method.getLocation()
+ }
+
+ override string toStringImpl() {
+ result = this.getParameter().toString()
+ or
+ not exists(this.getParameter()) and result = "&block"
+ }
+ }
+
+ /** A parameter for a library callable with a flow summary. */
+ class SummaryParameterNode extends ParameterNodeImpl, TSummaryParameterNode {
+ private FlowSummaryImpl::Public::SummarizedCallable sc;
+ private int pos;
+
+ SummaryParameterNode() { this = TSummaryParameterNode(sc, pos) }
+
+ override predicate isSourceParameterOf(Callable c, int i) { none() }
+
+ override predicate isParameterOf(DataFlowCallable c, int i) { sc = c and i = pos }
+
+ override CfgScope getCfgScope() { none() }
+
+ override DataFlowCallable getEnclosingCallable() { result = sc }
+
+ override EmptyLocation getLocationImpl() { any() }
+
+ override string toStringImpl() { result = "parameter " + pos + " of " + sc }
+ }
+}
+
+import ParameterNodes
+
+/** A data-flow node used to model flow summaries. */
+private class SummaryNode extends NodeImpl, TSummaryNode {
+ private FlowSummaryImpl::Public::SummarizedCallable c;
+ private FlowSummaryImpl::Private::SummaryNodeState state;
+
+ SummaryNode() { this = TSummaryNode(c, state) }
+
+ override CfgScope getCfgScope() { none() }
+
+ override DataFlowCallable getEnclosingCallable() { result = c }
+
+ override EmptyLocation getLocationImpl() { any() }
+
+ override string toStringImpl() { result = "[summary] " + state + " in " + c }
+}
+
+/** A data-flow node that represents a call argument. */
+abstract class ArgumentNode extends Node {
+ /** Holds if this argument occurs at the given position in the given call. */
+ predicate argumentOf(DataFlowCall call, int pos) { this.sourceArgumentOf(call.asCall(), pos) }
+
+ abstract predicate sourceArgumentOf(CfgNodes::ExprNodes::CallCfgNode call, int pos);
+
+ /** Gets the call in which this node is an argument. */
+ final DataFlowCall getCall() { this.argumentOf(result, _) }
+}
+
+private module ArgumentNodes {
+ /** A data-flow node that represents an explicit call argument. */
+ class ExplicitArgumentNode extends ArgumentNode {
+ Argument arg;
+
+ ExplicitArgumentNode() { this.asExpr() = arg }
+
+ override predicate sourceArgumentOf(CfgNodes::ExprNodes::CallCfgNode call, int pos) {
+ arg.isArgumentOf(call, pos)
+ }
+ }
+
+ /** A data-flow node that represents the `self` argument of a call. */
+ class SelfArgumentNode extends ExplicitArgumentNode {
+ SelfArgumentNode() { arg.isArgumentOf(_, -1) }
+ }
+
+ /** A data-flow node that represents a block argument. */
+ class BlockArgumentNode extends ArgumentNode {
+ BlockArgumentNode() {
+ this.asExpr().getExpr() instanceof BlockArgument or
+ exists(CfgNodes::ExprNodes::CallCfgNode c | c.getBlock() = this.asExpr())
+ }
+
+ override predicate sourceArgumentOf(CfgNodes::ExprNodes::CallCfgNode call, int pos) {
+ pos = -2 and
+ (
+ this.asExpr() = call.getBlock()
+ or
+ exists(CfgNodes::ExprCfgNode arg, int n |
+ arg = call.getArgument(n) and
+ this.asExpr() = arg and
+ arg.getExpr() instanceof BlockArgument
+ )
+ )
+ }
+ }
+
+ private class SummaryArgumentNode extends SummaryNode, ArgumentNode {
+ SummaryArgumentNode() { FlowSummaryImpl::Private::summaryArgumentNode(_, this, _) }
+
+ override predicate sourceArgumentOf(CfgNodes::ExprNodes::CallCfgNode call, int pos) { none() }
+
+ override predicate argumentOf(DataFlowCall call, int pos) {
+ FlowSummaryImpl::Private::summaryArgumentNode(call, this, pos)
+ }
+ }
+}
+
+import ArgumentNodes
+
+/** A data-flow node that represents a value syntactically returned by a callable. */
+abstract class ReturningNode extends Node {
+ /** Gets the kind of this return node. */
+ abstract ReturnKind getKind();
+}
+
+/** A data-flow node that represents a value returned by a callable. */
+abstract class ReturnNode extends Node {
+ /** Gets the kind of this return node. */
+ abstract ReturnKind getKind();
+}
+
+private module ReturnNodes {
+ private predicate isValid(CfgNodes::ReturningCfgNode node) {
+ exists(ReturningStmt stmt, Callable scope |
+ stmt = node.getNode() and
+ scope = node.getScope()
+ |
+ stmt instanceof ReturnStmt and
+ (scope instanceof Method or scope instanceof SingletonMethod or scope instanceof Lambda)
+ or
+ stmt instanceof NextStmt and
+ (scope instanceof Block or scope instanceof Lambda)
+ or
+ stmt instanceof BreakStmt and
+ (scope instanceof Block or scope instanceof Lambda)
+ )
+ }
+
+ /**
+ * A data-flow node that represents an expression explicitly returned by
+ * a callable.
+ */
+ class ExplicitReturnNode extends ReturningNode, ReturningStatementNode {
+ ExplicitReturnNode() {
+ isValid(n) and
+ n.getASuccessor().(CfgNodes::AnnotatedExitNode).isNormal() and
+ n.getScope() instanceof Callable
+ }
+
+ override ReturnKind getKind() {
+ if n.getNode() instanceof BreakStmt
+ then result instanceof BreakReturnKind
+ else result instanceof NormalReturnKind
+ }
+ }
+
+ pragma[noinline]
+ private AstNode implicitReturn(Callable c, ExprNode n) {
+ exists(CfgNodes::ExprCfgNode en |
+ en = n.getExprNode() and
+ en.getASuccessor().(CfgNodes::AnnotatedExitNode).isNormal() and
+ n.(NodeImpl).getCfgScope() = c and
+ result = en.getExpr()
+ )
+ or
+ result = implicitReturn(c, n).getParent()
+ }
+
+ /**
+ * A data-flow node that represents an expression implicitly returned by
+ * a callable. An implicit return happens when an expression can be the
+ * last thing that is evaluated in the body of the callable.
+ */
+ class ExprReturnNode extends ReturningNode, ExprNode {
+ ExprReturnNode() { exists(Callable c | implicitReturn(c, this) = c.getAStmt()) }
+
+ override ReturnKind getKind() { result instanceof NormalReturnKind }
+ }
+
+ /**
+ * A synthetic data-flow node for joining flow from different syntactic
+ * returns into a single node.
+ *
+ * This node only exists to avoid computing the product of a large fan-in
+ * with a large fan-out.
+ */
+ class SynthReturnNode extends NodeImpl, ReturnNode, TSynthReturnNode {
+ private CfgScope scope;
+ private ReturnKind kind;
+
+ SynthReturnNode() { this = TSynthReturnNode(scope, kind) }
+
+ /** Gets a syntactic return node that flows into this synthetic node. */
+ ReturningNode getAnInput() {
+ result.(NodeImpl).getCfgScope() = scope and
+ result.getKind() = kind
+ }
+
+ override ReturnKind getKind() { result = kind }
+
+ override CfgScope getCfgScope() { result = scope }
+
+ override Location getLocationImpl() { result = scope.getLocation() }
+
+ override string toStringImpl() { result = "return " + kind + " in " + scope }
+ }
+
+ private class SummaryReturnNode extends SummaryNode, ReturnNode {
+ private ReturnKind rk;
+
+ SummaryReturnNode() { FlowSummaryImpl::Private::summaryReturnNode(this, rk) }
+
+ override ReturnKind getKind() { result = rk }
+ }
+}
+
+import ReturnNodes
+
+/** A data-flow node that represents the output of a call. */
+abstract class OutNode extends Node {
+ /** Gets the underlying call, where this node is a corresponding output of kind `kind`. */
+ abstract DataFlowCall getCall(ReturnKind kind);
+}
+
+private module OutNodes {
+ /**
+ * A data-flow node that reads a value returned directly by a callable,
+ * either via a call or a `yield` of a block.
+ */
+ class ExprOutNode extends OutNode, ExprNode {
+ private DataFlowCall call;
+
+ ExprOutNode() { call.asCall() = this.getExprNode() }
+
+ override DataFlowCall getCall(ReturnKind kind) {
+ result = call and
+ kind instanceof NormalReturnKind
+ }
+ }
+
+ private class SummaryOutNode extends SummaryNode, OutNode {
+ SummaryOutNode() { FlowSummaryImpl::Private::summaryOutNode(_, this, _) }
+
+ override DataFlowCall getCall(ReturnKind kind) {
+ FlowSummaryImpl::Private::summaryOutNode(result, this, kind)
+ }
+ }
+}
+
+import OutNodes
+
+predicate jumpStep(Node pred, Node succ) {
+ SsaImpl::captureFlowIn(pred.(SsaDefinitionNode).getDefinition(),
+ succ.(SsaDefinitionNode).getDefinition())
+ or
+ SsaImpl::captureFlowOut(pred.(SsaDefinitionNode).getDefinition(),
+ succ.(SsaDefinitionNode).getDefinition())
+ or
+ exists(Self s, Method m |
+ s = succ.asExpr().getExpr() and
+ pred.(SelfParameterNode).getMethod() = m and
+ m = s.getEnclosingMethod() and
+ m != s.getEnclosingCallable()
+ )
+ or
+ succ.asExpr().getExpr().(ConstantReadAccess).getValue() = pred.asExpr().getExpr()
+}
+
+predicate storeStep(Node node1, Content c, Node node2) {
+ FlowSummaryImpl::Private::Steps::summaryStoreStep(node1, c, node2)
+}
+
+predicate readStep(Node node1, Content c, Node node2) {
+ FlowSummaryImpl::Private::Steps::summaryReadStep(node1, c, node2)
+}
+
+/**
+ * Holds if values stored inside content `c` are cleared at node `n`. For example,
+ * any value stored inside `f` is cleared at the pre-update node associated with `x`
+ * in `x.f = newValue`.
+ */
+predicate clearsContent(Node n, Content c) {
+ storeStep(_, c, n)
+ or
+ FlowSummaryImpl::Private::Steps::summaryClearsContent(n, c)
+}
+
+private newtype TDataFlowType = TTodoDataFlowType()
+
+class DataFlowType extends TDataFlowType {
+ string toString() { result = "" }
+}
+
+/** Gets the type of `n` used for type pruning. */
+DataFlowType getNodeType(NodeImpl n) { any() }
+
+/** Gets a string representation of a `DataFlowType`. */
+string ppReprType(DataFlowType t) { result = t.toString() }
+
+/**
+ * Holds if `t1` and `t2` are compatible, that is, whether data can flow from
+ * a node of type `t1` to a node of type `t2`.
+ */
+pragma[inline]
+predicate compatibleTypes(DataFlowType t1, DataFlowType t2) { any() }
+
+/**
+ * A node associated with an object after an operation that might have
+ * changed its state.
+ *
+ * This can be either the argument to a callable after the callable returns
+ * (which might have mutated the argument), or the qualifier of a field after
+ * an update to the field.
+ *
+ * Nodes corresponding to AST elements, for example `ExprNode`, usually refer
+ * to the value before the update.
+ */
+abstract class PostUpdateNode extends Node {
+ /** Gets the node before the state update. */
+ abstract Node getPreUpdateNode();
+}
+
+private module PostUpdateNodes {
+ class ExprPostUpdateNode extends PostUpdateNode, NodeImpl, TExprPostUpdateNode {
+ private CfgNodes::ExprCfgNode e;
+
+ ExprPostUpdateNode() { this = TExprPostUpdateNode(e) }
+
+ override ExprNode getPreUpdateNode() { e = result.getExprNode() }
+
+ override CfgScope getCfgScope() { result = e.getExpr().getCfgScope() }
+
+ override Location getLocationImpl() { result = e.getLocation() }
+
+ override string toStringImpl() { result = "[post] " + e.toString() }
+ }
+
+ private class SummaryPostUpdateNode extends SummaryNode, PostUpdateNode {
+ private Node pre;
+
+ SummaryPostUpdateNode() { FlowSummaryImpl::Private::summaryPostUpdateNode(this, pre) }
+
+ override Node getPreUpdateNode() { result = pre }
+ }
+}
+
+private import PostUpdateNodes
+
+/** A node that performs a type cast. */
+class CastNode extends Node {
+ CastNode() { this instanceof ReturningNode }
+}
+
+class DataFlowExpr = CfgNodes::ExprCfgNode;
+
+int accessPathLimit() { result = 5 }
+
+/**
+ * Holds if access paths with `c` at their head always should be tracked at high
+ * precision. This disables adaptive access path precision for such access paths.
+ */
+predicate forceHighPrecision(Content c) { none() }
+
+/** The unit type. */
+private newtype TUnit = TMkUnit()
+
+/** The trivial type with a single element. */
+class Unit extends TUnit {
+ /** Gets a textual representation of this element. */
+ string toString() { result = "unit" }
+}
+
+/**
+ * Holds if `n` does not require a `PostUpdateNode` as it either cannot be
+ * modified or its modification cannot be observed, for example if it is a
+ * freshly created object that is not saved in a variable.
+ *
+ * This predicate is only used for consistency checks.
+ */
+predicate isImmutableOrUnobservable(Node n) { n instanceof BlockArgumentNode }
+
+/**
+ * Holds if the node `n` is unreachable when the call context is `call`.
+ */
+predicate isUnreachableInCall(Node n, DataFlowCall call) { none() }
+
+newtype LambdaCallKind =
+ TYieldCallKind() or
+ TLambdaCallKind()
+
+/** Holds if `creation` is an expression that creates a lambda of kind `kind` for `c`. */
+predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) {
+ kind = TYieldCallKind() and
+ creation.asExpr().getExpr() = c.asCallable().(Block)
+ or
+ kind = TLambdaCallKind() and
+ (
+ creation.asExpr().getExpr() = c.asCallable().(Lambda)
+ or
+ creation.asExpr() =
+ any(CfgNodes::ExprNodes::MethodCallCfgNode mc |
+ c.asCallable() = mc.getBlock().getExpr() and
+ mc.getExpr().getMethodName() = "lambda"
+ )
+ )
+}
+
+/** Holds if `call` is a lambda call of kind `kind` where `receiver` is the lambda expression. */
+predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
+ kind = TYieldCallKind() and
+ receiver.(BlockParameterNode).getMethod() =
+ call.asCall().getExpr().(YieldCall).getEnclosingMethod()
+ or
+ kind = TLambdaCallKind() and
+ call.asCall() =
+ any(CfgNodes::ExprNodes::MethodCallCfgNode mc |
+ receiver.asExpr() = mc.getReceiver() and
+ mc.getExpr().getMethodName() = "call"
+ )
+ or
+ receiver = call.(SummaryCall).getReceiver() and
+ if receiver.(ParameterNodeImpl).isParameterOf(_, -2)
+ then kind = TYieldCallKind()
+ else kind = TLambdaCallKind()
+}
+
+/** Extra data-flow steps needed for lambda flow analysis. */
+predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() }
+
+/**
+ * Holds if flow is allowed to pass from parameter `p` and back to itself as a
+ * side-effect, resulting in a summary from `p` to itself.
+ *
+ * One example would be to allow flow like `p.foo = p.bar;`, which is disallowed
+ * by default as a heuristic.
+ */
+predicate allowParameterReturnInSelf(ParameterNode p) { none() }
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
new file mode 100644
index 00000000000..fd8ec51e869
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
@@ -0,0 +1,213 @@
+private import ruby
+private import DataFlowDispatch
+private import DataFlowPrivate
+private import codeql.ruby.CFG
+private import codeql.ruby.typetracking.TypeTracker
+private import codeql.ruby.dataflow.SSA
+private import FlowSummaryImpl as FlowSummaryImpl
+
+/**
+ * An element, viewed as a node in a data flow graph. Either an expression
+ * (`ExprNode`) or a parameter (`ParameterNode`).
+ */
+class Node extends TNode {
+ /** Gets the expression corresponding to this node, if any. */
+ CfgNodes::ExprCfgNode asExpr() { result = this.(ExprNode).getExprNode() }
+
+ /** Gets the parameter corresponding to this node, if any. */
+ Parameter asParameter() { result = this.(ParameterNode).getParameter() }
+
+ /** Gets a textual representation of this node. */
+ // TODO: cache
+ final string toString() { result = this.(NodeImpl).toStringImpl() }
+
+ /** Gets the location of this node. */
+ // TODO: cache
+ final Location getLocation() { result = this.(NodeImpl).getLocationImpl() }
+
+ /**
+ * Holds if this element is at the specified location.
+ * The location spans column `startcolumn` of line `startline` to
+ * column `endcolumn` of line `endline` in file `filepath`.
+ * For more information, see
+ * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
+ */
+ predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
+ this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ }
+
+ /**
+ * Gets a local source node from which data may flow to this node in zero or more local data-flow steps.
+ */
+ LocalSourceNode getALocalSource() { result.flowsTo(this) }
+}
+
+/** A data-flow node corresponding to a call in the control-flow graph. */
+class CallNode extends LocalSourceNode {
+ private CfgNodes::ExprNodes::CallCfgNode node;
+
+ CallNode() { node = this.asExpr() }
+
+ /** Gets the data-flow node corresponding to the receiver of the call corresponding to this data-flow node */
+ Node getReceiver() { result.asExpr() = node.getReceiver() }
+
+ /** Gets the data-flow node corresponding to the `n`th argument of the call corresponding to this data-flow node */
+ Node getArgument(int n) { result.asExpr() = node.getArgument(n) }
+
+ /** Gets the data-flow node corresponding to the named argument of the call corresponding to this data-flow node */
+ Node getKeywordArgument(string name) { result.asExpr() = node.getKeywordArgument(name) }
+
+ /** Gets the name of the the method called by the method call (if any) corresponding to this data-flow node */
+ string getMethodName() { result = node.getExpr().(MethodCall).getMethodName() }
+}
+
+/**
+ * An expression, viewed as a node in a data flow graph.
+ *
+ * Note that because of control-flow splitting, one `Expr` may correspond
+ * to multiple `ExprNode`s, just like it may correspond to multiple
+ * `ControlFlow::Node`s.
+ */
+class ExprNode extends Node, TExprNode {
+ private CfgNodes::ExprCfgNode n;
+
+ ExprNode() { this = TExprNode(n) }
+
+ /** Gets the expression corresponding to this node. */
+ CfgNodes::ExprCfgNode getExprNode() { result = n }
+}
+
+/**
+ * The value of a parameter at function entry, viewed as a node in a data
+ * flow graph.
+ */
+class ParameterNode extends Node, TParameterNode {
+ /** Gets the parameter corresponding to this node, if any. */
+ Parameter getParameter() { none() }
+}
+
+/**
+ * A data-flow node that is a source of local flow.
+ */
+class LocalSourceNode extends Node {
+ LocalSourceNode() { isLocalSourceNode(this) }
+
+ /** Holds if this `LocalSourceNode` can flow to `nodeTo` in one or more local flow steps. */
+ pragma[inline]
+ predicate flowsTo(Node nodeTo) { hasLocalSource(nodeTo, this) }
+
+ /**
+ * Gets a node that this node may flow to using one heap and/or interprocedural step.
+ *
+ * See `TypeTracker` for more details about how to use this.
+ */
+ pragma[inline]
+ LocalSourceNode track(TypeTracker t2, TypeTracker t) { t = t2.step(this, result) }
+
+ /**
+ * Gets a node that may flow into this one using one heap and/or interprocedural step.
+ *
+ * See `TypeBackTracker` for more details about how to use this.
+ */
+ pragma[inline]
+ LocalSourceNode backtrack(TypeBackTracker t2, TypeBackTracker t) { t2 = t.step(result, this) }
+}
+
+predicate hasLocalSource(Node sink, Node source) {
+ // Declaring `source` to be a `SourceNode` currently causes a redundant check in the
+ // recursive case, so instead we check it explicitly here.
+ source = sink and
+ source instanceof LocalSourceNode
+ or
+ exists(Node mid |
+ hasLocalSource(mid, source) and
+ localFlowStepTypeTracker(mid, sink)
+ )
+}
+
+/** Gets a node corresponding to expression `e`. */
+ExprNode exprNode(CfgNodes::ExprCfgNode e) { result.getExprNode() = e }
+
+/**
+ * Gets the node corresponding to the value of parameter `p` at function entry.
+ */
+ParameterNode parameterNode(Parameter p) { result.getParameter() = p }
+
+/**
+ * Holds if data flows from `nodeFrom` to `nodeTo` in exactly one local
+ * (intra-procedural) step.
+ */
+predicate localFlowStep = localFlowStepImpl/2;
+
+/**
+ * Holds if data flows from `source` to `sink` in zero or more local
+ * (intra-procedural) steps.
+ */
+predicate localFlow(Node source, Node sink) { localFlowStep*(source, sink) }
+
+/**
+ * Holds if data can flow from `e1` to `e2` in zero or more
+ * local (intra-procedural) steps.
+ */
+predicate localExprFlow(CfgNodes::ExprCfgNode e1, CfgNodes::ExprCfgNode e2) {
+ localFlow(exprNode(e1), exprNode(e2))
+}
+
+/**
+ * A reference contained in an object. This is either a field, a property,
+ * or an element in a collection.
+ */
+class Content extends TContent {
+ /** Gets a textual representation of this content. */
+ string toString() { none() }
+
+ /** Gets the location of this content. */
+ Location getLocation() { none() }
+}
+
+/**
+ * A guard that validates some expression.
+ *
+ * To use this in a configuration, extend the class and provide a
+ * characteristic predicate precisely specifying the guard, and override
+ * `checks` to specify what is being validated and in which branch.
+ *
+ * It is important that all extending classes in scope are disjoint.
+ */
+abstract class BarrierGuard extends CfgNodes::ExprCfgNode {
+ private ConditionBlock conditionBlock;
+
+ BarrierGuard() { this = conditionBlock.getLastNode() }
+
+ /** Holds if this guard controls block `b` upon evaluating to `branch`. */
+ private predicate controlsBlock(BasicBlock bb, boolean branch) {
+ exists(SuccessorTypes::BooleanSuccessor s | s.getValue() = branch |
+ conditionBlock.controls(bb, s)
+ )
+ }
+
+ /**
+ * Holds if this guard validates `expr` upon evaluating to `branch`.
+ * For example, the following code validates `foo` when the condition
+ * `foo == "foo"` is true.
+ * ```ruby
+ * if foo == "foo"
+ * do_something
+ * else
+ * do_something_else
+ * end
+ * ```
+ */
+ abstract predicate checks(CfgNode expr, boolean branch);
+
+ final Node getAGuardedNode() {
+ exists(boolean branch, CfgNodes::ExprCfgNode testedNode, Ssa::Definition def |
+ def.getARead() = testedNode and
+ def.getARead() = result.asExpr() and
+ this.checks(testedNode, branch) and
+ this.controlsBlock(result.asExpr().getBasicBlock(), branch)
+ )
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImpl.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImpl.qll
new file mode 100644
index 00000000000..5955285bd6f
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImpl.qll
@@ -0,0 +1,1020 @@
+/**
+ * Provides classes and predicates for defining flow summaries.
+ *
+ * The definitions in this file are language-independent, and language-specific
+ * definitions are passed in via the `DataFlowImplSpecific` and
+ * `FlowSummaryImplSpecific` modules.
+ */
+
+private import FlowSummaryImplSpecific
+private import DataFlowImplSpecific::Private
+private import DataFlowImplSpecific::Public
+private import DataFlowImplCommon
+
+/** Provides classes and predicates for defining flow summaries. */
+module Public {
+ private import Private
+
+ /**
+ * A component used in a flow summary.
+ *
+ * Either a parameter or an argument at a given position, a specific
+ * content type, or a return kind.
+ */
+ class SummaryComponent extends TSummaryComponent {
+ /** Gets a textual representation of this summary component. */
+ string toString() {
+ exists(Content c | this = TContentSummaryComponent(c) and result = c.toString())
+ or
+ exists(int i | this = TParameterSummaryComponent(i) and result = "parameter " + i)
+ or
+ exists(int i | this = TArgumentSummaryComponent(i) and result = "argument " + i)
+ or
+ exists(ReturnKind rk | this = TReturnSummaryComponent(rk) and result = "return (" + rk + ")")
+ }
+ }
+
+ /** Provides predicates for constructing summary components. */
+ module SummaryComponent {
+ /** Gets a summary component for content `c`. */
+ SummaryComponent content(Content c) { result = TContentSummaryComponent(c) }
+
+ /** Gets a summary component for parameter `i`. */
+ SummaryComponent parameter(int i) { result = TParameterSummaryComponent(i) }
+
+ /** Gets a summary component for argument `i`. */
+ SummaryComponent argument(int i) { result = TArgumentSummaryComponent(i) }
+
+ /** Gets a summary component for a return of kind `rk`. */
+ SummaryComponent return(ReturnKind rk) { result = TReturnSummaryComponent(rk) }
+ }
+
+ /**
+ * A (non-empty) stack of summary components.
+ *
+ * A stack is used to represent where data is read from (input) or where it
+ * is written to (output). For example, an input stack `[Field f, Argument 0]`
+ * means that data is read from field `f` from the `0`th argument, while an
+ * output stack `[Field g, Return]` means that data is written to the field
+ * `g` of the returned object.
+ */
+ class SummaryComponentStack extends TSummaryComponentStack {
+ /** Gets the head of this stack. */
+ SummaryComponent head() {
+ this = TSingletonSummaryComponentStack(result) or
+ this = TConsSummaryComponentStack(result, _)
+ }
+
+ /** Gets the tail of this stack, if any. */
+ SummaryComponentStack tail() { this = TConsSummaryComponentStack(_, result) }
+
+ /** Gets the length of this stack. */
+ int length() {
+ this = TSingletonSummaryComponentStack(_) and result = 1
+ or
+ result = 1 + this.tail().length()
+ }
+
+ /** Gets the stack obtained by dropping the first `i` elements, if any. */
+ SummaryComponentStack drop(int i) {
+ i = 0 and result = this
+ or
+ result = this.tail().drop(i - 1)
+ }
+
+ /** Holds if this stack contains summary component `c`. */
+ predicate contains(SummaryComponent c) { c = this.drop(_).head() }
+
+ /** Gets a textual representation of this stack. */
+ string toString() {
+ exists(SummaryComponent head, SummaryComponentStack tail |
+ head = this.head() and
+ tail = this.tail() and
+ result = head + " of " + tail
+ )
+ or
+ exists(SummaryComponent c |
+ this = TSingletonSummaryComponentStack(c) and
+ result = c.toString()
+ )
+ }
+ }
+
+ /** Provides predicates for constructing stacks of summary components. */
+ module SummaryComponentStack {
+ /** Gets a singleton stack containing `c`. */
+ SummaryComponentStack singleton(SummaryComponent c) {
+ result = TSingletonSummaryComponentStack(c)
+ }
+
+ /**
+ * Gets the stack obtained by pushing `head` onto `tail`.
+ *
+ * Make sure to override `RequiredSummaryComponentStack::required()` in order
+ * to ensure that the constructed stack exists.
+ */
+ SummaryComponentStack push(SummaryComponent head, SummaryComponentStack tail) {
+ result = TConsSummaryComponentStack(head, tail)
+ }
+
+ /** Gets a singleton stack for argument `i`. */
+ SummaryComponentStack argument(int i) { result = singleton(SummaryComponent::argument(i)) }
+
+ /** Gets a singleton stack representing a return of kind `rk`. */
+ SummaryComponentStack return(ReturnKind rk) { result = singleton(SummaryComponent::return(rk)) }
+ }
+
+ /**
+ * A class that exists for QL technical reasons only (the IPA type used
+ * to represent component stacks needs to be bounded).
+ */
+ abstract class RequiredSummaryComponentStack extends SummaryComponentStack {
+ /**
+ * Holds if the stack obtained by pushing `head` onto `tail` is required.
+ */
+ abstract predicate required(SummaryComponent c);
+ }
+
+ /** A callable with a flow summary. */
+ abstract class SummarizedCallable extends DataFlowCallable {
+ /**
+ * Holds if data may flow from `input` to `output` through this callable.
+ *
+ * `preservesValue` indicates whether this is a value-preserving step
+ * or a taint-step.
+ *
+ * Input specifications are restricted to stacks that end with
+ * `SummaryComponent::argument(_)`, preceded by zero or more
+ * `SummaryComponent::return(_)` or `SummaryComponent::content(_)` components.
+ *
+ * Output specifications are restricted to stacks that end with
+ * `SummaryComponent::return(_)` or `SummaryComponent::argument(_)`.
+ *
+ * Output stacks ending with `SummaryComponent::return(_)` can be preceded by zero
+ * or more `SummaryComponent::content(_)` components.
+ *
+ * Output stacks ending with `SummaryComponent::argument(_)` can be preceded by an
+ * optional `SummaryComponent::parameter(_)` component, which in turn can be preceded
+ * by zero or more `SummaryComponent::content(_)` components.
+ */
+ pragma[nomagic]
+ predicate propagatesFlow(
+ SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
+ ) {
+ none()
+ }
+
+ /**
+ * Holds if values stored inside `content` are cleared on objects passed as
+ * the `i`th argument to this callable.
+ */
+ pragma[nomagic]
+ predicate clearsContent(int i, Content content) { none() }
+ }
+}
+
+/**
+ * Provides predicates for compiling flow summaries down to atomic local steps,
+ * read steps, and store steps.
+ */
+module Private {
+ private import Public
+
+ newtype TSummaryComponent =
+ TContentSummaryComponent(Content c) or
+ TParameterSummaryComponent(int i) { parameterPosition(i) } or
+ TArgumentSummaryComponent(int i) { parameterPosition(i) } or
+ TReturnSummaryComponent(ReturnKind rk)
+
+ private TSummaryComponent thisParam() {
+ result = TParameterSummaryComponent(instanceParameterPosition())
+ }
+
+ newtype TSummaryComponentStack =
+ TSingletonSummaryComponentStack(SummaryComponent c) or
+ TConsSummaryComponentStack(SummaryComponent head, SummaryComponentStack tail) {
+ tail.(RequiredSummaryComponentStack).required(head)
+ or
+ tail.(RequiredSummaryComponentStack).required(TParameterSummaryComponent(_)) and
+ head = thisParam()
+ }
+
+ pragma[nomagic]
+ private predicate summary(
+ SummarizedCallable c, SummaryComponentStack input, SummaryComponentStack output,
+ boolean preservesValue
+ ) {
+ c.propagatesFlow(input, output, preservesValue)
+ or
+ // observe side effects of callbacks on input arguments
+ c.propagatesFlow(output, input, preservesValue) and
+ preservesValue = true and
+ isCallbackParameter(input) and
+ isContentOfArgument(output)
+ or
+ // flow from the receiver of a callback into the instance-parameter
+ exists(SummaryComponentStack s, SummaryComponentStack callbackRef |
+ c.propagatesFlow(s, _, _) or c.propagatesFlow(_, s, _)
+ |
+ callbackRef = s.drop(_) and
+ (isCallbackParameter(callbackRef) or callbackRef.head() = TReturnSummaryComponent(_)) and
+ input = callbackRef.tail() and
+ output = TConsSummaryComponentStack(thisParam(), input) and
+ preservesValue = true
+ )
+ }
+
+ private predicate isCallbackParameter(SummaryComponentStack s) {
+ s.head() = TParameterSummaryComponent(_) and exists(s.tail())
+ }
+
+ private predicate isContentOfArgument(SummaryComponentStack s) {
+ s.head() = TContentSummaryComponent(_) and isContentOfArgument(s.tail())
+ or
+ s = TSingletonSummaryComponentStack(TArgumentSummaryComponent(_))
+ }
+
+ private predicate outputState(SummarizedCallable c, SummaryComponentStack s) {
+ summary(c, _, s, _)
+ or
+ exists(SummaryComponentStack out |
+ outputState(c, out) and
+ out.head() = TContentSummaryComponent(_) and
+ s = out.tail()
+ )
+ or
+ // Add the argument node corresponding to the requested post-update node
+ inputState(c, s) and isCallbackParameter(s)
+ }
+
+ private predicate inputState(SummarizedCallable c, SummaryComponentStack s) {
+ summary(c, s, _, _)
+ or
+ exists(SummaryComponentStack inp | inputState(c, inp) and s = inp.tail())
+ or
+ exists(SummaryComponentStack out |
+ outputState(c, out) and
+ out.head() = TParameterSummaryComponent(_) and
+ s = out.tail()
+ )
+ }
+
+ private newtype TSummaryNodeState =
+ TSummaryNodeInputState(SummaryComponentStack s) { inputState(_, s) } or
+ TSummaryNodeOutputState(SummaryComponentStack s) { outputState(_, s) } or
+ TSummaryNodeClearsContentState(int i, boolean post) {
+ any(SummarizedCallable sc).clearsContent(i, _) and post in [false, true]
+ }
+
+ /**
+ * A state used to break up (complex) flow summaries into atomic flow steps.
+ * For a flow summary
+ *
+ * ```ql
+ * propagatesFlow(
+ * SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
+ * )
+ * ```
+ *
+ * the following states are used:
+ *
+ * - `TSummaryNodeInputState(SummaryComponentStack s)`:
+ * this state represents that the components in `s` _have been read_ from the
+ * input.
+ * - `TSummaryNodeOutputState(SummaryComponentStack s)`:
+ * this state represents that the components in `s` _remain to be written_ to
+ * the output.
+ */
+ class SummaryNodeState extends TSummaryNodeState {
+ /** Holds if this state is a valid input state for `c`. */
+ pragma[nomagic]
+ predicate isInputState(SummarizedCallable c, SummaryComponentStack s) {
+ this = TSummaryNodeInputState(s) and
+ inputState(c, s)
+ }
+
+ /** Holds if this state is a valid output state for `c`. */
+ pragma[nomagic]
+ predicate isOutputState(SummarizedCallable c, SummaryComponentStack s) {
+ this = TSummaryNodeOutputState(s) and
+ outputState(c, s)
+ }
+
+ /** Gets a textual representation of this state. */
+ string toString() {
+ exists(SummaryComponentStack s |
+ this = TSummaryNodeInputState(s) and
+ result = "read: " + s
+ )
+ or
+ exists(SummaryComponentStack s |
+ this = TSummaryNodeOutputState(s) and
+ result = "to write: " + s
+ )
+ or
+ exists(int i, boolean post, string postStr |
+ this = TSummaryNodeClearsContentState(i, post) and
+ (if post = true then postStr = " (post)" else postStr = "") and
+ result = "clear: " + i + postStr
+ )
+ }
+ }
+
+ /**
+ * Holds if `state` represents having read the `i`th argument for `c`. In this case
+ * we are not synthesizing a data-flow node, but instead assume that a relevant
+ * parameter node already exists.
+ */
+ private predicate parameterReadState(SummarizedCallable c, SummaryNodeState state, int i) {
+ state.isInputState(c, SummaryComponentStack::argument(i))
+ }
+
+ /**
+ * Holds if a synthesized summary node is needed for the state `state` in summarized
+ * callable `c`.
+ */
+ predicate summaryNodeRange(SummarizedCallable c, SummaryNodeState state) {
+ state.isInputState(c, _) and
+ not parameterReadState(c, state, _)
+ or
+ state.isOutputState(c, _)
+ or
+ exists(int i |
+ c.clearsContent(i, _) and
+ state = TSummaryNodeClearsContentState(i, _)
+ )
+ }
+
+ pragma[noinline]
+ private Node summaryNodeInputState(SummarizedCallable c, SummaryComponentStack s) {
+ exists(SummaryNodeState state | state.isInputState(c, s) |
+ result = summaryNode(c, state)
+ or
+ exists(int i |
+ parameterReadState(c, state, i) and
+ result.(ParamNode).isParameterOf(c, i)
+ )
+ )
+ }
+
+ pragma[noinline]
+ private Node summaryNodeOutputState(SummarizedCallable c, SummaryComponentStack s) {
+ exists(SummaryNodeState state |
+ state.isOutputState(c, s) and
+ result = summaryNode(c, state)
+ )
+ }
+
+ /**
+ * Holds if a write targets `post`, which is a post-update node for the `i`th
+ * parameter of `c`.
+ */
+ private predicate isParameterPostUpdate(Node post, SummarizedCallable c, int i) {
+ post = summaryNodeOutputState(c, SummaryComponentStack::argument(i))
+ }
+
+ /** Holds if a parameter node is required for the `i`th parameter of `c`. */
+ predicate summaryParameterNodeRange(SummarizedCallable c, int i) {
+ parameterReadState(c, _, i)
+ or
+ isParameterPostUpdate(_, c, i)
+ or
+ c.clearsContent(i, _)
+ }
+
+ private predicate callbackOutput(
+ SummarizedCallable c, SummaryComponentStack s, Node receiver, ReturnKind rk
+ ) {
+ any(SummaryNodeState state).isInputState(c, s) and
+ s.head() = TReturnSummaryComponent(rk) and
+ receiver = summaryNodeInputState(c, s.drop(1))
+ }
+
+ private predicate callbackInput(
+ SummarizedCallable c, SummaryComponentStack s, Node receiver, int i
+ ) {
+ any(SummaryNodeState state).isOutputState(c, s) and
+ s.head() = TParameterSummaryComponent(i) and
+ receiver = summaryNodeInputState(c, s.drop(1))
+ }
+
+ /** Holds if a call targeting `receiver` should be synthesized inside `c`. */
+ predicate summaryCallbackRange(SummarizedCallable c, Node receiver) {
+ callbackOutput(c, _, receiver, _)
+ or
+ callbackInput(c, _, receiver, _)
+ }
+
+ /**
+ * Gets the type of synthesized summary node `n`.
+ *
+ * The type is computed based on the language-specific predicates
+ * `getContentType()`, `getReturnType()`, `getCallbackParameterType()`, and
+ * `getCallbackReturnType()`.
+ */
+ DataFlowType summaryNodeType(Node n) {
+ exists(Node pre |
+ summaryPostUpdateNode(n, pre) and
+ result = getNodeType(pre)
+ )
+ or
+ exists(SummarizedCallable c, SummaryComponentStack s, SummaryComponent head | head = s.head() |
+ n = summaryNodeInputState(c, s) and
+ (
+ exists(Content cont |
+ head = TContentSummaryComponent(cont) and result = getContentType(cont)
+ )
+ or
+ exists(ReturnKind rk |
+ head = TReturnSummaryComponent(rk) and
+ result =
+ getCallbackReturnType(getNodeType(summaryNodeInputState(pragma[only_bind_out](c),
+ s.drop(1))), rk)
+ )
+ )
+ or
+ n = summaryNodeOutputState(c, s) and
+ (
+ exists(Content cont |
+ head = TContentSummaryComponent(cont) and result = getContentType(cont)
+ )
+ or
+ s.length() = 1 and
+ exists(ReturnKind rk |
+ head = TReturnSummaryComponent(rk) and
+ result = getReturnType(c, rk)
+ )
+ or
+ exists(int i | head = TParameterSummaryComponent(i) |
+ result =
+ getCallbackParameterType(getNodeType(summaryNodeInputState(pragma[only_bind_out](c),
+ s.drop(1))), i)
+ )
+ )
+ )
+ or
+ exists(SummarizedCallable c, int i, ParamNode p |
+ n = summaryNode(c, TSummaryNodeClearsContentState(i, false)) and
+ p.isParameterOf(c, i) and
+ result = getNodeType(p)
+ )
+ }
+
+ /** Holds if summary node `out` contains output of kind `rk` from call `c`. */
+ predicate summaryOutNode(DataFlowCall c, Node out, ReturnKind rk) {
+ exists(SummarizedCallable callable, SummaryComponentStack s, Node receiver |
+ callbackOutput(callable, s, receiver, rk) and
+ out = summaryNodeInputState(callable, s) and
+ c = summaryDataFlowCall(receiver)
+ )
+ }
+
+ /** Holds if summary node `arg` is the `i`th argument of call `c`. */
+ predicate summaryArgumentNode(DataFlowCall c, Node arg, int i) {
+ exists(SummarizedCallable callable, SummaryComponentStack s, Node receiver |
+ callbackInput(callable, s, receiver, i) and
+ arg = summaryNodeOutputState(callable, s) and
+ c = summaryDataFlowCall(receiver)
+ )
+ }
+
+ /** Holds if summary node `post` is a post-update node with pre-update node `pre`. */
+ predicate summaryPostUpdateNode(Node post, Node pre) {
+ exists(SummarizedCallable c, int i |
+ isParameterPostUpdate(post, c, i) and
+ pre.(ParamNode).isParameterOf(c, i)
+ or
+ pre = summaryNode(c, TSummaryNodeClearsContentState(i, false)) and
+ post = summaryNode(c, TSummaryNodeClearsContentState(i, true))
+ )
+ or
+ exists(SummarizedCallable callable, SummaryComponentStack s |
+ callbackInput(callable, s, _, _) and
+ pre = summaryNodeOutputState(callable, s) and
+ post = summaryNodeInputState(callable, s)
+ )
+ }
+
+ /** Holds if summary node `ret` is a return node of kind `rk`. */
+ predicate summaryReturnNode(Node ret, ReturnKind rk) {
+ exists(SummarizedCallable callable, SummaryComponentStack s |
+ ret = summaryNodeOutputState(callable, s) and
+ s = TSingletonSummaryComponentStack(TReturnSummaryComponent(rk))
+ )
+ }
+
+ /**
+ * Holds if flow is allowed to pass from parameter `p`, to a return
+ * node, and back out to `p`.
+ */
+ predicate summaryAllowParameterReturnInSelf(ParamNode p) {
+ exists(SummarizedCallable c, int i |
+ c.clearsContent(i, _) and
+ p.isParameterOf(c, i)
+ )
+ }
+
+ /** Provides a compilation of flow summaries to atomic data-flow steps. */
+ module Steps {
+ /**
+ * Holds if there is a local step from `pred` to `succ`, which is synthesized
+ * from a flow summary.
+ */
+ predicate summaryLocalStep(Node pred, Node succ, boolean preservesValue) {
+ exists(
+ SummarizedCallable c, SummaryComponentStack inputContents,
+ SummaryComponentStack outputContents
+ |
+ summary(c, inputContents, outputContents, preservesValue) and
+ pred = summaryNodeInputState(c, inputContents) and
+ succ = summaryNodeOutputState(c, outputContents)
+ |
+ preservesValue = true
+ or
+ preservesValue = false and not summary(c, inputContents, outputContents, true)
+ )
+ or
+ // If flow through a method updates a parameter from some input A, and that
+ // parameter also is returned through B, then we'd like a combined flow from A
+ // to B as well. As an example, this simplifies modeling of fluent methods:
+ // for `StringBuilder.append(x)` with a specified value flow from qualifier to
+ // return value and taint flow from argument 0 to the qualifier, then this
+ // allows us to infer taint flow from argument 0 to the return value.
+ succ instanceof ParamNode and
+ summaryPostUpdateNode(pred, succ) and
+ preservesValue = true
+ or
+ // Similarly we would like to chain together summaries where values get passed
+ // into callbacks along the way.
+ pred instanceof ArgNode and
+ summaryPostUpdateNode(succ, pred) and
+ preservesValue = true
+ or
+ exists(SummarizedCallable c, int i |
+ pred.(ParamNode).isParameterOf(c, i) and
+ succ = summaryNode(c, TSummaryNodeClearsContentState(i, _)) and
+ preservesValue = true
+ )
+ }
+
+ /**
+ * Holds if there is a read step of content `c` from `pred` to `succ`, which
+ * is synthesized from a flow summary.
+ */
+ predicate summaryReadStep(Node pred, Content c, Node succ) {
+ exists(SummarizedCallable sc, SummaryComponentStack s |
+ pred = summaryNodeInputState(sc, s.drop(1)) and
+ succ = summaryNodeInputState(sc, s) and
+ SummaryComponent::content(c) = s.head()
+ )
+ }
+
+ /**
+ * Holds if there is a store step of content `c` from `pred` to `succ`, which
+ * is synthesized from a flow summary.
+ */
+ predicate summaryStoreStep(Node pred, Content c, Node succ) {
+ exists(SummarizedCallable sc, SummaryComponentStack s |
+ pred = summaryNodeOutputState(sc, s) and
+ succ = summaryNodeOutputState(sc, s.drop(1)) and
+ SummaryComponent::content(c) = s.head()
+ )
+ }
+
+ /**
+ * Holds if values stored inside content `c` are cleared at `n`. `n` is a
+ * synthesized summary node, so in order for values to be cleared at calls
+ * to the relevant method, it is important that flow does not pass over
+ * the argument, either via use-use flow or def-use flow.
+ *
+ * Example:
+ *
+ * ```
+ * a.b = taint;
+ * a.clearB(); // assume we have a flow summary for `clearB` that clears `b` on the qualifier
+ * sink(a.b);
+ * ```
+ *
+ * In the above, flow should not pass from `a` on the first line (or the second
+ * line) to `a` on the third line. Instead, there will be synthesized flow from
+ * `a` on line 2 to the post-update node for `a` on that line (via an intermediate
+ * node where field `b` is cleared).
+ */
+ predicate summaryClearsContent(Node n, Content c) {
+ exists(SummarizedCallable sc, int i |
+ n = summaryNode(sc, TSummaryNodeClearsContentState(i, true)) and
+ sc.clearsContent(i, c)
+ )
+ }
+
+ /**
+ * Holds if values stored inside content `c` are cleared inside a
+ * callable to which `arg` is an argument.
+ *
+ * In such cases, it is important to prevent use-use flow out of
+ * `arg` (see comment for `summaryClearsContent`).
+ */
+ predicate summaryClearsContentArg(ArgNode arg, Content c) {
+ exists(DataFlowCall call, int i |
+ viableCallable(call).(SummarizedCallable).clearsContent(i, c) and
+ arg.argumentOf(call, i)
+ )
+ }
+
+ pragma[nomagic]
+ private ParamNode summaryArgParam(ArgNode arg, ReturnKindExt rk, OutNodeExt out) {
+ exists(DataFlowCall call, int pos, SummarizedCallable callable |
+ arg.argumentOf(call, pos) and
+ viableCallable(call) = callable and
+ result.isParameterOf(callable, pos) and
+ out = rk.getAnOutNode(call)
+ )
+ }
+
+ /**
+ * Holds if `arg` flows to `out` using a simple flow summary, that is, a flow
+ * summary without reads and stores.
+ *
+ * NOTE: This step should not be used in global data-flow/taint-tracking, but may
+ * be useful to include in the exposed local data-flow/taint-tracking relations.
+ */
+ predicate summaryThroughStep(ArgNode arg, Node out, boolean preservesValue) {
+ exists(ReturnKindExt rk, ReturnNodeExt ret |
+ summaryLocalStep(summaryArgParam(arg, rk, out), ret, preservesValue) and
+ ret.getKind() = rk
+ )
+ }
+
+ /**
+ * Holds if there is a read(+taint) of `c` from `arg` to `out` using a
+ * flow summary.
+ *
+ * NOTE: This step should not be used in global data-flow/taint-tracking, but may
+ * be useful to include in the exposed local data-flow/taint-tracking relations.
+ */
+ predicate summaryGetterStep(ArgNode arg, Content c, Node out) {
+ exists(ReturnKindExt rk, Node mid, ReturnNodeExt ret |
+ summaryReadStep(summaryArgParam(arg, rk, out), c, mid) and
+ summaryLocalStep(mid, ret, _) and
+ ret.getKind() = rk
+ )
+ }
+
+ /**
+ * Holds if there is a (taint+)store of `arg` into content `c` of `out` using a
+ * flow summary.
+ *
+ * NOTE: This step should not be used in global data-flow/taint-tracking, but may
+ * be useful to include in the exposed local data-flow/taint-tracking relations.
+ */
+ predicate summarySetterStep(ArgNode arg, Content c, Node out) {
+ exists(ReturnKindExt rk, Node mid, ReturnNodeExt ret |
+ summaryLocalStep(summaryArgParam(arg, rk, out), mid, _) and
+ summaryStoreStep(mid, c, ret) and
+ ret.getKind() = rk
+ )
+ }
+ }
+
+ /**
+ * Provides a means of translating externally (e.g., CSV) defined flow
+ * summaries into a `SummarizedCallable`s.
+ */
+ module External {
+ /** Holds if `spec` is a relevant external specification. */
+ private predicate relevantSpec(string spec) {
+ summaryElement(_, spec, _, _) or
+ summaryElement(_, _, spec, _) or
+ sourceElement(_, spec, _) or
+ sinkElement(_, spec, _)
+ }
+
+ /** Holds if the `n`th component of specification `s` is `c`. */
+ predicate specSplit(string s, string c, int n) { relevantSpec(s) and s.splitAt(" of ", n) = c }
+
+ /** Holds if specification `s` has length `len`. */
+ predicate specLength(string s, int len) { len = 1 + max(int n | specSplit(s, _, n)) }
+
+ /** Gets the last component of specification `s`. */
+ string specLast(string s) {
+ exists(int len |
+ specLength(s, len) and
+ specSplit(s, result, len - 1)
+ )
+ }
+
+ /** Holds if specification component `c` parses as parameter `n`. */
+ predicate parseParam(string c, int n) {
+ specSplit(_, c, _) and
+ (
+ c.regexpCapture("Parameter\\[([-0-9]+)\\]", 1).toInt() = n
+ or
+ exists(int n1, int n2 |
+ c.regexpCapture("Parameter\\[([-0-9]+)\\.\\.([0-9]+)\\]", 1).toInt() = n1 and
+ c.regexpCapture("Parameter\\[([-0-9]+)\\.\\.([0-9]+)\\]", 2).toInt() = n2 and
+ n = [n1 .. n2]
+ )
+ )
+ }
+
+ /** Holds if specification component `c` parses as argument `n`. */
+ predicate parseArg(string c, int n) {
+ specSplit(_, c, _) and
+ (
+ c.regexpCapture("Argument\\[([-0-9]+)\\]", 1).toInt() = n
+ or
+ exists(int n1, int n2 |
+ c.regexpCapture("Argument\\[([-0-9]+)\\.\\.([0-9]+)\\]", 1).toInt() = n1 and
+ c.regexpCapture("Argument\\[([-0-9]+)\\.\\.([0-9]+)\\]", 2).toInt() = n2 and
+ n = [n1 .. n2]
+ )
+ )
+ }
+
+ private SummaryComponent interpretComponent(string c) {
+ specSplit(_, c, _) and
+ (
+ exists(int pos | parseArg(c, pos) and result = SummaryComponent::argument(pos))
+ or
+ exists(int pos | parseParam(c, pos) and result = SummaryComponent::parameter(pos))
+ or
+ c = "ReturnValue" and result = SummaryComponent::return(getReturnValueKind())
+ or
+ result = interpretComponentSpecific(c)
+ )
+ }
+
+ /**
+ * Holds if `spec` specifies summary component stack `stack`.
+ */
+ predicate interpretSpec(string spec, SummaryComponentStack stack) {
+ interpretSpec(spec, 0, stack)
+ }
+
+ private predicate interpretSpec(string spec, int idx, SummaryComponentStack stack) {
+ exists(string c |
+ relevantSpec(spec) and
+ specLength(spec, idx + 1) and
+ specSplit(spec, c, idx) and
+ stack = SummaryComponentStack::singleton(interpretComponent(c))
+ )
+ or
+ exists(SummaryComponent head, SummaryComponentStack tail |
+ interpretSpec(spec, idx, head, tail) and
+ stack = SummaryComponentStack::push(head, tail)
+ )
+ }
+
+ private predicate interpretSpec(
+ string output, int idx, SummaryComponent head, SummaryComponentStack tail
+ ) {
+ exists(string c |
+ interpretSpec(output, idx + 1, tail) and
+ specSplit(output, c, idx) and
+ head = interpretComponent(c)
+ )
+ }
+
+ private class MkStack extends RequiredSummaryComponentStack {
+ MkStack() { interpretSpec(_, _, _, this) }
+
+ override predicate required(SummaryComponent c) { interpretSpec(_, _, c, this) }
+ }
+
+ private class SummarizedCallableExternal extends SummarizedCallable {
+ SummarizedCallableExternal() { summaryElement(this, _, _, _) }
+
+ override predicate propagatesFlow(
+ SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
+ ) {
+ exists(string inSpec, string outSpec, string kind |
+ summaryElement(this, inSpec, outSpec, kind) and
+ interpretSpec(inSpec, input) and
+ interpretSpec(outSpec, output)
+ |
+ kind = "value" and preservesValue = true
+ or
+ kind = "taint" and preservesValue = false
+ )
+ }
+ }
+
+ /** Holds if component `c` of specification `spec` cannot be parsed. */
+ predicate invalidSpecComponent(string spec, string c) {
+ specSplit(spec, c, _) and
+ not exists(interpretComponent(c))
+ }
+
+ private predicate inputNeedsReference(string c) {
+ c = "Argument" or
+ parseArg(c, _)
+ }
+
+ private predicate outputNeedsReference(string c) {
+ c = "Argument" or
+ parseArg(c, _) or
+ c = "ReturnValue"
+ }
+
+ private predicate sourceElementRef(InterpretNode ref, string output, string kind) {
+ exists(SourceOrSinkElement e |
+ sourceElement(e, output, kind) and
+ if outputNeedsReference(specLast(output))
+ then e = ref.getCallTarget()
+ else e = ref.asElement()
+ )
+ }
+
+ private predicate sinkElementRef(InterpretNode ref, string input, string kind) {
+ exists(SourceOrSinkElement e |
+ sinkElement(e, input, kind) and
+ if inputNeedsReference(specLast(input))
+ then e = ref.getCallTarget()
+ else e = ref.asElement()
+ )
+ }
+
+ private predicate interpretOutput(string output, int idx, InterpretNode ref, InterpretNode node) {
+ sourceElementRef(ref, output, _) and
+ specLength(output, idx) and
+ node = ref
+ or
+ exists(InterpretNode mid, string c |
+ interpretOutput(output, idx + 1, ref, mid) and
+ specSplit(output, c, idx)
+ |
+ exists(int pos |
+ node.asNode().(PostUpdateNode).getPreUpdateNode().(ArgNode).argumentOf(mid.asCall(), pos)
+ |
+ c = "Argument" or parseArg(c, pos)
+ )
+ or
+ exists(int pos | node.asNode().(ParamNode).isParameterOf(mid.asCallable(), pos) |
+ c = "Parameter" or parseParam(c, pos)
+ )
+ or
+ c = "ReturnValue" and
+ node.asNode() = getAnOutNodeExt(mid.asCall(), TValueReturn(getReturnValueKind()))
+ or
+ interpretOutputSpecific(c, mid, node)
+ )
+ }
+
+ private predicate interpretInput(string input, int idx, InterpretNode ref, InterpretNode node) {
+ sinkElementRef(ref, input, _) and
+ specLength(input, idx) and
+ node = ref
+ or
+ exists(InterpretNode mid, string c |
+ interpretInput(input, idx + 1, ref, mid) and
+ specSplit(input, c, idx)
+ |
+ exists(int pos | node.asNode().(ArgNode).argumentOf(mid.asCall(), pos) |
+ c = "Argument" or parseArg(c, pos)
+ )
+ or
+ exists(ReturnNodeExt ret |
+ c = "ReturnValue" and
+ ret = node.asNode() and
+ ret.getKind().(ValueReturnKind).getKind() = getReturnValueKind() and
+ mid.asCallable() = getNodeEnclosingCallable(ret)
+ )
+ or
+ interpretInputSpecific(c, mid, node)
+ )
+ }
+
+ /**
+ * Holds if `node` is specified as a source with the given kind in a CSV flow
+ * model.
+ */
+ predicate isSourceNode(InterpretNode node, string kind) {
+ exists(InterpretNode ref, string output |
+ sourceElementRef(ref, output, kind) and
+ interpretOutput(output, 0, ref, node)
+ )
+ }
+
+ /**
+ * Holds if `node` is specified as a sink with the given kind in a CSV flow
+ * model.
+ */
+ predicate isSinkNode(InterpretNode node, string kind) {
+ exists(InterpretNode ref, string input |
+ sinkElementRef(ref, input, kind) and
+ interpretInput(input, 0, ref, node)
+ )
+ }
+ }
+
+ /** Provides a query predicate for outputting a set of relevant flow summaries. */
+ module TestOutput {
+ /** A flow summary to include in the `summary/3` query predicate. */
+ abstract class RelevantSummarizedCallable extends SummarizedCallable {
+ /** Gets the string representation of this callable used by `summary/3`. */
+ string getFullString() { result = this.toString() }
+ }
+
+ /** A query predicate for outputting flow summaries in QL tests. */
+ query predicate summary(string callable, string flow, boolean preservesValue) {
+ exists(
+ RelevantSummarizedCallable c, SummaryComponentStack input, SummaryComponentStack output
+ |
+ callable = c.getFullString() and
+ c.propagatesFlow(input, output, preservesValue) and
+ flow = input + " -> " + output
+ )
+ }
+ }
+
+ /**
+ * Provides query predicates for rendering the generated data flow graph for
+ * a summarized callable.
+ *
+ * Import this module into a `.ql` file of `@kind graph` to render the graph.
+ * The graph is restricted to callables from `RelevantSummarizedCallable`.
+ */
+ module RenderSummarizedCallable {
+ /** A summarized callable to include in the graph. */
+ abstract class RelevantSummarizedCallable extends SummarizedCallable { }
+
+ private newtype TNodeOrCall =
+ MkNode(Node n) {
+ exists(RelevantSummarizedCallable c |
+ n = summaryNode(c, _)
+ or
+ n.(ParamNode).isParameterOf(c, _)
+ )
+ } or
+ MkCall(DataFlowCall call) {
+ call = summaryDataFlowCall(_) and
+ call.getEnclosingCallable() instanceof RelevantSummarizedCallable
+ }
+
+ private class NodeOrCall extends TNodeOrCall {
+ Node asNode() { this = MkNode(result) }
+
+ DataFlowCall asCall() { this = MkCall(result) }
+
+ string toString() {
+ result = this.asNode().toString()
+ or
+ result = this.asCall().toString()
+ }
+
+ /**
+ * Holds if this element is at the specified location.
+ * The location spans column `startcolumn` of line `startline` to
+ * column `endcolumn` of line `endline` in file `filepath`.
+ * For more information, see
+ * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
+ */
+ predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
+ this.asNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ or
+ this.asCall().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ }
+ }
+
+ query predicate nodes(NodeOrCall n, string key, string val) {
+ key = "semmle.label" and val = n.toString()
+ }
+
+ private predicate edgesComponent(NodeOrCall a, NodeOrCall b, string value) {
+ exists(boolean preservesValue |
+ Private::Steps::summaryLocalStep(a.asNode(), b.asNode(), preservesValue) and
+ if preservesValue = true then value = "value" else value = "taint"
+ )
+ or
+ exists(Content c |
+ Private::Steps::summaryReadStep(a.asNode(), c, b.asNode()) and
+ value = "read (" + c + ")"
+ or
+ Private::Steps::summaryStoreStep(a.asNode(), c, b.asNode()) and
+ value = "store (" + c + ")"
+ or
+ Private::Steps::summaryClearsContent(a.asNode(), c) and
+ b = a and
+ value = "clear (" + c + ")"
+ )
+ or
+ summaryPostUpdateNode(b.asNode(), a.asNode()) and
+ value = "post-update"
+ or
+ b.asCall() = summaryDataFlowCall(a.asNode()) and
+ value = "receiver"
+ or
+ exists(int i |
+ summaryArgumentNode(b.asCall(), a.asNode(), i) and
+ value = "argument (" + i + ")"
+ )
+ }
+
+ query predicate edges(NodeOrCall a, NodeOrCall b, string key, string value) {
+ key = "semmle.label" and
+ value = strictconcat(string s | edgesComponent(a, b, s) | s, " / ")
+ }
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImplSpecific.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImplSpecific.qll
new file mode 100644
index 00000000000..6127450fb9c
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImplSpecific.qll
@@ -0,0 +1,120 @@
+/**
+ * Provides Ruby specific classes and predicates for defining flow summaries.
+ */
+
+private import ruby
+private import DataFlowDispatch
+private import DataFlowPrivate
+private import DataFlowPublic
+private import DataFlowImplCommon
+private import FlowSummaryImpl::Private
+private import FlowSummaryImpl::Public
+private import codeql.ruby.dataflow.FlowSummary as FlowSummary
+
+/** Holds is `i` is a valid parameter position. */
+predicate parameterPosition(int i) { i in [-2 .. 10] }
+
+/** Gets the parameter position of the instance parameter. */
+int instanceParameterPosition() { none() } // disables implicit summary flow to `self` for callbacks
+
+/** Gets the synthesized summary data-flow node for the given values. */
+Node summaryNode(SummarizedCallable c, SummaryNodeState state) { result = TSummaryNode(c, state) }
+
+/** Gets the synthesized data-flow call for `receiver`. */
+SummaryCall summaryDataFlowCall(Node receiver) { receiver = result.getReceiver() }
+
+/** Gets the type of content `c`. */
+DataFlowType getContentType(Content c) { any() }
+
+/** Gets the return type of kind `rk` for callable `c`. */
+bindingset[c, rk]
+DataFlowType getReturnType(SummarizedCallable c, ReturnKind rk) { any() }
+
+/**
+ * Gets the type of the `i`th parameter in a synthesized call that targets a
+ * callback of type `t`.
+ */
+bindingset[t, i]
+DataFlowType getCallbackParameterType(DataFlowType t, int i) { any() }
+
+/**
+ * Gets the return type of kind `rk` in a synthesized call that targets a
+ * callback of type `t`.
+ */
+DataFlowType getCallbackReturnType(DataFlowType t, ReturnKind rk) { any() }
+
+/**
+ * Holds if an external flow summary exists for `c` with input specification
+ * `input`, output specification `output`, and kind `kind`.
+ */
+predicate summaryElement(DataFlowCallable c, string input, string output, string kind) {
+ exists(FlowSummary::SummarizedCallable sc, boolean preservesValue |
+ sc.propagatesFlowExt(input, output, preservesValue) and
+ c.asLibraryCallable() = sc and
+ if preservesValue = true then kind = "value" else kind = "taint"
+ )
+}
+
+/**
+ * Gets the summary component for specification component `c`, if any.
+ *
+ * This covers all the Ruby-specific components of a flow summary, and
+ * is currently restricted to `"BlockArgument"`.
+ */
+SummaryComponent interpretComponentSpecific(string c) {
+ c = "BlockArgument" and
+ result = FlowSummary::SummaryComponent::block()
+ or
+ c = "Argument[_]" and
+ result = FlowSummary::SummaryComponent::argument(any(int i | i >= 0))
+}
+
+/** Gets the return kind corresponding to specification `"ReturnValue"`. */
+NormalReturnKind getReturnValueKind() { any() }
+
+/**
+ * All definitions in this module are required by the shared implementation
+ * (for source/sink interpretation), but they are unused for Ruby, where
+ * we rely on API graphs instead.
+ */
+private module UnusedSourceSinkInterpretation {
+ /**
+ * Holds if an external source specification exists for `e` with output specification
+ * `output` and kind `kind`.
+ */
+ predicate sourceElement(AstNode n, string output, string kind) { none() }
+
+ /**
+ * Holds if an external sink specification exists for `n` with input specification
+ * `input` and kind `kind`.
+ */
+ predicate sinkElement(AstNode n, string input, string kind) { none() }
+
+ class SourceOrSinkElement = AstNode;
+
+ /** An entity used to interpret a source/sink specification. */
+ class InterpretNode extends AstNode {
+ /** Gets the element that this node corresponds to, if any. */
+ SourceOrSinkElement asElement() { none() }
+
+ /** Gets the data-flow node that this node corresponds to, if any. */
+ Node asNode() { none() }
+
+ /** Gets the call that this node corresponds to, if any. */
+ DataFlowCall asCall() { none() }
+
+ /** Gets the callable that this node corresponds to, if any. */
+ DataFlowCallable asCallable() { none() }
+
+ /** Gets the target of this call, if any. */
+ Callable getCallTarget() { none() }
+ }
+
+ /** Provides additional sink specification logic. */
+ predicate interpretOutputSpecific(string c, InterpretNode mid, InterpretNode node) { none() }
+
+ /** Provides additional source specification logic. */
+ predicate interpretInputSpecific(string c, InterpretNode mid, InterpretNode node) { none() }
+}
+
+import UnusedSourceSinkInterpretation
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImpl.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImpl.qll
new file mode 100644
index 00000000000..54269c5cb59
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImpl.qll
@@ -0,0 +1,289 @@
+private import SsaImplCommon
+private import codeql.ruby.AST
+private import codeql.ruby.CFG
+private import codeql.ruby.ast.Variable
+private import CfgNodes::ExprNodes
+
+/** Holds if `v` is uninitialized at index `i` in entry block `bb`. */
+predicate uninitializedWrite(EntryBasicBlock bb, int i, LocalVariable v) {
+ v.getDeclaringScope() = bb.getScope() and
+ i = -1
+}
+
+/** Holds if `bb` contains a caputured read of variable `v`. */
+pragma[noinline]
+private predicate hasCapturedVariableRead(BasicBlock bb, LocalVariable v) {
+ exists(LocalVariableReadAccess read |
+ read = bb.getANode().getNode() and
+ read.isCapturedAccess() and
+ read.getVariable() = v
+ )
+}
+
+/**
+ * Holds if an entry definition is needed for captured variable `v` at index
+ * `i` in entry block `bb`.
+ */
+predicate capturedEntryWrite(EntryBasicBlock bb, int i, LocalVariable v) {
+ hasCapturedVariableRead(bb.getASuccessor*(), v) and
+ i = -1
+}
+
+/** Holds if `bb` contains a caputured write to variable `v`. */
+pragma[noinline]
+private predicate writesCapturedVariable(BasicBlock bb, LocalVariable v) {
+ exists(LocalVariableWriteAccess write |
+ write = bb.getANode().getNode() and
+ write.isCapturedAccess() and
+ write.getVariable() = v
+ )
+}
+
+/**
+ * Holds if a pseudo read of captured variable `v` should be inserted
+ * at index `i` in exit block `bb`.
+ */
+private predicate capturedExitRead(AnnotatedExitBasicBlock bb, int i, LocalVariable v) {
+ bb.isNormal() and
+ writesCapturedVariable(bb.getAPredecessor*(), v) and
+ i = bb.length()
+}
+
+private CfgScope getCaptureOuterCfgScope(CfgScope scope) {
+ result = scope.getOuterCfgScope() and
+ (
+ scope instanceof Block
+ or
+ scope instanceof Lambda
+ )
+}
+
+/** Holds if captured variable `v` is read inside `scope`. */
+pragma[noinline]
+private predicate hasCapturedRead(Variable v, CfgScope scope) {
+ any(LocalVariableReadAccess read |
+ read.getVariable() = v and scope = getCaptureOuterCfgScope*(read.getCfgScope())
+ ).isCapturedAccess()
+}
+
+pragma[noinline]
+private predicate hasVariableWriteWithCapturedRead(BasicBlock bb, LocalVariable v, CfgScope scope) {
+ hasCapturedRead(v, scope) and
+ exists(VariableWriteAccess write |
+ write = bb.getANode().getNode() and
+ write.getVariable() = v and
+ bb.getScope() = scope.getOuterCfgScope()
+ )
+}
+
+/**
+ * Holds if the call at index `i` in basic block `bb` may reach a callable
+ * that reads captured variable `v`.
+ */
+private predicate capturedCallRead(BasicBlock bb, int i, LocalVariable v) {
+ exists(CfgScope scope |
+ hasVariableWriteWithCapturedRead(bb.getAPredecessor*(), v, scope) and
+ bb.getNode(i).getNode() instanceof Call
+ |
+ not scope instanceof Block
+ or
+ // If the read happens inside a block, we restrict to the call that
+ // contains the block
+ scope = any(MethodCall c | bb.getNode(i) = c.getAControlFlowNode()).getBlock()
+ )
+}
+
+/** Holds if captured variable `v` is written inside `scope`. */
+pragma[noinline]
+private predicate hasCapturedWrite(Variable v, CfgScope scope) {
+ any(LocalVariableWriteAccess write |
+ write.getVariable() = v and scope = getCaptureOuterCfgScope*(write.getCfgScope())
+ ).isCapturedAccess()
+}
+
+/** Holds if `v` is read at index `i` in basic block `bb`. */
+private predicate variableReadActual(BasicBlock bb, int i, LocalVariable v) {
+ exists(VariableReadAccess read |
+ read.getVariable() = v and
+ read = bb.getNode(i).getNode()
+ )
+}
+
+predicate variableRead(BasicBlock bb, int i, LocalVariable v, boolean certain) {
+ variableReadActual(bb, i, v) and
+ certain = true
+ or
+ capturedCallRead(bb, i, v) and
+ certain = false
+ or
+ capturedExitRead(bb, i, v) and
+ certain = false
+}
+
+pragma[noinline]
+private predicate hasVariableReadWithCapturedWrite(BasicBlock bb, LocalVariable v, CfgScope scope) {
+ hasCapturedWrite(v, scope) and
+ exists(VariableReadAccess read |
+ read = bb.getANode().getNode() and
+ read.getVariable() = v and
+ bb.getScope() = scope.getOuterCfgScope()
+ )
+}
+
+cached
+private module Cached {
+ /**
+ * Holds if the call at index `i` in basic block `bb` may reach a callable
+ * that writes captured variable `v`.
+ */
+ cached
+ predicate capturedCallWrite(BasicBlock bb, int i, LocalVariable v) {
+ exists(CfgScope scope |
+ hasVariableReadWithCapturedWrite(bb.getASuccessor*(), v, scope) and
+ bb.getNode(i).getNode() instanceof Call
+ |
+ not scope instanceof Block
+ or
+ // If the write happens inside a block, we restrict to the call that
+ // contains the block
+ scope = any(MethodCall c | bb.getNode(i) = c.getAControlFlowNode()).getBlock()
+ )
+ }
+
+ /**
+ * Holds if `v` is written at index `i` in basic block `bb`, and the corresponding
+ * AST write access is `write`.
+ */
+ cached
+ predicate variableWriteActual(BasicBlock bb, int i, LocalVariable v, VariableWriteAccess write) {
+ exists(AstNode n |
+ write.getVariable() = v and
+ n = bb.getNode(i).getNode()
+ |
+ write.isExplicitWrite(n)
+ or
+ write.isImplicitWrite() and
+ n = write
+ )
+ }
+
+ cached
+ VariableReadAccessCfgNode getARead(Definition def) {
+ exists(LocalVariable v, BasicBlock bb, int i |
+ ssaDefReachesRead(v, def, bb, i) and
+ variableReadActual(bb, i, v) and
+ result = bb.getNode(i)
+ )
+ }
+
+ /**
+ * Holds if there is flow for a captured variable from the enclosing scope into a block.
+ * ```rb
+ * foo = 0
+ * bar {
+ * puts foo
+ * }
+ * ```
+ */
+ cached
+ predicate captureFlowIn(Definition def, Definition entry) {
+ exists(LocalVariable v, BasicBlock bb, int i |
+ ssaDefReachesRead(v, def, bb, i) and
+ capturedCallRead(bb, i, v) and
+ exists(BasicBlock bb2, int i2 |
+ capturedEntryWrite(bb2, i2, v) and
+ entry.definesAt(v, bb2, i2)
+ )
+ )
+ }
+
+ /**
+ * Holds if there is outgoing flow for a captured variable that is updated in a block.
+ * ```rb
+ * foo = 0
+ * bar {
+ * foo += 10
+ * }
+ * puts foo
+ * ```
+ */
+ cached
+ predicate captureFlowOut(Definition def, Definition exit) {
+ exists(LocalVariable v, BasicBlock bb, int i |
+ ssaDefReachesRead(v, def, bb, i) and
+ capturedExitRead(bb, i, v) and
+ exists(BasicBlock bb2, int i2 |
+ capturedCallWrite(bb2, i2, v) and
+ exit.definesAt(v, bb2, i2)
+ )
+ )
+ }
+
+ cached
+ Definition phiHasInputFromBlock(PhiNode phi, BasicBlock bb) {
+ phiHasInputFromBlock(phi, result, bb)
+ }
+
+ /**
+ * Holds if the value defined at SSA definition `def` can reach a read at `read`,
+ * without passing through any other non-pseudo read.
+ */
+ cached
+ predicate firstRead(Definition def, VariableReadAccessCfgNode read) {
+ exists(BasicBlock bb1, int i1, BasicBlock bb2, int i2 |
+ def.definesAt(_, bb1, i1) and
+ adjacentDefNoUncertainReads(def, bb1, i1, bb2, i2) and
+ read = bb2.getNode(i2)
+ )
+ }
+
+ /**
+ * Holds if the read at `read2` is a read of the same SSA definition `def`
+ * as the read at `read1`, and `read2` can be reached from `read1` without
+ * passing through another non-pseudo read.
+ */
+ cached
+ predicate adjacentReadPair(
+ Definition def, VariableReadAccessCfgNode read1, VariableReadAccessCfgNode read2
+ ) {
+ exists(BasicBlock bb1, int i1, BasicBlock bb2, int i2 |
+ read1 = bb1.getNode(i1) and
+ variableReadActual(bb1, i1, _) and
+ adjacentDefNoUncertainReads(def, bb1, i1, bb2, i2) and
+ read2 = bb2.getNode(i2)
+ )
+ }
+
+ /**
+ * Holds if the read of `def` at `read` may be a last read. That is, `read`
+ * can either reach another definition of the underlying source variable or
+ * the end of the CFG scope, without passing through another non-pseudo read.
+ */
+ cached
+ predicate lastRead(Definition def, VariableReadAccessCfgNode read) {
+ exists(BasicBlock bb, int i |
+ lastRefNoUncertainReads(def, bb, i) and
+ variableReadActual(bb, i, _) and
+ read = bb.getNode(i)
+ )
+ }
+
+ /**
+ * Holds if the reference to `def` at index `i` in basic block `bb` can reach
+ * another definition `next` of the same underlying source variable, without
+ * passing through another write or non-pseudo read.
+ *
+ * The reference is either a read of `def` or `def` itself.
+ */
+ cached
+ predicate lastRefBeforeRedef(Definition def, BasicBlock bb, int i, Definition next) {
+ lastRefRedefNoUncertainReads(def, bb, i, next)
+ }
+
+ cached
+ Definition uncertainWriteDefinitionInput(UncertainWriteDefinition def) {
+ uncertainWriteDefinitionInput(def, result)
+ }
+}
+
+import Cached
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplCommon.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplCommon.qll
new file mode 100644
index 00000000000..eae5d23f544
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplCommon.qll
@@ -0,0 +1,636 @@
+/**
+ * Provides a language-independent implementation of static single assignment
+ * (SSA) form.
+ */
+
+private import SsaImplSpecific
+
+private BasicBlock getABasicBlockPredecessor(BasicBlock bb) { getABasicBlockSuccessor(result) = bb }
+
+/**
+ * Liveness analysis (based on source variables) to restrict the size of the
+ * SSA representation.
+ */
+private module Liveness {
+ /**
+ * A classification of variable references into reads (of a given kind) and
+ * (certain or uncertain) writes.
+ */
+ private newtype TRefKind =
+ Read(boolean certain) { certain in [false, true] } or
+ Write(boolean certain) { certain in [false, true] }
+
+ private class RefKind extends TRefKind {
+ string toString() {
+ exists(boolean certain | this = Read(certain) and result = "read (" + certain + ")")
+ or
+ exists(boolean certain | this = Write(certain) and result = "write (" + certain + ")")
+ }
+
+ int getOrder() {
+ this = Read(_) and
+ result = 0
+ or
+ this = Write(_) and
+ result = 1
+ }
+ }
+
+ /**
+ * Holds if the `i`th node of basic block `bb` is a reference to `v` of kind `k`.
+ */
+ private predicate ref(BasicBlock bb, int i, SourceVariable v, RefKind k) {
+ exists(boolean certain | variableRead(bb, i, v, certain) | k = Read(certain))
+ or
+ exists(boolean certain | variableWrite(bb, i, v, certain) | k = Write(certain))
+ }
+
+ private newtype OrderedRefIndex =
+ MkOrderedRefIndex(int i, int tag) {
+ exists(RefKind rk | ref(_, i, _, rk) | tag = rk.getOrder())
+ }
+
+ private OrderedRefIndex refOrd(BasicBlock bb, int i, SourceVariable v, RefKind k, int ord) {
+ ref(bb, i, v, k) and
+ result = MkOrderedRefIndex(i, ord) and
+ ord = k.getOrder()
+ }
+
+ /**
+ * Gets the (1-based) rank of the reference to `v` at the `i`th node of
+ * basic block `bb`, which has the given reference kind `k`.
+ *
+ * Reads are considered before writes when they happen at the same index.
+ */
+ private int refRank(BasicBlock bb, int i, SourceVariable v, RefKind k) {
+ refOrd(bb, i, v, k, _) =
+ rank[result](int j, int ord, OrderedRefIndex res |
+ res = refOrd(bb, j, v, _, ord)
+ |
+ res order by j, ord
+ )
+ }
+
+ private int maxRefRank(BasicBlock bb, SourceVariable v) {
+ result = refRank(bb, _, v, _) and
+ not result + 1 = refRank(bb, _, v, _)
+ }
+
+ /**
+ * Gets the (1-based) rank of the first reference to `v` inside basic block `bb`
+ * that is either a read or a certain write.
+ */
+ private int firstReadOrCertainWrite(BasicBlock bb, SourceVariable v) {
+ result =
+ min(int r, RefKind k |
+ r = refRank(bb, _, v, k) and
+ k != Write(false)
+ |
+ r
+ )
+ }
+
+ /**
+ * Holds if source variable `v` is live at the beginning of basic block `bb`.
+ */
+ predicate liveAtEntry(BasicBlock bb, SourceVariable v) {
+ // The first read or certain write to `v` inside `bb` is a read
+ refRank(bb, _, v, Read(_)) = firstReadOrCertainWrite(bb, v)
+ or
+ // There is no certain write to `v` inside `bb`, but `v` is live at entry
+ // to a successor basic block of `bb`
+ not exists(firstReadOrCertainWrite(bb, v)) and
+ liveAtExit(bb, v)
+ }
+
+ /**
+ * Holds if source variable `v` is live at the end of basic block `bb`.
+ */
+ predicate liveAtExit(BasicBlock bb, SourceVariable v) {
+ liveAtEntry(getABasicBlockSuccessor(bb), v)
+ }
+
+ /**
+ * Holds if variable `v` is live in basic block `bb` at index `i`.
+ * The rank of `i` is `rnk` as defined by `refRank()`.
+ */
+ private predicate liveAtRank(BasicBlock bb, int i, SourceVariable v, int rnk) {
+ exists(RefKind kind | rnk = refRank(bb, i, v, kind) |
+ rnk = maxRefRank(bb, v) and
+ liveAtExit(bb, v)
+ or
+ ref(bb, i, v, kind) and
+ kind = Read(_)
+ or
+ exists(RefKind nextKind |
+ liveAtRank(bb, _, v, rnk + 1) and
+ rnk + 1 = refRank(bb, _, v, nextKind) and
+ nextKind != Write(true)
+ )
+ )
+ }
+
+ /**
+ * Holds if variable `v` is live after the (certain or uncertain) write at
+ * index `i` inside basic block `bb`.
+ */
+ predicate liveAfterWrite(BasicBlock bb, int i, SourceVariable v) {
+ exists(int rnk | rnk = refRank(bb, i, v, Write(_)) | liveAtRank(bb, i, v, rnk))
+ }
+}
+
+private import Liveness
+
+/**
+ * Holds if `df` is in the dominance frontier of `bb`.
+ *
+ * This is equivalent to:
+ *
+ * ```ql
+ * bb = getImmediateBasicBlockDominator*(getABasicBlockPredecessor(df)) and
+ * not bb = getImmediateBasicBlockDominator+(df)
+ * ```
+ */
+private predicate inDominanceFrontier(BasicBlock bb, BasicBlock df) {
+ bb = getABasicBlockPredecessor(df) and not bb = getImmediateBasicBlockDominator(df)
+ or
+ exists(BasicBlock prev | inDominanceFrontier(prev, df) |
+ bb = getImmediateBasicBlockDominator(prev) and
+ not bb = getImmediateBasicBlockDominator(df)
+ )
+}
+
+/**
+ * Holds if `bb` is in the dominance frontier of a block containing a
+ * definition of `v`.
+ */
+pragma[noinline]
+private predicate inDefDominanceFrontier(BasicBlock bb, SourceVariable v) {
+ exists(BasicBlock defbb, Definition def |
+ def.definesAt(v, defbb, _) and
+ inDominanceFrontier(defbb, bb)
+ )
+}
+
+cached
+newtype TDefinition =
+ TWriteDef(SourceVariable v, BasicBlock bb, int i) {
+ variableWrite(bb, i, v, _) and
+ liveAfterWrite(bb, i, v)
+ } or
+ TPhiNode(SourceVariable v, BasicBlock bb) {
+ inDefDominanceFrontier(bb, v) and
+ liveAtEntry(bb, v)
+ }
+
+private module SsaDefReaches {
+ newtype TSsaRefKind =
+ SsaRead() or
+ SsaDef()
+
+ /**
+ * A classification of SSA variable references into reads and definitions.
+ */
+ class SsaRefKind extends TSsaRefKind {
+ string toString() {
+ this = SsaRead() and
+ result = "SsaRead"
+ or
+ this = SsaDef() and
+ result = "SsaDef"
+ }
+
+ int getOrder() {
+ this = SsaRead() and
+ result = 0
+ or
+ this = SsaDef() and
+ result = 1
+ }
+ }
+
+ /**
+ * Holds if the `i`th node of basic block `bb` is a reference to `v`,
+ * either a read (when `k` is `SsaRead()`) or an SSA definition (when `k`
+ * is `SsaDef()`).
+ *
+ * Unlike `Liveness::ref`, this includes `phi` nodes.
+ */
+ predicate ssaRef(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) {
+ variableRead(bb, i, v, _) and
+ k = SsaRead()
+ or
+ exists(Definition def | def.definesAt(v, bb, i)) and
+ k = SsaDef()
+ }
+
+ private newtype OrderedSsaRefIndex =
+ MkOrderedSsaRefIndex(int i, SsaRefKind k) { ssaRef(_, i, _, k) }
+
+ private OrderedSsaRefIndex ssaRefOrd(BasicBlock bb, int i, SourceVariable v, SsaRefKind k, int ord) {
+ ssaRef(bb, i, v, k) and
+ result = MkOrderedSsaRefIndex(i, k) and
+ ord = k.getOrder()
+ }
+
+ /**
+ * Gets the (1-based) rank of the reference to `v` at the `i`th node of basic
+ * block `bb`, which has the given reference kind `k`.
+ *
+ * For example, if `bb` is a basic block with a phi node for `v` (considered
+ * to be at index -1), reads `v` at node 2, and defines it at node 5, we have:
+ *
+ * ```ql
+ * ssaRefRank(bb, -1, v, SsaDef()) = 1 // phi node
+ * ssaRefRank(bb, 2, v, Read()) = 2 // read at node 2
+ * ssaRefRank(bb, 5, v, SsaDef()) = 3 // definition at node 5
+ * ```
+ *
+ * Reads are considered before writes when they happen at the same index.
+ */
+ int ssaRefRank(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) {
+ ssaRefOrd(bb, i, v, k, _) =
+ rank[result](int j, int ord, OrderedSsaRefIndex res |
+ res = ssaRefOrd(bb, j, v, _, ord)
+ |
+ res order by j, ord
+ )
+ }
+
+ int maxSsaRefRank(BasicBlock bb, SourceVariable v) {
+ result = ssaRefRank(bb, _, v, _) and
+ not result + 1 = ssaRefRank(bb, _, v, _)
+ }
+
+ /**
+ * Holds if the SSA definition `def` reaches rank index `rnk` in its own
+ * basic block `bb`.
+ */
+ predicate ssaDefReachesRank(BasicBlock bb, Definition def, int rnk, SourceVariable v) {
+ exists(int i |
+ rnk = ssaRefRank(bb, i, v, SsaDef()) and
+ def.definesAt(v, bb, i)
+ )
+ or
+ ssaDefReachesRank(bb, def, rnk - 1, v) and
+ rnk = ssaRefRank(bb, _, v, SsaRead())
+ }
+
+ /**
+ * Holds if the SSA definition of `v` at `def` reaches index `i` in the same
+ * basic block `bb`, without crossing another SSA definition of `v`.
+ */
+ predicate ssaDefReachesReadWithinBlock(SourceVariable v, Definition def, BasicBlock bb, int i) {
+ exists(int rnk |
+ ssaDefReachesRank(bb, def, rnk, v) and
+ rnk = ssaRefRank(bb, i, v, SsaRead())
+ )
+ }
+
+ /**
+ * Holds if the SSA definition of `v` at `def` reaches uncertain SSA definition
+ * `redef` in the same basic block, without crossing another SSA definition of `v`.
+ */
+ predicate ssaDefReachesUncertainDefWithinBlock(
+ SourceVariable v, Definition def, UncertainWriteDefinition redef
+ ) {
+ exists(BasicBlock bb, int rnk, int i |
+ ssaDefReachesRank(bb, def, rnk, v) and
+ rnk = ssaRefRank(bb, i, v, SsaDef()) - 1 and
+ redef.definesAt(v, bb, i)
+ )
+ }
+
+ /**
+ * Same as `ssaRefRank()`, but restricted to a particular SSA definition `def`.
+ */
+ int ssaDefRank(Definition def, SourceVariable v, BasicBlock bb, int i, SsaRefKind k) {
+ v = def.getSourceVariable() and
+ result = ssaRefRank(bb, i, v, k) and
+ (
+ ssaDefReachesRead(_, def, bb, i)
+ or
+ def.definesAt(_, bb, i)
+ )
+ }
+
+ /**
+ * Holds if the reference to `def` at index `i` in basic block `bb` is the
+ * last reference to `v` inside `bb`.
+ */
+ pragma[noinline]
+ predicate lastSsaRef(Definition def, SourceVariable v, BasicBlock bb, int i) {
+ ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v)
+ }
+
+ predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v) {
+ exists(ssaDefRank(def, v, bb, _, _))
+ }
+
+ pragma[noinline]
+ private predicate ssaDefReachesThroughBlock(Definition def, BasicBlock bb) {
+ ssaDefReachesEndOfBlock(bb, def, _) and
+ not defOccursInBlock(_, bb, def.getSourceVariable())
+ }
+
+ /**
+ * Holds if `def` is accessed in basic block `bb1` (either a read or a write),
+ * `bb2` is a transitive successor of `bb1`, `def` is live at the end of `bb1`,
+ * and the underlying variable for `def` is neither read nor written in any block
+ * on the path between `bb1` and `bb2`.
+ */
+ predicate varBlockReaches(Definition def, BasicBlock bb1, BasicBlock bb2) {
+ defOccursInBlock(def, bb1, _) and
+ bb2 = getABasicBlockSuccessor(bb1)
+ or
+ exists(BasicBlock mid |
+ varBlockReaches(def, bb1, mid) and
+ ssaDefReachesThroughBlock(def, mid) and
+ bb2 = getABasicBlockSuccessor(mid)
+ )
+ }
+
+ /**
+ * Holds if `def` is accessed in basic block `bb1` (either a read or a write),
+ * `def` is read at index `i2` in basic block `bb2`, `bb2` is in a transitive
+ * successor block of `bb1`, and `def` is neither read nor written in any block
+ * on a path between `bb1` and `bb2`.
+ */
+ predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) {
+ varBlockReaches(def, bb1, bb2) and
+ ssaRefRank(bb2, i2, def.getSourceVariable(), SsaRead()) = 1
+ }
+}
+
+private import SsaDefReaches
+
+pragma[nomagic]
+predicate liveThrough(BasicBlock bb, SourceVariable v) {
+ liveAtExit(bb, v) and
+ not ssaRef(bb, _, v, SsaDef())
+}
+
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Holds if the SSA definition of `v` at `def` reaches the end of basic
+ * block `bb`, at which point it is still live, without crossing another
+ * SSA definition of `v`.
+ */
+pragma[nomagic]
+predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def, SourceVariable v) {
+ exists(int last | last = maxSsaRefRank(bb, v) |
+ ssaDefReachesRank(bb, def, last, v) and
+ liveAtExit(bb, v)
+ )
+ or
+ // The construction of SSA form ensures that each read of a variable is
+ // dominated by its definition. An SSA definition therefore reaches a
+ // control flow node if it is the _closest_ SSA definition that dominates
+ // the node. If two definitions dominate a node then one must dominate the
+ // other, so therefore the definition of _closest_ is given by the dominator
+ // tree. Thus, reaching definitions can be calculated in terms of dominance.
+ ssaDefReachesEndOfBlock(getImmediateBasicBlockDominator(bb), def, pragma[only_bind_into](v)) and
+ liveThrough(bb, pragma[only_bind_into](v))
+}
+
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Holds if `inp` is an input to the phi node `phi` along the edge originating in `bb`.
+ */
+pragma[nomagic]
+predicate phiHasInputFromBlock(PhiNode phi, Definition inp, BasicBlock bb) {
+ exists(SourceVariable v, BasicBlock bbDef |
+ phi.definesAt(v, bbDef, _) and
+ getABasicBlockPredecessor(bbDef) = bb and
+ ssaDefReachesEndOfBlock(bb, inp, v)
+ )
+}
+
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Holds if the SSA definition of `v` at `def` reaches a read at index `i` in
+ * basic block `bb`, without crossing another SSA definition of `v`. The read
+ * is of kind `rk`.
+ */
+pragma[nomagic]
+predicate ssaDefReachesRead(SourceVariable v, Definition def, BasicBlock bb, int i) {
+ ssaDefReachesReadWithinBlock(v, def, bb, i)
+ or
+ variableRead(bb, i, v, _) and
+ ssaDefReachesEndOfBlock(getABasicBlockPredecessor(bb), def, v) and
+ not ssaDefReachesReadWithinBlock(v, _, bb, i)
+}
+
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Holds if `def` is accessed at index `i1` in basic block `bb1` (either a read
+ * or a write), `def` is read at index `i2` in basic block `bb2`, and there is a
+ * path between them without any read of `def`.
+ */
+pragma[nomagic]
+predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) {
+ exists(int rnk |
+ rnk = ssaDefRank(def, _, bb1, i1, _) and
+ rnk + 1 = ssaDefRank(def, _, bb1, i2, SsaRead()) and
+ variableRead(bb1, i2, _, _) and
+ bb2 = bb1
+ )
+ or
+ lastSsaRef(def, _, bb1, i1) and
+ defAdjacentRead(def, bb1, bb2, i2)
+}
+
+pragma[noinline]
+private predicate adjacentDefRead(
+ Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v
+) {
+ adjacentDefRead(def, bb1, i1, bb2, i2) and
+ v = def.getSourceVariable()
+}
+
+private predicate adjacentDefReachesRead(
+ Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2
+) {
+ exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) |
+ ssaRef(bb1, i1, v, SsaDef())
+ or
+ variableRead(bb1, i1, v, true)
+ )
+ or
+ exists(BasicBlock bb3, int i3 |
+ adjacentDefReachesRead(def, bb1, i1, bb3, i3) and
+ variableRead(bb3, i3, _, false) and
+ adjacentDefRead(def, bb3, i3, bb2, i2)
+ )
+}
+
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Same as `adjacentDefRead`, but ignores uncertain reads.
+ */
+pragma[nomagic]
+predicate adjacentDefNoUncertainReads(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) {
+ adjacentDefReachesRead(def, bb1, i1, bb2, i2) and
+ variableRead(bb2, i2, _, true)
+}
+
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Holds if the node at index `i` in `bb` is a last reference to SSA definition
+ * `def`. The reference is last because it can reach another write `next`,
+ * without passing through another read or write.
+ */
+pragma[nomagic]
+predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) {
+ exists(SourceVariable v |
+ // Next reference to `v` inside `bb` is a write
+ exists(int rnk, int j |
+ rnk = ssaDefRank(def, v, bb, i, _) and
+ next.definesAt(v, bb, j) and
+ rnk + 1 = ssaRefRank(bb, j, v, SsaDef())
+ )
+ or
+ // Can reach a write using one or more steps
+ lastSsaRef(def, v, bb, i) and
+ exists(BasicBlock bb2 |
+ varBlockReaches(def, bb, bb2) and
+ 1 = ssaDefRank(next, v, bb2, _, SsaDef())
+ )
+ )
+}
+
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Holds if `inp` is an immediately preceding definition of uncertain definition
+ * `def`. Since `def` is uncertain, the value from the preceding definition might
+ * still be valid.
+ */
+pragma[nomagic]
+predicate uncertainWriteDefinitionInput(UncertainWriteDefinition def, Definition inp) {
+ lastRefRedef(inp, _, _, def)
+}
+
+private predicate adjacentDefReachesUncertainRead(
+ Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2
+) {
+ adjacentDefReachesRead(def, bb1, i1, bb2, i2) and
+ variableRead(bb2, i2, _, false)
+}
+
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Same as `lastRefRedef`, but ignores uncertain reads.
+ */
+pragma[nomagic]
+predicate lastRefRedefNoUncertainReads(Definition def, BasicBlock bb, int i, Definition next) {
+ lastRefRedef(def, bb, i, next) and
+ not variableRead(bb, i, def.getSourceVariable(), false)
+ or
+ exists(BasicBlock bb0, int i0 |
+ lastRefRedef(def, bb0, i0, next) and
+ adjacentDefReachesUncertainRead(def, bb, i, bb0, i0)
+ )
+}
+
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Holds if the node at index `i` in `bb` is a last reference to SSA
+ * definition `def`.
+ *
+ * That is, the node can reach the end of the enclosing callable, or another
+ * SSA definition for the underlying source variable, without passing through
+ * another read.
+ */
+pragma[nomagic]
+predicate lastRef(Definition def, BasicBlock bb, int i) {
+ lastRefRedef(def, bb, i, _)
+ or
+ lastSsaRef(def, _, bb, i) and
+ (
+ // Can reach exit directly
+ bb instanceof ExitBasicBlock
+ or
+ // Can reach a block using one or more steps, where `def` is no longer live
+ exists(BasicBlock bb2 | varBlockReaches(def, bb, bb2) |
+ not defOccursInBlock(def, bb2, _) and
+ not ssaDefReachesEndOfBlock(bb2, def, _)
+ )
+ )
+}
+
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Same as `lastRefRedef`, but ignores uncertain reads.
+ */
+pragma[nomagic]
+predicate lastRefNoUncertainReads(Definition def, BasicBlock bb, int i) {
+ lastRef(def, bb, i) and
+ not variableRead(bb, i, def.getSourceVariable(), false)
+ or
+ exists(BasicBlock bb0, int i0 |
+ lastRef(def, bb0, i0) and
+ adjacentDefReachesUncertainRead(def, bb, i, bb0, i0)
+ )
+}
+
+/** A static single assignment (SSA) definition. */
+class Definition extends TDefinition {
+ /** Gets the source variable underlying this SSA definition. */
+ SourceVariable getSourceVariable() { this.definesAt(result, _, _) }
+
+ /**
+ * Holds if this SSA definition defines `v` at index `i` in basic block `bb`.
+ * Phi nodes are considered to be at index `-1`, while normal variable writes
+ * are at the index of the control flow node they wrap.
+ */
+ final predicate definesAt(SourceVariable v, BasicBlock bb, int i) {
+ this = TWriteDef(v, bb, i)
+ or
+ this = TPhiNode(v, bb) and i = -1
+ }
+
+ /** Gets the basic block to which this SSA definition belongs. */
+ final BasicBlock getBasicBlock() { this.definesAt(_, result, _) }
+
+ /** Gets a textual representation of this SSA definition. */
+ string toString() { none() }
+}
+
+/** An SSA definition that corresponds to a write. */
+class WriteDefinition extends Definition, TWriteDef {
+ private SourceVariable v;
+ private BasicBlock bb;
+ private int i;
+
+ WriteDefinition() { this = TWriteDef(v, bb, i) }
+
+ override string toString() { result = "WriteDef" }
+}
+
+/** A phi node. */
+class PhiNode extends Definition, TPhiNode {
+ override string toString() { result = "Phi" }
+}
+
+/**
+ * An SSA definition that represents an uncertain update of the underlying
+ * source variable.
+ */
+class UncertainWriteDefinition extends WriteDefinition {
+ UncertainWriteDefinition() {
+ exists(SourceVariable v, BasicBlock bb, int i |
+ this.definesAt(v, bb, i) and
+ variableWrite(bb, i, v, false)
+ )
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplSpecific.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplSpecific.qll
new file mode 100644
index 00000000000..b363dd526a3
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplSpecific.qll
@@ -0,0 +1,47 @@
+/** Provides the Ruby specific parameters for `SsaImplCommon.qll`. */
+
+private import SsaImpl as SsaImpl
+private import codeql.ruby.AST
+private import codeql.ruby.ast.Parameter
+private import codeql.ruby.ast.Variable
+private import codeql.ruby.controlflow.BasicBlocks as BasicBlocks
+private import codeql.ruby.controlflow.ControlFlowGraph
+
+class BasicBlock = BasicBlocks::BasicBlock;
+
+BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { result = bb.getImmediateDominator() }
+
+BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() }
+
+class ExitBasicBlock = BasicBlocks::ExitBasicBlock;
+
+class SourceVariable = LocalVariable;
+
+/**
+ * Holds if the statement at index `i` of basic block `bb` contains a write to variable `v`.
+ * `certain` is true if the write definitely occurs.
+ */
+predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) {
+ (
+ exists(Scope scope | scope = v.(SelfVariable).getDeclaringScope() |
+ // We consider the `self` variable to have a single write at the entry to a method block...
+ scope = bb.(BasicBlocks::EntryBasicBlock).getScope() and
+ i = 0
+ or
+ // ...or a class or module block.
+ bb.getNode(i).getNode() = scope.(ModuleBase).getAControlFlowEntryNode()
+ )
+ or
+ SsaImpl::uninitializedWrite(bb, i, v)
+ or
+ SsaImpl::capturedEntryWrite(bb, i, v)
+ or
+ SsaImpl::variableWriteActual(bb, i, v, _)
+ ) and
+ certain = true
+ or
+ SsaImpl::capturedCallWrite(bb, i, v) and
+ certain = false
+}
+
+predicate variableRead = SsaImpl::variableRead/4;
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/TaintTrackingPrivate.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/TaintTrackingPrivate.qll
new file mode 100755
index 00000000000..86c8ffb7f50
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/TaintTrackingPrivate.qll
@@ -0,0 +1,41 @@
+private import ruby
+private import TaintTrackingPublic
+private import codeql.ruby.CFG
+private import codeql.ruby.DataFlow
+private import FlowSummaryImpl as FlowSummaryImpl
+
+/**
+ * Holds if `node` should be a sanitizer in all global taint flow configurations
+ * but not in local taint.
+ */
+predicate defaultTaintSanitizer(DataFlow::Node node) { none() }
+
+/**
+ * Holds if default `TaintTracking::Configuration`s should allow implicit reads
+ * of `c` at sinks and inputs to additional taint steps.
+ */
+bindingset[node]
+predicate defaultImplicitTaintRead(DataFlow::Node node, DataFlow::Content c) { none() }
+
+/**
+ * Holds if the additional step from `nodeFrom` to `nodeTo` should be included
+ * in all global taint flow configurations.
+ */
+cached
+predicate defaultAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
+ // operation involving `nodeFrom`
+ exists(CfgNodes::ExprNodes::OperationCfgNode op |
+ op = nodeTo.asExpr() and
+ op.getAnOperand() = nodeFrom.asExpr() and
+ not op.getExpr() instanceof AssignExpr
+ )
+ or
+ // string interpolation of `nodeFrom` into `nodeTo`
+ nodeFrom.asExpr() =
+ nodeTo.asExpr().(CfgNodes::ExprNodes::StringlikeLiteralCfgNode).getAComponent()
+ or
+ // element reference from nodeFrom
+ nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::ElementReferenceCfgNode).getReceiver()
+ or
+ FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, false)
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/TaintTrackingPublic.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/TaintTrackingPublic.qll
new file mode 100755
index 00000000000..3fe5659bdc7
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/TaintTrackingPublic.qll
@@ -0,0 +1,31 @@
+private import ruby
+private import TaintTrackingPrivate
+private import codeql.ruby.CFG
+private import codeql.ruby.DataFlow
+private import FlowSummaryImpl as FlowSummaryImpl
+
+/**
+ * Holds if taint propagates from `source` to `sink` in zero or more local
+ * (intra-procedural) steps.
+ */
+predicate localTaint(DataFlow::Node source, DataFlow::Node sink) { localTaintStep*(source, sink) }
+
+/**
+ * Holds if taint can flow from `e1` to `e2` in zero or more
+ * local (intra-procedural) steps.
+ */
+predicate localExprTaint(CfgNodes::ExprCfgNode e1, CfgNodes::ExprCfgNode e2) {
+ localTaint(DataFlow::exprNode(e1), DataFlow::exprNode(e2))
+}
+
+/**
+ * Holds if taint propagates from `nodeFrom` to `nodeTo` in exactly one local
+ * (intra-procedural) step.
+ */
+predicate localTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
+ defaultAdditionalTaintStep(nodeFrom, nodeTo)
+ or
+ // Simple flow through library code is included in the exposed local
+ // step relation, even though flow is technically inter-procedural
+ FlowSummaryImpl::Private::Steps::summaryThroughStep(nodeFrom, nodeTo, false)
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/tainttracking1/TaintTrackingImpl.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/tainttracking1/TaintTrackingImpl.qll
new file mode 100644
index 00000000000..acb029c23d9
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/tainttracking1/TaintTrackingImpl.qll
@@ -0,0 +1,122 @@
+/**
+ * Provides an implementation of global (interprocedural) taint tracking.
+ * This file re-exports the local (intraprocedural) taint-tracking analysis
+ * from `TaintTrackingParameter::Public` and adds a global analysis, mainly
+ * exposed through the `Configuration` class. For some languages, this file
+ * exists in several identical copies, allowing queries to use multiple
+ * `Configuration` classes that depend on each other without introducing
+ * mutual recursion among those configurations.
+ */
+
+import TaintTrackingParameter::Public
+private import TaintTrackingParameter::Private
+
+/**
+ * A configuration of interprocedural taint tracking analysis. This defines
+ * sources, sinks, and any other configurable aspect of the analysis. Each
+ * use of the taint tracking library must define its own unique extension of
+ * this abstract class.
+ *
+ * A taint-tracking configuration is a special data flow configuration
+ * (`DataFlow::Configuration`) that allows for flow through nodes that do not
+ * necessarily preserve values but are still relevant from a taint tracking
+ * perspective. (For example, string concatenation, where one of the operands
+ * is tainted.)
+ *
+ * To create a configuration, extend this class with a subclass whose
+ * characteristic predicate is a unique singleton string. For example, write
+ *
+ * ```ql
+ * class MyAnalysisConfiguration extends TaintTracking::Configuration {
+ * MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" }
+ * // Override `isSource` and `isSink`.
+ * // Optionally override `isSanitizer`.
+ * // Optionally override `isSanitizerIn`.
+ * // Optionally override `isSanitizerOut`.
+ * // Optionally override `isSanitizerGuard`.
+ * // Optionally override `isAdditionalTaintStep`.
+ * }
+ * ```
+ *
+ * Then, to query whether there is flow between some `source` and `sink`,
+ * write
+ *
+ * ```ql
+ * exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink))
+ * ```
+ *
+ * Multiple configurations can coexist, but it is unsupported to depend on
+ * another `TaintTracking::Configuration` or a `DataFlow::Configuration` in the
+ * overridden predicates that define sources, sinks, or additional steps.
+ * Instead, the dependency should go to a `TaintTracking2::Configuration` or a
+ * `DataFlow2::Configuration`, `DataFlow3::Configuration`, etc.
+ */
+abstract class Configuration extends DataFlow::Configuration {
+ bindingset[this]
+ Configuration() { any() }
+
+ /**
+ * Holds if `source` is a relevant taint source.
+ *
+ * The smaller this predicate is, the faster `hasFlow()` will converge.
+ */
+ // overridden to provide taint-tracking specific qldoc
+ abstract override predicate isSource(DataFlow::Node source);
+
+ /**
+ * Holds if `sink` is a relevant taint sink.
+ *
+ * The smaller this predicate is, the faster `hasFlow()` will converge.
+ */
+ // overridden to provide taint-tracking specific qldoc
+ abstract override predicate isSink(DataFlow::Node sink);
+
+ /** Holds if the node `node` is a taint sanitizer. */
+ predicate isSanitizer(DataFlow::Node node) { none() }
+
+ final override predicate isBarrier(DataFlow::Node node) {
+ this.isSanitizer(node) or
+ defaultTaintSanitizer(node)
+ }
+
+ /** Holds if taint propagation into `node` is prohibited. */
+ predicate isSanitizerIn(DataFlow::Node node) { none() }
+
+ final override predicate isBarrierIn(DataFlow::Node node) { this.isSanitizerIn(node) }
+
+ /** Holds if taint propagation out of `node` is prohibited. */
+ predicate isSanitizerOut(DataFlow::Node node) { none() }
+
+ final override predicate isBarrierOut(DataFlow::Node node) { this.isSanitizerOut(node) }
+
+ /** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
+ predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
+
+ final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
+ this.isSanitizerGuard(guard)
+ }
+
+ /**
+ * Holds if the additional taint propagation step from `node1` to `node2`
+ * must be taken into account in the analysis.
+ */
+ predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
+
+ final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
+ this.isAdditionalTaintStep(node1, node2) or
+ defaultAdditionalTaintStep(node1, node2)
+ }
+
+ override predicate allowImplicitRead(DataFlow::Node node, DataFlow::Content c) {
+ (this.isSink(node) or this.isAdditionalTaintStep(node, _)) and
+ defaultImplicitTaintRead(node, c)
+ }
+
+ /**
+ * Holds if taint may flow from `source` to `sink` for this configuration.
+ */
+ // overridden to provide taint-tracking specific qldoc
+ override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) {
+ super.hasFlow(source, sink)
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/tainttracking1/TaintTrackingParameter.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/tainttracking1/TaintTrackingParameter.qll
new file mode 100644
index 00000000000..ce6f5ed1c48
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/dataflow/internal/tainttracking1/TaintTrackingParameter.qll
@@ -0,0 +1,6 @@
+import codeql.ruby.dataflow.internal.TaintTrackingPublic as Public
+
+module Private {
+ import codeql.ruby.DataFlow::DataFlow as DataFlow
+ import codeql.ruby.dataflow.internal.TaintTrackingPrivate
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/filters/GeneratedCode.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/filters/GeneratedCode.qll
new file mode 100644
index 00000000000..18d12be3aac
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/filters/GeneratedCode.qll
@@ -0,0 +1,43 @@
+/** Provides classes for detecting generated code. */
+
+private import ruby
+private import codeql.ruby.ast.internal.TreeSitter
+
+/** A source file that contains generated code. */
+abstract class GeneratedCodeFile extends RubyFile { }
+
+/** A file contining comments suggesting it contains generated code. */
+class GeneratedCommentFile extends GeneratedCodeFile {
+ GeneratedCommentFile() { this = any(GeneratedCodeComment c).getLocation().getFile() }
+}
+
+/** A comment line that indicates generated code. */
+abstract class GeneratedCodeComment extends Ruby::Comment { }
+
+/**
+ * A generic comment line that suggests that the file is generated.
+ */
+class GenericGeneratedCodeComment extends GeneratedCodeComment {
+ GenericGeneratedCodeComment() {
+ exists(string line, string entity, string was, string automatically | line = getValue() |
+ entity = "file|class|art[ei]fact|module|script" and
+ was = "was|is|has been" and
+ automatically = "automatically |mechanically |auto[- ]?" and
+ line.regexpMatch("(?i).*\\bThis (" + entity + ") (" + was + ") (" + automatically +
+ ")?generated\\b.*")
+ )
+ }
+}
+
+/** A comment warning against modifications. */
+class DontModifyMarkerComment extends GeneratedCodeComment {
+ DontModifyMarkerComment() {
+ exists(string line | line = getValue() |
+ line.regexpMatch("(?i).*\\bGenerated by\\b.*\\bDo not edit\\b.*") or
+ line.regexpMatch("(?i).*\\bAny modifications to this file will be lost\\b.*")
+ )
+ }
+}
+
+/** Holds if `file` looks like it contains generated code. */
+predicate isGeneratedCode(GeneratedCodeFile file) { any() }
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll
new file mode 100644
index 00000000000..0eec1e15f58
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll
@@ -0,0 +1,259 @@
+private import codeql.ruby.AST
+private import codeql.ruby.Concepts
+private import codeql.ruby.controlflow.CfgNodes
+private import codeql.ruby.DataFlow
+private import codeql.ruby.dataflow.RemoteFlowSources
+private import codeql.ruby.ast.internal.Module
+private import ActionView
+
+private class ActionControllerBaseAccess extends ConstantReadAccess {
+ ActionControllerBaseAccess() {
+ this.getName() = "Base" and
+ this.getScopeExpr().(ConstantAccess).getName() = "ActionController"
+ }
+}
+
+// ApplicationController extends ActionController::Base, but we
+// treat it separately in case the ApplicationController definition
+// is not in the database
+private class ApplicationControllerAccess extends ConstantReadAccess {
+ ApplicationControllerAccess() { this.getName() = "ApplicationController" }
+}
+
+/**
+ * A `ClassDeclaration` for a class that extends `ActionController::Base`.
+ * For example,
+ *
+ * ```rb
+ * class FooController < ActionController::Base
+ * def delete_handler
+ * uid = params[:id]
+ * User.delete_by("id = ?", uid)
+ * end
+ * end
+ * ```
+ */
+class ActionControllerControllerClass extends ClassDeclaration {
+ ActionControllerControllerClass() {
+ // class FooController < ActionController::Base
+ this.getSuperclassExpr() instanceof ActionControllerBaseAccess
+ or
+ // class FooController < ApplicationController
+ this.getSuperclassExpr() instanceof ApplicationControllerAccess
+ or
+ // class BarController < FooController
+ exists(ActionControllerControllerClass other |
+ other.getModule() = resolveScopeExpr(this.getSuperclassExpr())
+ )
+ }
+
+ /**
+ * Gets a `ActionControllerActionMethod` defined in this class.
+ */
+ ActionControllerActionMethod getAnAction() { result = this.getAMethod() }
+}
+
+/**
+ * An instance method defined within an `ActionController` controller class.
+ * This may be the target of a route handler, if such a route is defined.
+ */
+class ActionControllerActionMethod extends Method, HTTP::Server::RequestHandler::Range {
+ private ActionControllerControllerClass controllerClass;
+
+ ActionControllerActionMethod() { this = controllerClass.getAMethod() }
+
+ /**
+ * Establishes a mapping between a method within the file
+ * `app/controllers/_controller.rb` and the
+ * corresponding template file at
+ * `app/views//.html.erb`.
+ */
+ ErbFile getDefaultTemplateFile() {
+ controllerTemplateFile(this.getControllerClass(), result) and
+ result.getBaseName() = this.getName() + ".html.erb"
+ }
+
+ // params come from `params` method rather than a method parameter
+ override Parameter getARoutedParameter() { none() }
+
+ override string getFramework() { result = "ActionController" }
+
+ /** Gets a call to render from within this method. */
+ RenderCall getARenderCall() { result.getParent+() = this }
+
+ // TODO: model the implicit render call when a path through the method does
+ // not end at an explicit render or redirect
+ /** Gets the controller class containing this method. */
+ ActionControllerControllerClass getControllerClass() { result = controllerClass }
+}
+
+// A method call with a `self` receiver from within a controller class
+private class ActionControllerContextCall extends MethodCall {
+ private ActionControllerControllerClass controllerClass;
+
+ ActionControllerContextCall() {
+ this.getReceiver() instanceof Self and
+ this.getEnclosingModule() = controllerClass
+ }
+
+ ActionControllerControllerClass getControllerClass() { result = controllerClass }
+}
+
+/**
+ * A call to the `params` method to fetch the request parameters.
+ */
+abstract class ParamsCall extends MethodCall {
+ ParamsCall() { this.getMethodName() = "params" }
+}
+
+/**
+ * A `RemoteFlowSource::Range` to represent accessing the
+ * ActionController parameters available via the `params` method.
+ */
+class ParamsSource extends RemoteFlowSource::Range {
+ ParamsCall call;
+
+ ParamsSource() { this.asExpr().getExpr() = call }
+
+ override string getSourceType() { result = "ActionController::Metal#params" }
+}
+
+// A call to `params` from within a controller.
+private class ActionControllerParamsCall extends ActionControllerContextCall, ParamsCall { }
+
+// A call to `render` from within a controller.
+private class ActionControllerRenderCall extends ActionControllerContextCall, RenderCall { }
+
+// A call to `render_to` from within a controller.
+private class ActionControllerRenderToCall extends ActionControllerContextCall, RenderToCall { }
+
+// A call to `html_safe` from within a controller.
+private class ActionControllerHtmlSafeCall extends HtmlSafeCall {
+ ActionControllerHtmlSafeCall() {
+ this.getEnclosingModule() instanceof ActionControllerControllerClass
+ }
+}
+
+// A call to `html_escape` from within a controller.
+private class ActionControllerHtmlEscapeCall extends HtmlEscapeCall {
+ ActionControllerHtmlEscapeCall() {
+ this.getEnclosingModule() instanceof ActionControllerControllerClass
+ }
+}
+
+/**
+ * A call to the `redirect_to` method, used in an action to redirect to a
+ * specific URL/path or to a different action in this controller.
+ */
+class RedirectToCall extends ActionControllerContextCall {
+ RedirectToCall() { this.getMethodName() = "redirect_to" }
+
+ /** Gets the `Expr` representing the URL to redirect to, if any */
+ Expr getRedirectUrl() { result = this.getArgument(0) }
+
+ /** Gets the `ActionControllerActionMethod` to redirect to, if any */
+ ActionControllerActionMethod getRedirectActionMethod() {
+ exists(string methodName |
+ methodName = this.getKeywordArgument("action").(StringlikeLiteral).getValueText() and
+ methodName = result.getName() and
+ result.getEnclosingModule() = this.getControllerClass()
+ )
+ }
+}
+
+/**
+ * A call to the `redirect_to` method, as an `HttpRedirectResponse`.
+ */
+class ActionControllerRedirectResponse extends HTTP::Server::HttpRedirectResponse::Range {
+ RedirectToCall redirectToCall;
+
+ ActionControllerRedirectResponse() { this.asExpr().getExpr() = redirectToCall }
+
+ override DataFlow::Node getBody() { none() }
+
+ override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
+
+ override string getMimetypeDefault() { none() }
+
+ override DataFlow::Node getRedirectLocation() {
+ result.asExpr().getExpr() = redirectToCall.getRedirectUrl()
+ }
+}
+
+/**
+ * A method in an `ActionController` class that is accessible from within a
+ * Rails view as a helper method. For instance, in:
+ *
+ * ```rb
+ * class FooController < ActionController::Base
+ * helper_method :logged_in?
+ * def logged_in?
+ * @current_user != nil
+ * end
+ * end
+ * ```
+ *
+ * the `logged_in?` method is a helper method.
+ * See also https://api.rubyonrails.org/classes/AbstractController/Helpers/ClassMethods.html#method-i-helper_method
+ */
+class ActionControllerHelperMethod extends Method {
+ private ActionControllerControllerClass controllerClass;
+
+ ActionControllerHelperMethod() {
+ this.getEnclosingModule() = controllerClass and
+ exists(MethodCall helperMethodMarker |
+ helperMethodMarker.getMethodName() = "helper_method" and
+ helperMethodMarker.getAnArgument().(StringlikeLiteral).getValueText() = this.getName() and
+ helperMethodMarker.getEnclosingModule() = controllerClass
+ )
+ }
+
+ /** Gets the class containing this helper method. */
+ ActionControllerControllerClass getControllerClass() { result = controllerClass }
+}
+
+/**
+ * Gets an `ActionControllerControllerClass` associated with the given `ErbFile`
+ * according to Rails path conventions.
+ * For instance, a template file at `app/views/foo/bar/baz.html.erb` will be
+ * mapped to a controller class in `app/controllers/foo/bar/baz_controller.rb`,
+ * if such a controller class exists.
+ */
+ActionControllerControllerClass getAssociatedControllerClass(ErbFile f) {
+ // There is a direct mapping from template file to controller class
+ controllerTemplateFile(result, f)
+ or
+ // The template `f` is a partial, and it is rendered from within another
+ // template file, `fp`. In this case, `f` inherits the associated
+ // controller classes from `fp`.
+ f.isPartial() and
+ exists(RenderCall r, ErbFile fp |
+ r.getLocation().getFile() = fp and
+ r.getTemplateFile() = f and
+ result = getAssociatedControllerClass(fp)
+ )
+}
+
+// TODO: improve layout support, e.g. for `layout` method
+// https://guides.rubyonrails.org/layouts_and_rendering.html
+/**
+ * Holds if `templatesFile` is a viable file "belonging" to the given
+ * `ActionControllerControllerClass`, according to Rails conventions.
+ *
+ * This handles mappings between controllers in `app/controllers/`, and
+ * templates in `app/views/` and `app/views/layouts/`.
+ */
+predicate controllerTemplateFile(ActionControllerControllerClass cls, ErbFile templateFile) {
+ exists(string templatesPath, string sourcePrefix, string subPath, string controllerPath |
+ controllerPath = cls.getLocation().getFile().getRelativePath() and
+ templatesPath = templateFile.getParentContainer().getRelativePath() and
+ // `sourcePrefix` is either a prefix path ending in a slash, or empty if
+ // the rails app is at the source root
+ sourcePrefix = [controllerPath.regexpCapture("^(.*/)app/controllers/(?:.*?)/(?:[^/]*)$", 1), ""] and
+ controllerPath = sourcePrefix + "app/controllers/" + subPath + "_controller.rb" and
+ (
+ templatesPath = sourcePrefix + "app/views/" + subPath or
+ templateFile.getRelativePath().matches(sourcePrefix + "app/views/layouts/" + subPath + "%")
+ )
+ )
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/ActionView.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/ActionView.qll
new file mode 100644
index 00000000000..55638ab6584
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/ActionView.qll
@@ -0,0 +1,138 @@
+private import codeql.ruby.AST
+private import codeql.ruby.Concepts
+private import codeql.ruby.controlflow.CfgNodes
+private import codeql.ruby.DataFlow
+private import codeql.ruby.dataflow.RemoteFlowSources
+private import codeql.ruby.ast.internal.Module
+private import ActionController
+
+predicate inActionViewContext(AstNode n) {
+ // Within a template
+ n.getLocation().getFile() instanceof ErbFile
+}
+
+/**
+ * A method call on a string to mark it as HTML safe for Rails.
+ * Strings marked as such will not be automatically escaped when inserted into
+ * HTML.
+ */
+abstract class HtmlSafeCall extends MethodCall {
+ HtmlSafeCall() { this.getMethodName() = "html_safe" }
+}
+
+// A call to `html_safe` from within a template.
+private class ActionViewHtmlSafeCall extends HtmlSafeCall {
+ ActionViewHtmlSafeCall() { inActionViewContext(this) }
+}
+
+/**
+ * A call to a method named "html_escape", "html_escape_once", or "h".
+ */
+abstract class HtmlEscapeCall extends MethodCall {
+ // "h" is aliased to "html_escape" in ActiveSupport
+ HtmlEscapeCall() { this.getMethodName() = ["html_escape", "html_escape_once", "h"] }
+}
+
+class RailsHtmlEscaping extends Escaping::Range, DataFlow::CallNode {
+ RailsHtmlEscaping() { this.asExpr().getExpr() instanceof HtmlEscapeCall }
+
+ override DataFlow::Node getAnInput() { result = this.getArgument(0) }
+
+ override DataFlow::Node getOutput() { result = this }
+
+ override string getKind() { result = Escaping::getHtmlKind() }
+}
+
+// A call to `html_escape` from within a template.
+private class ActionViewHtmlEscapeCall extends HtmlEscapeCall {
+ ActionViewHtmlEscapeCall() { inActionViewContext(this) }
+}
+
+// A call in a context where some commonly used `ActionView` methods are available.
+private class ActionViewContextCall extends MethodCall {
+ ActionViewContextCall() {
+ this.getReceiver() instanceof Self and
+ inActionViewContext(this)
+ }
+
+ predicate isInErbFile() { this.getLocation().getFile() instanceof ErbFile }
+}
+
+/** A call to the `raw` method to output a value without HTML escaping. */
+class RawCall extends ActionViewContextCall {
+ RawCall() { this.getMethodName() = "raw" }
+}
+
+// A call to the `params` method within the context of a template.
+private class ActionViewParamsCall extends ActionViewContextCall, ParamsCall { }
+
+/**
+ * A call to a `render` method that will populate the response body with the
+ * rendered content.
+ */
+abstract class RenderCall extends MethodCall {
+ RenderCall() { this.getMethodName() = "render" }
+
+ private Expr getTemplatePathArgument() {
+ // TODO: support other ways of specifying paths (e.g. `file`)
+ result = [this.getKeywordArgument(["partial", "template", "action"]), this.getArgument(0)]
+ }
+
+ private string getTemplatePathValue() { result = this.getTemplatePathArgument().getValueText() }
+
+ // everything up to and including the final slash, but ignoring any leading slash
+ private string getSubPath() {
+ result = this.getTemplatePathValue().regexpCapture("^/?(.*/)?(?:[^/]*?)$", 1)
+ }
+
+ // everything after the final slash, or the whole string if there is no slash
+ private string getBaseName() {
+ result = this.getTemplatePathValue().regexpCapture("^/?(?:.*/)?([^/]*?)$", 1)
+ }
+
+ /**
+ * Gets the template file to be rendered by this call, if any.
+ */
+ ErbFile getTemplateFile() {
+ result.getTemplateName() = this.getBaseName() and
+ result.getRelativePath().matches("%app/views/" + this.getSubPath() + "%")
+ }
+
+ /**
+ * Get the local variables passed as context to the renderer
+ */
+ HashLiteral getLocals() { result = this.getKeywordArgument("locals") }
+ // TODO: implicit renders in controller actions
+}
+
+// A call to the `render` method within the context of a template.
+private class ActionViewRenderCall extends RenderCall, ActionViewContextCall { }
+
+/**
+ * A render call that does not automatically set the HTTP response body.
+ */
+abstract class RenderToCall extends MethodCall {
+ RenderToCall() { this.getMethodName() = ["render_to_body", "render_to_string"] }
+}
+
+// A call to `render_to` from within a template.
+private class ActionViewRenderToCall extends ActionViewContextCall, RenderToCall { }
+
+/**
+ * A call to the ActionView `link_to` helper method.
+ *
+ * This generates an HTML anchor tag. The method is not designed to expect
+ * user-input, so provided paths are not automatically HTML escaped.
+ */
+class LinkToCall extends ActionViewContextCall {
+ LinkToCall() { this.getMethodName() = "link_to" }
+
+ Expr getPathArgument() {
+ // When `link_to` is called with a block, it uses the first argument as the
+ // path, and otherwise the second argument.
+ exists(this.getBlock()) and result = this.getArgument(0)
+ or
+ not exists(this.getBlock()) and result = this.getArgument(1)
+ }
+}
+// TODO: model flow in/out of template files properly,
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll
new file mode 100644
index 00000000000..2a13b51acfb
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll
@@ -0,0 +1,319 @@
+private import codeql.ruby.AST
+private import codeql.ruby.Concepts
+private import codeql.ruby.controlflow.CfgNodes
+private import codeql.ruby.DataFlow
+private import codeql.ruby.dataflow.internal.DataFlowDispatch
+private import codeql.ruby.ast.internal.Module
+private import codeql.ruby.ApiGraphs
+private import codeql.ruby.frameworks.StandardLibrary
+
+private class ActiveRecordBaseAccess extends ConstantReadAccess {
+ ActiveRecordBaseAccess() {
+ this.getName() = "Base" and
+ this.getScopeExpr().(ConstantAccess).getName() = "ActiveRecord"
+ }
+}
+
+// ApplicationRecord extends ActiveRecord::Base, but we
+// treat it separately in case the ApplicationRecord definition
+// is not in the database
+private class ApplicationRecordAccess extends ConstantReadAccess {
+ ApplicationRecordAccess() { this.getName() = "ApplicationRecord" }
+}
+
+/// See https://api.rubyonrails.org/classes/ActiveRecord/Persistence.html
+private string activeRecordPersistenceInstanceMethodName() {
+ result =
+ [
+ "becomes", "becomes!", "decrement", "decrement!", "delete", "delete!", "destroy", "destroy!",
+ "destroyed?", "increment", "increment!", "new_record?", "persisted?",
+ "previously_new_record?", "reload", "save", "save!", "toggle", "toggle!", "touch", "update",
+ "update!", "update_attribute", "update_column", "update_columns"
+ ]
+}
+
+// Methods with these names are defined for all active record model instances,
+// so they are unlikely to refer to a database field.
+private predicate isBuiltInMethodForActiveRecordModelInstance(string methodName) {
+ methodName = activeRecordPersistenceInstanceMethodName() or
+ methodName = basicObjectInstanceMethodName() or
+ methodName = objectInstanceMethodName()
+}
+
+/**
+ * A `ClassDeclaration` for a class that extends `ActiveRecord::Base`. For example,
+ *
+ * ```rb
+ * class UserGroup < ActiveRecord::Base
+ * has_many :users
+ * end
+ * ```
+ */
+class ActiveRecordModelClass extends ClassDeclaration {
+ ActiveRecordModelClass() {
+ // class Foo < ActiveRecord::Base
+ this.getSuperclassExpr() instanceof ActiveRecordBaseAccess
+ or
+ // class Foo < ApplicationRecord
+ this.getSuperclassExpr() instanceof ApplicationRecordAccess
+ or
+ // class Bar < Foo
+ exists(ActiveRecordModelClass other |
+ other.getModule() = resolveScopeExpr(this.getSuperclassExpr())
+ )
+ }
+
+ // Gets the class declaration for this class and all of its super classes
+ private ModuleBase getAllClassDeclarations() {
+ result = this.getModule().getSuperClass*().getADeclaration()
+ }
+
+ /**
+ * Gets methods defined in this class that may access a field from the database.
+ */
+ Method getAPotentialFieldAccessMethod() {
+ // It's a method on this class or one of its super classes
+ result = this.getAllClassDeclarations().getAMethod() and
+ // There is a value that can be returned by this method which may include field data
+ exists(DataFlow::Node returned, ActiveRecordInstanceMethodCall cNode, MethodCall c |
+ exprNodeReturnedFrom(returned, result) and
+ cNode.flowsTo(returned) and
+ c = cNode.asExpr().getExpr()
+ |
+ // The referenced method is not built-in, and...
+ not isBuiltInMethodForActiveRecordModelInstance(c.getMethodName()) and
+ (
+ // ...The receiver does not have a matching method definition, or...
+ not exists(
+ cNode.getInstance().getClass().getAllClassDeclarations().getMethod(c.getMethodName())
+ )
+ or
+ // ...the called method can access a field
+ c.getATarget() = cNode.getInstance().getClass().getAPotentialFieldAccessMethod()
+ )
+ )
+ }
+}
+
+/** A class method call whose receiver is an `ActiveRecordModelClass`. */
+class ActiveRecordModelClassMethodCall extends MethodCall {
+ private ActiveRecordModelClass recvCls;
+
+ ActiveRecordModelClassMethodCall() {
+ // e.g. Foo.where(...)
+ recvCls.getModule() = resolveScopeExpr(this.getReceiver())
+ or
+ // e.g. Foo.joins(:bars).where(...)
+ recvCls = this.getReceiver().(ActiveRecordModelClassMethodCall).getReceiverClass()
+ or
+ // e.g. self.where(...) within an ActiveRecordModelClass
+ this.getReceiver() instanceof Self and
+ this.getEnclosingModule() = recvCls
+ }
+
+ /** The `ActiveRecordModelClass` of the receiver of this method. */
+ ActiveRecordModelClass getReceiverClass() { result = recvCls }
+}
+
+private Expr sqlFragmentArgument(MethodCall call) {
+ exists(string methodName |
+ methodName = call.getMethodName() and
+ (
+ methodName =
+ [
+ "delete_all", "delete_by", "destroy_all", "destroy_by", "exists?", "find_by", "find_by!",
+ "find_or_create_by", "find_or_create_by!", "find_or_initialize_by", "find_by_sql", "from",
+ "group", "having", "joins", "lock", "not", "order", "pluck", "where", "rewhere", "select",
+ "reselect", "update_all"
+ ] and
+ result = call.getArgument(0)
+ or
+ methodName = "calculate" and result = call.getArgument(1)
+ or
+ methodName in ["average", "count", "maximum", "minimum", "sum"] and
+ result = call.getArgument(0)
+ or
+ // This format was supported until Rails 2.3.8
+ methodName = ["all", "find", "first", "last"] and
+ result = call.getKeywordArgument("conditions")
+ or
+ methodName = "reload" and
+ result = call.getKeywordArgument("lock")
+ )
+ )
+}
+
+// An expression that, if tainted by unsanitized input, should not be used as
+// part of an argument to an SQL executing method
+private predicate unsafeSqlExpr(Expr sqlFragmentExpr) {
+ // Literals containing an interpolated value
+ exists(StringInterpolationComponent interpolated |
+ interpolated = sqlFragmentExpr.(StringlikeLiteral).getComponent(_)
+ )
+ or
+ // String concatenations
+ sqlFragmentExpr instanceof AddExpr
+ or
+ // Variable reads
+ sqlFragmentExpr instanceof VariableReadAccess
+ or
+ // Method call
+ sqlFragmentExpr instanceof MethodCall
+}
+
+/**
+ * A method call that may result in executing unintended user-controlled SQL
+ * queries if the `getSqlFragmentSinkArgument()` expression is tainted by
+ * unsanitized user-controlled input. For example, supposing that `User` is an
+ * `ActiveRecord` model class, then
+ *
+ * ```rb
+ * User.where("name = '#{user_name}'")
+ * ```
+ *
+ * may be unsafe if `user_name` is from unsanitized user input, as a value such
+ * as `"') OR 1=1 --"` could result in the application looking up all users
+ * rather than just one with a matching name.
+ */
+class PotentiallyUnsafeSqlExecutingMethodCall extends ActiveRecordModelClassMethodCall {
+ // The SQL fragment argument itself
+ private Expr sqlFragmentExpr;
+
+ PotentiallyUnsafeSqlExecutingMethodCall() {
+ exists(Expr arg |
+ arg = sqlFragmentArgument(this) and
+ unsafeSqlExpr(sqlFragmentExpr) and
+ (
+ sqlFragmentExpr = arg
+ or
+ sqlFragmentExpr = arg.(ArrayLiteral).getElement(0)
+ ) and
+ // Check that method has not been overridden
+ not exists(SingletonMethod m |
+ m.getName() = this.getMethodName() and
+ m.getOuterScope() = this.getReceiverClass()
+ )
+ )
+ }
+
+ Expr getSqlFragmentSinkArgument() { result = sqlFragmentExpr }
+}
+
+/**
+ * An `SqlExecution::Range` for an argument to a
+ * `PotentiallyUnsafeSqlExecutingMethodCall` that may be vulnerable to being
+ * controlled by user input.
+ */
+class ActiveRecordSqlExecutionRange extends SqlExecution::Range {
+ ActiveRecordSqlExecutionRange() {
+ exists(PotentiallyUnsafeSqlExecutingMethodCall mc |
+ this.asExpr().getNode() = mc.getSqlFragmentSinkArgument()
+ )
+ }
+
+ override DataFlow::Node getSql() { result = this }
+}
+
+// TODO: model `ActiveRecord` sanitizers
+// https://api.rubyonrails.org/classes/ActiveRecord/Sanitization/ClassMethods.html
+/**
+ * A node that may evaluate to one or more `ActiveRecordModelClass` instances.
+ */
+abstract class ActiveRecordModelInstantiation extends OrmInstantiation::Range,
+ DataFlow::LocalSourceNode {
+ abstract ActiveRecordModelClass getClass();
+
+ bindingset[methodName]
+ override predicate methodCallMayAccessField(string methodName) {
+ // The method is not a built-in, and...
+ not isBuiltInMethodForActiveRecordModelInstance(methodName) and
+ (
+ // ...There is no matching method definition in the class, or...
+ not exists(this.getClass().getMethod(methodName))
+ or
+ // ...the called method can access a field.
+ exists(Method m | m = this.getClass().getAPotentialFieldAccessMethod() |
+ m.getName() = methodName
+ )
+ )
+ }
+}
+
+// Names of class methods on ActiveRecord models that may return one or more
+// instances of that model. This also includes the `initialize` method.
+// See https://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html
+private string finderMethodName() {
+ exists(string baseName |
+ baseName =
+ [
+ "fifth", "find", "find_by", "find_or_initialize_by", "find_or_create_by", "first",
+ "forty_two", "fourth", "last", "second", "second_to_last", "take", "third", "third_to_last"
+ ] and
+ result = baseName + ["", "!"]
+ )
+ or
+ result = "new"
+}
+
+// Gets the "final" receiver in a chain of method calls.
+// For example, in `Foo.bar`, this would give the `Foo` access, and in
+// `foo.bar.baz("arg")` it would give the `foo` variable access
+private Expr getUltimateReceiver(MethodCall call) {
+ exists(Expr recv |
+ recv = call.getReceiver() and
+ (
+ result = getUltimateReceiver(recv)
+ or
+ not recv instanceof MethodCall and result = recv
+ )
+ )
+}
+
+// A call to `find`, `where`, etc. that may return active record model object(s)
+private class ActiveRecordModelFinderCall extends ActiveRecordModelInstantiation, DataFlow::CallNode {
+ private MethodCall call;
+ private ActiveRecordModelClass cls;
+ private Expr recv;
+
+ ActiveRecordModelFinderCall() {
+ call = this.asExpr().getExpr() and
+ recv = getUltimateReceiver(call) and
+ resolveConstant(recv) = cls.getQualifiedName() and
+ call.getMethodName() = finderMethodName()
+ }
+
+ final override ActiveRecordModelClass getClass() { result = cls }
+}
+
+// A `self` reference that may resolve to an active record model object
+private class ActiveRecordModelClassSelfReference extends ActiveRecordModelInstantiation {
+ private ActiveRecordModelClass cls;
+
+ ActiveRecordModelClassSelfReference() {
+ exists(Self s |
+ s.getEnclosingModule() = cls and
+ s.getEnclosingMethod() = cls.getAMethod() and
+ s = this.asExpr().getExpr()
+ )
+ }
+
+ final override ActiveRecordModelClass getClass() { result = cls }
+}
+
+// A (locally tracked) active record model object
+private class ActiveRecordInstance extends DataFlow::Node {
+ private ActiveRecordModelInstantiation instantiation;
+
+ ActiveRecordInstance() { this = instantiation or instantiation.flowsTo(this) }
+
+ ActiveRecordModelClass getClass() { result = instantiation.getClass() }
+}
+
+// A call whose receiver may be an active record model object
+private class ActiveRecordInstanceMethodCall extends DataFlow::CallNode {
+ private ActiveRecordInstance instance;
+
+ ActiveRecordInstanceMethodCall() { this.getReceiver() = instance }
+
+ ActiveRecordInstance getInstance() { result = instance }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/ActiveStorage.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/ActiveStorage.qll
new file mode 100644
index 00000000000..93ede4d9925
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/ActiveStorage.qll
@@ -0,0 +1,55 @@
+private import codeql.ruby.AST
+private import codeql.ruby.ApiGraphs
+private import codeql.ruby.Concepts
+private import codeql.ruby.DataFlow
+private import codeql.ruby.dataflow.FlowSummary
+
+/** Defines calls to `ActiveStorage::Filename#sanitized` as path sanitizers. */
+class ActiveStorageFilenameSanitizedCall extends Path::PathSanitization::Range, DataFlow::CallNode {
+ ActiveStorageFilenameSanitizedCall() {
+ this.getReceiver() =
+ API::getTopLevelMember("ActiveStorage").getMember("Filename").getAnInstantiation() and
+ this.getMethodName() = "sanitized"
+ }
+}
+
+/** Taint summary for `ActiveStorage::Filename.new`. */
+class ActiveStorageFilenameNewSummary extends SummarizedCallable {
+ ActiveStorageFilenameNewSummary() { this = "ActiveStorage::Filename.new" }
+
+ override MethodCall getACall() {
+ result =
+ API::getTopLevelMember("ActiveStorage")
+ .getMember("Filename")
+ .getAnInstantiation()
+ .asExpr()
+ .getExpr()
+ }
+
+ override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
+ input = "Argument[0]" and
+ output = "ReturnValue" and
+ preservesValue = false
+ }
+}
+
+/** Taint summary for `ActiveStorage::Filename#sanitized`. */
+class ActiveStorageFilenameSanitizedSummary extends SummarizedCallable {
+ ActiveStorageFilenameSanitizedSummary() { this = "ActiveStorage::Filename#sanitized" }
+
+ override MethodCall getACall() {
+ result =
+ API::getTopLevelMember("ActiveStorage")
+ .getMember("Filename")
+ .getInstance()
+ .getAMethodCall("sanitized")
+ .asExpr()
+ .getExpr()
+ }
+
+ override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
+ input = "Argument[-1]" and
+ output = "ReturnValue" and
+ preservesValue = false
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/Files.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/Files.qll
new file mode 100644
index 00000000000..0ee0c00b63a
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/Files.qll
@@ -0,0 +1,339 @@
+/**
+ * Provides classes for working with file system libraries.
+ */
+
+private import ruby
+private import codeql.ruby.Concepts
+private import codeql.ruby.ApiGraphs
+private import codeql.ruby.DataFlow
+private import codeql.ruby.frameworks.StandardLibrary
+private import codeql.ruby.dataflow.FlowSummary
+
+private DataFlow::Node ioInstanceInstantiation() {
+ result = API::getTopLevelMember("IO").getAnInstantiation() or
+ result = API::getTopLevelMember("IO").getAMethodCall(["for_fd", "open", "try_convert"])
+}
+
+private DataFlow::Node ioInstance() {
+ result = ioInstanceInstantiation()
+ or
+ exists(DataFlow::Node inst |
+ inst = ioInstance() and
+ inst.(DataFlow::LocalSourceNode).flowsTo(result)
+ )
+}
+
+// Match some simple cases where a path argument specifies a shell command to
+// be executed. For example, the `"|date"` argument in `IO.read("|date")`, which
+// will execute a shell command and read its output rather than reading from the
+// filesystem.
+private predicate pathArgSpawnsSubprocess(Expr arg) {
+ arg.(StringlikeLiteral).getValueText().charAt(0) = "|"
+}
+
+private DataFlow::Node fileInstanceInstantiation() {
+ result = API::getTopLevelMember("File").getAnInstantiation()
+ or
+ result = API::getTopLevelMember("File").getAMethodCall("open")
+ or
+ // Calls to `Kernel.open` can yield `File` instances
+ result.(KernelMethodCall).getMethodName() = "open" and
+ // Assume that calls that don't invoke shell commands will instead open
+ // a file.
+ not pathArgSpawnsSubprocess(result.(KernelMethodCall).getArgument(0).asExpr().getExpr())
+}
+
+private DataFlow::Node fileInstance() {
+ result = fileInstanceInstantiation()
+ or
+ exists(DataFlow::Node inst |
+ inst = fileInstance() and
+ inst.(DataFlow::LocalSourceNode).flowsTo(result)
+ )
+}
+
+private string ioFileReaderClassMethodName() {
+ result = ["binread", "foreach", "read", "readlines", "try_convert"]
+}
+
+private string ioFileReaderInstanceMethodName() {
+ result =
+ [
+ "getbyte", "getc", "gets", "pread", "read", "read_nonblock", "readbyte", "readchar",
+ "readline", "readlines", "readpartial", "sysread"
+ ]
+}
+
+private string ioFileReaderMethodName(boolean classMethodCall) {
+ classMethodCall = true and result = ioFileReaderClassMethodName()
+ or
+ classMethodCall = false and result = ioFileReaderInstanceMethodName()
+}
+
+/**
+ * Classes and predicates for modeling the core `IO` module.
+ */
+module IO {
+ /**
+ * An instance of the `IO` class, for example in
+ *
+ * ```rb
+ * rand = IO.new(IO.sysopen("/dev/random", "r"), "r")
+ * rand_data = rand.read(32)
+ * ```
+ *
+ * there are 3 `IOInstance`s - the call to `IO.new`, the assignment
+ * `rand = ...`, and the read access to `rand` on the second line.
+ */
+ class IOInstance extends DataFlow::Node {
+ IOInstance() {
+ this = ioInstance() or
+ this = fileInstance()
+ }
+ }
+
+ // "Direct" `IO` instances, i.e. cases where there is no more specific
+ // subtype such as `File`
+ private class IOInstanceStrict extends IOInstance {
+ IOInstanceStrict() { this = ioInstance() }
+ }
+
+ /**
+ * A `DataFlow::CallNode` that reads data using the `IO` class. For example,
+ * the `IO.read call in:
+ *
+ * ```rb
+ * IO.read("|date")
+ * ```
+ *
+ * returns the output of the `date` shell command, invoked as a subprocess.
+ *
+ * This class includes reads both from shell commands and reads from the
+ * filesystem. For working with filesystem accesses specifically, see
+ * `IOFileReader` or the `FileSystemReadAccess` concept.
+ */
+ class IOReader extends DataFlow::CallNode {
+ private boolean classMethodCall;
+ private string api;
+
+ IOReader() {
+ // Class methods
+ api = ["File", "IO"] and
+ classMethodCall = true and
+ this = API::getTopLevelMember(api).getAMethodCall(ioFileReaderMethodName(classMethodCall))
+ or
+ // IO instance methods
+ classMethodCall = false and
+ api = "IO" and
+ exists(IOInstanceStrict ii |
+ this.getReceiver() = ii and
+ this.getMethodName() = ioFileReaderMethodName(classMethodCall)
+ )
+ or
+ // File instance methods
+ classMethodCall = false and
+ api = "File" and
+ exists(File::FileInstance fi |
+ this.getReceiver() = fi and
+ this.getMethodName() = ioFileReaderMethodName(classMethodCall)
+ )
+ // TODO: enumeration style methods such as `each`, `foreach`, etc.
+ }
+
+ /**
+ * Returns the most specific core class used for this read, `IO` or `File`
+ */
+ string getAPI() { result = api }
+
+ predicate isClassMethodCall() { classMethodCall = true }
+ }
+
+ /**
+ * A `DataFlow::CallNode` that reads data from the filesystem using the `IO`
+ * class. For example, the `IO.read call in:
+ *
+ * ```rb
+ * IO.read("foo.txt")
+ * ```
+ *
+ * reads the file `foo.txt` and returns its contents as a string.
+ */
+ class IOFileReader extends IOReader, FileSystemReadAccess::Range {
+ IOFileReader() {
+ this.getAPI() = "File"
+ or
+ this.isClassMethodCall() and
+ // Assume that calls that don't invoke shell commands will instead
+ // read from a file.
+ not pathArgSpawnsSubprocess(this.getArgument(0).asExpr().getExpr())
+ }
+
+ // TODO: can we infer a path argument for instance method calls?
+ // e.g. by tracing back to the instantiation of that instance
+ override DataFlow::Node getAPathArgument() {
+ result = this.getArgument(0) and this.isClassMethodCall()
+ }
+
+ // This class represents calls that return data
+ override DataFlow::Node getADataNode() { result = this }
+ }
+}
+
+/**
+ * Classes and predicates for modeling the core `File` module.
+ *
+ * Because `File` is a subclass of `IO`, all `FileInstance`s and
+ * `FileModuleReader`s are also `IOInstance`s and `IOModuleReader`s
+ * respectively.
+ */
+module File {
+ /**
+ * An instance of the `File` class, for example in
+ *
+ * ```rb
+ * f = File.new("foo.txt")
+ * puts f.read()
+ * ```
+ *
+ * there are 3 `FileInstance`s - the call to `File.new`, the assignment
+ * `f = ...`, and the read access to `f` on the second line.
+ */
+ class FileInstance extends IO::IOInstance {
+ FileInstance() { this = fileInstance() }
+ }
+
+ /**
+ * A read using the `File` module, e.g. the `f.read` call in
+ *
+ * ```rb
+ * f = File.new("foo.txt")
+ * puts f.read()
+ * ```
+ */
+ class FileModuleReader extends IO::IOFileReader {
+ FileModuleReader() { this.getAPI() = "File" }
+ }
+
+ /**
+ * A call to a `File` method that may return one or more filenames.
+ */
+ class FileModuleFilenameSource extends FileNameSource, DataFlow::CallNode {
+ FileModuleFilenameSource() {
+ // Class methods
+ this =
+ API::getTopLevelMember("File")
+ .getAMethodCall([
+ "absolute_path", "basename", "expand_path", "join", "path", "readlink",
+ "realdirpath", "realpath"
+ ])
+ or
+ // Instance methods
+ exists(FileInstance fi |
+ this.getReceiver() = fi and
+ this.getMethodName() = ["path", "to_path"]
+ )
+ }
+ }
+
+ private class FileModulePermissionModification extends FileSystemPermissionModification::Range,
+ DataFlow::CallNode {
+ private DataFlow::Node permissionArg;
+
+ FileModulePermissionModification() {
+ exists(string methodName | this = API::getTopLevelMember("File").getAMethodCall(methodName) |
+ methodName in ["chmod", "lchmod"] and permissionArg = this.getArgument(0)
+ or
+ methodName = "mkfifo" and permissionArg = this.getArgument(1)
+ or
+ methodName in ["new", "open"] and permissionArg = this.getArgument(2)
+ // TODO: defaults for optional args? This may depend on the umask
+ )
+ }
+
+ override DataFlow::Node getAPermissionNode() { result = permissionArg }
+ }
+
+ /**
+ * Flow summary for several methods on the `File` class that propagate taint
+ * from their first argument to the return value.
+ */
+ class FilePathConversionSummary extends SummarizedCallable {
+ string methodName;
+
+ FilePathConversionSummary() {
+ methodName = ["absolute_path", "dirname", "expand_path", "path", "realdirpath", "realpath"] and
+ this = "File." + methodName
+ }
+
+ override MethodCall getACall() {
+ result = API::getTopLevelMember("File").getAMethodCall(methodName).asExpr().getExpr()
+ }
+
+ override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
+ input = "Argument[0]" and
+ output = "ReturnValue" and
+ preservesValue = false
+ }
+ }
+
+ /**
+ * Flow summary for `File.join`, which propagates taint from every argument to
+ * its return value.
+ */
+ class FileJoinSummary extends SummarizedCallable {
+ FileJoinSummary() { this = "File.join" }
+
+ override MethodCall getACall() {
+ result = API::getTopLevelMember("File").getAMethodCall("join").asExpr().getExpr()
+ }
+
+ override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
+ input = "Argument[_]" and
+ output = "ReturnValue" and
+ preservesValue = false
+ }
+ }
+}
+
+/**
+ * Classes and predicates for modeling the `FileUtils` module from the standard
+ * library.
+ */
+module FileUtils {
+ /**
+ * A call to a FileUtils method that may return one or more filenames.
+ */
+ class FileUtilsFilenameSource extends FileNameSource {
+ FileUtilsFilenameSource() {
+ // Note that many methods in FileUtils accept a `noop` option that will
+ // perform a dry run of the command. This means that, for instance, `rm`
+ // and similar methods may not actually delete/unlink a file when called.
+ this =
+ API::getTopLevelMember("FileUtils")
+ .getAMethodCall([
+ "chmod", "chmod_R", "chown", "chown_R", "getwd", "makedirs", "mkdir", "mkdir_p",
+ "mkpath", "remove", "remove_dir", "remove_entry", "rm", "rm_f", "rm_r", "rm_rf",
+ "rmdir", "rmtree", "safe_unlink", "touch"
+ ])
+ }
+ }
+
+ private class FileUtilsPermissionModification extends FileSystemPermissionModification::Range,
+ DataFlow::CallNode {
+ private DataFlow::Node permissionArg;
+
+ FileUtilsPermissionModification() {
+ exists(string methodName |
+ this = API::getTopLevelMember("FileUtils").getAMethodCall(methodName)
+ |
+ methodName in ["chmod", "chmod_R"] and permissionArg = this.getArgument(0)
+ or
+ methodName in ["install", "makedirs", "mkdir", "mkdir_p", "mkpath"] and
+ permissionArg = this.getKeywordArgument("mode")
+ // TODO: defaults for optional args? This may depend on the umask
+ )
+ }
+
+ override DataFlow::Node getAPermissionNode() { result = permissionArg }
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/HttpClients.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/HttpClients.qll
new file mode 100644
index 00000000000..acb902694fe
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/HttpClients.qll
@@ -0,0 +1,12 @@
+/**
+ * Helper file that imports all HTTP clients.
+ */
+
+private import codeql.ruby.frameworks.http_clients.NetHttp
+private import codeql.ruby.frameworks.http_clients.Excon
+private import codeql.ruby.frameworks.http_clients.Faraday
+private import codeql.ruby.frameworks.http_clients.RestClient
+private import codeql.ruby.frameworks.http_clients.Httparty
+private import codeql.ruby.frameworks.http_clients.HttpClient
+private import codeql.ruby.frameworks.http_clients.OpenURI
+private import codeql.ruby.frameworks.http_clients.Typhoeus
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/StandardLibrary.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/StandardLibrary.qll
new file mode 100644
index 00000000000..7858ed030c0
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/StandardLibrary.qll
@@ -0,0 +1,335 @@
+private import codeql.ruby.AST
+private import codeql.ruby.Concepts
+private import codeql.ruby.DataFlow
+private import codeql.ruby.ApiGraphs
+
+/**
+ * The `Kernel` module is included by the `Object` class, so its methods are available
+ * in every Ruby object. In addition, its module methods can be called by
+ * providing a specific receiver as in `Kernel.exit`.
+ */
+class KernelMethodCall extends DataFlow::CallNode {
+ private MethodCall methodCall;
+
+ KernelMethodCall() {
+ methodCall = this.asExpr().getExpr() and
+ (
+ this = API::getTopLevelMember("Kernel").getAMethodCall(_)
+ or
+ methodCall instanceof UnknownMethodCall and
+ (
+ this.getReceiver().asExpr().getExpr() instanceof Self and
+ isPrivateKernelMethod(methodCall.getMethodName())
+ or
+ isPublicKernelMethod(methodCall.getMethodName())
+ )
+ )
+ }
+
+ int getNumberOfArguments() { result = methodCall.getNumberOfArguments() }
+}
+
+/**
+ * Public methods in the `Kernel` module. These can be invoked on any object via the usual dot syntax.
+ * ```ruby
+ * arr = []
+ * arr.send("push", 5) # => [5]
+ * ```
+ */
+private predicate isPublicKernelMethod(string method) {
+ method in ["class", "clone", "frozen?", "tap", "then", "yield_self", "send"]
+}
+
+/**
+ * Private methods in the `Kernel` module.
+ * These can be be invoked on `self`, on `Kernel`, or using a low-level primitive like `send` or `instance_eval`.
+ * ```ruby
+ * puts "hello world"
+ * Kernel.puts "hello world"
+ * 5.instance_eval { puts "hello world" }
+ * 5.send("puts", "hello world")
+ * ```
+ */
+private predicate isPrivateKernelMethod(string method) {
+ method in [
+ "Array", "Complex", "Float", "Hash", "Integer", "Rational", "String", "__callee__", "__dir__",
+ "__method__", "`", "abort", "at_exit", "autoload", "autoload?", "binding", "block_given?",
+ "callcc", "caller", "caller_locations", "catch", "chomp", "chop", "eval", "exec", "exit",
+ "exit!", "fail", "fork", "format", "gets", "global_variables", "gsub", "iterator?", "lambda",
+ "load", "local_variables", "loop", "open", "p", "pp", "print", "printf", "proc", "putc",
+ "puts", "raise", "rand", "readline", "readlines", "require", "require_relative", "select",
+ "set_trace_func", "sleep", "spawn", "sprintf", "srand", "sub", "syscall", "system", "test",
+ "throw", "trace_var", "trap", "untrace_var", "warn"
+ ]
+}
+
+string basicObjectInstanceMethodName() {
+ result in [
+ "equal?", "instance_eval", "instance_exec", "method_missing", "singleton_method_added",
+ "singleton_method_removed", "singleton_method_undefined"
+ ]
+}
+
+/**
+ * Instance methods on `BasicObject`, which are available to all classes.
+ */
+class BasicObjectInstanceMethodCall extends UnknownMethodCall {
+ BasicObjectInstanceMethodCall() { this.getMethodName() = basicObjectInstanceMethodName() }
+}
+
+string objectInstanceMethodName() {
+ result in [
+ "!~", "<=>", "===", "=~", "callable_methods", "define_singleton_method", "display",
+ "do_until", "do_while", "dup", "enum_for", "eql?", "extend", "f", "freeze", "h", "hash",
+ "inspect", "instance_of?", "instance_variable_defined?", "instance_variable_get",
+ "instance_variable_set", "instance_variables", "is_a?", "itself", "kind_of?",
+ "matching_methods", "method", "method_missing", "methods", "nil?", "object_id",
+ "private_methods", "protected_methods", "public_method", "public_methods", "public_send",
+ "remove_instance_variable", "respond_to?", "respond_to_missing?", "send",
+ "shortest_abbreviation", "singleton_class", "singleton_method", "singleton_methods", "taint",
+ "tainted?", "to_enum", "to_s", "trust", "untaint", "untrust", "untrusted?"
+ ]
+}
+
+/**
+ * Instance methods on `Object`, which are available to all classes except `BasicObject`.
+ */
+class ObjectInstanceMethodCall extends UnknownMethodCall {
+ ObjectInstanceMethodCall() { this.getMethodName() = objectInstanceMethodName() }
+}
+
+/**
+ * Method calls which have no known target.
+ * These will typically be calls to methods inherited from a superclass.
+ */
+class UnknownMethodCall extends MethodCall {
+ UnknownMethodCall() { not exists(this.(Call).getATarget()) }
+}
+
+/**
+ * A system command executed via subshell literal syntax.
+ * E.g.
+ * ```ruby
+ * `cat foo.txt`
+ * %x(cat foo.txt)
+ * %x[cat foo.txt]
+ * %x{cat foo.txt}
+ * %x/cat foo.txt/
+ * ```
+ */
+class SubshellLiteralExecution extends SystemCommandExecution::Range {
+ SubshellLiteral literal;
+
+ SubshellLiteralExecution() { this.asExpr().getExpr() = literal }
+
+ override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = literal.getComponent(_) }
+
+ override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getAnArgument() }
+}
+
+/**
+ * A system command executed via shell heredoc syntax.
+ * E.g.
+ * ```ruby
+ * <<`EOF`
+ * cat foo.text
+ * EOF
+ * ```
+ */
+class SubshellHeredocExecution extends SystemCommandExecution::Range {
+ HereDoc heredoc;
+
+ SubshellHeredocExecution() { this.asExpr().getExpr() = heredoc and heredoc.isSubShell() }
+
+ override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = heredoc.getComponent(_) }
+
+ override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getAnArgument() }
+}
+
+/**
+ * A system command executed via the `Kernel.system` method.
+ * `Kernel.system` accepts three argument forms:
+ * - A single string. If it contains no shell meta characters, keywords or
+ * builtins, it is executed directly in a subprocess.
+ * Otherwise, it is executed in a subshell.
+ * ```ruby
+ * system("cat foo.txt | tail")
+ * ```
+ * - A command and one or more arguments.
+ * The command is executed in a subprocess.
+ * ```ruby
+ * system("cat", "foo.txt")
+ * ```
+ * - An array containing the command name and argv[0], followed by zero or more arguments.
+ * The command is executed in a subprocess.
+ * ```ruby
+ * system(["cat", "cat"], "foo.txt")
+ * ```
+ * In addition, `Kernel.system` accepts an optional environment hash as the
+ * first argument and an optional options hash as the last argument.
+ * We don't yet distinguish between these arguments and the command arguments.
+ * ```ruby
+ * system({"FOO" => "BAR"}, "cat foo.txt | tail", {unsetenv_others: true})
+ * ```
+ * Ruby documentation: https://docs.ruby-lang.org/en/3.0.0/Kernel.html#method-i-system
+ */
+class KernelSystemCall extends SystemCommandExecution::Range, KernelMethodCall {
+ KernelSystemCall() { this.getMethodName() = "system" }
+
+ override DataFlow::Node getAnArgument() { result = this.getArgument(_) }
+
+ override predicate isShellInterpreted(DataFlow::Node arg) {
+ // Kernel.system invokes a subshell if you provide a single string as argument
+ this.getNumberOfArguments() = 1 and arg = this.getAnArgument()
+ }
+}
+
+/**
+ * A system command executed via the `Kernel.exec` method.
+ * `Kernel.exec` takes the same argument forms as `Kernel.system`. See `KernelSystemCall` for details.
+ * Ruby documentation: https://docs.ruby-lang.org/en/3.0.0/Kernel.html#method-i-exec
+ */
+class KernelExecCall extends SystemCommandExecution::Range, KernelMethodCall {
+ KernelExecCall() { this.getMethodName() = "exec" }
+
+ override DataFlow::Node getAnArgument() { result = this.getArgument(_) }
+
+ override predicate isShellInterpreted(DataFlow::Node arg) {
+ // Kernel.exec invokes a subshell if you provide a single string as argument
+ this.getNumberOfArguments() = 1 and arg = this.getAnArgument()
+ }
+}
+
+/**
+ * A system command executed via the `Kernel.spawn` method.
+ * `Kernel.spawn` takes the same argument forms as `Kernel.system`.
+ * See `KernelSystemCall` for details.
+ * Ruby documentation: https://docs.ruby-lang.org/en/3.0.0/Kernel.html#method-i-spawn
+ * TODO: document and handle the env and option arguments.
+ * ```
+ * spawn([env,] command... [,options]) -> pid
+ * ```
+ */
+class KernelSpawnCall extends SystemCommandExecution::Range, KernelMethodCall {
+ KernelSpawnCall() { this.getMethodName() = "spawn" }
+
+ override DataFlow::Node getAnArgument() { result = this.getArgument(_) }
+
+ override predicate isShellInterpreted(DataFlow::Node arg) {
+ // Kernel.spawn invokes a subshell if you provide a single string as argument
+ this.getNumberOfArguments() = 1 and arg = this.getAnArgument()
+ }
+}
+
+/**
+ * A system command executed via one of the `Open3` methods.
+ * These methods take the same argument forms as `Kernel.system`.
+ * See `KernelSystemCall` for details.
+ */
+class Open3Call extends SystemCommandExecution::Range {
+ MethodCall methodCall;
+
+ Open3Call() {
+ this.asExpr().getExpr() = methodCall and
+ this =
+ API::getTopLevelMember("Open3")
+ .getAMethodCall(["popen3", "popen2", "popen2e", "capture3", "capture2", "capture2e"])
+ }
+
+ override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = methodCall.getAnArgument() }
+
+ override predicate isShellInterpreted(DataFlow::Node arg) {
+ // These Open3 methods invoke a subshell if you provide a single string as argument
+ methodCall.getNumberOfArguments() = 1 and arg.asExpr().getExpr() = methodCall.getAnArgument()
+ }
+}
+
+/**
+ * A pipeline of system commands constructed via one of the `Open3` methods.
+ * These methods accept a variable argument list of commands.
+ * Commands can be in any form supported by `Kernel.system`. See `KernelSystemCall` for details.
+ * ```ruby
+ * Open3.pipeline("cat foo.txt", "tail")
+ * Open3.pipeline(["cat", "foo.txt"], "tail")
+ * Open3.pipeline([{}, "cat", "foo.txt"], "tail")
+ * Open3.pipeline([["cat", "cat"], "foo.txt"], "tail")
+ */
+class Open3PipelineCall extends SystemCommandExecution::Range {
+ MethodCall methodCall;
+
+ Open3PipelineCall() {
+ this.asExpr().getExpr() = methodCall and
+ this =
+ API::getTopLevelMember("Open3")
+ .getAMethodCall(["pipeline_rw", "pipeline_r", "pipeline_w", "pipeline_start", "pipeline"])
+ }
+
+ override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = methodCall.getAnArgument() }
+
+ override predicate isShellInterpreted(DataFlow::Node arg) {
+ // A command in the pipeline is executed in a subshell if it is given as a single string argument.
+ arg.asExpr().getExpr() instanceof StringlikeLiteral and
+ arg.asExpr().getExpr() = methodCall.getAnArgument()
+ }
+}
+
+/**
+ * A call to `Kernel.eval`, which executes its first argument as Ruby code.
+ * ```ruby
+ * a = 1
+ * Kernel.eval("a = 2")
+ * a # => 2
+ * ```
+ */
+class EvalCallCodeExecution extends CodeExecution::Range, KernelMethodCall {
+ EvalCallCodeExecution() { this.getMethodName() = "eval" }
+
+ override DataFlow::Node getCode() { result = this.getArgument(0) }
+}
+
+/**
+ * A call to `Kernel#send`, which executes its first argument as a Ruby method call.
+ * ```ruby
+ * arr = []
+ * arr.send("push", 1)
+ * arr # => [1]
+ * ```
+ */
+class SendCallCodeExecution extends CodeExecution::Range, KernelMethodCall {
+ SendCallCodeExecution() { this.getMethodName() = "send" }
+
+ override DataFlow::Node getCode() { result = this.getArgument(0) }
+}
+
+/**
+ * A call to `BasicObject#instance_eval`, which executes its first argument as Ruby code.
+ */
+class InstanceEvalCallCodeExecution extends CodeExecution::Range, DataFlow::CallNode {
+ InstanceEvalCallCodeExecution() {
+ this.asExpr().getExpr().(UnknownMethodCall).getMethodName() = "instance_eval"
+ }
+
+ override DataFlow::Node getCode() { result = this.getArgument(0) }
+}
+
+/**
+ * A call to `Module#class_eval`, which executes its first argument as Ruby code.
+ */
+class ClassEvalCallCodeExecution extends CodeExecution::Range, DataFlow::CallNode {
+ ClassEvalCallCodeExecution() {
+ this.asExpr().getExpr().(UnknownMethodCall).getMethodName() = "class_eval"
+ }
+
+ override DataFlow::Node getCode() { result = this.getArgument(0) }
+}
+
+/**
+ * A call to `Module#module_eval`, which executes its first argument as Ruby code.
+ */
+class ModuleEvalCallCodeExecution extends CodeExecution::Range, DataFlow::CallNode {
+ ModuleEvalCallCodeExecution() {
+ this.asExpr().getExpr().(UnknownMethodCall).getMethodName() = "module_eval"
+ }
+
+ override DataFlow::Node getCode() { result = this.getArgument(0) }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/XmlParsing.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/XmlParsing.qll
new file mode 100644
index 00000000000..2545c97f044
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/XmlParsing.qll
@@ -0,0 +1,182 @@
+private import codeql.ruby.Concepts
+private import codeql.ruby.AST
+private import codeql.ruby.DataFlow
+private import codeql.ruby.typetracking.TypeTracker
+private import codeql.ruby.ApiGraphs
+private import codeql.ruby.controlflow.CfgNodes as CfgNodes
+
+private class NokogiriXmlParserCall extends XmlParserCall::Range, DataFlow::CallNode {
+ NokogiriXmlParserCall() {
+ this =
+ [
+ API::getTopLevelMember("Nokogiri").getMember("XML"),
+ API::getTopLevelMember("Nokogiri").getMember("XML").getMember("Document"),
+ API::getTopLevelMember("Nokogiri")
+ .getMember("XML")
+ .getMember("SAX")
+ .getMember("Parser")
+ .getInstance()
+ ].getAMethodCall("parse")
+ }
+
+ override DataFlow::Node getInput() { result = this.getArgument(0) }
+
+ override predicate externalEntitiesEnabled() {
+ this.getArgument(3) =
+ [trackEnableFeature(TNOENT()), trackEnableFeature(TDTDLOAD()), trackDisableFeature(TNONET())]
+ or
+ // calls to methods that enable/disable features in a block argument passed to this parser call.
+ // For example:
+ // ```ruby
+ // doc.parse(...) { |options| options.nononet; options.noent }
+ // ```
+ this.asExpr()
+ .getExpr()
+ .(MethodCall)
+ .getBlock()
+ .getAStmt()
+ .getAChild*()
+ .(MethodCall)
+ .getMethodName() = ["noent", "dtdload", "nononet"]
+ }
+}
+
+private class LibXmlRubyXmlParserCall extends XmlParserCall::Range, DataFlow::CallNode {
+ LibXmlRubyXmlParserCall() {
+ this =
+ [API::getTopLevelMember("LibXML").getMember("XML"), API::getTopLevelMember("XML")]
+ .getMember(["Document", "Parser"])
+ .getAMethodCall(["file", "io", "string"])
+ }
+
+ override DataFlow::Node getInput() { result = this.getArgument(0) }
+
+ override predicate externalEntitiesEnabled() {
+ exists(Pair pair |
+ pair = this.getArgument(1).asExpr().getExpr().(HashLiteral).getAKeyValuePair() and
+ pair.getKey().(Literal).getValueText() = "options" and
+ pair.getValue() =
+ [
+ trackEnableFeature(TNOENT()), trackEnableFeature(TDTDLOAD()),
+ trackDisableFeature(TNONET())
+ ].asExpr().getExpr()
+ )
+ }
+}
+
+private newtype TFeature =
+ TNOENT() or
+ TNONET() or
+ TDTDLOAD()
+
+class Feature extends TFeature {
+ abstract int getValue();
+
+ string toString() { result = this.getConstantName() }
+
+ abstract string getConstantName();
+}
+
+private class FeatureNOENT extends Feature, TNOENT {
+ override int getValue() { result = 2 }
+
+ override string getConstantName() { result = "NOENT" }
+}
+
+private class FeatureNONET extends Feature, TNONET {
+ override int getValue() { result = 2048 }
+
+ override string getConstantName() { result = "NONET" }
+}
+
+private class FeatureDTDLOAD extends Feature, TDTDLOAD {
+ override int getValue() { result = 4 }
+
+ override string getConstantName() { result = "DTDLOAD" }
+}
+
+private API::Node parseOptionsModule() {
+ result = API::getTopLevelMember("Nokogiri").getMember("XML").getMember("ParseOptions")
+ or
+ result =
+ API::getTopLevelMember("LibXML").getMember("XML").getMember("Parser").getMember("Options")
+ or
+ result = API::getTopLevelMember("XML").getMember("Parser").getMember("Options")
+}
+
+private predicate bitWiseAndOr(CfgNodes::ExprNodes::OperationCfgNode operation) {
+ operation.getExpr() instanceof BitwiseAndExpr or
+ operation.getExpr() instanceof AssignBitwiseAndExpr or
+ operation.getExpr() instanceof BitwiseOrExpr or
+ operation.getExpr() instanceof AssignBitwiseOrExpr
+}
+
+private DataFlow::LocalSourceNode trackFeature(Feature f, boolean enable, TypeTracker t) {
+ t.start() and
+ (
+ // An integer literal with the feature-bit enabled/disabled
+ exists(int bitValue |
+ bitValue = result.asExpr().getExpr().(IntegerLiteral).getValue().bitAnd(f.getValue())
+ |
+ if bitValue = 0 then enable = false else enable = true
+ )
+ or
+ // Use of a constant f
+ enable = true and
+ result = parseOptionsModule().getMember(f.getConstantName()).getAUse()
+ or
+ // Treat `&`, `&=`, `|` and `|=` operators as if they preserve the on/off states
+ // of their operands. This is an overapproximation but likely to work well in practice
+ // because it makes little sense to explicitly set a feature to both `on` and `off` in the
+ // same code.
+ exists(CfgNodes::ExprNodes::OperationCfgNode operation |
+ bitWiseAndOr(operation) and
+ operation = result.asExpr() and
+ operation.getAnOperand() = trackFeature(f, enable).asExpr()
+ )
+ or
+ // The complement operator toggles a feature from enabled to disabled and vice-versa
+ result.asExpr().getExpr() instanceof ComplementExpr and
+ result.asExpr().(CfgNodes::ExprNodes::OperationCfgNode).getAnOperand() =
+ trackFeature(f, enable.booleanNot()).asExpr()
+ or
+ // Nokogiri has a ParseOptions class that is a wrapper around the bit-fields and
+ // provides methods for querying and updating the fields.
+ result =
+ API::getTopLevelMember("Nokogiri")
+ .getMember("XML")
+ .getMember("ParseOptions")
+ .getAnInstantiation() and
+ result.asExpr().(CfgNodes::ExprNodes::CallCfgNode).getArgument(0) =
+ trackFeature(f, enable).asExpr()
+ or
+ // The Nokogiri ParseOptions class has methods for setting/unsetting features.
+ // The method names are the lowercase variants of the constant names, with a "no"
+ // prefix for unsetting a feature.
+ exists(CfgNodes::ExprNodes::CallCfgNode call |
+ enable = true and
+ call.getExpr().(MethodCall).getMethodName() = f.getConstantName().toLowerCase()
+ or
+ enable = false and
+ call.getExpr().(MethodCall).getMethodName() = "no" + f.getConstantName().toLowerCase()
+ |
+ (
+ // these methods update the receiver
+ result.flowsTo(any(DataFlow::Node n | n.asExpr() = call.getReceiver()))
+ or
+ // in addition they return the (updated) receiver to allow chaining calls.
+ result.asExpr() = call
+ )
+ )
+ )
+ or
+ exists(TypeTracker t2 | result = trackFeature(f, enable, t2).track(t2, t))
+}
+
+private DataFlow::Node trackFeature(Feature f, boolean enable) {
+ trackFeature(f, enable, TypeTracker::end()).flowsTo(result)
+}
+
+private DataFlow::Node trackEnableFeature(Feature f) { result = trackFeature(f, true) }
+
+private DataFlow::Node trackDisableFeature(Feature f) { result = trackFeature(f, false) }
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Excon.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Excon.qll
new file mode 100644
index 00000000000..efb9d7be66c
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Excon.qll
@@ -0,0 +1,130 @@
+private import ruby
+private import codeql.ruby.Concepts
+private import codeql.ruby.ApiGraphs
+
+/**
+ * A call that makes an HTTP request using `Excon`.
+ * ```ruby
+ * # one-off request
+ * Excon.get("http://example.com").body
+ *
+ * # connection re-use
+ * connection = Excon.new("http://example.com")
+ * connection.get(path: "/").body
+ * connection.request(method: :get, path: "/")
+ * ```
+ *
+ * TODO: pipelining, streaming responses
+ * https://github.com/excon/excon/blob/master/README.md
+ */
+class ExconHttpRequest extends HTTP::Client::Request::Range {
+ DataFlow::Node requestUse;
+ API::Node requestNode;
+ API::Node connectionNode;
+
+ ExconHttpRequest() {
+ requestUse = requestNode.getAnImmediateUse() and
+ connectionNode =
+ [
+ // one-off requests
+ API::getTopLevelMember("Excon"),
+ // connection re-use
+ API::getTopLevelMember("Excon").getInstance(),
+ API::getTopLevelMember("Excon").getMember("Connection").getInstance()
+ ] and
+ requestNode =
+ connectionNode
+ .getReturn([
+ // Excon#request exists but Excon.request doesn't.
+ // This shouldn't be a problem - in real code the latter would raise NoMethodError anyway.
+ "get", "head", "delete", "options", "post", "put", "patch", "trace", "request"
+ ]) and
+ this = requestUse.asExpr().getExpr()
+ }
+
+ override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
+
+ override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
+ // Check for `ssl_verify_peer: false` in the options hash.
+ exists(DataFlow::Node arg, int i |
+ i > 0 and arg = connectionNode.getAUse().(DataFlow::CallNode).getArgument(i)
+ |
+ argSetsVerifyPeer(arg, false, disablingNode)
+ )
+ or
+ // Or we see a call to `Excon.defaults[:ssl_verify_peer] = false` before the
+ // request, and no `ssl_verify_peer: true` in the explicit options hash for
+ // the request call.
+ exists(DataFlow::CallNode disableCall |
+ setsDefaultVerification(disableCall, false) and
+ disableCall.asExpr().getASuccessor+() = requestUse.asExpr() and
+ disablingNode = disableCall and
+ not exists(DataFlow::Node arg, int i |
+ i > 0 and arg = connectionNode.getAUse().(DataFlow::CallNode).getArgument(i)
+ |
+ argSetsVerifyPeer(arg, true, _)
+ )
+ )
+ }
+
+ override string getFramework() { result = "Excon" }
+}
+
+/**
+ * Holds if `arg` represents an options hash that contains the key
+ * `:ssl_verify_peer` with `value`, where `kvNode` is the data-flow node for
+ * this key-value pair.
+ */
+predicate argSetsVerifyPeer(DataFlow::Node arg, boolean value, DataFlow::Node kvNode) {
+ // Either passed as an individual key:value argument, e.g.:
+ // Excon.get(..., ssl_verify_peer: false)
+ isSslVerifyPeerPair(arg.asExpr().getExpr(), value) and
+ kvNode = arg
+ or
+ // Or as a single hash argument, e.g.:
+ // Excon.get(..., { ssl_verify_peer: false, ... })
+ exists(DataFlow::LocalSourceNode optionsNode, Pair p |
+ p = optionsNode.asExpr().getExpr().(HashLiteral).getAKeyValuePair() and
+ isSslVerifyPeerPair(p, value) and
+ optionsNode.flowsTo(arg) and
+ kvNode.asExpr().getExpr() = p
+ )
+}
+
+/**
+ * Holds if `callNode` sets `Excon.defaults[:ssl_verify_peer]` or
+ * `Excon.ssl_verify_peer` to `value`.
+ */
+private predicate setsDefaultVerification(DataFlow::CallNode callNode, boolean value) {
+ callNode = API::getTopLevelMember("Excon").getReturn("defaults").getAMethodCall("[]=") and
+ isSslVerifyPeerLiteral(callNode.getArgument(0)) and
+ hasBooleanValue(callNode.getArgument(1), value)
+ or
+ callNode = API::getTopLevelMember("Excon").getAMethodCall("ssl_verify_peer=") and
+ hasBooleanValue(callNode.getArgument(0), value)
+}
+
+private predicate isSslVerifyPeerLiteral(DataFlow::Node node) {
+ exists(DataFlow::LocalSourceNode literal |
+ literal.asExpr().getExpr().(SymbolLiteral).getValueText() = "ssl_verify_peer" and
+ literal.flowsTo(node)
+ )
+}
+
+/** Holds if `node` can contain `value`. */
+private predicate hasBooleanValue(DataFlow::Node node, boolean value) {
+ exists(DataFlow::LocalSourceNode literal |
+ literal.asExpr().getExpr().(BooleanLiteral).getValue() = value and
+ literal.flowsTo(node)
+ )
+}
+
+/** Holds if `p` is the pair `ssl_verify_peer: `. */
+private predicate isSslVerifyPeerPair(Pair p, boolean value) {
+ exists(DataFlow::Node key, DataFlow::Node valueNode |
+ key.asExpr().getExpr() = p.getKey() and valueNode.asExpr().getExpr() = p.getValue()
+ |
+ isSslVerifyPeerLiteral(key) and
+ hasBooleanValue(valueNode, value)
+ )
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Faraday.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Faraday.qll
new file mode 100644
index 00000000000..de3f6f5f811
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Faraday.qll
@@ -0,0 +1,140 @@
+private import ruby
+private import codeql.ruby.Concepts
+private import codeql.ruby.ApiGraphs
+
+/**
+ * A call that makes an HTTP request using `Faraday`.
+ * ```ruby
+ * # one-off request
+ * Faraday.get("http://example.com").body
+ *
+ * # connection re-use
+ * connection = Faraday.new("http://example.com")
+ * connection.get("/").body
+ * ```
+ */
+class FaradayHttpRequest extends HTTP::Client::Request::Range {
+ DataFlow::Node requestUse;
+ API::Node requestNode;
+ API::Node connectionNode;
+
+ FaradayHttpRequest() {
+ connectionNode =
+ [
+ // one-off requests
+ API::getTopLevelMember("Faraday"),
+ // connection re-use
+ API::getTopLevelMember("Faraday").getInstance()
+ ] and
+ requestNode =
+ connectionNode.getReturn(["get", "head", "delete", "post", "put", "patch", "trace"]) and
+ requestUse = requestNode.getAnImmediateUse() and
+ this = requestUse.asExpr().getExpr()
+ }
+
+ override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
+
+ override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
+ // `Faraday::new` takes an options hash as its second argument, and we're
+ // looking for
+ // `{ ssl: { verify: false } }`
+ // or
+ // `{ ssl: { verify_mode: OpenSSL::SSL::VERIFY_NONE } }`
+ exists(DataFlow::Node arg, int i |
+ i > 0 and arg = connectionNode.getAUse().(DataFlow::CallNode).getArgument(i)
+ |
+ // Either passed as an individual key:value argument, e.g.:
+ // Faraday.new(..., ssl: {...})
+ isSslOptionsPairDisablingValidation(arg.asExpr().getExpr()) and
+ disablingNode = arg
+ or
+ // Or as a single hash argument, e.g.:
+ // Faraday.new(..., { ssl: {...} })
+ exists(DataFlow::LocalSourceNode optionsNode, Pair p |
+ p = optionsNode.asExpr().getExpr().(HashLiteral).getAKeyValuePair() and
+ isSslOptionsPairDisablingValidation(p) and
+ optionsNode.flowsTo(arg) and
+ disablingNode.asExpr().getExpr() = p
+ )
+ )
+ }
+
+ override string getFramework() { result = "Faraday" }
+}
+
+/**
+ * Holds if the pair `p` contains the key `:ssl` for which the value is a hash
+ * containing either `verify: false` or
+ * `verify_mode: OpenSSL::SSL::VERIFY_NONE`.
+ */
+private predicate isSslOptionsPairDisablingValidation(Pair p) {
+ exists(DataFlow::Node key, DataFlow::Node value |
+ key.asExpr().getExpr() = p.getKey() and value.asExpr().getExpr() = p.getValue()
+ |
+ isSymbolLiteral(key, "ssl") and
+ (isHashWithVerifyFalse(value) or isHashWithVerifyModeNone(value))
+ )
+}
+
+/** Holds if `node` represents the symbol literal with the given `valueText`. */
+private predicate isSymbolLiteral(DataFlow::Node node, string valueText) {
+ exists(DataFlow::LocalSourceNode literal |
+ literal.asExpr().getExpr().(SymbolLiteral).getValueText() = valueText and
+ literal.flowsTo(node)
+ )
+}
+
+/**
+ * Holds if `node` represents a hash containing the key-value pair
+ * `verify: false`.
+ */
+private predicate isHashWithVerifyFalse(DataFlow::Node node) {
+ exists(DataFlow::LocalSourceNode hash |
+ isVerifyFalsePair(hash.asExpr().getExpr().(HashLiteral).getAKeyValuePair()) and
+ hash.flowsTo(node)
+ )
+}
+
+/**
+ * Holds if `node` represents a hash containing the key-value pair
+ * `verify_mode: OpenSSL::SSL::VERIFY_NONE`.
+ */
+private predicate isHashWithVerifyModeNone(DataFlow::Node node) {
+ exists(DataFlow::LocalSourceNode hash |
+ isVerifyModeNonePair(hash.asExpr().getExpr().(HashLiteral).getAKeyValuePair()) and
+ hash.flowsTo(node)
+ )
+}
+
+/**
+ * Holds if the pair `p` has the key `:verify_mode` and the value
+ * `OpenSSL::SSL::VERIFY_NONE`.
+ */
+private predicate isVerifyModeNonePair(Pair p) {
+ exists(DataFlow::Node key, DataFlow::Node value |
+ key.asExpr().getExpr() = p.getKey() and value.asExpr().getExpr() = p.getValue()
+ |
+ isSymbolLiteral(key, "verify_mode") and
+ value = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse()
+ )
+}
+
+/**
+ * Holds if the pair `p` has the key `:verify` and the value `false`.
+ */
+private predicate isVerifyFalsePair(Pair p) {
+ exists(DataFlow::Node key, DataFlow::Node value |
+ key.asExpr().getExpr() = p.getKey() and value.asExpr().getExpr() = p.getValue()
+ |
+ isSymbolLiteral(key, "verify") and
+ isFalse(value)
+ )
+}
+
+/** Holds if `node` can contain the Boolean value `false`. */
+private predicate isFalse(DataFlow::Node node) {
+ exists(DataFlow::LocalSourceNode literal |
+ literal.asExpr().getExpr().(BooleanLiteral).isFalse() and
+ literal.flowsTo(node)
+ )
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/http_clients/HttpClient.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/http_clients/HttpClient.qll
new file mode 100644
index 00000000000..3db9c653a5c
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/http_clients/HttpClient.qll
@@ -0,0 +1,55 @@
+private import ruby
+private import codeql.ruby.Concepts
+private import codeql.ruby.ApiGraphs
+
+/**
+ * A call that makes an HTTP request using `HTTPClient`.
+ * ```ruby
+ * HTTPClient.get("http://example.com").body
+ * HTTPClient.get_content("http://example.com")
+ * ```
+ */
+class HttpClientRequest extends HTTP::Client::Request::Range {
+ API::Node requestNode;
+ API::Node connectionNode;
+ DataFlow::Node requestUse;
+ string method;
+
+ HttpClientRequest() {
+ connectionNode =
+ [
+ // One-off requests
+ API::getTopLevelMember("HTTPClient"),
+ // Conncection re-use
+ API::getTopLevelMember("HTTPClient").getInstance()
+ ] and
+ requestNode = connectionNode.getReturn(method) and
+ requestUse = requestNode.getAnImmediateUse() and
+ method in [
+ "get", "head", "delete", "options", "post", "put", "trace", "get_content", "post_content"
+ ] and
+ this = requestUse.asExpr().getExpr()
+ }
+
+ override DataFlow::Node getResponseBody() {
+ // The `get_content` and `post_content` methods return the response body as
+ // a string. The other methods return a `HTTPClient::Message` object which
+ // has various methods that return the response body.
+ method in ["get_content", "post_content"] and result = requestUse
+ or
+ not method in ["get_content", "put_content"] and
+ result = requestNode.getAMethodCall(["body", "http_body", "content", "dump"])
+ }
+
+ override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
+ // Look for calls to set
+ // `c.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE`
+ // on an HTTPClient connection object `c`.
+ disablingNode =
+ connectionNode.getReturn("ssl_config").getReturn("verify_mode=").getAnImmediateUse() and
+ disablingNode.(DataFlow::CallNode).getArgument(0) =
+ API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse()
+ }
+
+ override string getFramework() { result = "HTTPClient" }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Httparty.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Httparty.qll
new file mode 100644
index 00000000000..b1746692bf7
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Httparty.qll
@@ -0,0 +1,95 @@
+private import ruby
+private import codeql.ruby.Concepts
+private import codeql.ruby.ApiGraphs
+
+/**
+ * A call that makes an HTTP request using `HTTParty`.
+ * ```ruby
+ * # one-off request - returns the response body
+ * HTTParty.get("http://example.com")
+ *
+ * # TODO: module inclusion
+ * class MyClass
+ * include HTTParty
+ * end
+ *
+ * MyClass.new("http://example.com")
+ * ```
+ */
+class HttpartyRequest extends HTTP::Client::Request::Range {
+ API::Node requestNode;
+ DataFlow::Node requestUse;
+
+ HttpartyRequest() {
+ requestUse = requestNode.getAnImmediateUse() and
+ requestNode =
+ API::getTopLevelMember("HTTParty")
+ .getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and
+ this = requestUse.asExpr().getExpr()
+ }
+
+ override DataFlow::Node getResponseBody() {
+ // If HTTParty can recognise the response type, it will parse and return it
+ // directly from the request call. Otherwise, it will return a `HTTParty::Response`
+ // object that has a `#body` method.
+ // So if there's a call to `#body` on the response, treat that as the response body.
+ exists(DataFlow::Node r | r = requestNode.getAMethodCall("body") | result = r)
+ or
+ // Otherwise, treat the response as the response body.
+ not exists(DataFlow::Node r | r = requestNode.getAMethodCall("body")) and
+ result = requestUse
+ }
+
+ override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
+ // The various request methods take an options hash as their second
+ // argument, and we're looking for `{ verify: false }` or
+ // `{ verify_peer: false }`.
+ exists(DataFlow::Node arg, int i |
+ i > 0 and arg.asExpr().getExpr() = requestUse.asExpr().getExpr().(MethodCall).getArgument(i)
+ |
+ // Either passed as an individual key:value argument, e.g.:
+ // HTTParty.get(..., verify: false)
+ isVerifyFalsePair(arg.asExpr().getExpr()) and
+ disablingNode = arg
+ or
+ // Or as a single hash argument, e.g.:
+ // HTTParty.get(..., { verify: false, ... })
+ exists(DataFlow::LocalSourceNode optionsNode, Pair p |
+ p = optionsNode.asExpr().getExpr().(HashLiteral).getAKeyValuePair() and
+ isVerifyFalsePair(p) and
+ optionsNode.flowsTo(arg) and
+ disablingNode.asExpr().getExpr() = p
+ )
+ )
+ }
+
+ override string getFramework() { result = "HTTParty" }
+}
+
+/** Holds if `node` represents the symbol literal `verify` or `verify_peer`. */
+private predicate isVerifyLiteral(DataFlow::Node node) {
+ exists(DataFlow::LocalSourceNode literal |
+ literal.asExpr().getExpr().(SymbolLiteral).getValueText() = ["verify", "verify_peer"] and
+ literal.flowsTo(node)
+ )
+}
+
+/** Holds if `node` can contain the Boolean value `false`. */
+private predicate isFalse(DataFlow::Node node) {
+ exists(DataFlow::LocalSourceNode literal |
+ literal.asExpr().getExpr().(BooleanLiteral).isFalse() and
+ literal.flowsTo(node)
+ )
+}
+
+/**
+ * Holds if `p` is the pair `verify: false` or `verify_peer: false`.
+ */
+private predicate isVerifyFalsePair(Pair p) {
+ exists(DataFlow::Node key, DataFlow::Node value |
+ key.asExpr().getExpr() = p.getKey() and value.asExpr().getExpr() = p.getValue()
+ |
+ isVerifyLiteral(key) and
+ isFalse(value)
+ )
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/http_clients/NetHttp.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/http_clients/NetHttp.qll
new file mode 100644
index 00000000000..9d9c6f7aff3
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/http_clients/NetHttp.qll
@@ -0,0 +1,69 @@
+private import codeql.ruby.AST
+private import codeql.ruby.Concepts
+private import codeql.ruby.dataflow.RemoteFlowSources
+private import codeql.ruby.ApiGraphs
+private import codeql.ruby.dataflow.internal.DataFlowPublic
+
+/**
+ * A `Net::HTTP` call which initiates an HTTP request.
+ * ```ruby
+ * Net::HTTP.get("http://example.com/")
+ * Net::HTTP.post("http://example.com/", "some_data")
+ * req = Net::HTTP.new("example.com")
+ * response = req.get("/")
+ * ```
+ */
+class NetHttpRequest extends HTTP::Client::Request::Range {
+ private DataFlow::CallNode request;
+ private DataFlow::Node responseBody;
+
+ NetHttpRequest() {
+ exists(API::Node requestNode, string method |
+ request = requestNode.getAnImmediateUse() and
+ this = request.asExpr().getExpr()
+ |
+ // Net::HTTP.get(...)
+ method = "get" and
+ requestNode = API::getTopLevelMember("Net").getMember("HTTP").getReturn(method) and
+ responseBody = request
+ or
+ // Net::HTTP.post(...).body
+ method in ["post", "post_form"] and
+ requestNode = API::getTopLevelMember("Net").getMember("HTTP").getReturn(method) and
+ responseBody = requestNode.getAMethodCall(["body", "read_body", "entity"])
+ or
+ // Net::HTTP.new(..).get(..).body
+ method in [
+ "get", "get2", "request_get", "head", "head2", "request_head", "delete", "put", "patch",
+ "post", "post2", "request_post", "request"
+ ] and
+ requestNode = API::getTopLevelMember("Net").getMember("HTTP").getInstance().getReturn(method) and
+ responseBody = requestNode.getAMethodCall(["body", "read_body", "entity"])
+ )
+ }
+
+ /**
+ * Gets the node representing the URL of the request.
+ * Currently unused, but may be useful in future, e.g. to filter out certain requests.
+ */
+ DataFlow::Node getURLArgument() { result = request.getArgument(0) }
+
+ override DataFlow::Node getResponseBody() { result = responseBody }
+
+ override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
+ // A Net::HTTP request bypasses certificate validation if we see a setter
+ // call like this:
+ // foo.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ // and then the receiver of that call flows to the receiver in the request:
+ // foo.request(...)
+ exists(DataFlow::CallNode setter |
+ disablingNode =
+ API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse() and
+ setter.asExpr().getExpr().(SetterMethodCall).getMethodName() = "verify_mode=" and
+ disablingNode = setter.getArgument(0) and
+ localFlow(setter.getReceiver(), request.getReceiver())
+ )
+ }
+
+ override string getFramework() { result = "Net::HTTP" }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/http_clients/OpenURI.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/http_clients/OpenURI.qll
new file mode 100644
index 00000000000..54a2c180fec
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/http_clients/OpenURI.qll
@@ -0,0 +1,113 @@
+private import ruby
+private import codeql.ruby.Concepts
+private import codeql.ruby.ApiGraphs
+private import codeql.ruby.frameworks.StandardLibrary
+
+/**
+ * A call that makes an HTTP request using `OpenURI` via `URI.open` or
+ * `URI.parse(...).open`.
+ *
+ * ```ruby
+ * URI.open("http://example.com").readlines
+ * URI.parse("http://example.com").open.read
+ * ```
+ */
+class OpenUriRequest extends HTTP::Client::Request::Range {
+ API::Node requestNode;
+ DataFlow::Node requestUse;
+
+ OpenUriRequest() {
+ requestNode =
+ [API::getTopLevelMember("URI"), API::getTopLevelMember("URI").getReturn("parse")]
+ .getReturn("open") and
+ requestUse = requestNode.getAnImmediateUse() and
+ this = requestUse.asExpr().getExpr()
+ }
+
+ override DataFlow::Node getResponseBody() {
+ result = requestNode.getAMethodCall(["read", "readlines"])
+ }
+
+ override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
+ exists(DataFlow::Node arg |
+ arg.asExpr().getExpr() = requestUse.asExpr().getExpr().(MethodCall).getArgument(_)
+ |
+ argumentDisablesValidation(arg, disablingNode)
+ )
+ }
+
+ override string getFramework() { result = "OpenURI" }
+}
+
+/**
+ * A call that makes an HTTP request using `OpenURI` and its `Kernel.open`
+ * interface.
+ *
+ * ```ruby
+ * Kernel.open("http://example.com").read
+ * ```
+ */
+class OpenUriKernelOpenRequest extends HTTP::Client::Request::Range {
+ DataFlow::Node requestUse;
+
+ OpenUriKernelOpenRequest() {
+ requestUse instanceof KernelMethodCall and
+ this.getMethodName() = "open" and
+ this = requestUse.asExpr().getExpr()
+ }
+
+ override DataFlow::CallNode getResponseBody() {
+ result.asExpr().getExpr().(MethodCall).getMethodName() in ["read", "readlines"] and
+ requestUse.(DataFlow::LocalSourceNode).flowsTo(result.getReceiver())
+ }
+
+ override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
+ exists(DataFlow::Node arg, int i |
+ i > 0 and
+ arg.asExpr().getExpr() = requestUse.asExpr().getExpr().(MethodCall).getArgument(i)
+ |
+ argumentDisablesValidation(arg, disablingNode)
+ )
+ }
+
+ override string getFramework() { result = "OpenURI" }
+}
+
+/**
+ * Holds if the argument `arg` is an options hash that disables certificate
+ * validation, and `disablingNode` is the specific node representing the
+ * `ssl_verify_mode: OpenSSL::SSL_VERIFY_NONE` pair.
+ */
+private predicate argumentDisablesValidation(DataFlow::Node arg, DataFlow::Node disablingNode) {
+ // Either passed as an individual key:value argument, e.g.:
+ // URI.open(..., ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE)
+ isSslVerifyModeNonePair(arg.asExpr().getExpr()) and
+ disablingNode = arg
+ or
+ // Or as a single hash argument, e.g.:
+ // URI.open(..., { ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE, ... })
+ exists(DataFlow::LocalSourceNode optionsNode, Pair p |
+ p = optionsNode.asExpr().getExpr().(HashLiteral).getAKeyValuePair() and
+ isSslVerifyModeNonePair(p) and
+ optionsNode.flowsTo(arg) and
+ disablingNode.asExpr().getExpr() = p
+ )
+}
+
+/** Holds if `p` is the pair `ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE`. */
+private predicate isSslVerifyModeNonePair(Pair p) {
+ exists(DataFlow::Node key, DataFlow::Node value |
+ key.asExpr().getExpr() = p.getKey() and value.asExpr().getExpr() = p.getValue()
+ |
+ isSslVerifyModeLiteral(key) and
+ value = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse()
+ )
+}
+
+/** Holds if `node` can represent the symbol literal `:ssl_verify_mode`. */
+private predicate isSslVerifyModeLiteral(DataFlow::Node node) {
+ exists(DataFlow::LocalSourceNode literal |
+ literal.asExpr().getExpr().(SymbolLiteral).getValueText() = "ssl_verify_mode" and
+ literal.flowsTo(node)
+ )
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/http_clients/RestClient.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/http_clients/RestClient.qll
new file mode 100644
index 00000000000..3b6ff318b66
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/http_clients/RestClient.qll
@@ -0,0 +1,71 @@
+private import ruby
+private import codeql.ruby.Concepts
+private import codeql.ruby.ApiGraphs
+
+/**
+ * A call that makes an HTTP request using `RestClient`.
+ * ```ruby
+ * RestClient.get("http://example.com").body
+ * ```
+ */
+class RestClientHttpRequest extends HTTP::Client::Request::Range {
+ DataFlow::Node requestUse;
+ API::Node requestNode;
+ API::Node connectionNode;
+
+ RestClientHttpRequest() {
+ connectionNode =
+ [
+ API::getTopLevelMember("RestClient"),
+ API::getTopLevelMember("RestClient").getMember("Resource").getInstance()
+ ] and
+ requestNode =
+ connectionNode.getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and
+ requestUse = requestNode.getAnImmediateUse() and
+ this = requestUse.asExpr().getExpr()
+ }
+
+ override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
+
+ override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
+ // `RestClient::Resource::new` takes an options hash argument, and we're
+ // looking for `{ verify_ssl: OpenSSL::SSL::VERIFY_NONE }`.
+ exists(DataFlow::Node arg, int i |
+ i > 0 and arg = connectionNode.getAUse().(DataFlow::CallNode).getArgument(i)
+ |
+ // Either passed as an individual key:value argument, e.g.:
+ // RestClient::Resource.new(..., verify_ssl: OpenSSL::SSL::VERIFY_NONE)
+ isVerifySslNonePair(arg.asExpr().getExpr()) and
+ disablingNode = arg
+ or
+ // Or as a single hash argument, e.g.:
+ // RestClient::Resource.new(..., { verify_ssl: OpenSSL::SSL::VERIFY_NONE })
+ exists(DataFlow::LocalSourceNode optionsNode, Pair p |
+ p = optionsNode.asExpr().getExpr().(HashLiteral).getAKeyValuePair() and
+ isVerifySslNonePair(p) and
+ optionsNode.flowsTo(arg) and
+ disablingNode.asExpr().getExpr() = p
+ )
+ )
+ }
+
+ override string getFramework() { result = "RestClient" }
+}
+
+/** Holds if `p` is the pair `verify_ssl: OpenSSL::SSL::VERIFY_NONE`. */
+private predicate isVerifySslNonePair(Pair p) {
+ exists(DataFlow::Node key, DataFlow::Node value |
+ key.asExpr().getExpr() = p.getKey() and value.asExpr().getExpr() = p.getValue()
+ |
+ isSslVerifyModeLiteral(key) and
+ value = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse()
+ )
+}
+
+/** Holds if `node` can represent the symbol literal `:verify_ssl`. */
+private predicate isSslVerifyModeLiteral(DataFlow::Node node) {
+ exists(DataFlow::LocalSourceNode literal |
+ literal.asExpr().getExpr().(SymbolLiteral).getValueText() = "verify_ssl" and
+ literal.flowsTo(node)
+ )
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Typhoeus.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Typhoeus.qll
new file mode 100644
index 00000000000..38fa5288079
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Typhoeus.qll
@@ -0,0 +1,74 @@
+private import ruby
+private import codeql.ruby.Concepts
+private import codeql.ruby.ApiGraphs
+
+/**
+ * A call that makes an HTTP request using `Typhoeus`.
+ * ```ruby
+ * Typhoeus.get("http://example.com").body
+ * ```
+ */
+class TyphoeusHttpRequest extends HTTP::Client::Request::Range {
+ DataFlow::Node requestUse;
+ API::Node requestNode;
+
+ TyphoeusHttpRequest() {
+ requestUse = requestNode.getAnImmediateUse() and
+ requestNode =
+ API::getTopLevelMember("Typhoeus")
+ .getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and
+ this = requestUse.asExpr().getExpr()
+ }
+
+ override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
+
+ override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
+ // Check for `ssl_verifypeer: false` in the options hash.
+ exists(DataFlow::Node arg, int i |
+ i > 0 and arg.asExpr().getExpr() = requestUse.asExpr().getExpr().(MethodCall).getArgument(i)
+ |
+ // Either passed as an individual key:value argument, e.g.:
+ // Typhoeus.get(..., ssl_verifypeer: false)
+ isSslVerifyPeerFalsePair(arg.asExpr().getExpr()) and
+ disablingNode = arg
+ or
+ // Or as a single hash argument, e.g.:
+ // Typhoeus.get(..., { ssl_verifypeer: false, ... })
+ exists(DataFlow::LocalSourceNode optionsNode, Pair p |
+ p = optionsNode.asExpr().getExpr().(HashLiteral).getAKeyValuePair() and
+ isSslVerifyPeerFalsePair(p) and
+ optionsNode.flowsTo(arg) and
+ disablingNode.asExpr().getExpr() = p
+ )
+ )
+ }
+
+ override string getFramework() { result = "Typhoeus" }
+}
+
+/** Holds if `p` is the pair `ssl_verifypeer: false`. */
+private predicate isSslVerifyPeerFalsePair(Pair p) {
+ exists(DataFlow::Node key, DataFlow::Node value |
+ key.asExpr().getExpr() = p.getKey() and
+ value.asExpr().getExpr() = p.getValue()
+ |
+ isSslVerifyPeerLiteral(key) and
+ isFalse(value)
+ )
+}
+
+/** Holds if `node` represents the symbol literal `verify` or `verify_peer`. */
+private predicate isSslVerifyPeerLiteral(DataFlow::Node node) {
+ exists(DataFlow::LocalSourceNode literal |
+ literal.asExpr().getExpr().(SymbolLiteral).getValueText() = "ssl_verifypeer" and
+ literal.flowsTo(node)
+ )
+}
+
+/** Holds if `node` can contain the Boolean value `false`. */
+private predicate isFalse(DataFlow::Node node) {
+ exists(DataFlow::LocalSourceNode literal |
+ literal.asExpr().getExpr().(BooleanLiteral).isFalse() and
+ literal.flowsTo(node)
+ )
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/printAst.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/printAst.qll
new file mode 100644
index 00000000000..2f8da6c75ae
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/printAst.qll
@@ -0,0 +1,203 @@
+/**
+ * Provides queries to pretty-print a Ruby abstract syntax tree as a graph.
+ *
+ * By default, this will print the AST for all nodes in the database. To change
+ * this behavior, extend `PrintASTConfiguration` and override `shouldPrintNode`
+ * to hold for only the AST nodes you wish to view.
+ */
+
+private import AST
+private import codeql.ruby.security.performance.RegExpTreeView as RETV
+
+/** Holds if `n` appears in the desugaring of some other node. */
+predicate isDesugared(AstNode n) {
+ n = any(AstNode sugar).getDesugared()
+ or
+ isDesugared(n.getParent())
+}
+
+/**
+ * The query can extend this class to control which nodes are printed.
+ */
+class PrintAstConfiguration extends string {
+ PrintAstConfiguration() { this = "PrintAstConfiguration" }
+
+ /**
+ * Holds if the given node should be printed.
+ */
+ predicate shouldPrintNode(AstNode n) {
+ not isDesugared(n)
+ or
+ not n.isSynthesized()
+ or
+ n.isSynthesized() and
+ not n = any(AstNode sugar).getDesugared() and
+ exists(AstNode parent |
+ parent = n.getParent() and
+ not parent.isSynthesized() and
+ not n = parent.getDesugared()
+ )
+ }
+
+ predicate shouldPrintAstEdge(AstNode parent, string edgeName, AstNode child) {
+ child = parent.getAChild(edgeName) and
+ not child = parent.getDesugared()
+ }
+}
+
+private predicate shouldPrintNode(AstNode n) {
+ any(PrintAstConfiguration config).shouldPrintNode(n)
+}
+
+private predicate shouldPrintAstEdge(AstNode parent, string edgeName, AstNode child) {
+ any(PrintAstConfiguration config).shouldPrintAstEdge(parent, edgeName, child)
+}
+
+newtype TPrintNode =
+ TPrintRegularAstNode(AstNode n) { shouldPrintNode(n) } or
+ TPrintRegExpNode(RETV::RegExpTerm term) {
+ exists(RegExpLiteral literal |
+ shouldPrintNode(literal) and
+ term.getRootTerm() = literal.getParsed()
+ )
+ }
+
+/**
+ * A node in the output tree.
+ */
+class PrintAstNode extends TPrintNode {
+ /** Gets a textual representation of this node in the PrintAst output tree. */
+ string toString() { none() }
+
+ /**
+ * Gets the child node with name `edgeName`. Typically this is the name of the
+ * predicate used to access the child.
+ */
+ PrintAstNode getChild(string edgeName) { none() }
+
+ /** Gets a child of this node. */
+ final PrintAstNode getAChild() { result = this.getChild(_) }
+
+ /** Gets the parent of this node, if any. */
+ final PrintAstNode getParent() { result.getAChild() = this }
+
+ /**
+ * Holds if this node is at the specified location. The location spans column
+ * `startcolumn` of line `startline` to column `endcolumn` of line `endline`
+ * in file `filepath`. For more information, see
+ * [LGTM locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
+ */
+ predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
+ none()
+ }
+
+ /** Gets a value used to order this node amongst its siblings. */
+ int getOrder() { none() }
+
+ /**
+ * Gets the value of the property of this node, where the name of the property
+ * is `key`.
+ */
+ final string getProperty(string key) {
+ key = "semmle.label" and
+ result = this.toString()
+ or
+ key = "semmle.order" and result = this.getOrder().toString()
+ }
+}
+
+/** An `AstNode` in the output tree. */
+class PrintRegularAstNode extends PrintAstNode, TPrintRegularAstNode {
+ AstNode astNode;
+
+ PrintRegularAstNode() { this = TPrintRegularAstNode(astNode) }
+
+ override string toString() {
+ result = "[" + concat(astNode.getAPrimaryQlClass(), ", ") + "] " + astNode.toString()
+ }
+
+ override PrintAstNode getChild(string edgeName) {
+ exists(AstNode child | shouldPrintAstEdge(astNode, edgeName, child) |
+ result = TPrintRegularAstNode(child)
+ )
+ or
+ // If this AST node is a regexp literal, add the parsed regexp tree as a
+ // child.
+ exists(RETV::RegExpTerm t | t = astNode.(RegExpLiteral).getParsed() |
+ result = TPrintRegExpNode(t) and edgeName = "getParsed"
+ )
+ }
+
+ override int getOrder() {
+ this =
+ rank[result](PrintRegularAstNode p, Location l, File f |
+ l = p.getLocation() and
+ f = l.getFile()
+ |
+ p order by f.getBaseName(), f.getAbsolutePath(), l.getStartLine(), l.getStartColumn()
+ )
+ }
+
+ /** Gets the location of this node. */
+ Location getLocation() { result = astNode.getLocation() }
+
+ override predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
+ astNode.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ }
+}
+
+/** A parsed regexp node in the output tree. */
+class PrintRegExpNode extends PrintAstNode, TPrintRegExpNode {
+ RETV::RegExpTerm regexNode;
+
+ PrintRegExpNode() { this = TPrintRegExpNode(regexNode) }
+
+ override string toString() {
+ result = "[" + concat(regexNode.getAPrimaryQlClass(), ", ") + "] " + regexNode.toString()
+ }
+
+ override PrintAstNode getChild(string edgeName) {
+ // Use the child index as an edge name.
+ exists(int i | result = TPrintRegExpNode(regexNode.getChild(i)) and edgeName = i.toString())
+ }
+
+ override int getOrder() { exists(RETV::RegExpTerm p | p.getChild(result) = regexNode) }
+
+ override predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
+ regexNode.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ }
+}
+
+/**
+ * Holds if `node` belongs to the output tree, and its property `key` has the
+ * given `value`.
+ */
+query predicate nodes(PrintAstNode node, string key, string value) { value = node.getProperty(key) }
+
+/**
+ * Holds if `target` is a child of `source` in the AST, and property `key` of
+ * the edge has the given `value`.
+ */
+query predicate edges(PrintAstNode source, PrintAstNode target, string key, string value) {
+ target = source.getChild(_) and
+ (
+ key = "semmle.label" and
+ value = strictconcat(string name | source.getChild(name) = target | name, "/")
+ or
+ key = "semmle.order" and
+ value = target.getProperty("semmle.order")
+ )
+}
+
+/**
+ * Holds if property `key` of the graph has the given `value`.
+ */
+query predicate graphProperties(string key, string value) {
+ key = "semmle.graphKind" and value = "tree"
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/BadTagFilterQuery.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/BadTagFilterQuery.qll
new file mode 100644
index 00000000000..1b94173ca49
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/BadTagFilterQuery.qll
@@ -0,0 +1,306 @@
+/**
+ * Provides precicates for reasoning about bad tag filter vulnerabilities.
+ */
+
+import performance.ReDoSUtil
+
+/**
+ * A module for determining if a regexp matches a given string,
+ * and reasoning about which capture groups are filled by a given string.
+ */
+private module RegexpMatching {
+ /**
+ * A class to test whether a regular expression matches a string.
+ * Override this class and extend `test`/`testWithGroups` to configure which strings should be tested for acceptance by this regular expression.
+ * The result can afterwards be read from the `matches` predicate.
+ *
+ * Strings in the `testWithGroups` predicate are also tested for which capture groups are filled by the given string.
+ * The result is available in the `fillCaptureGroup` predicate.
+ */
+ abstract class MatchedRegExp extends RegExpTerm {
+ MatchedRegExp() { this.isRootTerm() }
+
+ /**
+ * Holds if it should be tested whether this regular expression matches `str`.
+ *
+ * If `ignorePrefix` is true, then a regexp without a start anchor will be treated as if it had a start anchor.
+ * E.g. a regular expression `/foo$/` will match any string that ends with "foo",
+ * but if `ignorePrefix` is true, it will only match "foo".
+ */
+ predicate test(string str, boolean ignorePrefix) {
+ none() // maybe overriden in subclasses
+ }
+
+ /**
+ * Same as `test(..)`, but where the `fillsCaptureGroup` afterwards tells which capture groups were filled by the given string.
+ */
+ predicate testWithGroups(string str, boolean ignorePrefix) {
+ none() // maybe overriden in subclasses
+ }
+
+ /**
+ * Holds if this RegExp matches `str`, where `str` is either in the `test` or `testWithGroups` predicate.
+ */
+ final predicate matches(string str) {
+ exists(State state | state = getAState(this, str.length() - 1, str, _) |
+ epsilonSucc*(state) = Accept(_)
+ )
+ }
+
+ /**
+ * Holds if matching `str` may fill capture group number `g`.
+ * Only holds if `str` is in the `testWithGroups` predicate.
+ */
+ final predicate fillsCaptureGroup(string str, int g) {
+ exists(State s |
+ s = getAStateThatReachesAccept(this, _, str, _) and
+ g = group(s.getRepr())
+ )
+ }
+ }
+
+ /**
+ * Gets a state the regular expression `reg` can be in after matching the `i`th char in `str`.
+ * The regular expression is modelled as a non-determistic finite automaton,
+ * the regular expression can therefore be in multiple states after matching a character.
+ *
+ * It's a forward search to all possible states, and there is thus no guarantee that the state is on a path to an accepting state.
+ */
+ private State getAState(MatchedRegExp reg, int i, string str, boolean ignorePrefix) {
+ // start state, the -1 position before any chars have been matched
+ i = -1 and
+ (
+ reg.test(str, ignorePrefix)
+ or
+ reg.testWithGroups(str, ignorePrefix)
+ ) and
+ result.getRepr().getRootTerm() = reg and
+ isStartState(result)
+ or
+ // recursive case
+ result = getAStateAfterMatching(reg, _, str, i, _, ignorePrefix)
+ }
+
+ /**
+ * Gets the next state after the `prev` state from `reg`.
+ * `prev` is the state after matching `fromIndex` chars in `str`,
+ * and the result is the state after matching `toIndex` chars in `str`.
+ *
+ * This predicate is used as a step relation in the forwards search (`getAState`),
+ * and also as a step relation in the later backwards search (`getAStateThatReachesAccept`).
+ */
+ private State getAStateAfterMatching(
+ MatchedRegExp reg, State prev, string str, int toIndex, int fromIndex, boolean ignorePrefix
+ ) {
+ // the basic recursive case - outlined into a noopt helper to make performance work out.
+ result = getAStateAfterMatchingAux(reg, prev, str, toIndex, fromIndex, ignorePrefix)
+ or
+ // we can skip past word boundaries if the next char is a non-word char.
+ fromIndex = toIndex and
+ prev.getRepr() instanceof RegExpWordBoundary and
+ prev = getAState(reg, toIndex, str, ignorePrefix) and
+ after(prev.getRepr()) = result and
+ str.charAt(toIndex + 1).regexpMatch("\\W") // \W matches any non-word char.
+ }
+
+ pragma[noopt]
+ private State getAStateAfterMatchingAux(
+ MatchedRegExp reg, State prev, string str, int toIndex, int fromIndex, boolean ignorePrefix
+ ) {
+ prev = getAState(reg, fromIndex, str, ignorePrefix) and
+ fromIndex = toIndex - 1 and
+ exists(string char | char = str.charAt(toIndex) | specializedDeltaClosed(prev, char, result)) and
+ not discardedPrefixStep(prev, result, ignorePrefix)
+ }
+
+ /** Holds if a step from `prev` to `next` should be discarded when the `ignorePrefix` flag is set. */
+ private predicate discardedPrefixStep(State prev, State next, boolean ignorePrefix) {
+ prev = mkMatch(any(RegExpRoot r)) and
+ ignorePrefix = true and
+ next = prev
+ }
+
+ // The `deltaClosed` relation specialized to the chars that exists in strings tested by a `MatchedRegExp`.
+ private predicate specializedDeltaClosed(State prev, string char, State next) {
+ deltaClosed(prev, specializedGetAnInputSymbolMatching(char), next)
+ }
+
+ // The `getAnInputSymbolMatching` relation specialized to the chars that exists in strings tested by a `MatchedRegExp`.
+ pragma[noinline]
+ private InputSymbol specializedGetAnInputSymbolMatching(string char) {
+ exists(string s, MatchedRegExp r |
+ r.test(s, _)
+ or
+ r.testWithGroups(s, _)
+ |
+ char = s.charAt(_)
+ ) and
+ result = getAnInputSymbolMatching(char)
+ }
+
+ /**
+ * Gets the `i`th state on a path to the accepting state when `reg` matches `str`.
+ * Starts with an accepting state as found by `getAState` and searches backwards
+ * to the start state through the reachable states (as found by `getAState`).
+ *
+ * This predicate holds the invariant that the result state can be reached with `i` steps from a start state,
+ * and an accepting state can be found after (`str.length() - 1 - i`) steps from the result.
+ * The result state is therefore always on a valid path where `reg` accepts `str`.
+ *
+ * This predicate is only used to find which capture groups a regular expression has filled,
+ * and thus the search is only performed for the strings in the `testWithGroups(..)` predicate.
+ */
+ private State getAStateThatReachesAccept(
+ MatchedRegExp reg, int i, string str, boolean ignorePrefix
+ ) {
+ // base case, reaches an accepting state from the last state in `getAState(..)`
+ reg.testWithGroups(str, ignorePrefix) and
+ i = str.length() - 1 and
+ result = getAState(reg, i, str, ignorePrefix) and
+ epsilonSucc*(result) = Accept(_)
+ or
+ // recursive case. `next` is the next state to be matched after matching `prev`.
+ // this predicate is doing a backwards search, so `prev` is the result we are looking for.
+ exists(State next, State prev, int fromIndex, int toIndex |
+ next = getAStateThatReachesAccept(reg, toIndex, str, ignorePrefix) and
+ next = getAStateAfterMatching(reg, prev, str, toIndex, fromIndex, ignorePrefix) and
+ i = fromIndex and
+ result = prev
+ )
+ }
+
+ /** Gets the capture group number that `term` belongs to. */
+ private int group(RegExpTerm term) {
+ exists(RegExpGroup grp | grp.getNumber() = result | term.getParent*() = grp)
+ }
+}
+
+/** A class to test whether a regular expression matches certain HTML tags. */
+class HTMLMatchingRegExp extends RegexpMatching::MatchedRegExp {
+ HTMLMatchingRegExp() {
+ // the regexp must mention "<" and ">" explicitly.
+ forall(string angleBracket | angleBracket = ["<", ">"] |
+ any(RegExpConstant term | term.getValue().matches("%" + angleBracket + "%")).getRootTerm() =
+ this
+ )
+ }
+
+ override predicate testWithGroups(string str, boolean ignorePrefix) {
+ ignorePrefix = true and
+ str = ["", "", "", "", "",
+ "", "", "", "",
+ "", "",
+ "", "", "",
+ "", "", "",
+ "", ""
+ ]
+ }
+}
+
+/**
+ * Holds if `regexp` matches some HTML tags, but misses some HTML tags that it should match.
+ *
+ * When adding a new case to this predicate, make sure the test string used in `matches(..)` calls are present in `HTMLMatchingRegExp::test` / `HTMLMatchingRegExp::testWithGroups`.
+ */
+predicate isBadRegexpFilter(HTMLMatchingRegExp regexp, string msg) {
+ // CVE-2021-33829 - matching both "" and "", but in different capture groups
+ regexp.matches("") and
+ regexp.matches("") and
+ exists(int a, int b | a != b |
+ regexp.fillsCaptureGroup("", a) and
+ // might be ambigously parsed (matching both capture groups), and that is ok here.
+ regexp.fillsCaptureGroup("", b) and
+ not regexp.fillsCaptureGroup("", a) and
+ msg =
+ "Comments ending with --> are matched differently from comments ending with --!>. The first is matched with capture group "
+ + a + " and comments ending with --!> are matched with capture group " +
+ strictconcat(int i | regexp.fillsCaptureGroup("", i) | i.toString(), ", ") +
+ "."
+ )
+ or
+ // CVE-2020-17480 - matching "" and other tags, but not "".
+ exists(int group, int other |
+ group != other and
+ regexp.fillsCaptureGroup("", group) and
+ regexp.fillsCaptureGroup("", other) and
+ not regexp.matches("") and
+ not regexp.fillsCaptureGroup("", any(int i | i != group)) and
+ not regexp.fillsCaptureGroup("", group) and
+ not regexp.fillsCaptureGroup("", group) and
+ not regexp.fillsCaptureGroup("") and
+ regexp.matches("") and
+ not regexp.matches("") and
+ (
+ not regexp.matches("") and
+ msg = "This regular expression matches , but not "
+ or
+ not regexp.matches("") and
+ msg = "This regular expression matches , but not "
+ )
+ or
+ regexp.matches("") and
+ regexp.matches("") and
+ not regexp.matches("") and
+ not regexp.matches("") and
+ msg = "This regular expression does not match script tags where the attribute uses single-quotes."
+ or
+ regexp.matches("") and
+ regexp.matches("") and
+ not regexp.matches("") and
+ not regexp.matches("") and
+ msg = "This regular expression does not match script tags where the attribute uses double-quotes."
+ or
+ regexp.matches("") and
+ regexp.matches("") and
+ not regexp.matches("") and
+ not regexp.matches("") and
+ not regexp.matches("") and
+ msg = "This regular expression does not match script tags where tabs are used between attributes."
+ or
+ regexp.matches("") and
+ not RegExpFlags::isIgnoreCase(regexp) and
+ not regexp.matches("") and
+ not regexp.matches("") and
+ (
+ not regexp.matches("") and
+ msg = "This regular expression does not match upper case ") and
+ regexp.matches("") and
+ msg = "This regular expression does not match mixed case ") and
+ not regexp.matches("") and
+ not regexp.matches("") and
+ (
+ not regexp.matches("") and
+ msg = "This regular expression does not match script end tags like ."
+ or
+ not regexp.matches("") and
+ msg = "This regular expression does not match script end tags like ."
+ or
+ not regexp.matches("") and
+ msg = "This regular expression does not match script end tags like ."
+ )
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/CodeInjectionCustomizations.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/CodeInjectionCustomizations.qll
new file mode 100644
index 00000000000..4baceba42db
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/CodeInjectionCustomizations.qll
@@ -0,0 +1,40 @@
+private import ruby
+private import codeql.ruby.DataFlow
+private import codeql.ruby.Concepts
+private import codeql.ruby.Frameworks
+private import codeql.ruby.dataflow.RemoteFlowSources
+private import codeql.ruby.dataflow.BarrierGuards
+
+/**
+ * Provides default sources, sinks and sanitizers for detecting
+ * "Code injection" vulnerabilities, as well as extension points for
+ * adding your own.
+ */
+module CodeInjection {
+ /**
+ * A data flow source for "Code injection" vulnerabilities.
+ */
+ abstract class Source extends DataFlow::Node { }
+
+ /**
+ * A data flow sink for "Code injection" vulnerabilities.
+ */
+ abstract class Sink extends DataFlow::Node { }
+
+ /**
+ * A sanitizer guard for "Code injection" vulnerabilities.
+ */
+ abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
+
+ /**
+ * A source of remote user input, considered as a flow source.
+ */
+ class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
+
+ /**
+ * A call that evaluates its arguments as Ruby code, considered as a flow sink.
+ */
+ class CodeExecutionAsSink extends Sink {
+ CodeExecutionAsSink() { this = any(CodeExecution c).getCode() }
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/CodeInjectionQuery.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/CodeInjectionQuery.qll
new file mode 100644
index 00000000000..95e08a82dc3
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/CodeInjectionQuery.qll
@@ -0,0 +1,29 @@
+/**
+ * Provides a taint-tracking configuration for detecting "Code injection" vulnerabilities.
+ *
+ * Note, for performance reasons: only import this file if `Configuration` is needed,
+ * otherwise `CodeInjectionCustomizations` should be imported instead.
+ */
+
+import codeql.ruby.DataFlow::DataFlow::PathGraph
+import codeql.ruby.DataFlow
+import codeql.ruby.TaintTracking
+import CodeInjectionCustomizations::CodeInjection
+import codeql.ruby.dataflow.BarrierGuards
+
+/**
+ * A taint-tracking configuration for detecting "Code injection" vulnerabilities.
+ */
+class Configuration extends TaintTracking::Configuration {
+ Configuration() { this = "CodeInjection" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof Source }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
+
+ override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
+ guard instanceof SanitizerGuard or
+ guard instanceof StringConstCompare or
+ guard instanceof StringConstArrayInclusionCall
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll
new file mode 100644
index 00000000000..b39455195be
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll
@@ -0,0 +1,54 @@
+/**
+ * Provides default sources, sinks and sanitizers for reasoning about
+ * command-injection vulnerabilities, as well as extension points for
+ * adding your own.
+ */
+
+private import codeql.ruby.DataFlow
+private import codeql.ruby.dataflow.RemoteFlowSources
+private import codeql.ruby.Concepts
+private import codeql.ruby.Frameworks
+private import codeql.ruby.ApiGraphs
+
+module CommandInjection {
+ /**
+ * A data flow source for command-injection vulnerabilities.
+ */
+ abstract class Source extends DataFlow::Node {
+ /** Gets a string that describes the type of this remote flow source. */
+ abstract string getSourceType();
+ }
+
+ /**
+ * A data flow sink for command-injection vulnerabilities.
+ */
+ abstract class Sink extends DataFlow::Node { }
+
+ /**
+ * A sanitizer for command-injection vulnerabilities.
+ */
+ abstract class Sanitizer extends DataFlow::Node { }
+
+ /** A source of remote user input, considered as a flow source for command injection. */
+ class RemoteFlowSourceAsSource extends Source {
+ RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource }
+
+ override string getSourceType() { result = "a user-provided value" }
+ }
+
+ /**
+ * A command argument to a function that initiates an operating system command.
+ */
+ class SystemCommandExecutionSink extends Sink {
+ SystemCommandExecutionSink() { exists(SystemCommandExecution c | c.isShellInterpreted(this)) }
+ }
+
+ /**
+ * A call to `Shellwords.escape` or `Shellwords.shellescape` sanitizes its input.
+ */
+ class ShellwordsEscapeAsSanitizer extends Sanitizer {
+ ShellwordsEscapeAsSanitizer() {
+ this = API::getTopLevelMember("Shellwords").getAMethodCall(["escape", "shellescape"])
+ }
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/CommandInjectionQuery.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/CommandInjectionQuery.qll
new file mode 100644
index 00000000000..25460ad65df
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/CommandInjectionQuery.qll
@@ -0,0 +1,32 @@
+/**
+ * Provides a taint tracking configuration for reasoning about
+ * command-injection vulnerabilities (CWE-078).
+ *
+ * Note, for performance reasons: only import this file if
+ * `CommandInjection::Configuration` is needed, otherwise
+ * `CommandInjectionCustomizations` should be imported instead.
+ */
+
+import ruby
+import codeql.ruby.TaintTracking
+import CommandInjectionCustomizations::CommandInjection
+import codeql.ruby.DataFlow
+import codeql.ruby.dataflow.BarrierGuards
+
+/**
+ * A taint-tracking configuration for reasoning about command-injection vulnerabilities.
+ */
+class Configuration extends TaintTracking::Configuration {
+ Configuration() { this = "CommandInjection" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof Source }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
+
+ override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
+
+ override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
+ guard instanceof StringConstCompare or
+ guard instanceof StringConstArrayInclusionCall
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/PathInjectionCustomizations.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/PathInjectionCustomizations.qll
new file mode 100644
index 00000000000..a2c3d907c5f
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/PathInjectionCustomizations.qll
@@ -0,0 +1,54 @@
+/**
+ * Provides default sources, sinks and sanitizers for reasoning about
+ * path injection vulnerabilities, as well as extension points for
+ * adding your own.
+ */
+
+private import ruby
+private import codeql.ruby.ApiGraphs
+private import codeql.ruby.CFG
+private import codeql.ruby.Concepts
+private import codeql.ruby.DataFlow
+private import codeql.ruby.dataflow.BarrierGuards
+private import codeql.ruby.dataflow.RemoteFlowSources
+
+module PathInjection {
+ /**
+ * A data flow source for path injection vulnerabilities.
+ */
+ abstract class Source extends DataFlow::Node { }
+
+ /**
+ * A data flow sink for path injection vulnerabilities.
+ */
+ abstract class Sink extends DataFlow::Node { }
+
+ /**
+ * A sanitizer guard for path injection vulnerabilities.
+ */
+ abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
+
+ /**
+ * A source of remote user input, considered as a flow source.
+ */
+ class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
+
+ /**
+ * A file system access, considered as a flow sink.
+ */
+ class FileSystemAccessAsSink extends Sink {
+ FileSystemAccessAsSink() { this = any(FileSystemAccess e).getAPathArgument() }
+ }
+
+ /**
+ * A comparison with a constant string, considered as a sanitizer-guard.
+ */
+ class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { }
+
+ /**
+ * An inclusion check against an array of constant strings, considered as a
+ * sanitizer-guard.
+ */
+ class StringConstArrayInclusionCallAsSanitizerGuard extends SanitizerGuard,
+ StringConstArrayInclusionCall { }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/PathInjectionQuery.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/PathInjectionQuery.qll
new file mode 100644
index 00000000000..1901486c498
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/PathInjectionQuery.qll
@@ -0,0 +1,31 @@
+/**
+ * Provides a taint tracking configuration for reasoning about
+ * path injection vulnerabilities.
+ *
+ * Note, for performance reasons: only import this file if
+ * `PathInjection::Configuration` is needed, otherwise
+ * `PathInjectionCustomizations` should be imported instead.
+ */
+
+import PathInjectionCustomizations
+private import codeql.ruby.Concepts
+private import codeql.ruby.DataFlow
+private import codeql.ruby.TaintTracking
+
+/**
+ * A taint-tracking configuration for reasoning about path injection
+ * vulnerabilities.
+ */
+class Configuration extends TaintTracking::Configuration {
+ Configuration() { this = "PathInjection" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof PathInjection::Source }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof PathInjection::Sink }
+
+ override predicate isSanitizer(DataFlow::Node node) { node instanceof Path::PathSanitization }
+
+ override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
+ guard instanceof PathInjection::SanitizerGuard
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/ReflectedXSSQuery.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/ReflectedXSSQuery.qll
new file mode 100644
index 00000000000..60e152a06fc
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/ReflectedXSSQuery.qll
@@ -0,0 +1,39 @@
+/**
+ * Provides a taint-tracking configuration for detecting "reflected server-side cross-site scripting" vulnerabilities.
+ *
+ * Note, for performance reasons: only import this file if
+ * `ReflectedXSS::Configuration` is needed, otherwise
+ * `XSS::ReflectedXSS` should be imported instead.
+ */
+
+private import ruby
+import codeql.ruby.DataFlow
+import codeql.ruby.TaintTracking
+
+/**
+ * Provides a taint-tracking configuration for detecting "reflected server-side cross-site scripting" vulnerabilities.
+ */
+module ReflectedXSS {
+ import XSS::ReflectedXSS
+
+ /**
+ * A taint-tracking configuration for detecting "reflected server-side cross-site scripting" vulnerabilities.
+ */
+ class Configuration extends TaintTracking::Configuration {
+ Configuration() { this = "ReflectedXSS" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof Source }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
+
+ override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
+
+ override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
+ guard instanceof SanitizerGuard
+ }
+
+ override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
+ isAdditionalXSSTaintStep(node1, node2)
+ }
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/StoredXSSQuery.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/StoredXSSQuery.qll
new file mode 100644
index 00000000000..2a089050e5a
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/StoredXSSQuery.qll
@@ -0,0 +1,40 @@
+/**
+ * Provides a taint-tracking configuration for reasoning about stored
+ * cross-site scripting vulnerabilities.
+ *
+ * Note, for performance reasons: only import this file if
+ * `StoredXSS::Configuration` is needed, otherwise
+ * `XSS::StoredXSS` should be imported instead.
+ */
+
+import ruby
+import codeql.ruby.DataFlow
+import codeql.ruby.TaintTracking
+
+module StoredXSS {
+ import XSS::StoredXSS
+
+ /**
+ * A taint-tracking configuration for reasoning about Stored XSS.
+ */
+ class Configuration extends TaintTracking::Configuration {
+ Configuration() { this = "StoredXss" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof Source }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
+
+ override predicate isSanitizer(DataFlow::Node node) {
+ super.isSanitizer(node) or
+ node instanceof Sanitizer
+ }
+
+ override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
+ guard instanceof SanitizerGuard
+ }
+
+ override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
+ isAdditionalXSSTaintStep(node1, node2)
+ }
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/UnsafeDeserializationCustomizations.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/UnsafeDeserializationCustomizations.qll
new file mode 100644
index 00000000000..0e39e053b2a
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/UnsafeDeserializationCustomizations.qll
@@ -0,0 +1,190 @@
+/**
+ * Provides default sources, sinks and sanitizers for reasoning about unsafe
+ * deserialization, as well as extension points for adding your own.
+ */
+
+private import ruby
+private import codeql.ruby.ApiGraphs
+private import codeql.ruby.CFG
+private import codeql.ruby.DataFlow
+private import codeql.ruby.dataflow.RemoteFlowSources
+
+module UnsafeDeserialization {
+ /**
+ * A data flow source for unsafe deserialization vulnerabilities.
+ */
+ abstract class Source extends DataFlow::Node { }
+
+ /**
+ * A data flow sink for unsafe deserialization vulnerabilities.
+ */
+ abstract class Sink extends DataFlow::Node { }
+
+ /**
+ * A sanitizer for unsafe deserialization vulnerabilities.
+ */
+ abstract class Sanitizer extends DataFlow::Node { }
+
+ /**
+ * Additional taint steps for "unsafe deserialization" vulnerabilities.
+ */
+ predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
+ base64DecodeTaintStep(fromNode, toNode)
+ }
+
+ /** A source of remote user input, considered as a flow source for unsafe deserialization. */
+ class RemoteFlowSourceAsSource extends Source {
+ RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource }
+ }
+
+ /**
+ * An argument in a call to `Marshal.load` or `Marshal.restore`, considered a
+ * sink for unsafe deserialization.
+ */
+ class MarshalLoadOrRestoreArgument extends Sink {
+ MarshalLoadOrRestoreArgument() {
+ this = API::getTopLevelMember("Marshal").getAMethodCall(["load", "restore"]).getArgument(0)
+ }
+ }
+
+ /**
+ * An argument in a call to `YAML.load`, considered a sink for unsafe
+ * deserialization.
+ */
+ class YamlLoadArgument extends Sink {
+ YamlLoadArgument() {
+ this = API::getTopLevelMember("YAML").getAMethodCall("load").getArgument(0)
+ }
+ }
+
+ /**
+ * An argument in a call to `JSON.load` or `JSON.restore`, considered a sink
+ * for unsafe deserialization.
+ */
+ class JsonLoadArgument extends Sink {
+ JsonLoadArgument() {
+ this = API::getTopLevelMember("JSON").getAMethodCall(["load", "restore"]).getArgument(0)
+ }
+ }
+
+ private string getAKnownOjModeName(boolean isSafe) {
+ result = ["compat", "custom", "json", "null", "rails", "strict", "wab"] and isSafe = true
+ or
+ result = "object" and isSafe = false
+ }
+
+ private predicate isOjModePair(Pair p, string modeValue) {
+ p.getKey().getValueText() = "mode" and
+ exists(DataFlow::LocalSourceNode symbolLiteral, DataFlow::Node value |
+ symbolLiteral.asExpr().getExpr().(SymbolLiteral).getValueText() = modeValue and
+ symbolLiteral.flowsTo(value) and
+ value.asExpr().getExpr() = p.getValue()
+ )
+ }
+
+ /**
+ * A node representing a hash that contains the key `:mode`.
+ */
+ private class OjOptionsHashWithModeKey extends DataFlow::Node {
+ private string modeValue;
+
+ OjOptionsHashWithModeKey() {
+ exists(DataFlow::LocalSourceNode options |
+ options.flowsTo(this) and
+ isOjModePair(options.asExpr().getExpr().(HashLiteral).getAKeyValuePair(), modeValue)
+ )
+ }
+
+ /**
+ * Holds if this hash node contains a `:mode` key whose value is one known
+ * to be `isSafe` with untrusted data.
+ */
+ predicate hasKnownMode(boolean isSafe) { modeValue = getAKnownOjModeName(isSafe) }
+
+ /**
+ * Holds if this hash node contains a `:mode` key whose value is one of the
+ * `Oj` modes known to be safe to use with untrusted data.
+ */
+ predicate hasSafeMode() { this.hasKnownMode(true) }
+ }
+
+ /**
+ * A call node that sets `Oj.default_options`.
+ *
+ * ```rb
+ * Oj.default_options = { allow_blank: true, mode: :compat }
+ * ```
+ */
+ private class SetOjDefaultOptionsCall extends DataFlow::CallNode {
+ SetOjDefaultOptionsCall() {
+ this = API::getTopLevelMember("Oj").getAMethodCall("default_options=")
+ }
+
+ /**
+ * Gets the value being assigned to `Oj.default_options`.
+ */
+ DataFlow::Node getValue() {
+ result.asExpr() =
+ this.getArgument(0).asExpr().(CfgNodes::ExprNodes::AssignExprCfgNode).getRhs()
+ }
+ }
+
+ /**
+ * A call to `Oj.load`.
+ */
+ private class OjLoadCall extends DataFlow::CallNode {
+ OjLoadCall() { this = API::getTopLevelMember("Oj").getAMethodCall("load") }
+
+ /**
+ * Holds if this call to `Oj.load` includes an explicit options hash
+ * argument that sets the mode to one that is known to be `isSafe`.
+ */
+ predicate hasExplicitKnownMode(boolean isSafe) {
+ exists(DataFlow::Node arg, int i | i >= 1 and arg = this.getArgument(i) |
+ arg.(OjOptionsHashWithModeKey).hasKnownMode(isSafe)
+ or
+ isOjModePair(arg.asExpr().getExpr(), getAKnownOjModeName(isSafe))
+ )
+ }
+ }
+
+ /**
+ * An argument in a call to `Oj.load` where the mode is `:object` (which is
+ * the default), considered a sink for unsafe deserialization.
+ */
+ class UnsafeOjLoadArgument extends Sink {
+ UnsafeOjLoadArgument() {
+ exists(OjLoadCall ojLoad |
+ this = ojLoad.getArgument(0) and
+ // Exclude calls that explicitly pass a safe mode option.
+ not ojLoad.hasExplicitKnownMode(true) and
+ (
+ // Sinks to include:
+ // - Calls with an explicit, unsafe mode option.
+ ojLoad.hasExplicitKnownMode(false)
+ or
+ // - Calls with no explicit mode option, unless there exists a call
+ // anywhere to set the default options to a known safe mode.
+ not ojLoad.hasExplicitKnownMode(_) and
+ not exists(SetOjDefaultOptionsCall setOpts |
+ setOpts.getValue().(OjOptionsHashWithModeKey).hasSafeMode()
+ )
+ )
+ )
+ }
+ }
+
+ /**
+ * `Base64.decode64` propagates taint from its argument to its return value.
+ */
+ predicate base64DecodeTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
+ exists(DataFlow::CallNode callNode |
+ callNode =
+ API::getTopLevelMember("Base64")
+ .getAMethodCall(["decode64", "strict_decode64", "urlsafe_decode64"])
+ |
+ fromNode = callNode.getArgument(0) and
+ toNode = callNode
+ )
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/UnsafeDeserializationQuery.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/UnsafeDeserializationQuery.qll
new file mode 100644
index 00000000000..d08b73da936
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/UnsafeDeserializationQuery.qll
@@ -0,0 +1,34 @@
+/**
+ * Provides a taint-tracking configuration for reasoning about unsafe deserialization.
+ *
+ * Note, for performance reasons: only import this file if
+ * `UnsafeDeserialization::Configuration` is needed, otherwise
+ * `UnsafeDeserializationCustomizations` should be imported instead.
+ */
+
+private import ruby
+private import codeql.ruby.DataFlow
+private import codeql.ruby.TaintTracking
+import UnsafeDeserializationCustomizations
+
+/**
+ * A taint-tracking configuration for reasoning about unsafe deserialization.
+ */
+class Configuration extends TaintTracking::Configuration {
+ Configuration() { this = "UnsafeDeserialization" }
+
+ override predicate isSource(DataFlow::Node source) {
+ source instanceof UnsafeDeserialization::Source
+ }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof UnsafeDeserialization::Sink }
+
+ override predicate isSanitizer(DataFlow::Node node) {
+ super.isSanitizer(node) or
+ node instanceof UnsafeDeserialization::Sanitizer
+ }
+
+ override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
+ UnsafeDeserialization::isAdditionalTaintStep(fromNode, toNode)
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/UrlRedirectCustomizations.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/UrlRedirectCustomizations.qll
new file mode 100644
index 00000000000..c21355375ec
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/UrlRedirectCustomizations.qll
@@ -0,0 +1,127 @@
+/**
+ * Provides default sources, sinks and sanitizers for detecting "URL
+ * redirection" vulnerabilities, as well as extension points for adding your
+ * own.
+ */
+
+private import ruby
+private import codeql.ruby.DataFlow
+private import codeql.ruby.Concepts
+private import codeql.ruby.dataflow.RemoteFlowSources
+private import codeql.ruby.dataflow.BarrierGuards
+
+/**
+ * Provides default sources, sinks and sanitizers for detecting
+ * "URL redirection" vulnerabilities, as well as extension points for
+ * adding your own.
+ */
+module UrlRedirect {
+ /**
+ * A data flow source for "URL redirection" vulnerabilities.
+ */
+ abstract class Source extends DataFlow::Node { }
+
+ /**
+ * A data flow sink for "URL redirection" vulnerabilities.
+ */
+ abstract class Sink extends DataFlow::Node { }
+
+ /**
+ * A sanitizer for "URL redirection" vulnerabilities.
+ */
+ abstract class Sanitizer extends DataFlow::Node { }
+
+ /**
+ * A sanitizer guard for "URL redirection" vulnerabilities.
+ */
+ abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
+
+ /**
+ * Additional taint steps for "URL redirection" vulnerabilities.
+ */
+ predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
+ taintStepViaMethodCallReturnValue(node1, node2)
+ }
+
+ /**
+ * A source of remote user input, considered as a flow source.
+ */
+ class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
+
+ /**
+ * A HTTP redirect response, considered as a flow sink.
+ */
+ class RedirectLocationAsSink extends Sink {
+ RedirectLocationAsSink() {
+ exists(HTTP::Server::HttpRedirectResponse e |
+ this = e.getRedirectLocation() and
+ // As a rough heuristic, assume that methods with these names are handlers for POST/PUT/PATCH/DELETE requests,
+ // which are not as vulnerable to URL redirection because browsers will not initiate them from clicking a link.
+ not this.asExpr()
+ .getExpr()
+ .getEnclosingMethod()
+ .getName()
+ .regexpMatch(".*(create|update|destroy).*")
+ )
+ }
+ }
+
+ /**
+ * A comparison with a constant string, considered as a sanitizer-guard.
+ */
+ class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { }
+
+ /**
+ * Some methods will propagate taint to their return values.
+ * Here we cover a few common ones related to `ActionController::Parameters`.
+ * TODO: use ApiGraphs or something to restrict these method calls to the correct receiver, rather
+ * than matching on method name alone.
+ */
+ predicate taintStepViaMethodCallReturnValue(DataFlow::Node node1, DataFlow::Node node2) {
+ exists(MethodCall m | m = node2.asExpr().getExpr() |
+ m.getReceiver() = node1.asExpr().getExpr() and
+ (actionControllerTaintedMethod(m) or hashTaintedMethod(m))
+ )
+ }
+
+ /**
+ * String interpolation is considered safe, provided the string is prefixed by a non-tainted value.
+ * In most cases this will prevent the tainted value from controlling e.g. the host of the URL.
+ *
+ * For example:
+ *
+ * ```ruby
+ * redirect_to "/users/#{params[:key]}" # safe
+ * redirect_to "#{params[:key]}/users" # unsafe
+ * ```
+ *
+ * There are prefixed interpolations that are not safe, e.g.
+ *
+ * ```ruby
+ * redirect_to "foo#{params[:key]}/users" # => "foo-malicious-site.com/users"
+ * ```
+ *
+ * We currently don't catch these cases.
+ */
+ class StringInterpolationAsSanitizer extends Sanitizer {
+ StringInterpolationAsSanitizer() {
+ exists(StringlikeLiteral str, int n | str.getComponent(n) = this.asExpr().getExpr() and n > 0)
+ }
+ }
+
+ /**
+ * These methods return a new `ActionController::Parameters` or a `Hash` containing a subset of
+ * the original values. This may still contain user input, so the results are tainted.
+ * TODO: flesh this out to cover the whole API.
+ */
+ predicate actionControllerTaintedMethod(MethodCall m) {
+ m.getMethodName() in ["to_unsafe_hash", "to_unsafe_h", "permit", "require"]
+ }
+
+ /**
+ * These `Hash` methods preserve taint because they return a new hash which may still contain keys
+ * with user input.
+ * TODO: flesh this out to cover the whole API.
+ */
+ predicate hashTaintedMethod(MethodCall m) { m.getMethodName() in ["merge", "fetch"] }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/UrlRedirectQuery.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/UrlRedirectQuery.qll
new file mode 100644
index 00000000000..5a984d1fd6e
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/UrlRedirectQuery.qll
@@ -0,0 +1,34 @@
+/**
+ * Provides a taint-tracking configuration for detecting "URL redirection" vulnerabilities.
+ *
+ * Note, for performance reasons: only import this file if `Configuration` is needed,
+ * otherwise `UrlRedirectCustomizations` should be imported instead.
+ */
+
+private import ruby
+import codeql.ruby.DataFlow::DataFlow::PathGraph
+import codeql.ruby.DataFlow
+import codeql.ruby.TaintTracking
+import UrlRedirectCustomizations
+import UrlRedirectCustomizations::UrlRedirect
+
+/**
+ * A taint-tracking configuration for detecting "URL redirection" vulnerabilities.
+ */
+class Configuration extends TaintTracking::Configuration {
+ Configuration() { this = "UrlRedirect" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof Source }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
+
+ override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
+
+ override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
+ guard instanceof SanitizerGuard
+ }
+
+ override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
+ UrlRedirect::isAdditionalTaintStep(node1, node2)
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/XSS.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/XSS.qll
new file mode 100644
index 00000000000..8f8f15b630a
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/XSS.qll
@@ -0,0 +1,369 @@
+/**
+ * Provides classes and predicates used by the XSS queries.
+ */
+
+private import ruby
+private import codeql.ruby.DataFlow
+private import codeql.ruby.DataFlow2
+private import codeql.ruby.CFG
+private import codeql.ruby.Concepts
+private import codeql.ruby.Frameworks
+private import codeql.ruby.frameworks.ActionController
+private import codeql.ruby.frameworks.ActionView
+private import codeql.ruby.dataflow.RemoteFlowSources
+private import codeql.ruby.dataflow.BarrierGuards
+private import codeql.ruby.dataflow.internal.DataFlowDispatch
+
+/**
+ * Provides default sources, sinks and sanitizers for detecting
+ * "server-side cross-site scripting" vulnerabilities, as well as
+ * extension points for adding your own.
+ */
+private module Shared {
+ /**
+ * A data flow source for "server-side cross-site scripting" vulnerabilities.
+ */
+ abstract class Source extends DataFlow::Node { }
+
+ /**
+ * A data flow sink for "server-side cross-site scripting" vulnerabilities.
+ */
+ abstract class Sink extends DataFlow::Node { }
+
+ /**
+ * A sanitizer for "server-side cross-site scripting" vulnerabilities.
+ */
+ abstract class Sanitizer extends DataFlow::Node { }
+
+ /**
+ * A sanitizer guard for "server-side cross-site scripting" vulnerabilities.
+ */
+ abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
+
+ private class ErbOutputMethodCallArgumentNode extends DataFlow::Node {
+ private MethodCall call;
+
+ ErbOutputMethodCallArgumentNode() {
+ exists(ErbOutputDirective d |
+ call = d.getTerminalStmt() and
+ this.asExpr().getExpr() = call.getAnArgument()
+ )
+ }
+
+ MethodCall getCall() { result = call }
+ }
+
+ /**
+ * An `html_safe` call marking the output as not requiring HTML escaping,
+ * considered as a flow sink.
+ */
+ class HtmlSafeCallAsSink extends Sink {
+ HtmlSafeCallAsSink() {
+ exists(HtmlSafeCall c, ErbOutputDirective d |
+ this.asExpr().getExpr() = c.getReceiver() and
+ c = d.getTerminalStmt()
+ )
+ }
+ }
+
+ /**
+ * An argument to a call to the `raw` method, considered as a flow sink.
+ */
+ class RawCallArgumentAsSink extends Sink, ErbOutputMethodCallArgumentNode {
+ RawCallArgumentAsSink() { this.getCall() instanceof RawCall }
+ }
+
+ /**
+ * A argument to a call to the `link_to` method, which does not expect
+ * unsanitized user-input, considered as a flow sink.
+ */
+ class LinkToCallArgumentAsSink extends Sink, ErbOutputMethodCallArgumentNode {
+ LinkToCallArgumentAsSink() {
+ this.asExpr().getExpr() = this.getCall().(LinkToCall).getPathArgument()
+ }
+ }
+
+ /**
+ * An HTML escaping, considered as a sanitizer.
+ */
+ class HtmlEscapingAsSanitizer extends Sanitizer {
+ HtmlEscapingAsSanitizer() { this = any(HtmlEscaping esc).getOutput() }
+ }
+
+ /**
+ * A comparison with a constant string, considered as a sanitizer-guard.
+ */
+ class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { }
+
+ /**
+ * An inclusion check against an array of constant strings, considered as a sanitizer-guard.
+ */
+ class StringConstArrayInclusionCallAsSanitizerGuard extends SanitizerGuard,
+ StringConstArrayInclusionCall { }
+
+ /**
+ * A `VariableWriteAccessCfgNode` that is not succeeded (locally) by another
+ * write to that variable.
+ */
+ private class FinalInstanceVarWrite extends CfgNodes::ExprNodes::InstanceVariableWriteAccessCfgNode {
+ private InstanceVariable var;
+
+ FinalInstanceVarWrite() {
+ var = this.getExpr().getVariable() and
+ not exists(CfgNodes::ExprNodes::InstanceVariableWriteAccessCfgNode succWrite |
+ succWrite.getExpr().getVariable() = var
+ |
+ succWrite = this.getASuccessor+()
+ )
+ }
+
+ InstanceVariable getVariable() { result = var }
+
+ AssignExpr getAnAssignExpr() { result.getLeftOperand() = this.getExpr() }
+ }
+
+ /**
+ * Holds if `call` is a method call in ERB file `erb`, targeting a method
+ * named `name`.
+ */
+ pragma[noinline]
+ private predicate isMethodCall(MethodCall call, string name, ErbFile erb) {
+ name = call.getMethodName() and
+ erb = call.getLocation().getFile()
+ }
+
+ /**
+ * Holds if some render call passes `value` for `hashKey` in the `locals`
+ * argument, in ERB file `erb`.
+ */
+ pragma[noinline]
+ private predicate renderCallLocals(string hashKey, Expr value, ErbFile erb) {
+ exists(RenderCall call, Pair kvPair |
+ call.getLocals().getAKeyValuePair() = kvPair and
+ kvPair.getValue() = value and
+ kvPair.getKey().getValueText() = hashKey and
+ call.getTemplateFile() = erb
+ )
+ }
+
+ pragma[noinline]
+ private predicate isFlowFromLocals0(
+ CfgNodes::ExprNodes::ElementReferenceCfgNode refNode, string hashKey, ErbFile erb
+ ) {
+ exists(DataFlow::Node argNode, CfgNodes::ExprNodes::StringlikeLiteralCfgNode strNode |
+ argNode.asExpr() = refNode.getArgument(0) and
+ refNode.getReceiver().getExpr().(MethodCall).getMethodName() = "local_assigns" and
+ argNode.getALocalSource() = DataFlow::exprNode(strNode) and
+ strNode.getExpr().getValueText() = hashKey and
+ erb = refNode.getFile()
+ )
+ }
+
+ private predicate isFlowFromLocals(DataFlow::Node node1, DataFlow::Node node2) {
+ exists(string hashKey, ErbFile erb |
+ // node1 is a `locals` argument to a render call...
+ renderCallLocals(hashKey, node1.asExpr().getExpr(), erb)
+ |
+ // node2 is an element reference against `local_assigns`
+ isFlowFromLocals0(node2.asExpr(), hashKey, erb)
+ or
+ // ...node2 is a "method call" to a "method" with `hashKey` as its name
+ // TODO: This may be a variable read in reality that we interpret as a method call
+ isMethodCall(node2.asExpr().getExpr(), hashKey, erb)
+ )
+ }
+
+ /**
+ * Holds if `action` contains an assignment of `value` to an instance
+ * variable named `name`, in ERB file `erb`.
+ */
+ pragma[noinline]
+ private predicate actionAssigns(
+ ActionControllerActionMethod action, string name, Expr value, ErbFile erb
+ ) {
+ exists(AssignExpr ae, FinalInstanceVarWrite controllerVarWrite |
+ action.getDefaultTemplateFile() = erb and
+ ae.getParent+() = action and
+ ae = controllerVarWrite.getAnAssignExpr() and
+ name = controllerVarWrite.getVariable().getName() and
+ value = ae.getRightOperand()
+ )
+ }
+
+ pragma[noinline]
+ private predicate isVariableReadAccess(VariableReadAccess viewVarRead, string name, ErbFile erb) {
+ erb = viewVarRead.getLocation().getFile() and
+ viewVarRead.getVariable().getName() = name
+ }
+
+ private predicate isFlowFromControllerInstanceVariable(DataFlow::Node node1, DataFlow::Node node2) {
+ // instance variables in the controller
+ exists(ActionControllerActionMethod action, string name, ErbFile template |
+ // match read to write on variable name
+ actionAssigns(action, name, node1.asExpr().getExpr(), template) and
+ // propagate taint from assignment RHS expr to variable read access in view
+ isVariableReadAccess(node2.asExpr().getExpr(), name, template)
+ )
+ }
+
+ /**
+ * Holds if `helperMethod` is a helper method named `name` that is associated
+ * with ERB file `erb`.
+ */
+ pragma[noinline]
+ private predicate isHelperMethod(
+ ActionControllerHelperMethod helperMethod, string name, ErbFile erb
+ ) {
+ helperMethod.getName() = name and
+ helperMethod.getControllerClass() = getAssociatedControllerClass(erb)
+ }
+
+ private predicate isFlowIntoHelperMethod(DataFlow::Node node1, DataFlow::Node node2) {
+ // flow from template into controller helper method
+ exists(
+ ErbFile template, ActionControllerHelperMethod helperMethod, string name,
+ CfgNodes::ExprNodes::MethodCallCfgNode helperMethodCall, int argIdx
+ |
+ isHelperMethod(helperMethod, name, template) and
+ isMethodCall(helperMethodCall.getExpr(), name, template) and
+ helperMethodCall.getArgument(pragma[only_bind_into](argIdx)) = node1.asExpr() and
+ helperMethod.getParameter(pragma[only_bind_into](argIdx)) = node2.asExpr().getExpr()
+ )
+ }
+
+ private predicate isFlowFromHelperMethod(DataFlow::Node node1, DataFlow::Node node2) {
+ // flow out of controller helper method into template
+ exists(ErbFile template, ActionControllerHelperMethod helperMethod, string name |
+ // `node1` is an expr node that may be returned by the helper method
+ exprNodeReturnedFrom(node1, helperMethod) and
+ // `node2` is a call to the helper method
+ isHelperMethod(helperMethod, name, template) and
+ isMethodCall(node2.asExpr().getExpr(), name, template)
+ )
+ }
+
+ /**
+ * An additional step that is preserves dataflow in the context of XSS.
+ */
+ predicate isAdditionalXSSFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
+ isFlowFromLocals(node1, node2)
+ or
+ isFlowFromControllerInstanceVariable(node1, node2)
+ or
+ isFlowIntoHelperMethod(node1, node2)
+ or
+ isFlowFromHelperMethod(node1, node2)
+ }
+}
+
+/**
+ * Provides default sources, sinks and sanitizers for detecting
+ * "reflected cross-site scripting" vulnerabilities, as well as
+ * extension points for adding your own.
+ */
+module ReflectedXSS {
+ /** A data flow source for stored XSS vulnerabilities. */
+ abstract class Source extends Shared::Source { }
+
+ /** A data flow sink for stored XSS vulnerabilities. */
+ abstract class Sink extends Shared::Sink { }
+
+ /** A sanitizer for stored XSS vulnerabilities. */
+ abstract class Sanitizer extends Shared::Sanitizer { }
+
+ /** A sanitizer guard for stored XSS vulnerabilities. */
+ abstract class SanitizerGuard extends Shared::SanitizerGuard { }
+
+ // Consider all arbitrary XSS sinks to be reflected XSS sinks
+ private class AnySink extends Sink instanceof Shared::Sink { }
+
+ // Consider all arbitrary XSS sanitizers to be reflected XSS sanitizers
+ private class AnySanitizer extends Sanitizer instanceof Shared::Sanitizer { }
+
+ // Consider all arbitrary XSS sanitizer guards to be reflected XSS sanitizer guards
+ private class AnySanitizerGuard extends SanitizerGuard instanceof Shared::SanitizerGuard {
+ override predicate checks(CfgNode expr, boolean branch) {
+ Shared::SanitizerGuard.super.checks(expr, branch)
+ }
+ }
+
+ /**
+ * An additional step that is preserves dataflow in the context of reflected XSS.
+ */
+ predicate isAdditionalXSSTaintStep = Shared::isAdditionalXSSFlowStep/2;
+
+ /**
+ * A source of remote user input, considered as a flow source.
+ */
+ class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
+}
+
+private module OrmTracking {
+ /**
+ * A data flow configuration to track flow from finder calls to field accesses.
+ */
+ class Configuration extends DataFlow2::Configuration {
+ Configuration() { this = "OrmTracking" }
+
+ override predicate isSource(DataFlow2::Node source) { source instanceof OrmInstantiation }
+
+ // Select any call node and narrow down later
+ override predicate isSink(DataFlow2::Node sink) { sink instanceof DataFlow2::CallNode }
+
+ override predicate isAdditionalFlowStep(DataFlow2::Node node1, DataFlow2::Node node2) {
+ Shared::isAdditionalXSSFlowStep(node1, node2)
+ or
+ // Propagate flow through arbitrary method calls
+ node2.(DataFlow2::CallNode).getReceiver() = node1
+ or
+ // Propagate flow through "or" expressions `or`/`||`
+ node2.asExpr().getExpr().(LogicalOrExpr).getAnOperand() = node1.asExpr().getExpr()
+ }
+ }
+}
+
+module StoredXSS {
+ /** A data flow source for stored XSS vulnerabilities. */
+ abstract class Source extends Shared::Source { }
+
+ /** A data flow sink for stored XSS vulnerabilities. */
+ abstract class Sink extends Shared::Sink { }
+
+ /** A sanitizer for stored XSS vulnerabilities. */
+ abstract class Sanitizer extends Shared::Sanitizer { }
+
+ /** A sanitizer guard for stored XSS vulnerabilities. */
+ abstract class SanitizerGuard extends Shared::SanitizerGuard { }
+
+ // Consider all arbitrary XSS sinks to be stored XSS sinks
+ private class AnySink extends Sink instanceof Shared::Sink { }
+
+ // Consider all arbitrary XSS sanitizers to be stored XSS sanitizers
+ private class AnySanitizer extends Sanitizer instanceof Shared::Sanitizer { }
+
+ // Consider all arbitrary XSS sanitizer guards to be stored XSS sanitizer guards
+ private class AnySanitizerGuard extends SanitizerGuard instanceof Shared::SanitizerGuard {
+ override predicate checks(CfgNode expr, boolean branch) {
+ Shared::SanitizerGuard.super.checks(expr, branch)
+ }
+ }
+
+ /**
+ * An additional step that preserves dataflow in the context of stored XSS.
+ */
+ predicate isAdditionalXSSTaintStep = Shared::isAdditionalXSSFlowStep/2;
+
+ private class OrmFieldAsSource extends Source instanceof DataFlow2::CallNode {
+ OrmFieldAsSource() {
+ exists(OrmTracking::Configuration subConfig, DataFlow2::CallNode subSrc, MethodCall call |
+ subConfig.hasFlow(subSrc, this) and
+ call = this.asExpr().getExpr() and
+ subSrc.(OrmInstantiation).methodCallMayAccessField(call.getMethodName())
+ )
+ }
+ }
+
+ /** A file read, considered as a flow source for stored XSS. */
+ private class FileSystemReadAccessAsSource extends Source instanceof FileSystemReadAccess { }
+ // TODO: Consider `FileNameSource` flowing to script tag `src` attributes and similar
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/performance/ExponentialBackTracking.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/performance/ExponentialBackTracking.qll
new file mode 100644
index 00000000000..a805366bab8
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/performance/ExponentialBackTracking.qll
@@ -0,0 +1,343 @@
+private import ReDoSUtil
+private import RegExpTreeView
+private import codeql.Locations
+
+/*
+ * This query implements the analysis described in the following two papers:
+ *
+ * James Kirrage, Asiri Rathnayake, Hayo Thielecke: Static Analysis for
+ * Regular Expression Denial-of-Service Attacks. NSS 2013.
+ * (http://www.cs.bham.ac.uk/~hxt/research/reg-exp-sec.pdf)
+ * Asiri Rathnayake, Hayo Thielecke: Static Analysis for Regular Expression
+ * Exponential Runtime via Substructural Logics. 2014.
+ * (https://www.cs.bham.ac.uk/~hxt/research/redos_full.pdf)
+ *
+ * The basic idea is to search for overlapping cycles in the NFA, that is,
+ * states `q` such that there are two distinct paths from `q` to itself
+ * that consume the same word `w`.
+ *
+ * For any such state `q`, an attack string can be constructed as follows:
+ * concatenate a prefix `v` that takes the NFA to `q` with `n` copies of
+ * the word `w` that leads back to `q` along two different paths, followed
+ * by a suffix `x` that is _not_ accepted in state `q`. A backtracking
+ * implementation will need to explore at least 2^n different ways of going
+ * from `q` back to itself while trying to match the `n` copies of `w`
+ * before finally giving up.
+ *
+ * Now in order to identify overlapping cycles, all we have to do is find
+ * pumpable forks, that is, states `q` that can transition to two different
+ * states `r1` and `r2` on the same input symbol `c`, such that there are
+ * paths from both `r1` and `r2` to `q` that consume the same word. The latter
+ * condition is equivalent to saying that `(q, q)` is reachable from `(r1, r2)`
+ * in the product NFA.
+ *
+ * This is what the query does. It makes a simple attempt to construct a
+ * prefix `v` leading into `q`, but only to improve the alert message.
+ * And the query tries to prove the existence of a suffix that ensures
+ * rejection. This check might fail, which can cause false positives.
+ *
+ * Finally, sometimes it depends on the translation whether the NFA generated
+ * for a regular expression has a pumpable fork or not. We implement one
+ * particular translation, which may result in false positives or negatives
+ * relative to some particular JavaScript engine.
+ *
+ * More precisely, the query constructs an NFA from a regular expression `r`
+ * as follows:
+ *
+ * * Every sub-term `t` gives rise to an NFA state `Match(t,i)`, representing
+ * the state of the automaton before attempting to match the `i`th character in `t`.
+ * * There is one accepting state `Accept(r)`.
+ * * There is a special `AcceptAnySuffix(r)` state, which accepts any suffix string
+ * by using an epsilon transition to `Accept(r)` and an any transition to itself.
+ * * Transitions between states may be labelled with epsilon, or an abstract
+ * input symbol.
+ * * Each abstract input symbol represents a set of concrete input characters:
+ * either a single character, a set of characters represented by a
+ * character class, or the set of all characters.
+ * * The product automaton is constructed lazily, starting with pair states
+ * `(q, q)` where `q` is a fork, and proceding along an over-approximate
+ * step relation.
+ * * The over-approximate step relation allows transitions along pairs of
+ * abstract input symbols where the symbols have overlap in the characters they accept.
+ * * Once a trace of pairs of abstract input symbols that leads from a fork
+ * back to itself has been identified, we attempt to construct a concrete
+ * string corresponding to it, which may fail.
+ * * Lastly we ensure that any state reached by repeating `n` copies of `w` has
+ * a suffix `x` (possible empty) that is most likely __not__ accepted.
+ */
+
+/**
+ * Holds if state `s` might be inside a backtracking repetition.
+ */
+pragma[noinline]
+private predicate stateInsideBacktracking(State s) {
+ s.getRepr().getParent*() instanceof MaybeBacktrackingRepetition
+}
+
+/**
+ * A infinitely repeating quantifier that might backtrack.
+ */
+private class MaybeBacktrackingRepetition extends InfiniteRepetitionQuantifier {
+ MaybeBacktrackingRepetition() {
+ exists(RegExpTerm child |
+ child instanceof RegExpAlt or
+ child instanceof RegExpQuantifier
+ |
+ child.getParent+() = this
+ )
+ }
+}
+
+/**
+ * A state in the product automaton.
+ *
+ * We lazily only construct those states that we are actually
+ * going to need: `(q, q)` for every fork state `q`, and any
+ * pair of states that can be reached from a pair that we have
+ * already constructed. To cut down on the number of states,
+ * we only represent states `(q1, q2)` where `q1` is lexicographically
+ * no bigger than `q2`.
+ *
+ * States are only constructed if both states in the pair are
+ * inside a repetition that might backtrack.
+ */
+private newtype TStatePair =
+ MkStatePair(State q1, State q2) {
+ isFork(q1, _, _, _, _) and q2 = q1
+ or
+ (step(_, _, _, q1, q2) or step(_, _, _, q2, q1)) and
+ rankState(q1) <= rankState(q2)
+ }
+
+/**
+ * Gets a unique number for a `state`.
+ * Is used to create an ordering of states, where states with the same `toString()` will be ordered differently.
+ */
+private int rankState(State state) {
+ state =
+ rank[result](State s, Location l |
+ l = s.getRepr().getLocation()
+ |
+ s order by l.getStartLine(), l.getStartColumn(), s.toString()
+ )
+}
+
+/**
+ * A state in the product automaton.
+ */
+private class StatePair extends TStatePair {
+ State q1;
+ State q2;
+
+ StatePair() { this = MkStatePair(q1, q2) }
+
+ /** Gets a textual representation of this element. */
+ string toString() { result = "(" + q1 + ", " + q2 + ")" }
+
+ /** Gets the first component of the state pair. */
+ State getLeft() { result = q1 }
+
+ /** Gets the second component of the state pair. */
+ State getRight() { result = q2 }
+}
+
+/**
+ * Holds for all constructed state pairs.
+ *
+ * Used in `statePairDist`
+ */
+private predicate isStatePair(StatePair p) { any() }
+
+/**
+ * Holds if there are transitions from the components of `q` to the corresponding
+ * components of `r`.
+ *
+ * Used in `statePairDist`
+ */
+private predicate delta2(StatePair q, StatePair r) { step(q, _, _, r) }
+
+/**
+ * Gets the minimum length of a path from `q` to `r` in the
+ * product automaton.
+ */
+private int statePairDist(StatePair q, StatePair r) =
+ shortestDistances(isStatePair/1, delta2/2)(q, r, result)
+
+/**
+ * Holds if there are transitions from `q` to `r1` and from `q` to `r2`
+ * labelled with `s1` and `s2`, respectively, where `s1` and `s2` do not
+ * trivially have an empty intersection.
+ *
+ * This predicate only holds for states associated with regular expressions
+ * that have at least one repetition quantifier in them (otherwise the
+ * expression cannot be vulnerable to ReDoS attacks anyway).
+ */
+pragma[noopt]
+private predicate isFork(State q, InputSymbol s1, InputSymbol s2, State r1, State r2) {
+ stateInsideBacktracking(q) and
+ exists(State q1, State q2 |
+ q1 = epsilonSucc*(q) and
+ delta(q1, s1, r1) and
+ q2 = epsilonSucc*(q) and
+ delta(q2, s2, r2) and
+ // Use pragma[noopt] to prevent intersect(s1,s2) from being the starting point of the join.
+ // From (s1,s2) it would find a huge number of intermediate state pairs (q1,q2) originating from different literals,
+ // and discover at the end that no `q` can reach both `q1` and `q2` by epsilon transitions.
+ exists(intersect(s1, s2))
+ |
+ s1 != s2
+ or
+ r1 != r2
+ or
+ r1 = r2 and q1 != q2
+ or
+ // If q can reach itself by epsilon transitions, then there are two distinct paths to the q1/q2 state:
+ // one that uses the loop and one that doesn't. The engine will separately attempt to match with each path,
+ // despite ending in the same state. The "fork" thus arises from the choice of whether to use the loop or not.
+ // To avoid every state in the loop becoming a fork state,
+ // we arbitrarily pick the InfiniteRepetitionQuantifier state as the canonical fork state for the loop
+ // (every epsilon-loop must contain such a state).
+ //
+ // We additionally require that the there exists another InfiniteRepetitionQuantifier `mid` on the path from `q` to itself.
+ // This is done to avoid flagging regular expressions such as `/(a?)*b/` - that only has polynomial runtime, and is detected by `js/polynomial-redos`.
+ // The below code is therefore a heuritic, that only flags regular expressions such as `/(a*)*b/`,
+ // and does not flag regular expressions such as `/(a?b?)c/`, but the latter pattern is not used frequently.
+ r1 = r2 and
+ q1 = q2 and
+ epsilonSucc+(q) = q and
+ exists(RegExpTerm term | term = q.getRepr() | term instanceof InfiniteRepetitionQuantifier) and
+ // One of the mid states is an infinite quantifier itself
+ exists(State mid, RegExpTerm term |
+ mid = epsilonSucc+(q) and
+ term = mid.getRepr() and
+ term instanceof InfiniteRepetitionQuantifier and
+ q = epsilonSucc+(mid) and
+ not mid = q
+ )
+ ) and
+ stateInsideBacktracking(r1) and
+ stateInsideBacktracking(r2)
+}
+
+/**
+ * Gets the state pair `(q1, q2)` or `(q2, q1)`; note that only
+ * one or the other is defined.
+ */
+private StatePair mkStatePair(State q1, State q2) {
+ result = MkStatePair(q1, q2) or result = MkStatePair(q2, q1)
+}
+
+/**
+ * Holds if there are transitions from the components of `q` to the corresponding
+ * components of `r` labelled with `s1` and `s2`, respectively.
+ */
+private predicate step(StatePair q, InputSymbol s1, InputSymbol s2, StatePair r) {
+ exists(State r1, State r2 | step(q, s1, s2, r1, r2) and r = mkStatePair(r1, r2))
+}
+
+/**
+ * Holds if there are transitions from the components of `q` to `r1` and `r2`
+ * labelled with `s1` and `s2`, respectively.
+ *
+ * We only consider transitions where the resulting states `(r1, r2)` are both
+ * inside a repetition that might backtrack.
+ */
+pragma[noopt]
+private predicate step(StatePair q, InputSymbol s1, InputSymbol s2, State r1, State r2) {
+ exists(State q1, State q2 | q.getLeft() = q1 and q.getRight() = q2 |
+ deltaClosed(q1, s1, r1) and
+ deltaClosed(q2, s2, r2) and
+ // use noopt to force the join on `intersect` to happen last.
+ exists(intersect(s1, s2))
+ ) and
+ stateInsideBacktracking(r1) and
+ stateInsideBacktracking(r2)
+}
+
+private newtype TTrace =
+ Nil() or
+ Step(InputSymbol s1, InputSymbol s2, TTrace t) {
+ exists(StatePair p |
+ isReachableFromFork(_, p, t, _) and
+ step(p, s1, s2, _)
+ )
+ or
+ t = Nil() and isFork(_, s1, s2, _, _)
+ }
+
+/**
+ * A list of pairs of input symbols that describe a path in the product automaton
+ * starting from some fork state.
+ */
+private class Trace extends TTrace {
+ /** Gets a textual representation of this element. */
+ string toString() {
+ this = Nil() and result = "Nil()"
+ or
+ exists(InputSymbol s1, InputSymbol s2, Trace t | this = Step(s1, s2, t) |
+ result = "Step(" + s1 + ", " + s2 + ", " + t + ")"
+ )
+ }
+}
+
+/**
+ * Gets a string corresponding to the trace `t`.
+ */
+private string concretise(Trace t) {
+ t = Nil() and result = ""
+ or
+ exists(InputSymbol s1, InputSymbol s2, Trace rest | t = Step(s1, s2, rest) |
+ result = concretise(rest) + intersect(s1, s2)
+ )
+}
+
+/**
+ * Holds if `r` is reachable from `(fork, fork)` under input `w`, and there is
+ * a path from `r` back to `(fork, fork)` with `rem` steps.
+ */
+private predicate isReachableFromFork(State fork, StatePair r, Trace w, int rem) {
+ // base case
+ exists(InputSymbol s1, InputSymbol s2, State q1, State q2 |
+ isFork(fork, s1, s2, q1, q2) and
+ r = MkStatePair(q1, q2) and
+ w = Step(s1, s2, Nil()) and
+ rem = statePairDist(r, MkStatePair(fork, fork))
+ )
+ or
+ // recursive case
+ exists(StatePair p, Trace v, InputSymbol s1, InputSymbol s2 |
+ isReachableFromFork(fork, p, v, rem + 1) and
+ step(p, s1, s2, r) and
+ w = Step(s1, s2, v) and
+ rem >= statePairDist(r, MkStatePair(fork, fork))
+ )
+}
+
+/**
+ * Gets a state in the product automaton from which `(fork, fork)` is
+ * reachable in zero or more epsilon transitions.
+ */
+private StatePair getAForkPair(State fork) {
+ isFork(fork, _, _, _, _) and
+ result = MkStatePair(epsilonPred*(fork), epsilonPred*(fork))
+}
+
+/**
+ * Holds if `fork` is a pumpable fork with word `w`.
+ */
+private predicate isPumpable(State fork, string w) {
+ exists(StatePair q, Trace t |
+ isReachableFromFork(fork, q, t, _) and
+ q = getAForkPair(fork) and
+ w = concretise(t)
+ )
+}
+
+/**
+ * An instantiation of `ReDoSConfiguration` for exponential backtracking.
+ */
+class ExponentialReDoSConfiguration extends ReDoSConfiguration {
+ ExponentialReDoSConfiguration() { this = "ExponentialReDoSConfiguration" }
+
+ override predicate isReDoSCandidate(State state, string pump) { isPumpable(state, pump) }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/performance/ParseRegExp.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/performance/ParseRegExp.qll
new file mode 100644
index 00000000000..2af4b67cd07
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/performance/ParseRegExp.qll
@@ -0,0 +1,891 @@
+/**
+ * Library for parsing for Ruby regular expressions.
+ *
+ * N.B. does not yet handle stripping whitespace and comments in regexes with
+ * the `x` (free-spacing) flag.
+ */
+
+private import codeql.ruby.ast.Literal as AST
+private import codeql.Locations
+
+class RegExp extends AST::RegExpLiteral {
+ /**
+ * Helper predicate for `charSetStart(int start, int end)`.
+ *
+ * In order to identify left brackets ('[') which actually start a character class,
+ * we perform a left to right scan of the string.
+ *
+ * To avoid negative recursion we return a boolean. See `escaping`,
+ * the helper for `escapingChar`, for a clean use of this pattern.
+ *
+ * result is true for those start chars that actually mark a start of a char set.
+ */
+ boolean charSetStart(int pos) {
+ exists(int index |
+ // is opening bracket
+ this.charSetDelimiter(index, pos) = true and
+ (
+ // if this is the first bracket, `pos` starts a char set
+ index = 1 and result = true
+ or
+ // if the previous char set delimiter was not a closing bracket, `pos` does
+ // not start a char set. This is needed to handle cases such as `[[]` (a
+ // char set that matches the `[` char)
+ index > 1 and
+ not this.charSetDelimiter(index - 1, _) = false and
+ result = false
+ or
+ // special handling of cases such as `[][]` (the character-set of the characters `]` and `[`).
+ exists(int prevClosingBracketPos |
+ // previous bracket is a closing bracket
+ this.charSetDelimiter(index - 1, prevClosingBracketPos) = false and
+ if
+ // check if the character that comes before the previous closing bracket
+ // is an opening bracket (taking `^` into account)
+ // check if the character that comes before the previous closing bracket
+ // is an opening bracket (taking `^` into account)
+ exists(int posBeforePrevClosingBracket |
+ if this.getChar(prevClosingBracketPos - 1) = "^"
+ then posBeforePrevClosingBracket = prevClosingBracketPos - 2
+ else posBeforePrevClosingBracket = prevClosingBracketPos - 1
+ |
+ this.charSetDelimiter(index - 2, posBeforePrevClosingBracket) = true
+ )
+ then
+ // brackets without anything in between is not valid character ranges, so
+ // the first closing bracket in `[]]` and `[^]]` does not count,
+ //
+ // and we should _not_ mark the second opening bracket in `[][]` and `[^][]`
+ // as starting a new char set. ^ ^
+ exists(int posBeforePrevClosingBracket |
+ this.charSetDelimiter(index - 2, posBeforePrevClosingBracket) = true
+ |
+ result = this.charSetStart(posBeforePrevClosingBracket).booleanNot()
+ )
+ else
+ // if not, `pos` does in fact mark a real start of a character range
+ result = true
+ )
+ )
+ )
+ }
+
+ /**
+ * Helper predicate for chars that could be character-set delimiters.
+ * Holds if the (non-escaped) char at `pos` in the string, is the (one-based) `index` occurrence of a bracket (`[` or `]`) in the string.
+ * Result if `true` is the char is `[`, and `false` if the char is `]`.
+ */
+ boolean charSetDelimiter(int index, int pos) {
+ pos =
+ rank[index](int p |
+ (this.nonEscapedCharAt(p) = "[" or this.nonEscapedCharAt(p) = "]") and
+ // Brackets that art part of POSIX expressions should not count as
+ // char-set delimiters.
+ not exists(int x, int y |
+ this.posixStyleNamedCharacterProperty(x, y, _) and pos >= x and pos < y
+ )
+ ) and
+ (
+ this.nonEscapedCharAt(pos) = "[" and result = true
+ or
+ this.nonEscapedCharAt(pos) = "]" and result = false
+ )
+ }
+
+ predicate charSetStart(int start, int end) {
+ this.charSetStart(start) = true and
+ (
+ this.getChar(start + 1) = "^" and end = start + 2
+ or
+ not this.getChar(start + 1) = "^" and end = start + 1
+ )
+ }
+
+ /** Whether there is a character class, between start (inclusive) and end (exclusive) */
+ predicate charSet(int start, int end) {
+ exists(int innerStart, int innerEnd |
+ this.charSetStart(start, innerStart) and
+ not this.charSetStart(_, start)
+ |
+ end = innerEnd + 1 and
+ innerEnd =
+ min(int e |
+ e > innerStart and
+ this.nonEscapedCharAt(e) = "]" and
+ not exists(int x, int y |
+ this.posixStyleNamedCharacterProperty(x, y, _) and e >= x and e < y
+ )
+ |
+ e
+ )
+ )
+ }
+
+ predicate charSetToken(int charsetStart, int index, int tokenStart, int tokenEnd) {
+ tokenStart =
+ rank[index](int start, int end | this.charSetToken(charsetStart, start, end) | start) and
+ this.charSetToken(charsetStart, tokenStart, tokenEnd)
+ }
+
+ /** Either a char or a - */
+ predicate charSetToken(int charsetStart, int start, int end) {
+ this.charSetStart(charsetStart, start) and
+ (
+ this.escapedCharacter(start, end)
+ or
+ this.namedCharacterProperty(start, end, _)
+ or
+ exists(this.nonEscapedCharAt(start)) and end = start + 1
+ )
+ or
+ this.charSetToken(charsetStart, _, start) and
+ (
+ this.escapedCharacter(start, end)
+ or
+ this.namedCharacterProperty(start, end, _)
+ or
+ exists(this.nonEscapedCharAt(start)) and
+ end = start + 1 and
+ not this.getChar(start) = "]"
+ )
+ }
+
+ predicate charSetChild(int charsetStart, int start, int end) {
+ this.charSetToken(charsetStart, start, end) and
+ not exists(int rangeStart, int rangeEnd |
+ this.charRange(charsetStart, rangeStart, _, _, rangeEnd) and
+ rangeStart <= start and
+ rangeEnd >= end
+ )
+ or
+ this.charRange(charsetStart, start, _, _, end)
+ }
+
+ predicate charRange(int charsetStart, int start, int lowerEnd, int upperStart, int end) {
+ exists(int index |
+ this.charRangeEnd(charsetStart, index) = true and
+ this.charSetToken(charsetStart, index - 2, start, lowerEnd) and
+ this.charSetToken(charsetStart, index, upperStart, end)
+ )
+ }
+
+ private boolean charRangeEnd(int charsetStart, int index) {
+ this.charSetToken(charsetStart, index, _, _) and
+ (
+ index in [1, 2] and result = false
+ or
+ index > 2 and
+ exists(int connectorStart |
+ this.charSetToken(charsetStart, index - 1, connectorStart, _) and
+ this.nonEscapedCharAt(connectorStart) = "-" and
+ result =
+ this.charRangeEnd(charsetStart, index - 2)
+ .booleanNot()
+ .booleanAnd(this.charRangeEnd(charsetStart, index - 1).booleanNot())
+ )
+ or
+ not exists(int connectorStart |
+ this.charSetToken(charsetStart, index - 1, connectorStart, _) and
+ this.nonEscapedCharAt(connectorStart) = "-"
+ ) and
+ result = false
+ )
+ }
+
+ predicate escapingChar(int pos) { this.escaping(pos) = true }
+
+ private boolean escaping(int pos) {
+ pos = -1 and result = false
+ or
+ this.getChar(pos) = "\\" and result = this.escaping(pos - 1).booleanNot()
+ or
+ this.getChar(pos) != "\\" and result = false
+ }
+
+ /** Gets the text of this regex */
+ string getText() { result = this.getValueText() }
+
+ string getChar(int i) { result = this.getText().charAt(i) }
+
+ string nonEscapedCharAt(int i) {
+ result = this.getText().charAt(i) and
+ not exists(int x, int y | this.escapedCharacter(x, y) and i in [x .. y - 1])
+ }
+
+ private predicate isOptionDivider(int i) { this.nonEscapedCharAt(i) = "|" }
+
+ private predicate isGroupEnd(int i) { this.nonEscapedCharAt(i) = ")" and not this.inCharSet(i) }
+
+ private predicate isGroupStart(int i) { this.nonEscapedCharAt(i) = "(" and not this.inCharSet(i) }
+
+ predicate failedToParse(int i) {
+ exists(this.getChar(i)) and
+ not exists(int start, int end |
+ this.topLevel(start, end) and
+ start <= i and
+ end > i
+ )
+ }
+
+ /** Matches named character properties such as `\p{Word}` and `[[:digit:]]` */
+ predicate namedCharacterProperty(int start, int end, string name) {
+ this.pStyleNamedCharacterProperty(start, end, name) or
+ this.posixStyleNamedCharacterProperty(start, end, name)
+ }
+
+ /** Gets the name of the character property in start,end */
+ string getCharacterPropertyName(int start, int end) {
+ this.namedCharacterProperty(start, end, result)
+ }
+
+ /** Matches a POSIX bracket expression such as `[:alnum:]` within a character class. */
+ private predicate posixStyleNamedCharacterProperty(int start, int end, string name) {
+ this.getChar(start) = "[" and
+ this.getChar(start + 1) = ":" and
+ end =
+ min(int e |
+ e > start and
+ this.getChar(e - 2) = ":" and
+ this.getChar(e - 1) = "]"
+ |
+ e
+ ) and
+ exists(int nameStart |
+ this.getChar(start + 2) = "^" and nameStart = start + 3
+ or
+ not this.getChar(start + 2) = "^" and nameStart = start + 2
+ |
+ name = this.getText().substring(nameStart, end - 2)
+ )
+ }
+
+ /**
+ * Matches named character properties. For example:
+ * - `\p{Space}`
+ * - `\P{Digit}` upper-case P means inverted
+ * - `\p{^Word}` caret also means inverted
+ *
+ * These can occur both inside and outside of character classes.
+ */
+ private predicate pStyleNamedCharacterProperty(int start, int end, string name) {
+ this.escapingChar(start) and
+ this.getChar(start + 1) in ["p", "P"] and
+ this.getChar(start + 2) = "{" and
+ this.getChar(end - 1) = "}" and
+ end > start and
+ not exists(int i | start + 2 < i and i < end - 1 | this.getChar(i) = "}") and
+ exists(int nameStart |
+ this.getChar(start + 3) = "^" and nameStart = start + 4
+ or
+ not this.getChar(start + 3) = "^" and nameStart = start + 3
+ |
+ name = this.getText().substring(nameStart, end - 1)
+ )
+ }
+
+ /**
+ * Holds if the named character property is inverted. Examples for which it holds:
+ * - `\P{Digit}` upper-case P means inverted
+ * - `\p{^Word}` caret also means inverted
+ * - `[[:^digit:]]`
+ *
+ * Examples for which it doesn't hold:
+ * - `\p{Word}`
+ * - `\P{^Space}` - upper-case P and caret cancel each other out
+ * - `[[:alnum:]]`
+ */
+ predicate namedCharacterPropertyIsInverted(int start, int end) {
+ this.pStyleNamedCharacterProperty(start, end, _) and
+ exists(boolean upperP, boolean caret |
+ (if this.getChar(start + 1) = "P" then upperP = true else upperP = false) and
+ (if this.getChar(start + 3) = "^" then caret = true else caret = false)
+ |
+ upperP.booleanXor(caret) = true
+ )
+ or
+ this.posixStyleNamedCharacterProperty(start, end, _) and
+ this.getChar(start + 3) = "^"
+ }
+
+ predicate escapedCharacter(int start, int end) {
+ this.escapingChar(start) and
+ not this.numberedBackreference(start, _, _) and
+ not this.namedBackreference(start, _, _) and
+ not this.pStyleNamedCharacterProperty(start, _, _) and
+ (
+ // hex char \xhh
+ this.getChar(start + 1) = "x" and end = start + 4
+ or
+ // wide hex char \uhhhh
+ this.getChar(start + 1) = "u" and end = start + 6
+ or
+ // escape not handled above; update when adding a new case
+ not this.getChar(start + 1) in ["x", "u"] and
+ not exists(this.getChar(start + 1).toInt()) and
+ end = start + 2
+ )
+ }
+
+ predicate inCharSet(int index) {
+ exists(int x, int y | this.charSet(x, y) and index in [x + 1 .. y - 2])
+ }
+
+ predicate inPosixBracket(int index) {
+ exists(int x, int y |
+ this.posixStyleNamedCharacterProperty(x, y, _) and index in [x + 1 .. y - 2]
+ )
+ }
+
+ /** 'Simple' characters are any that don't alter the parsing of the regex. */
+ private predicate simpleCharacter(int start, int end) {
+ end = start + 1 and
+ not this.charSet(start, _) and
+ not this.charSet(_, start + 1) and
+ not exists(int x, int y |
+ this.posixStyleNamedCharacterProperty(x, y, _) and
+ start >= x and
+ end <= y
+ ) and
+ exists(string c | c = this.getChar(start) |
+ exists(int x, int y, int z |
+ this.charSet(x, z) and
+ this.charSetStart(x, y)
+ |
+ start = y
+ or
+ start = z - 2
+ or
+ start > y and start < z - 2 and not this.charRange(_, _, start, end, _)
+ )
+ or
+ not this.inCharSet(start) and
+ not c = "(" and
+ not c = "[" and
+ not c = ")" and
+ not c = "|" and
+ not this.qualifier(start, _, _, _)
+ )
+ }
+
+ predicate character(int start, int end) {
+ (
+ this.simpleCharacter(start, end) and
+ not exists(int x, int y | this.escapedCharacter(x, y) and x <= start and y >= end)
+ or
+ this.escapedCharacter(start, end)
+ ) and
+ not exists(int x, int y | this.groupStart(x, y) and x <= start and y >= end) and
+ not exists(int x, int y | this.backreference(x, y) and x <= start and y >= end) and
+ not exists(int x, int y |
+ this.pStyleNamedCharacterProperty(x, y, _) and x <= start and y >= end
+ )
+ }
+
+ predicate normalCharacter(int start, int end) {
+ this.character(start, end) and
+ not this.specialCharacter(start, end, _)
+ }
+
+ predicate specialCharacter(int start, int end, string char) {
+ this.character(start, end) and
+ not this.inCharSet(start) and
+ (
+ end = start + 1 and
+ char = this.getChar(start) and
+ (char = "$" or char = "^" or char = ".")
+ or
+ end = start + 2 and
+ this.escapingChar(start) and
+ char = this.getText().substring(start, end) and
+ char = ["\\A", "\\Z", "\\z"]
+ )
+ }
+
+ /** Whether the text in the range `start,end` is a group */
+ predicate group(int start, int end) {
+ this.groupContents(start, end, _, _)
+ or
+ this.emptyGroup(start, end)
+ }
+
+ /** Gets the number of the group in start,end */
+ int getGroupNumber(int start, int end) {
+ this.group(start, end) and
+ result =
+ count(int i | this.group(i, _) and i < start and not this.nonCapturingGroupStart(i, _)) + 1
+ }
+
+ /** Gets the name, if it has one, of the group in start,end */
+ string getGroupName(int start, int end) {
+ this.group(start, end) and
+ exists(int nameEnd |
+ this.namedGroupStart(start, nameEnd) and
+ result = this.getText().substring(start + 4, nameEnd - 1)
+ )
+ }
+
+ /** Whether the text in the range start, end is a group and can match the empty string. */
+ predicate zeroWidthMatch(int start, int end) {
+ this.emptyGroup(start, end)
+ or
+ this.negativeAssertionGroup(start, end)
+ or
+ this.positiveLookaheadAssertionGroup(start, end)
+ or
+ this.positiveLookbehindAssertionGroup(start, end)
+ }
+
+ predicate emptyGroup(int start, int end) {
+ exists(int endm1 | end = endm1 + 1 |
+ this.groupStart(start, endm1) and
+ this.isGroupEnd(endm1)
+ )
+ }
+
+ private predicate emptyMatchAtStartGroup(int start, int end) {
+ this.emptyGroup(start, end)
+ or
+ this.negativeAssertionGroup(start, end)
+ or
+ this.positiveLookaheadAssertionGroup(start, end)
+ }
+
+ private predicate emptyMatchAtEndGroup(int start, int end) {
+ this.emptyGroup(start, end)
+ or
+ this.negativeAssertionGroup(start, end)
+ or
+ this.positiveLookbehindAssertionGroup(start, end)
+ }
+
+ private predicate negativeAssertionGroup(int start, int end) {
+ exists(int inStart |
+ this.negativeLookaheadAssertionStart(start, inStart)
+ or
+ this.negativeLookbehindAssertionStart(start, inStart)
+ |
+ this.groupContents(start, end, inStart, _)
+ )
+ }
+
+ predicate negativeLookaheadAssertionGroup(int start, int end) {
+ exists(int inStart | this.negativeLookaheadAssertionStart(start, inStart) |
+ this.groupContents(start, end, inStart, _)
+ )
+ }
+
+ predicate negativeLookbehindAssertionGroup(int start, int end) {
+ exists(int inStart | this.negativeLookbehindAssertionStart(start, inStart) |
+ this.groupContents(start, end, inStart, _)
+ )
+ }
+
+ predicate positiveLookaheadAssertionGroup(int start, int end) {
+ exists(int inStart | this.lookaheadAssertionStart(start, inStart) |
+ this.groupContents(start, end, inStart, _)
+ )
+ }
+
+ predicate positiveLookbehindAssertionGroup(int start, int end) {
+ exists(int inStart | this.lookbehindAssertionStart(start, inStart) |
+ this.groupContents(start, end, inStart, _)
+ )
+ }
+
+ private predicate groupStart(int start, int end) {
+ this.nonCapturingGroupStart(start, end)
+ or
+ this.namedGroupStart(start, end)
+ or
+ this.lookaheadAssertionStart(start, end)
+ or
+ this.negativeLookaheadAssertionStart(start, end)
+ or
+ this.lookbehindAssertionStart(start, end)
+ or
+ this.negativeLookbehindAssertionStart(start, end)
+ or
+ this.commentGroupStart(start, end)
+ or
+ this.simpleGroupStart(start, end)
+ }
+
+ /** Matches the start of a non-capturing group, e.g. `(?:` */
+ private predicate nonCapturingGroupStart(int start, int end) {
+ this.isGroupStart(start) and
+ this.getChar(start + 1) = "?" and
+ this.getChar(start + 2) = ":" and
+ end = start + 3
+ }
+
+ /** Matches the start of a simple group, e.g. `(a+)`. */
+ private predicate simpleGroupStart(int start, int end) {
+ this.isGroupStart(start) and
+ this.getChar(start + 1) != "?" and
+ end = start + 1
+ }
+
+ /**
+ * Matches the start of a named group, such as:
+ * - `(?\w+)`
+ * - `(?'name'\w+)`
+ */
+ private predicate namedGroupStart(int start, int end) {
+ this.isGroupStart(start) and
+ this.getChar(start + 1) = "?" and
+ (
+ this.getChar(start + 2) = "<" and
+ not this.getChar(start + 3) = "=" and // (?<=foo) is a positive lookbehind assertion
+ not this.getChar(start + 3) = "!" and // (? start + 3 and this.getChar(i) = ">") and
+ end = nameEnd + 1
+ )
+ or
+ this.getChar(start + 2) = "'" and
+ exists(int nameEnd |
+ nameEnd = min(int i | i > start + 2 and this.getChar(i) = "'") and end = nameEnd + 1
+ )
+ )
+ }
+
+ /** Matches the start of a positive lookahead assertion, i.e. `(?=`. */
+ private predicate lookaheadAssertionStart(int start, int end) {
+ this.isGroupStart(start) and
+ this.getChar(start + 1) = "?" and
+ this.getChar(start + 2) = "=" and
+ end = start + 3
+ }
+
+ /** Matches the start of a negative lookahead assertion, i.e. `(?!`. */
+ private predicate negativeLookaheadAssertionStart(int start, int end) {
+ this.isGroupStart(start) and
+ this.getChar(start + 1) = "?" and
+ this.getChar(start + 2) = "!" and
+ end = start + 3
+ }
+
+ /** Matches the start of a positive lookbehind assertion, i.e. `(?<=`. */
+ private predicate lookbehindAssertionStart(int start, int end) {
+ this.isGroupStart(start) and
+ this.getChar(start + 1) = "?" and
+ this.getChar(start + 2) = "<" and
+ this.getChar(start + 3) = "=" and
+ end = start + 4
+ }
+
+ /** Matches the start of a negative lookbehind assertion, i.e. `(?`. */
+ predicate namedBackreference(int start, int end, string name) {
+ this.escapingChar(start) and
+ this.getChar(start + 1) = "k" and
+ this.getChar(start + 2) = "<" and
+ exists(int nameEnd | nameEnd = min(int i | i > start + 3 and this.getChar(i) = ">") |
+ end = nameEnd + 1 and
+ name = this.getText().substring(start + 3, nameEnd)
+ )
+ }
+
+ /** Matches a numbered backreference, e.g. `\1`. */
+ predicate numberedBackreference(int start, int end, int value) {
+ this.escapingChar(start) and
+ not this.getChar(start + 1) = "0" and
+ exists(string text, string svalue, int len |
+ end = start + len and
+ text = this.getText() and
+ len in [2 .. 3]
+ |
+ svalue = text.substring(start + 1, start + len) and
+ value = svalue.toInt() and
+ not exists(text.substring(start + 1, start + len + 1).toInt()) and
+ value > 0
+ )
+ }
+
+ /** Whether the text in the range `start,end` is a back reference */
+ predicate backreference(int start, int end) {
+ this.numberedBackreference(start, end, _)
+ or
+ this.namedBackreference(start, end, _)
+ }
+
+ /** Gets the number of the back reference in start,end */
+ int getBackRefNumber(int start, int end) { this.numberedBackreference(start, end, result) }
+
+ /** Gets the name, if it has one, of the back reference in start,end */
+ string getBackRefName(int start, int end) { this.namedBackreference(start, end, result) }
+
+ private predicate baseItem(int start, int end) {
+ this.character(start, end) and
+ not exists(int x, int y | this.charSet(x, y) and x <= start and y >= end)
+ or
+ this.group(start, end)
+ or
+ this.charSet(start, end)
+ or
+ this.backreference(start, end)
+ or
+ this.pStyleNamedCharacterProperty(start, end, _)
+ }
+
+ private predicate qualifier(int start, int end, boolean maybeEmpty, boolean mayRepeatForever) {
+ this.shortQualifier(start, end, maybeEmpty, mayRepeatForever) and
+ not this.getChar(end) = "?"
+ or
+ exists(int shortEnd | this.shortQualifier(start, shortEnd, maybeEmpty, mayRepeatForever) |
+ if this.getChar(shortEnd) = "?" then end = shortEnd + 1 else end = shortEnd
+ )
+ }
+
+ private predicate shortQualifier(int start, int end, boolean maybeEmpty, boolean mayRepeatForever) {
+ (
+ this.getChar(start) = "+" and maybeEmpty = false and mayRepeatForever = true
+ or
+ this.getChar(start) = "*" and maybeEmpty = true and mayRepeatForever = true
+ or
+ this.getChar(start) = "?" and maybeEmpty = true and mayRepeatForever = false
+ ) and
+ end = start + 1
+ or
+ exists(string lower, string upper |
+ this.multiples(start, end, lower, upper) and
+ (if lower = "" or lower.toInt() = 0 then maybeEmpty = true else maybeEmpty = false) and
+ if upper = "" then mayRepeatForever = true else mayRepeatForever = false
+ )
+ }
+
+ predicate multiples(int start, int end, string lower, string upper) {
+ exists(string text, string match, string inner |
+ text = this.getText() and
+ end = start + match.length() and
+ inner = match.substring(1, match.length() - 1)
+ |
+ match = text.regexpFind("\\{[0-9]+\\}", _, start) and
+ lower = inner and
+ upper = lower
+ or
+ match = text.regexpFind("\\{[0-9]*,[0-9]*\\}", _, start) and
+ exists(int commaIndex |
+ commaIndex = inner.indexOf(",") and
+ lower = inner.prefix(commaIndex) and
+ upper = inner.suffix(commaIndex + 1)
+ )
+ )
+ }
+
+ /**
+ * Whether the text in the range start,end is a qualified item, where item is a character,
+ * a character set or a group.
+ */
+ predicate qualifiedItem(int start, int end, boolean maybeEmpty, boolean mayRepeatForever) {
+ this.qualifiedPart(start, _, end, maybeEmpty, mayRepeatForever)
+ }
+
+ predicate qualifiedPart(
+ int start, int partEnd, int end, boolean maybeEmpty, boolean mayRepeatForever
+ ) {
+ this.baseItem(start, partEnd) and
+ this.qualifier(partEnd, end, maybeEmpty, mayRepeatForever)
+ }
+
+ predicate item(int start, int end) {
+ this.qualifiedItem(start, end, _, _)
+ or
+ this.baseItem(start, end) and not this.qualifier(end, _, _, _)
+ }
+
+ private predicate subsequence(int start, int end) {
+ (
+ start = 0 or
+ this.groupStart(_, start) or
+ this.isOptionDivider(start - 1)
+ ) and
+ this.item(start, end)
+ or
+ exists(int mid |
+ this.subsequence(start, mid) and
+ this.item(mid, end)
+ )
+ }
+
+ /**
+ * Whether the text in the range start,end is a sequence of 1 or more items, where an item is a character,
+ * a character set or a group.
+ */
+ predicate sequence(int start, int end) {
+ this.sequenceOrQualified(start, end) and
+ not this.qualifiedItem(start, end, _, _)
+ }
+
+ private predicate sequenceOrQualified(int start, int end) {
+ this.subsequence(start, end) and
+ not this.itemStart(end)
+ }
+
+ private predicate itemStart(int start) {
+ this.character(start, _) or
+ this.isGroupStart(start) or
+ this.charSet(start, _) or
+ this.backreference(start, _) or
+ this.namedCharacterProperty(start, _, _)
+ }
+
+ private predicate itemEnd(int end) {
+ this.character(_, end)
+ or
+ exists(int endm1 | this.isGroupEnd(endm1) and end = endm1 + 1)
+ or
+ this.charSet(_, end)
+ or
+ this.qualifier(_, end, _, _)
+ }
+
+ private predicate topLevel(int start, int end) {
+ this.subalternation(start, end, _) and
+ not this.isOptionDivider(end)
+ }
+
+ private predicate subalternation(int start, int end, int itemStart) {
+ this.sequenceOrQualified(start, end) and
+ not this.isOptionDivider(start - 1) and
+ itemStart = start
+ or
+ start = end and
+ not this.itemEnd(start) and
+ this.isOptionDivider(end) and
+ itemStart = start
+ or
+ exists(int mid |
+ this.subalternation(start, mid, _) and
+ this.isOptionDivider(mid) and
+ itemStart = mid + 1
+ |
+ this.sequenceOrQualified(itemStart, end)
+ or
+ not this.itemStart(end) and end = itemStart
+ )
+ }
+
+ /**
+ * Whether the text in the range start,end is an alternation
+ */
+ predicate alternation(int start, int end) {
+ this.topLevel(start, end) and
+ exists(int less | this.subalternation(start, less, _) and less < end)
+ }
+
+ /**
+ * Whether the text in the range start,end is an alternation and the text in partStart, partEnd is one of the
+ * options in that alternation.
+ */
+ predicate alternationOption(int start, int end, int partStart, int partEnd) {
+ this.alternation(start, end) and
+ this.subalternation(start, partEnd, partStart)
+ }
+
+ /** A part of the regex that may match the start of the string. */
+ private predicate firstPart(int start, int end) {
+ start = 0 and end = this.getText().length()
+ or
+ exists(int x | this.firstPart(x, end) |
+ this.emptyMatchAtStartGroup(x, start)
+ or
+ this.qualifiedItem(x, start, true, _)
+ or
+ // ^ and \A match the start of the string
+ this.specialCharacter(x, start, ["^", "\\A"])
+ )
+ or
+ exists(int y | this.firstPart(start, y) |
+ this.item(start, end)
+ or
+ this.qualifiedPart(start, end, y, _, _)
+ )
+ or
+ exists(int x, int y | this.firstPart(x, y) |
+ this.groupContents(x, y, start, end)
+ or
+ this.alternationOption(x, y, start, end)
+ )
+ }
+
+ /** A part of the regex that may match the end of the string. */
+ private predicate lastPart(int start, int end) {
+ start = 0 and end = this.getText().length()
+ or
+ exists(int y | this.lastPart(start, y) |
+ this.emptyMatchAtEndGroup(end, y)
+ or
+ this.qualifiedItem(end, y, true, _)
+ or
+ // $, \Z, and \z match the end of the string.
+ this.specialCharacter(end, y, ["$", "\\Z", "\\z"])
+ )
+ or
+ exists(int x |
+ this.lastPart(x, end) and
+ this.item(start, end)
+ )
+ or
+ exists(int y | this.lastPart(start, y) | this.qualifiedPart(start, end, y, _, _))
+ or
+ exists(int x, int y | this.lastPart(x, y) |
+ this.groupContents(x, y, start, end)
+ or
+ this.alternationOption(x, y, start, end)
+ )
+ }
+
+ /**
+ * Whether the item at [start, end) is one of the first items
+ * to be matched.
+ */
+ predicate firstItem(int start, int end) {
+ (
+ this.character(start, end)
+ or
+ this.qualifiedItem(start, end, _, _)
+ or
+ this.charSet(start, end)
+ ) and
+ this.firstPart(start, end)
+ }
+
+ /**
+ * Whether the item at [start, end) is one of the last items
+ * to be matched.
+ */
+ predicate lastItem(int start, int end) {
+ (
+ this.character(start, end)
+ or
+ this.qualifiedItem(start, end, _, _)
+ or
+ this.charSet(start, end)
+ ) and
+ this.lastPart(start, end)
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/performance/PolynomialReDoSCustomizations.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/performance/PolynomialReDoSCustomizations.qll
new file mode 100644
index 00000000000..d32734eb02b
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/performance/PolynomialReDoSCustomizations.qll
@@ -0,0 +1,131 @@
+/**
+ * Provides default sources, sinks and sanitizers for reasoning about
+ * polynomial regular expression denial-of-service attacks, as well
+ * as extension points for adding your own.
+ */
+
+private import codeql.ruby.AST as AST
+private import codeql.ruby.CFG
+private import codeql.ruby.DataFlow
+private import codeql.ruby.dataflow.RemoteFlowSources
+private import codeql.ruby.security.performance.ParseRegExp as RegExp
+private import codeql.ruby.security.performance.RegExpTreeView
+private import codeql.ruby.security.performance.SuperlinearBackTracking
+
+module PolynomialReDoS {
+ /**
+ * A data flow source node for polynomial regular expression denial-of-service vulnerabilities.
+ */
+ abstract class Source extends DataFlow::Node { }
+
+ /**
+ * A data flow sink node for polynomial regular expression denial-of-service vulnerabilities.
+ */
+ abstract class Sink extends DataFlow::Node {
+ /** Gets the regex that is being executed by this node. */
+ abstract RegExpTerm getRegExp();
+
+ /** Gets the node to highlight in the alert message. */
+ DataFlow::Node getHighlight() { result = this }
+ }
+
+ /**
+ * A sanitizer for polynomial regular expression denial-of-service vulnerabilities.
+ */
+ abstract class Sanitizer extends DataFlow::Node { }
+
+ /**
+ * A sanitizer guard for polynomial regular expression denial of service
+ * vulnerabilities.
+ */
+ abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
+
+ /**
+ * A source of remote user input, considered as a flow source.
+ */
+ class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
+
+ /**
+ * Gets the AST of a regular expression object that can flow to `node`.
+ */
+ RegExpTerm getRegExpObjectFromNode(DataFlow::Node node) {
+ exists(DataFlow::LocalSourceNode regexp |
+ regexp.flowsTo(node) and
+ result = regexp.asExpr().(CfgNodes::ExprNodes::RegExpLiteralCfgNode).getExpr().getParsed()
+ )
+ }
+
+ /**
+ * A regexp match against a superlinear backtracking term, seen as a sink for
+ * polynomial regular expression denial-of-service vulnerabilities.
+ */
+ class PolynomialBackTrackingTermMatch extends Sink {
+ PolynomialBackTrackingTerm term;
+ DataFlow::ExprNode matchNode;
+
+ PolynomialBackTrackingTermMatch() {
+ exists(DataFlow::Node regexp |
+ term.getRootTerm() = getRegExpObjectFromNode(regexp) and
+ (
+ // `=~` or `!~`
+ exists(CfgNodes::ExprNodes::BinaryOperationCfgNode op |
+ matchNode.asExpr() = op and
+ (
+ op.getExpr() instanceof AST::RegExpMatchExpr or
+ op.getExpr() instanceof AST::NoRegExpMatchExpr
+ ) and
+ (
+ this.asExpr() = op.getLeftOperand() and regexp.asExpr() = op.getRightOperand()
+ or
+ this.asExpr() = op.getRightOperand() and regexp.asExpr() = op.getLeftOperand()
+ )
+ )
+ or
+ // Any of the methods on `String` that take a regexp.
+ exists(CfgNodes::ExprNodes::MethodCallCfgNode call |
+ matchNode.asExpr() = call and
+ call.getExpr().getMethodName() =
+ [
+ "[]", "gsub", "gsub!", "index", "match", "match?", "partition", "rindex",
+ "rpartition", "scan", "slice!", "split", "sub", "sub!"
+ ] and
+ this.asExpr() = call.getReceiver() and
+ regexp.asExpr() = call.getArgument(0)
+ )
+ or
+ // A call to `match` or `match?` where the regexp is the receiver.
+ exists(CfgNodes::ExprNodes::MethodCallCfgNode call |
+ matchNode.asExpr() = call and
+ call.getExpr().getMethodName() = ["match", "match?"] and
+ regexp.asExpr() = call.getReceiver() and
+ this.asExpr() = call.getArgument(0)
+ )
+ )
+ )
+ }
+
+ override RegExpTerm getRegExp() { result = term }
+
+ override DataFlow::Node getHighlight() { result = matchNode }
+ }
+
+ /**
+ * A check on the length of a string, seen as a sanitizer guard.
+ */
+ class LengthGuard extends SanitizerGuard, CfgNodes::ExprNodes::RelationalOperationCfgNode {
+ private DataFlow::Node input;
+
+ LengthGuard() {
+ exists(DataFlow::CallNode length, DataFlow::ExprNode operand |
+ length.asExpr().getExpr().(AST::MethodCall).getMethodName() = "length" and
+ length.getReceiver() = input and
+ length.flowsTo(operand) and
+ operand.getExprNode() = this.getAnOperand()
+ )
+ }
+
+ override predicate checks(CfgNode node, boolean branch) {
+ node = input.asExpr() and branch = true
+ }
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/performance/PolynomialReDoSQuery.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/performance/PolynomialReDoSQuery.qll
new file mode 100644
index 00000000000..db7269d7fdb
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/performance/PolynomialReDoSQuery.qll
@@ -0,0 +1,37 @@
+/**
+ * Provides a taint tracking configuration for reasoning about polynomial
+ * regular expression denial-of-service attacks.
+ *
+ * Note, for performance reasons: only import this file if `Configuration` is
+ * needed. Otherwise, `PolynomialReDoSCustomizations` should be imported
+ * instead.
+ */
+
+private import codeql.ruby.DataFlow
+private import codeql.ruby.TaintTracking
+
+/**
+ * Provides a taint-tracking configuration for detecting polynomial regular
+ * expression denial of service vulnerabilities.
+ */
+module PolynomialReDoS {
+ import PolynomialReDoSCustomizations::PolynomialReDoS
+
+ /**
+ * A taint-tracking configuration for detecting polynomial regular expression
+ * denial of service vulnerabilities.
+ */
+ class Configuration extends TaintTracking::Configuration {
+ Configuration() { this = "PolynomialReDoS" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof Source }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
+
+ override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
+
+ override predicate isSanitizerGuard(DataFlow::BarrierGuard node) {
+ node instanceof SanitizerGuard
+ }
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/performance/ReDoSUtil.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/performance/ReDoSUtil.qll
new file mode 100644
index 00000000000..3062390c5db
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/performance/ReDoSUtil.qll
@@ -0,0 +1,1241 @@
+/**
+ * Provides classes for working with regular expressions that can
+ * perform backtracking in superlinear/exponential time.
+ *
+ * This module contains a number of utility predicates for compiling a regular expression into a NFA and reasoning about this NFA.
+ *
+ * The `ReDoSConfiguration` contains a `isReDoSCandidate` predicate that is used to
+ * to determine which states the prefix/suffix search should happen on.
+ * There is only meant to exist one `ReDoSConfiguration` at a time.
+ *
+ * The predicate `hasReDoSResult` outputs a de-duplicated set of
+ * states that will cause backtracking (a rejecting suffix exists).
+ */
+
+import RegExpTreeView
+private import codeql.Locations
+
+/**
+ * A configuration for which parts of a regular expression should be considered relevant for
+ * the different predicates in `ReDoS.qll`.
+ * Used to adjust the computations for either superlinear or exponential backtracking.
+ */
+abstract class ReDoSConfiguration extends string {
+ bindingset[this]
+ ReDoSConfiguration() { any() }
+
+ /**
+ * Holds if `state` with the pump string `pump` is a candidate for a
+ * ReDoS vulnerable state.
+ * This is used to determine which states are considered for the prefix/suffix construction.
+ */
+ abstract predicate isReDoSCandidate(State state, string pump);
+}
+
+/**
+ * Holds if repeating `pump' starting at `state` is a candidate for causing backtracking.
+ * No check whether a rejected suffix exists has been made.
+ */
+private predicate isReDoSCandidate(State state, string pump) {
+ any(ReDoSConfiguration conf).isReDoSCandidate(state, pump) and
+ (
+ not any(ReDoSConfiguration conf).isReDoSCandidate(epsilonSucc+(state), _)
+ or
+ epsilonSucc+(state) = state and
+ state =
+ max(State s, Location l |
+ s = epsilonSucc+(state) and
+ l = s.getRepr().getLocation() and
+ any(ReDoSConfiguration conf).isReDoSCandidate(s, _) and
+ s.getRepr() instanceof InfiniteRepetitionQuantifier
+ |
+ s order by l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine()
+ )
+ )
+}
+
+/**
+ * Gets the char after `c` (from a simplified ASCII table).
+ */
+private string nextChar(string c) { exists(int code | code = ascii(c) | code + 1 = ascii(result)) }
+
+/**
+ * Gets an approximation for the ASCII code for `char`.
+ * Only the easily printable chars are included (so no newline, tab, null, etc).
+ */
+private int ascii(string char) {
+ char =
+ rank[result](string c |
+ c =
+ "! \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
+ .charAt(_)
+ )
+}
+
+/**
+ * Holds if `t` matches at least an epsilon symbol.
+ *
+ * That is, this term does not restrict the language of the enclosing regular expression.
+ *
+ * This is implemented as an under-approximation, and this predicate does not hold for sub-patterns in particular.
+ */
+predicate matchesEpsilon(RegExpTerm t) {
+ t instanceof RegExpStar
+ or
+ t instanceof RegExpOpt
+ or
+ t.(RegExpRange).getLowerBound() = 0
+ or
+ exists(RegExpTerm child |
+ child = t.getAChild() and
+ matchesEpsilon(child)
+ |
+ t instanceof RegExpAlt or
+ t instanceof RegExpGroup or
+ t instanceof RegExpPlus or
+ t instanceof RegExpRange
+ )
+ or
+ matchesEpsilon(t.(RegExpBackRef).getGroup())
+ or
+ forex(RegExpTerm child | child = t.(RegExpSequence).getAChild() | matchesEpsilon(child))
+}
+
+/**
+ * A lookahead/lookbehind that matches the empty string.
+ */
+class EmptyPositiveSubPatttern extends RegExpSubPattern {
+ EmptyPositiveSubPatttern() {
+ (
+ this instanceof RegExpPositiveLookahead
+ or
+ this instanceof RegExpPositiveLookbehind
+ ) and
+ matchesEpsilon(this.getOperand())
+ }
+}
+
+/**
+ * A branch in a disjunction that is the root node in a literal, or a literal
+ * whose root node is not a disjunction.
+ */
+class RegExpRoot extends RegExpTerm {
+ RegExpParent parent;
+
+ RegExpRoot() {
+ exists(RegExpAlt alt |
+ alt.isRootTerm() and
+ this = alt.getAChild() and
+ parent = alt.getParent()
+ )
+ or
+ this.isRootTerm() and
+ not this instanceof RegExpAlt and
+ parent = this.getParent()
+ }
+
+ /**
+ * Holds if this root term is relevant to the ReDoS analysis.
+ */
+ predicate isRelevant() {
+ // there is at least one repetition
+ getRoot(any(InfiniteRepetitionQuantifier q)) = this and
+ // is actually used as a RegExp
+ isUsedAsRegExp() and
+ // not excluded for library specific reasons
+ not isExcluded(getRootTerm().getParent())
+ }
+}
+
+/**
+ * A constant in a regular expression that represents valid Unicode character(s).
+ */
+private class RegexpCharacterConstant extends RegExpConstant {
+ RegexpCharacterConstant() { this.isCharacter() }
+}
+
+/**
+ * A regexp term that is relevant for this ReDoS analysis.
+ */
+class RelevantRegExpTerm extends RegExpTerm {
+ RelevantRegExpTerm() { getRoot(this).isRelevant() }
+}
+
+/**
+ * Holds if `term` is the chosen canonical representative for all terms with string representation `str`.
+ * The string representation includes which flags are used with the regular expression.
+ *
+ * Using canonical representatives gives a huge performance boost when working with tuples containing multiple `InputSymbol`s.
+ * The number of `InputSymbol`s is decreased by 3 orders of magnitude or more in some larger benchmarks.
+ */
+private predicate isCanonicalTerm(RelevantRegExpTerm term, string str) {
+ term =
+ min(RelevantRegExpTerm t, Location loc, File file |
+ loc = t.getLocation() and
+ file = t.getFile() and
+ str = t.getRawValue() + "|" + getCanonicalizationFlags(t.getRootTerm())
+ |
+ t order by t.getFile().getRelativePath(), loc.getStartLine(), loc.getStartColumn()
+ )
+}
+
+/**
+ * Gets a string reperesentation of the flags used with the regular expression.
+ * Only the flags that are relevant for the canonicalization are included.
+ */
+string getCanonicalizationFlags(RegExpTerm root) {
+ root.isRootTerm() and
+ (if RegExpFlags::isIgnoreCase(root) then result = "i" else result = "")
+}
+
+/**
+ * An abstract input symbol, representing a set of concrete characters.
+ */
+private newtype TInputSymbol =
+ /** An input symbol corresponding to character `c`. */
+ Char(string c) {
+ c =
+ any(RegexpCharacterConstant cc |
+ cc instanceof RelevantRegExpTerm and
+ not RegExpFlags::isIgnoreCase(cc.getRootTerm())
+ ).getValue().charAt(_)
+ or
+ // normalize everything to lower case if the regexp is case insensitive
+ c =
+ any(RegexpCharacterConstant cc, string char |
+ cc instanceof RelevantRegExpTerm and
+ RegExpFlags::isIgnoreCase(cc.getRootTerm()) and
+ char = cc.getValue().charAt(_)
+ |
+ char.toLowerCase()
+ )
+ } or
+ /**
+ * An input symbol representing all characters matched by
+ * a (non-universal) character class that has string representation `charClassString`.
+ */
+ CharClass(string charClassString) {
+ exists(RelevantRegExpTerm recc | isCanonicalTerm(recc, charClassString) |
+ recc instanceof RegExpCharacterClass and
+ not recc.(RegExpCharacterClass).isUniversalClass()
+ or
+ recc instanceof RegExpCharacterClassEscape
+ or
+ recc instanceof RegExpNamedCharacterProperty
+ )
+ } or
+ /** An input symbol representing all characters matched by `.`. */
+ Dot() or
+ /** An input symbol representing all characters. */
+ Any() or
+ /** An epsilon transition in the automaton. */
+ Epsilon()
+
+/**
+ * Gets the canonical CharClass for `term`.
+ */
+CharClass getCanonicalCharClass(RegExpTerm term) {
+ exists(string str | isCanonicalTerm(term, str) | result = CharClass(str))
+}
+
+/**
+ * Holds if `a` and `b` are input symbols from the same regexp.
+ */
+private predicate sharesRoot(TInputSymbol a, TInputSymbol b) {
+ exists(RegExpRoot root |
+ belongsTo(a, root) and
+ belongsTo(b, root)
+ )
+}
+
+/**
+ * Holds if the `a` is an input symbol from a regexp that has root `root`.
+ */
+private predicate belongsTo(TInputSymbol a, RegExpRoot root) {
+ exists(State s | getRoot(s.getRepr()) = root |
+ delta(s, a, _)
+ or
+ delta(_, a, s)
+ )
+}
+
+/**
+ * An abstract input symbol, representing a set of concrete characters.
+ */
+class InputSymbol extends TInputSymbol {
+ InputSymbol() { not this instanceof Epsilon }
+
+ /**
+ * Gets a string representation of this input symbol.
+ */
+ string toString() {
+ this = Char(result)
+ or
+ this = CharClass(result)
+ or
+ this = Dot() and result = "."
+ or
+ this = Any() and result = "[^]"
+ }
+}
+
+/**
+ * An abstract input symbol that represents a character class.
+ */
+abstract class CharacterClass extends InputSymbol {
+ /**
+ * Gets a character that is relevant for intersection-tests involving this
+ * character class.
+ *
+ * Specifically, this is any of the characters mentioned explicitly in the
+ * character class, offset by one if it is inverted. For character class escapes,
+ * the result is as if the class had been written out as a series of intervals.
+ *
+ * This set is large enough to ensure that for any two intersecting character
+ * classes, one contains a relevant character from the other.
+ */
+ abstract string getARelevantChar();
+
+ /**
+ * Holds if this character class matches `char`.
+ */
+ bindingset[char]
+ abstract predicate matches(string char);
+
+ /**
+ * Gets a character matched by this character class.
+ */
+ string choose() { result = getARelevantChar() and matches(result) }
+}
+
+/**
+ * Provides implementations for `CharacterClass`.
+ */
+private module CharacterClasses {
+ /**
+ * Holds if the character class `cc` has a child (constant or range) that matches `char`.
+ */
+ pragma[noinline]
+ predicate hasChildThatMatches(RegExpCharacterClass cc, string char) {
+ if RegExpFlags::isIgnoreCase(cc.getRootTerm())
+ then
+ // normalize everything to lower case if the regexp is case insensitive
+ exists(string c | hasChildThatMatchesIgnoringCasingFlags(cc, c) | char = c.toLowerCase())
+ else hasChildThatMatchesIgnoringCasingFlags(cc, char)
+ }
+
+ /**
+ * Holds if the character class `cc` has a child (constant or range) that matches `char`.
+ * Ignores whether the character class is inside a regular expression that has the ignore case flag.
+ */
+ pragma[noinline]
+ predicate hasChildThatMatchesIgnoringCasingFlags(RegExpCharacterClass cc, string char) {
+ exists(getCanonicalCharClass(cc)) and
+ exists(RegExpTerm child | child = cc.getAChild() |
+ char = child.(RegexpCharacterConstant).getValue()
+ or
+ rangeMatchesOnLetterOrDigits(child, char)
+ or
+ not rangeMatchesOnLetterOrDigits(child, _) and
+ char = getARelevantChar() and
+ exists(string lo, string hi | child.(RegExpCharacterRange).isRange(lo, hi) |
+ lo <= char and
+ char <= hi
+ )
+ or
+ exists(RegExpCharacterClassEscape escape | escape = child |
+ escape.getValue() = escape.getValue().toLowerCase() and
+ classEscapeMatches(escape.getValue(), char)
+ or
+ char = getARelevantChar() and
+ escape.getValue() = escape.getValue().toUpperCase() and
+ not classEscapeMatches(escape.getValue().toLowerCase(), char)
+ )
+ or
+ exists(RegExpNamedCharacterProperty charProp | charProp = child |
+ not charProp.isInverted() and
+ namedCharacterPropertyMatches(charProp.getName(), char)
+ or
+ char = getARelevantChar() and
+ charProp.isInverted() and
+ not namedCharacterPropertyMatches(charProp.getName(), char)
+ )
+ )
+ }
+
+ /**
+ * Holds if `range` is a range on lower-case, upper-case, or digits, and matches `char`.
+ * This predicate is used to restrict the searchspace for ranges by only joining `getAnyPossiblyMatchedChar`
+ * on a few ranges.
+ */
+ private predicate rangeMatchesOnLetterOrDigits(RegExpCharacterRange range, string char) {
+ exists(string lo, string hi |
+ range.isRange(lo, hi) and lo = lowercaseLetter() and hi = lowercaseLetter()
+ |
+ lo <= char and
+ char <= hi and
+ char = lowercaseLetter()
+ )
+ or
+ exists(string lo, string hi |
+ range.isRange(lo, hi) and lo = upperCaseLetter() and hi = upperCaseLetter()
+ |
+ lo <= char and
+ char <= hi and
+ char = upperCaseLetter()
+ )
+ or
+ exists(string lo, string hi | range.isRange(lo, hi) and lo = digit() and hi = digit() |
+ lo <= char and
+ char <= hi and
+ char = digit()
+ )
+ }
+
+ private string lowercaseLetter() { result = "abdcefghijklmnopqrstuvwxyz".charAt(_) }
+
+ private string upperCaseLetter() { result = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".charAt(_) }
+
+ private string digit() { result = [0 .. 9].toString() }
+
+ /**
+ * Gets a char that could be matched by a regular expression.
+ * Includes all printable ascii chars, all constants mentioned in a regexp, and all chars matches by the regexp `/\s|\d|\w/`.
+ */
+ string getARelevantChar() {
+ exists(ascii(result))
+ or
+ exists(RegexpCharacterConstant c | result = c.getValue().charAt(_))
+ or
+ classEscapeMatches(_, result)
+ }
+
+ /**
+ * Gets a char that is mentioned in the character class `c`.
+ */
+ private string getAMentionedChar(RegExpCharacterClass c) {
+ exists(RegExpTerm child | child = c.getAChild() |
+ result = child.(RegexpCharacterConstant).getValue()
+ or
+ child.(RegExpCharacterRange).isRange(result, _)
+ or
+ child.(RegExpCharacterRange).isRange(_, result)
+ or
+ exists(RegExpCharacterClassEscape escape | child = escape |
+ result = min(string s | classEscapeMatches(escape.getValue().toLowerCase(), s))
+ or
+ result = max(string s | classEscapeMatches(escape.getValue().toLowerCase(), s))
+ )
+ or
+ exists(RegExpNamedCharacterProperty charProp | child = charProp |
+ result = min(string s | namedCharacterPropertyMatches(charProp.getName(), s))
+ or
+ result = max(string s | namedCharacterPropertyMatches(charProp.getName(), s))
+ )
+ )
+ }
+
+ /**
+ * An implementation of `CharacterClass` for positive (non inverted) character classes.
+ */
+ private class PositiveCharacterClass extends CharacterClass {
+ RegExpCharacterClass cc;
+
+ PositiveCharacterClass() { this = getCanonicalCharClass(cc) and not cc.isInverted() }
+
+ override string getARelevantChar() { result = getAMentionedChar(cc) }
+
+ override predicate matches(string char) { hasChildThatMatches(cc, char) }
+ }
+
+ /**
+ * An implementation of `CharacterClass` for inverted character classes.
+ */
+ private class InvertedCharacterClass extends CharacterClass {
+ RegExpCharacterClass cc;
+
+ InvertedCharacterClass() { this = getCanonicalCharClass(cc) and cc.isInverted() }
+
+ override string getARelevantChar() {
+ result = nextChar(getAMentionedChar(cc)) or
+ nextChar(result) = getAMentionedChar(cc)
+ }
+
+ bindingset[char]
+ override predicate matches(string char) { not hasChildThatMatches(cc, char) }
+ }
+
+ /**
+ * Holds if the character class escape `clazz` (\d, \s, or \w) matches `char`.
+ */
+ pragma[noinline]
+ private predicate classEscapeMatches(string clazz, string char) {
+ clazz = "d" and
+ char = "0123456789".charAt(_)
+ or
+ clazz = "s" and
+ char = [" ", "\t", "\r", "\n", 11.toUnicode(), 12.toUnicode()] // 11.toUnicode() = \v, 12.toUnicode() = \f
+ or
+ clazz = "w" and
+ char = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_".charAt(_)
+ }
+
+ /**
+ * Holds if the named character property (e.g. from a POSIX bracket
+ * expression) `propName` matches `char`. For example, it holds when `name` is
+ * `"word"` and `char` is `"a"`.
+ *
+ * TODO: expand to cover more properties.
+ */
+ private predicate namedCharacterPropertyMatches(string propName, string char) {
+ propName = ["digit", "Digit"] and
+ char = "0123456789".charAt(_)
+ or
+ propName = ["space", "Space"] and
+ (
+ char = [" ", "\t", "\r", "\n"]
+ or
+ char = getARelevantChar() and
+ char.regexpMatch("\\u000b|\\u000c") // \v|\f (vertical tab | form feed)
+ )
+ or
+ propName = ["word", "Word"] and
+ char = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_".charAt(_)
+ }
+
+ /**
+ * An implementation of `CharacterClass` for \d, \s, and \w.
+ */
+ private class PositiveCharacterClassEscape extends CharacterClass {
+ RegExpCharacterClassEscape cc;
+
+ PositiveCharacterClassEscape() {
+ this = getCanonicalCharClass(cc) and cc.getValue() = ["d", "s", "w"]
+ }
+
+ override string getARelevantChar() {
+ cc.getValue() = "d" and
+ result = ["0", "9"]
+ or
+ cc.getValue() = "s" and
+ result = " "
+ or
+ cc.getValue() = "w" and
+ result = ["a", "Z", "_", "0", "9"]
+ }
+
+ override predicate matches(string char) { classEscapeMatches(cc.getValue(), char) }
+
+ override string choose() {
+ cc.getValue() = "d" and
+ result = "9"
+ or
+ cc.getValue() = "s" and
+ result = " "
+ or
+ cc.getValue() = "w" and
+ result = "a"
+ }
+ }
+
+ /**
+ * An implementation of `CharacterClass` for \D, \S, and \W.
+ */
+ private class NegativeCharacterClassEscape extends CharacterClass {
+ RegExpCharacterClassEscape cc;
+
+ NegativeCharacterClassEscape() {
+ this = getCanonicalCharClass(cc) and cc.getValue() = ["D", "S", "W"]
+ }
+
+ override string getARelevantChar() {
+ cc.getValue() = "D" and
+ result = ["a", "Z", "!"]
+ or
+ cc.getValue() = "S" and
+ result = ["a", "9", "!"]
+ or
+ cc.getValue() = "W" and
+ result = [" ", "!"]
+ }
+
+ bindingset[char]
+ override predicate matches(string char) {
+ not classEscapeMatches(cc.getValue().toLowerCase(), char)
+ }
+ }
+
+ /**
+ * An implementation of `NamedCharacterProperty` for positive (non-inverted)
+ * character properties.
+ */
+ private class PositiveNamedCharacterProperty extends CharacterClass {
+ RegExpNamedCharacterProperty cp;
+
+ PositiveNamedCharacterProperty() { this = getCanonicalCharClass(cp) and not cp.isInverted() }
+
+ override string getARelevantChar() {
+ exists(string lowerName | lowerName = cp.getName().toLowerCase() |
+ lowerName = "digit" and
+ result = ["0", "9"]
+ or
+ lowerName = "space" and
+ result = [" "]
+ or
+ lowerName = "word" and
+ result = ["a", "Z", "_", "0", "9"]
+ )
+ }
+
+ override predicate matches(string char) { namedCharacterPropertyMatches(cp.getName(), char) }
+
+ override string choose() {
+ exists(string lowerName | lowerName = cp.getName().toLowerCase() |
+ lowerName = "digit" and
+ result = "9"
+ or
+ lowerName = "space" and
+ result = " "
+ or
+ lowerName = "word" and
+ result = "a"
+ )
+ }
+ }
+
+ private class InvertedNamedCharacterProperty extends CharacterClass {
+ RegExpNamedCharacterProperty cp;
+
+ InvertedNamedCharacterProperty() { this = getCanonicalCharClass(cp) and cp.isInverted() }
+
+ override string getARelevantChar() {
+ exists(string lowerName | lowerName = cp.getName().toLowerCase() |
+ lowerName = "digit" and
+ result = ["a", "Z", "!"]
+ or
+ lowerName = "space" and
+ result = ["a", "9", "!"]
+ or
+ lowerName = "word" and
+ result = [" ", "!"]
+ )
+ }
+
+ bindingset[char]
+ override predicate matches(string char) {
+ not namedCharacterPropertyMatches(cp.getName(), char)
+ }
+ }
+}
+
+private class EdgeLabel extends TInputSymbol {
+ string toString() {
+ this = Epsilon() and result = ""
+ or
+ exists(InputSymbol s | this = s and result = s.toString())
+ }
+}
+
+/**
+ * Gets the state before matching `t`.
+ */
+pragma[inline]
+private State before(RegExpTerm t) { result = Match(t, 0) }
+
+/**
+ * Gets a state the NFA may be in after matching `t`.
+ */
+State after(RegExpTerm t) {
+ exists(RegExpAlt alt | t = alt.getAChild() | result = after(alt))
+ or
+ exists(RegExpSequence seq, int i | t = seq.getChild(i) |
+ result = before(seq.getChild(i + 1))
+ or
+ i + 1 = seq.getNumChild() and result = after(seq)
+ )
+ or
+ exists(RegExpGroup grp | t = grp.getAChild() | result = after(grp))
+ or
+ exists(RegExpStar star | t = star.getAChild() | result = before(star))
+ or
+ exists(RegExpPlus plus | t = plus.getAChild() |
+ result = before(plus) or
+ result = after(plus)
+ )
+ or
+ exists(RegExpOpt opt | t = opt.getAChild() | result = after(opt))
+ or
+ exists(RegExpRoot root | t = root | result = AcceptAnySuffix(root))
+}
+
+/**
+ * Holds if the NFA has a transition from `q1` to `q2` labelled with `lbl`.
+ */
+predicate delta(State q1, EdgeLabel lbl, State q2) {
+ exists(RegexpCharacterConstant s, int i |
+ q1 = Match(s, i) and
+ (
+ not RegExpFlags::isIgnoreCase(s.getRootTerm()) and
+ lbl = Char(s.getValue().charAt(i))
+ or
+ // normalize everything to lower case if the regexp is case insensitive
+ RegExpFlags::isIgnoreCase(s.getRootTerm()) and
+ exists(string c | c = s.getValue().charAt(i) | lbl = Char(c.toLowerCase()))
+ ) and
+ (
+ q2 = Match(s, i + 1)
+ or
+ s.getValue().length() = i + 1 and
+ q2 = after(s)
+ )
+ )
+ or
+ exists(RegExpDot dot | q1 = before(dot) and q2 = after(dot) |
+ if RegExpFlags::isDotAll(dot.getRootTerm()) then lbl = Any() else lbl = Dot()
+ )
+ or
+ exists(RegExpCharacterClass cc |
+ cc.isUniversalClass() and q1 = before(cc) and lbl = Any() and q2 = after(cc)
+ or
+ q1 = before(cc) and
+ lbl = CharClass(cc.getRawValue() + "|" + getCanonicalizationFlags(cc.getRootTerm())) and
+ q2 = after(cc)
+ )
+ or
+ exists(RegExpCharacterClassEscape cc |
+ q1 = before(cc) and
+ lbl = CharClass(cc.getRawValue() + "|" + getCanonicalizationFlags(cc.getRootTerm())) and
+ q2 = after(cc)
+ )
+ or
+ exists(RegExpNamedCharacterProperty cp |
+ q1 = before(cp) and
+ lbl = CharClass(cp.getRawValue()) and
+ q2 = after(cp)
+ )
+ or
+ exists(RegExpAlt alt | lbl = Epsilon() | q1 = before(alt) and q2 = before(alt.getAChild()))
+ or
+ exists(RegExpSequence seq | lbl = Epsilon() | q1 = before(seq) and q2 = before(seq.getChild(0)))
+ or
+ exists(RegExpGroup grp | lbl = Epsilon() | q1 = before(grp) and q2 = before(grp.getChild(0)))
+ or
+ exists(RegExpStar star | lbl = Epsilon() |
+ q1 = before(star) and q2 = before(star.getChild(0))
+ or
+ q1 = before(star) and q2 = after(star)
+ )
+ or
+ exists(RegExpPlus plus | lbl = Epsilon() | q1 = before(plus) and q2 = before(plus.getChild(0)))
+ or
+ exists(RegExpOpt opt | lbl = Epsilon() |
+ q1 = before(opt) and q2 = before(opt.getChild(0))
+ or
+ q1 = before(opt) and q2 = after(opt)
+ )
+ or
+ exists(RegExpRoot root | q1 = AcceptAnySuffix(root) |
+ lbl = Any() and q2 = q1
+ or
+ lbl = Epsilon() and q2 = Accept(root)
+ )
+ or
+ exists(RegExpRoot root | q1 = Match(root, 0) | lbl = Any() and q2 = q1)
+ or
+ exists(RegExpDollar dollar | q1 = before(dollar) |
+ lbl = Epsilon() and q2 = Accept(getRoot(dollar))
+ )
+ or
+ exists(EmptyPositiveSubPatttern empty | q1 = before(empty) |
+ lbl = Epsilon() and q2 = after(empty)
+ )
+}
+
+/**
+ * Gets a state that `q` has an epsilon transition to.
+ */
+State epsilonSucc(State q) { delta(q, Epsilon(), result) }
+
+/**
+ * Gets a state that has an epsilon transition to `q`.
+ */
+State epsilonPred(State q) { q = epsilonSucc(result) }
+
+/**
+ * Holds if there is a state `q` that can be reached from `q1`
+ * along epsilon edges, such that there is a transition from
+ * `q` to `q2` that consumes symbol `s`.
+ */
+predicate deltaClosed(State q1, InputSymbol s, State q2) { delta(epsilonSucc*(q1), s, q2) }
+
+/**
+ * Gets the root containing the given term, that is, the root of the literal,
+ * or a branch of the root disjunction.
+ */
+RegExpRoot getRoot(RegExpTerm term) {
+ result = term or
+ result = getRoot(term.getParent())
+}
+
+/**
+ * A state in the NFA.
+ */
+newtype TState =
+ /**
+ * A state representing that the NFA is about to match a term.
+ * `i` is used to index into multi-char literals.
+ */
+ Match(RelevantRegExpTerm t, int i) {
+ i = 0
+ or
+ exists(t.(RegexpCharacterConstant).getValue().charAt(i))
+ } or
+ /**
+ * An accept state, where exactly the given input string is accepted.
+ */
+ Accept(RegExpRoot l) { l.isRelevant() } or
+ /**
+ * An accept state, where the given input string, or any string that has this
+ * string as a prefix, is accepted.
+ */
+ AcceptAnySuffix(RegExpRoot l) { l.isRelevant() }
+
+/**
+ * Gets a state that is about to match the regular expression `t`.
+ */
+State mkMatch(RegExpTerm t) { result = Match(t, 0) }
+
+/**
+ * A state in the NFA corresponding to a regular expression.
+ *
+ * Each regular expression literal `l` has one accepting state
+ * `Accept(l)`, one state that accepts all suffixes `AcceptAnySuffix(l)`,
+ * and a state `Match(t, i)` for every subterm `t`,
+ * which represents the state of the NFA before starting to
+ * match `t`, or the `i`th character in `t` if `t` is a constant.
+ */
+class State extends TState {
+ RegExpTerm repr;
+
+ State() {
+ this = Match(repr, _) or
+ this = Accept(repr) or
+ this = AcceptAnySuffix(repr)
+ }
+
+ /**
+ * Gets a string representation for this state in a regular expression.
+ */
+ string toString() {
+ exists(int i | this = Match(repr, i) | result = "Match(" + repr + "," + i + ")")
+ or
+ this instanceof Accept and
+ result = "Accept(" + repr + ")"
+ or
+ this instanceof AcceptAnySuffix and
+ result = "AcceptAny(" + repr + ")"
+ }
+
+ /**
+ * Gets the location for this state.
+ */
+ Location getLocation() { result = repr.getLocation() }
+
+ /**
+ * Gets the term represented by this state.
+ */
+ RegExpTerm getRepr() { result = repr }
+}
+
+/**
+ * Gets the minimum char that is matched by both the character classes `c` and `d`.
+ */
+private string getMinOverlapBetweenCharacterClasses(CharacterClass c, CharacterClass d) {
+ result = min(getAOverlapBetweenCharacterClasses(c, d))
+}
+
+/**
+ * Gets a char that is matched by both the character classes `c` and `d`.
+ * And `c` and `d` is not the same character class.
+ */
+private string getAOverlapBetweenCharacterClasses(CharacterClass c, CharacterClass d) {
+ sharesRoot(c, d) and
+ result = [c.getARelevantChar(), d.getARelevantChar()] and
+ c.matches(result) and
+ d.matches(result) and
+ not c = d
+}
+
+/**
+ * Gets a character that is represented by both `c` and `d`.
+ */
+string intersect(InputSymbol c, InputSymbol d) {
+ (sharesRoot(c, d) or [c, d] = Any()) and
+ (
+ c = Char(result) and
+ d = getAnInputSymbolMatching(result)
+ or
+ result = getMinOverlapBetweenCharacterClasses(c, d)
+ or
+ result = c.(CharacterClass).choose() and
+ (
+ d = c
+ or
+ d = Dot() and
+ not (result = "\n" or result = "\r")
+ or
+ d = Any()
+ )
+ or
+ (c = Dot() or c = Any()) and
+ (d = Dot() or d = Any()) and
+ result = "a"
+ )
+ or
+ result = intersect(d, c)
+}
+
+/**
+ * Gets a symbol that matches `char`.
+ */
+bindingset[char]
+InputSymbol getAnInputSymbolMatching(string char) {
+ result = Char(char)
+ or
+ result.(CharacterClass).matches(char)
+ or
+ result = Dot() and
+ not (char = "\n" or char = "\r")
+ or
+ result = Any()
+}
+
+/**
+ * Holds if `state` is a start state.
+ */
+predicate isStartState(State state) {
+ state = mkMatch(any(RegExpRoot r))
+ or
+ exists(RegExpCaret car | state = after(car))
+}
+
+/**
+ * Predicates for constructing a prefix string that leads to a given state.
+ */
+private module PrefixConstruction {
+ /**
+ * Holds if `state` is the textually last start state for the regular expression.
+ */
+ private predicate lastStartState(State state) {
+ exists(RegExpRoot root |
+ state =
+ max(StateInPumpableRegexp s, Location l |
+ isStartState(s) and getRoot(s.getRepr()) = root and l = s.getRepr().getLocation()
+ |
+ s
+ order by
+ l.getStartLine(), l.getStartColumn(), s.getRepr().toString(), l.getEndColumn(),
+ l.getEndLine()
+ )
+ )
+ }
+
+ /**
+ * Holds if there exists any transition (Epsilon() or other) from `a` to `b`.
+ */
+ private predicate existsTransition(State a, State b) { delta(a, _, b) }
+
+ /**
+ * Gets the minimum number of transitions it takes to reach `state` from the `start` state.
+ */
+ int prefixLength(State start, State state) =
+ shortestDistances(lastStartState/1, existsTransition/2)(start, state, result)
+
+ /**
+ * Gets the minimum number of transitions it takes to reach `state` from the start state.
+ */
+ private int lengthFromStart(State state) { result = prefixLength(_, state) }
+
+ /**
+ * Gets a string for which the regular expression will reach `state`.
+ *
+ * Has at most one result for any given `state`.
+ * This predicate will not always have a result even if there is a ReDoS issue in
+ * the regular expression.
+ */
+ string prefix(State state) {
+ lastStartState(state) and
+ result = ""
+ or
+ // the search stops past the last redos candidate state.
+ lengthFromStart(state) <= max(lengthFromStart(any(State s | isReDoSCandidate(s, _)))) and
+ exists(State prev |
+ // select a unique predecessor (by an arbitrary measure)
+ prev =
+ min(State s, Location loc |
+ lengthFromStart(s) = lengthFromStart(state) - 1 and
+ loc = s.getRepr().getLocation() and
+ delta(s, _, state)
+ |
+ s
+ order by
+ loc.getStartLine(), loc.getStartColumn(), loc.getEndLine(), loc.getEndColumn(),
+ s.getRepr().toString()
+ )
+ |
+ // greedy search for the shortest prefix
+ result = prefix(prev) and delta(prev, Epsilon(), state)
+ or
+ not delta(prev, Epsilon(), state) and
+ result = prefix(prev) + getCanonicalEdgeChar(prev, state)
+ )
+ }
+
+ /**
+ * Gets a canonical char for which there exists a transition from `prev` to `next` in the NFA.
+ */
+ private string getCanonicalEdgeChar(State prev, State next) {
+ result =
+ min(string c | delta(prev, any(InputSymbol symbol | c = intersect(Any(), symbol)), next))
+ }
+
+ /**
+ * A state within a regular expression that has a pumpable state.
+ */
+ class StateInPumpableRegexp extends State {
+ pragma[noinline]
+ StateInPumpableRegexp() {
+ exists(State s | isReDoSCandidate(s, _) | getRoot(s.getRepr()) = getRoot(this.getRepr()))
+ }
+ }
+}
+
+/**
+ * Predicates for testing the presence of a rejecting suffix.
+ *
+ * These predicates are used to ensure that the all states reached from the fork
+ * by repeating `w` have a rejecting suffix.
+ *
+ * For example, a regexp like `/^(a+)+/` will accept any string as long the prefix is
+ * some number of `"a"`s, and it is therefore not possible to construct a rejecting suffix.
+ *
+ * A regexp like `/(a+)+$/` or `/(a+)+b/` trivially has a rejecting suffix,
+ * as the suffix "X" will cause both the regular expressions to be rejected.
+ *
+ * The string `w` is repeated any number of times because it needs to be
+ * infinitely repeatedable for the attack to work.
+ * For the regular expression `/((ab)+)*abab/` the accepting state is not reachable from the fork
+ * using epsilon transitions. But any attempt at repeating `w` will end in a state that accepts all suffixes.
+ */
+private module SuffixConstruction {
+ import PrefixConstruction
+
+ /**
+ * Holds if all states reachable from `fork` by repeating `w`
+ * are likely rejectable by appending some suffix.
+ */
+ predicate reachesOnlyRejectableSuffixes(State fork, string w) {
+ isReDoSCandidate(fork, w) and
+ forex(State next | next = process(fork, w, w.length() - 1) | isLikelyRejectable(next))
+ }
+
+ /**
+ * Holds if there likely exists a suffix starting from `s` that leads to the regular expression being rejected.
+ * This predicate might find impossible suffixes when searching for suffixes of length > 1, which can cause FPs.
+ */
+ pragma[noinline]
+ private predicate isLikelyRejectable(StateInPumpableRegexp s) {
+ // exists a reject edge with some char.
+ hasRejectEdge(s)
+ or
+ hasEdgeToLikelyRejectable(s)
+ or
+ // stopping here is rejection
+ isRejectState(s)
+ }
+
+ /**
+ * Holds if `s` is not an accept state, and there is no epsilon transition to an accept state.
+ */
+ predicate isRejectState(StateInPumpableRegexp s) { not epsilonSucc*(s) = Accept(_) }
+
+ /**
+ * Holds if there is likely a non-empty suffix leading to rejection starting in `s`.
+ */
+ pragma[noopt]
+ predicate hasEdgeToLikelyRejectable(StateInPumpableRegexp s) {
+ // all edges (at least one) with some char leads to another state that is rejectable.
+ // the `next` states might not share a common suffix, which can cause FPs.
+ exists(string char | char = hasEdgeToLikelyRejectableHelper(s) |
+ // noopt to force `hasEdgeToLikelyRejectableHelper` to be first in the join-order.
+ exists(State next | deltaClosedChar(s, char, next) | isLikelyRejectable(next)) and
+ forall(State next | deltaClosedChar(s, char, next) | isLikelyRejectable(next))
+ )
+ }
+
+ /**
+ * Gets a char for there exists a transition away from `s`,
+ * and `s` has not been found to be rejectable by `hasRejectEdge` or `isRejectState`.
+ */
+ pragma[noinline]
+ private string hasEdgeToLikelyRejectableHelper(StateInPumpableRegexp s) {
+ not hasRejectEdge(s) and
+ not isRejectState(s) and
+ deltaClosedChar(s, result, _)
+ }
+
+ /**
+ * Holds if there is a state `next` that can be reached from `prev`
+ * along epsilon edges, such that there is a transition from
+ * `prev` to `next` that the character symbol `char`.
+ */
+ predicate deltaClosedChar(StateInPumpableRegexp prev, string char, StateInPumpableRegexp next) {
+ deltaClosed(prev, getAnInputSymbolMatchingRelevant(char), next)
+ }
+
+ pragma[noinline]
+ InputSymbol getAnInputSymbolMatchingRelevant(string char) {
+ char = relevant(_) and
+ result = getAnInputSymbolMatching(char)
+ }
+
+ /**
+ * Gets a char used for finding possible suffixes inside `root`.
+ */
+ pragma[noinline]
+ private string relevant(RegExpRoot root) {
+ exists(ascii(result))
+ or
+ exists(InputSymbol s | belongsTo(s, root) | result = intersect(s, _))
+ or
+ // The characters from `hasSimpleRejectEdge`. Only `\n` is really needed (as `\n` is not in the `ascii` relation).
+ // The three chars must be kept in sync with `hasSimpleRejectEdge`.
+ result = ["|", "\n", "Z"]
+ }
+
+ /**
+ * Holds if there exists a `char` such that there is no edge from `s` labeled `char` in our NFA.
+ * The NFA does not model reject states, so the above is the same as saying there is a reject edge.
+ */
+ private predicate hasRejectEdge(State s) {
+ hasSimpleRejectEdge(s)
+ or
+ not hasSimpleRejectEdge(s) and
+ exists(string char | char = relevant(getRoot(s.getRepr())) | not deltaClosedChar(s, char, _))
+ }
+
+ /**
+ * Holds if there is no edge from `s` labeled with "|", "\n", or "Z" in our NFA.
+ * This predicate is used as a cheap pre-processing to speed up `hasRejectEdge`.
+ */
+ private predicate hasSimpleRejectEdge(State s) {
+ // The three chars were chosen arbitrarily. The three chars must be kept in sync with `relevant`.
+ exists(string char | char = ["|", "\n", "Z"] | not deltaClosedChar(s, char, _))
+ }
+
+ /**
+ * Gets a state that can be reached from pumpable `fork` consuming all
+ * chars in `w` any number of times followed by the first `i+1` characters of `w`.
+ */
+ pragma[noopt]
+ private State process(State fork, string w, int i) {
+ exists(State prev | prev = getProcessPrevious(fork, i, w) |
+ exists(string char, InputSymbol sym |
+ char = w.charAt(i) and
+ deltaClosed(prev, sym, result) and
+ // noopt to prevent joining `prev` with all possible `chars` that could transition away from `prev`.
+ // Instead only join with the set of `chars` where a relevant `InputSymbol` has already been found.
+ sym = getAProcessInputSymbol(char)
+ )
+ )
+ }
+
+ /**
+ * Gets a state that can be reached from pumpable `fork` consuming all
+ * chars in `w` any number of times followed by the first `i` characters of `w`.
+ */
+ private State getProcessPrevious(State fork, int i, string w) {
+ isReDoSCandidate(fork, w) and
+ (
+ i = 0 and result = fork
+ or
+ result = process(fork, w, i - 1)
+ or
+ // repeat until fixpoint
+ i = 0 and
+ result = process(fork, w, w.length() - 1)
+ )
+ }
+
+ /**
+ * Gets an InputSymbol that matches `char`.
+ * The predicate is specialized to only have a result for the `char`s that are relevant for the `process` predicate.
+ */
+ private InputSymbol getAProcessInputSymbol(string char) {
+ char = getAProcessChar() and
+ result = getAnInputSymbolMatching(char)
+ }
+
+ /**
+ * Gets a `char` that occurs in a `pump` string.
+ */
+ private string getAProcessChar() { result = any(string s | isReDoSCandidate(_, s)).charAt(_) }
+}
+
+/**
+ * Gets the result of backslash-escaping newlines, carriage-returns and
+ * backslashes in `s`.
+ */
+bindingset[s]
+private string escape(string s) {
+ result =
+ s.replaceAll("\\", "\\\\")
+ .replaceAll("\n", "\\n")
+ .replaceAll("\r", "\\r")
+ .replaceAll("\t", "\\t")
+}
+
+/**
+ * Gets `str` with the last `i` characters moved to the front.
+ *
+ * We use this to adjust the pump string to match with the beginning of
+ * a RegExpTerm, so it doesn't start in the middle of a constant.
+ */
+bindingset[str, i]
+private string rotate(string str, int i) {
+ result = str.suffix(str.length() - i) + str.prefix(str.length() - i)
+}
+
+/**
+ * Holds if `term` may cause superlinear backtracking on strings containing many repetitions of `pump`.
+ * Gets the shortest string that causes superlinear backtracking.
+ */
+private predicate isReDoSAttackable(RegExpTerm term, string pump, State s) {
+ exists(int i, string c | s = Match(term, i) |
+ c =
+ min(string w |
+ any(ReDoSConfiguration conf).isReDoSCandidate(s, w) and
+ SuffixConstruction::reachesOnlyRejectableSuffixes(s, w)
+ |
+ w order by w.length(), w
+ ) and
+ pump = escape(rotate(c, i))
+ )
+}
+
+/**
+ * Holds if the state `s` (represented by the term `t`) can have backtracking with repetitions of `pump`.
+ *
+ * `prefixMsg` contains a friendly message for a prefix that reaches `s` (or `prefixMsg` is the empty string if the prefix is empty or if no prefix could be found).
+ */
+predicate hasReDoSResult(RegExpTerm t, string pump, State s, string prefixMsg) {
+ isReDoSAttackable(t, pump, s) and
+ (
+ prefixMsg = "starting with '" + escape(PrefixConstruction::prefix(s)) + "' and " and
+ not PrefixConstruction::prefix(s) = ""
+ or
+ PrefixConstruction::prefix(s) = "" and prefixMsg = ""
+ or
+ not exists(PrefixConstruction::prefix(s)) and prefixMsg = ""
+ )
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/performance/RegExpTreeView.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/performance/RegExpTreeView.qll
new file mode 100644
index 00000000000..b9312816136
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/security/performance/RegExpTreeView.qll
@@ -0,0 +1,771 @@
+private import codeql.ruby.ast.Literal as AST
+private import codeql.Locations
+private import ParseRegExp
+
+/**
+ * Holds if the regular expression should not be considered.
+ */
+predicate isExcluded(RegExpParent parent) {
+ parent.(RegExpTerm).getRegExp().hasFreeSpacingFlag() // exclude free-spacing mode regexes
+}
+
+/**
+ * A module containing predicates for determining which flags a regular expression have.
+ */
+module RegExpFlags {
+ /**
+ * Holds if `root` has the `i` flag for case-insensitive matching.
+ */
+ predicate isIgnoreCase(RegExpTerm root) {
+ root.isRootTerm() and
+ root.getLiteral().isIgnoreCase()
+ }
+
+ /**
+ * Gets the flags for `root`, or the empty string if `root` has no flags.
+ */
+ string getFlags(RegExpTerm root) {
+ root.isRootTerm() and
+ result = root.getLiteral().getFlags()
+ }
+
+ /**
+ * Holds if `root` has the `s` flag for multi-line matching.
+ */
+ predicate isDotAll(RegExpTerm root) {
+ root.isRootTerm() and
+ root.getLiteral().isDotAll()
+ }
+}
+
+/**
+ * An element containing a regular expression term, that is, either
+ * a string literal (parsed as a regular expression)
+ * or another regular expression term.
+ */
+class RegExpParent extends TRegExpParent {
+ string toString() { result = "RegExpParent" }
+
+ RegExpTerm getChild(int i) { none() }
+
+ RegExpTerm getAChild() { result = this.getChild(_) }
+
+ int getNumChild() { result = count(this.getAChild()) }
+
+ /**
+ * Gets the name of a primary CodeQL class to which this regular
+ * expression term belongs.
+ */
+ string getAPrimaryQlClass() { result = "RegExpParent" }
+
+ /**
+ * Gets a comma-separated list of the names of the primary CodeQL classes to
+ * which this regular expression term belongs.
+ */
+ final string getPrimaryQlClasses() { result = concat(this.getAPrimaryQlClass(), ",") }
+}
+
+class RegExpLiteral extends TRegExpLiteral, RegExpParent {
+ RegExp re;
+
+ RegExpLiteral() { this = TRegExpLiteral(re) }
+
+ override RegExpTerm getChild(int i) { i = 0 and result.getRegExp() = re and result.isRootTerm() }
+
+ predicate isDotAll() { re.hasMultilineFlag() }
+
+ predicate isIgnoreCase() { re.hasCaseInsensitiveFlag() }
+
+ string getFlags() { result = re.getFlagString() }
+
+ override string getAPrimaryQlClass() { result = "RegExpLiteral" }
+}
+
+class RegExpTerm extends RegExpParent {
+ RegExp re;
+ int start;
+ int end;
+
+ RegExpTerm() {
+ this = TRegExpAlt(re, start, end)
+ or
+ this = TRegExpBackRef(re, start, end)
+ or
+ this = TRegExpCharacterClass(re, start, end)
+ or
+ this = TRegExpCharacterRange(re, start, end)
+ or
+ this = TRegExpNormalChar(re, start, end)
+ or
+ this = TRegExpGroup(re, start, end)
+ or
+ this = TRegExpQuantifier(re, start, end)
+ or
+ this = TRegExpSequence(re, start, end) and
+ exists(seqChild(re, start, end, 1)) // if a sequence does not have more than one element, it should be treated as that element instead.
+ or
+ this = TRegExpSpecialChar(re, start, end)
+ or
+ this = TRegExpNamedCharacterProperty(re, start, end)
+ }
+
+ RegExpTerm getRootTerm() {
+ this.isRootTerm() and result = this
+ or
+ result = this.getParent().(RegExpTerm).getRootTerm()
+ }
+
+ predicate isUsedAsRegExp() { any() }
+
+ predicate isRootTerm() { start = 0 and end = re.getText().length() }
+
+ override RegExpTerm getChild(int i) {
+ result = this.(RegExpAlt).getChild(i)
+ or
+ result = this.(RegExpBackRef).getChild(i)
+ or
+ result = this.(RegExpCharacterClass).getChild(i)
+ or
+ result = this.(RegExpCharacterRange).getChild(i)
+ or
+ result = this.(RegExpNormalChar).getChild(i)
+ or
+ result = this.(RegExpGroup).getChild(i)
+ or
+ result = this.(RegExpQuantifier).getChild(i)
+ or
+ result = this.(RegExpSequence).getChild(i)
+ or
+ result = this.(RegExpSpecialChar).getChild(i)
+ or
+ result = this.(RegExpNamedCharacterProperty).getChild(i)
+ }
+
+ RegExpParent getParent() { result.getAChild() = this }
+
+ RegExp getRegExp() { result = re }
+
+ int getStart() { result = start }
+
+ int getEnd() { result = end }
+
+ override string toString() { result = re.getText().substring(start, end) }
+
+ override string getAPrimaryQlClass() { result = "RegExpTerm" }
+
+ Location getLocation() { result = re.getLocation() }
+
+ predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
+ exists(int re_start, int re_end |
+ re.getComponent(0).getLocation().hasLocationInfo(filepath, startline, re_start, _, _) and
+ re.getComponent(re.getNumberOfComponents() - 1)
+ .getLocation()
+ .hasLocationInfo(filepath, _, _, endline, re_end)
+ |
+ startcolumn = re_start + start and
+ endcolumn = re_start + end - 1
+ )
+ }
+
+ File getFile() { result = this.getLocation().getFile() }
+
+ string getRawValue() { result = this.toString() }
+
+ RegExpLiteral getLiteral() { result = TRegExpLiteral(re) }
+
+ /** Gets the regular expression term that is matched (textually) before this one, if any. */
+ RegExpTerm getPredecessor() {
+ exists(RegExpTerm parent | parent = this.getParent() |
+ result = parent.(RegExpSequence).previousElement(this)
+ or
+ not exists(parent.(RegExpSequence).previousElement(this)) and
+ not parent instanceof RegExpSubPattern and
+ result = parent.getPredecessor()
+ )
+ }
+
+ /** Gets the regular expression term that is matched (textually) after this one, if any. */
+ RegExpTerm getSuccessor() {
+ exists(RegExpTerm parent | parent = this.getParent() |
+ result = parent.(RegExpSequence).nextElement(this)
+ or
+ not exists(parent.(RegExpSequence).nextElement(this)) and
+ not parent instanceof RegExpSubPattern and
+ result = parent.getSuccessor()
+ )
+ }
+}
+
+newtype TRegExpParent =
+ TRegExpLiteral(RegExp re) or
+ TRegExpQuantifier(RegExp re, int start, int end) { re.qualifiedItem(start, end, _, _) } or
+ TRegExpSequence(RegExp re, int start, int end) { re.sequence(start, end) } or
+ TRegExpAlt(RegExp re, int start, int end) { re.alternation(start, end) } or
+ TRegExpCharacterClass(RegExp re, int start, int end) { re.charSet(start, end) } or
+ TRegExpCharacterRange(RegExp re, int start, int end) { re.charRange(_, start, _, _, end) } or
+ TRegExpGroup(RegExp re, int start, int end) { re.group(start, end) } or
+ TRegExpSpecialChar(RegExp re, int start, int end) { re.specialCharacter(start, end, _) } or
+ TRegExpNormalChar(RegExp re, int start, int end) { re.normalCharacter(start, end) } or
+ TRegExpBackRef(RegExp re, int start, int end) { re.backreference(start, end) } or
+ TRegExpNamedCharacterProperty(RegExp re, int start, int end) {
+ re.namedCharacterProperty(start, end, _)
+ }
+
+class RegExpQuantifier extends RegExpTerm, TRegExpQuantifier {
+ int part_end;
+ boolean maybe_empty;
+ boolean may_repeat_forever;
+
+ RegExpQuantifier() {
+ this = TRegExpQuantifier(re, start, end) and
+ re.qualifiedPart(start, part_end, end, maybe_empty, may_repeat_forever)
+ }
+
+ override RegExpTerm getChild(int i) {
+ i = 0 and
+ result.getRegExp() = re and
+ result.getStart() = start and
+ result.getEnd() = part_end
+ }
+
+ predicate mayRepeatForever() { may_repeat_forever = true }
+
+ string getQualifier() { result = re.getText().substring(part_end, end) }
+
+ override string getAPrimaryQlClass() { result = "RegExpQuantifier" }
+}
+
+class InfiniteRepetitionQuantifier extends RegExpQuantifier {
+ InfiniteRepetitionQuantifier() { this.mayRepeatForever() }
+
+ override string getAPrimaryQlClass() { result = "InfiniteRepetitionQuantifier" }
+}
+
+class RegExpStar extends InfiniteRepetitionQuantifier {
+ RegExpStar() { this.getQualifier().charAt(0) = "*" }
+
+ override string getAPrimaryQlClass() { result = "RegExpStar" }
+}
+
+class RegExpPlus extends InfiniteRepetitionQuantifier {
+ RegExpPlus() { this.getQualifier().charAt(0) = "+" }
+
+ override string getAPrimaryQlClass() { result = "RegExpPlus" }
+}
+
+class RegExpOpt extends RegExpQuantifier {
+ RegExpOpt() { this.getQualifier().charAt(0) = "?" }
+
+ override string getAPrimaryQlClass() { result = "RegExpOpt" }
+}
+
+class RegExpRange extends RegExpQuantifier {
+ string upper;
+ string lower;
+
+ RegExpRange() { re.multiples(part_end, end, lower, upper) }
+
+ string getUpper() { result = upper }
+
+ string getLower() { result = lower }
+
+ /**
+ * Gets the upper bound of the range, if any.
+ *
+ * If there is no upper bound, any number of repetitions is allowed.
+ * For a term of the form `r{lo}`, both the lower and the upper bound
+ * are `lo`.
+ */
+ int getUpperBound() { result = this.getUpper().toInt() }
+
+ /** Gets the lower bound of the range. */
+ int getLowerBound() { result = this.getLower().toInt() }
+
+ override string getAPrimaryQlClass() { result = "RegExpRange" }
+}
+
+class RegExpSequence extends RegExpTerm, TRegExpSequence {
+ RegExpSequence() {
+ this = TRegExpSequence(re, start, end) and
+ exists(seqChild(re, start, end, 1)) // if a sequence does not have more than one element, it should be treated as that element instead.
+ }
+
+ override RegExpTerm getChild(int i) { result = seqChild(re, start, end, i) }
+
+ /** Gets the element preceding `element` in this sequence. */
+ RegExpTerm previousElement(RegExpTerm element) { element = this.nextElement(result) }
+
+ /** Gets the element following `element` in this sequence. */
+ RegExpTerm nextElement(RegExpTerm element) {
+ exists(int i |
+ element = this.getChild(i) and
+ result = this.getChild(i + 1)
+ )
+ }
+
+ override string getAPrimaryQlClass() { result = "RegExpSequence" }
+}
+
+pragma[nomagic]
+private int seqChildEnd(RegExp re, int start, int end, int i) {
+ result = seqChild(re, start, end, i).getEnd()
+}
+
+// moved out so we can use it in the charpred
+private RegExpTerm seqChild(RegExp re, int start, int end, int i) {
+ re.sequence(start, end) and
+ (
+ i = 0 and
+ result.getRegExp() = re and
+ result.getStart() = start and
+ exists(int itemEnd |
+ re.item(start, itemEnd) and
+ result.getEnd() = itemEnd
+ )
+ or
+ i > 0 and
+ result.getRegExp() = re and
+ exists(int itemStart | itemStart = seqChildEnd(re, start, end, i - 1) |
+ result.getStart() = itemStart and
+ re.item(itemStart, result.getEnd())
+ )
+ )
+}
+
+class RegExpAlt extends RegExpTerm, TRegExpAlt {
+ RegExpAlt() { this = TRegExpAlt(re, start, end) }
+
+ override RegExpTerm getChild(int i) {
+ i = 0 and
+ result.getRegExp() = re and
+ result.getStart() = start and
+ exists(int part_end |
+ re.alternationOption(start, end, start, part_end) and
+ result.getEnd() = part_end
+ )
+ or
+ i > 0 and
+ result.getRegExp() = re and
+ exists(int part_start |
+ part_start = this.getChild(i - 1).getEnd() + 1 // allow for the |
+ |
+ result.getStart() = part_start and
+ re.alternationOption(start, end, part_start, result.getEnd())
+ )
+ }
+
+ override string getAPrimaryQlClass() { result = "RegExpAlt" }
+}
+
+class RegExpEscape extends RegExpNormalChar {
+ RegExpEscape() { re.escapedCharacter(start, end) }
+
+ /**
+ * Gets the name of the escaped; for example, `w` for `\w`.
+ * TODO: Handle named escapes.
+ */
+ override string getValue() {
+ this.isIdentityEscape() and result = this.getUnescaped()
+ or
+ this.getUnescaped() = "n" and result = "\n"
+ or
+ this.getUnescaped() = "r" and result = "\r"
+ or
+ this.getUnescaped() = "t" and result = "\t"
+ or
+ this.isUnicode() and
+ result = this.getUnicode()
+ }
+
+ predicate isIdentityEscape() { not this.getUnescaped() in ["n", "r", "t"] }
+
+ /**
+ * Gets the text for this escape. That is e.g. "\w".
+ */
+ private string getText() { result = re.getText().substring(start, end) }
+
+ /**
+ * Holds if this is a unicode escape.
+ */
+ private predicate isUnicode() { this.getText().prefix(2) = ["\\u", "\\U"] }
+
+ /**
+ * Gets the unicode char for this escape.
+ * E.g. for `\u0061` this returns "a".
+ */
+ private string getUnicode() {
+ exists(int codepoint | codepoint = sum(this.getHexValueFromUnicode(_)) |
+ result = codepoint.toUnicode()
+ )
+ }
+
+ /**
+ * Gets int value for the `index`th char in the hex number of the unicode escape.
+ * E.g. for `\u0061` and `index = 2` this returns 96 (the number `6` interpreted as hex).
+ */
+ private int getHexValueFromUnicode(int index) {
+ this.isUnicode() and
+ exists(string hex, string char | hex = this.getText().suffix(2) |
+ char = hex.charAt(index) and
+ result = 16.pow(hex.length() - index - 1) * toHex(char)
+ )
+ }
+
+ string getUnescaped() { result = this.getText().suffix(1) }
+
+ override string getAPrimaryQlClass() { result = "RegExpEscape" }
+}
+
+/**
+ * Gets the hex number for the `hex` char.
+ */
+private int toHex(string hex) {
+ hex = [0 .. 9].toString() and
+ result = hex.toInt()
+ or
+ result = 10 and hex = ["a", "A"]
+ or
+ result = 11 and hex = ["b", "B"]
+ or
+ result = 12 and hex = ["c", "C"]
+ or
+ result = 13 and hex = ["d", "D"]
+ or
+ result = 14 and hex = ["e", "E"]
+ or
+ result = 15 and hex = ["f", "F"]
+}
+
+/**
+ * A word boundary, that is, a regular expression term of the form `\b`.
+ */
+class RegExpWordBoundary extends RegExpEscape {
+ RegExpWordBoundary() { this.getUnescaped() = "b" }
+}
+
+/**
+ * A character class escape in a regular expression.
+ * That is, an escaped character that denotes multiple characters.
+ *
+ * Examples:
+ *
+ * ```
+ * \w
+ * \S
+ * ```
+ */
+class RegExpCharacterClassEscape extends RegExpEscape {
+ RegExpCharacterClassEscape() { this.getValue() in ["d", "D", "s", "S", "w", "W", "h", "H"] }
+
+ /** Gets the name of the character class; for example, `w` for `\w`. */
+ // override string getValue() { result = value }
+ override RegExpTerm getChild(int i) { none() }
+
+ override string getAPrimaryQlClass() { result = "RegExpCharacterClassEscape" }
+}
+
+/**
+ * A character class.
+ *
+ * Examples:
+ *
+ * ```rb
+ * /[a-fA-F0-9]/
+ * /[^abc]/
+ * ```
+ */
+class RegExpCharacterClass extends RegExpTerm, TRegExpCharacterClass {
+ RegExpCharacterClass() { this = TRegExpCharacterClass(re, start, end) }
+
+ predicate isInverted() { re.getChar(start + 1) = "^" }
+
+ predicate isUniversalClass() {
+ // [^]
+ this.isInverted() and not exists(this.getAChild())
+ or
+ // [\w\W] and similar
+ not this.isInverted() and
+ exists(string cce1, string cce2 |
+ cce1 = this.getAChild().(RegExpCharacterClassEscape).getValue() and
+ cce2 = this.getAChild().(RegExpCharacterClassEscape).getValue()
+ |
+ cce1 != cce2 and cce1.toLowerCase() = cce2.toLowerCase()
+ )
+ }
+
+ override RegExpTerm getChild(int i) {
+ i = 0 and
+ result.getRegExp() = re and
+ exists(int itemStart, int itemEnd |
+ result.getStart() = itemStart and
+ re.charSetStart(start, itemStart) and
+ re.charSetChild(start, itemStart, itemEnd) and
+ result.getEnd() = itemEnd
+ )
+ or
+ i > 0 and
+ result.getRegExp() = re and
+ exists(int itemStart | itemStart = this.getChild(i - 1).getEnd() |
+ result.getStart() = itemStart and
+ re.charSetChild(start, itemStart, result.getEnd())
+ )
+ }
+
+ override string getAPrimaryQlClass() { result = "RegExpCharacterClass" }
+}
+
+class RegExpCharacterRange extends RegExpTerm, TRegExpCharacterRange {
+ int lower_end;
+ int upper_start;
+
+ RegExpCharacterRange() {
+ this = TRegExpCharacterRange(re, start, end) and
+ re.charRange(_, start, lower_end, upper_start, end)
+ }
+
+ predicate isRange(string lo, string hi) {
+ lo = re.getText().substring(start, lower_end) and
+ hi = re.getText().substring(upper_start, end)
+ }
+
+ override RegExpTerm getChild(int i) {
+ i = 0 and
+ result.getRegExp() = re and
+ result.getStart() = start and
+ result.getEnd() = lower_end
+ or
+ i = 1 and
+ result.getRegExp() = re and
+ result.getStart() = upper_start and
+ result.getEnd() = end
+ }
+
+ override string getAPrimaryQlClass() { result = "RegExpCharacterRange" }
+}
+
+class RegExpNormalChar extends RegExpTerm, TRegExpNormalChar {
+ RegExpNormalChar() { this = TRegExpNormalChar(re, start, end) }
+
+ predicate isCharacter() { any() }
+
+ string getValue() { result = re.getText().substring(start, end) }
+
+ override RegExpTerm getChild(int i) { none() }
+
+ override string getAPrimaryQlClass() { result = "RegExpNormalChar" }
+}
+
+class RegExpConstant extends RegExpTerm {
+ string value;
+
+ RegExpConstant() {
+ this = TRegExpNormalChar(re, start, end) and
+ not this instanceof RegExpCharacterClassEscape and
+ // exclude chars in qualifiers
+ // TODO: push this into regex library
+ not exists(int qstart, int qend | re.qualifiedPart(_, qstart, qend, _, _) |
+ qstart <= start and end <= qend
+ ) and
+ value = this.(RegExpNormalChar).getValue()
+ or
+ this = TRegExpSpecialChar(re, start, end) and
+ re.inCharSet(start) and
+ value = this.(RegExpSpecialChar).getChar()
+ }
+
+ predicate isCharacter() { any() }
+
+ string getValue() { result = value }
+
+ override RegExpTerm getChild(int i) { none() }
+
+ override string getAPrimaryQlClass() { result = "RegExpConstant" }
+}
+
+class RegExpGroup extends RegExpTerm, TRegExpGroup {
+ RegExpGroup() { this = TRegExpGroup(re, start, end) }
+
+ /**
+ * Gets the index of this capture group within the enclosing regular
+ * expression literal.
+ *
+ * For example, in the regular expression `/((a?).)(?:b)/`, the
+ * group `((a?).)` has index 1, the group `(a?)` nested inside it
+ * has index 2, and the group `(?:b)` has no index, since it is
+ * not a capture group.
+ */
+ int getNumber() { result = re.getGroupNumber(start, end) }
+
+ /** Holds if this is a named capture group. */
+ predicate isNamed() { exists(this.getName()) }
+
+ /** Gets the name of this capture group, if any. */
+ string getName() { result = re.getGroupName(start, end) }
+
+ predicate isCharacter() { any() }
+
+ string getValue() { result = re.getText().substring(start, end) }
+
+ override RegExpTerm getChild(int i) {
+ result.getRegExp() = re and
+ i = 0 and
+ re.groupContents(start, end, result.getStart(), result.getEnd())
+ }
+
+ override string getAPrimaryQlClass() { result = "RegExpGroup" }
+}
+
+class RegExpSpecialChar extends RegExpTerm, TRegExpSpecialChar {
+ string char;
+
+ RegExpSpecialChar() {
+ this = TRegExpSpecialChar(re, start, end) and
+ re.specialCharacter(start, end, char)
+ }
+
+ predicate isCharacter() { any() }
+
+ string getChar() { result = char }
+
+ override RegExpTerm getChild(int i) { none() }
+
+ override string getAPrimaryQlClass() { result = "RegExpSpecialChar" }
+}
+
+class RegExpDot extends RegExpSpecialChar {
+ RegExpDot() { this.getChar() = "." }
+
+ override string getAPrimaryQlClass() { result = "RegExpDot" }
+}
+
+class RegExpDollar extends RegExpSpecialChar {
+ RegExpDollar() { this.getChar() = ["$", "\\Z", "\\z"] }
+
+ override string getAPrimaryQlClass() { result = "RegExpDollar" }
+}
+
+class RegExpCaret extends RegExpSpecialChar {
+ RegExpCaret() { this.getChar() = ["^", "\\A"] }
+
+ override string getAPrimaryQlClass() { result = "RegExpCaret" }
+}
+
+class RegExpZeroWidthMatch extends RegExpGroup {
+ RegExpZeroWidthMatch() { re.zeroWidthMatch(start, end) }
+
+ override predicate isCharacter() { any() }
+
+ override RegExpTerm getChild(int i) { none() }
+
+ override string getAPrimaryQlClass() { result = "RegExpZeroWidthMatch" }
+}
+
+/**
+ * A zero-width lookahead or lookbehind assertion.
+ *
+ * Examples:
+ *
+ * ```
+ * (?=\w)
+ * (?!\n)
+ * (?<=\.)
+ * (? (d,e,f)` in the product automaton
+ * iff there exists three transitions in the NFA `a->d, b->e, c->f` where those three
+ * transitions all match a shared character `char`. (see `getAThreewayIntersect`)
+ *
+ * We start a search in the product automaton at `(pivot, pivot, succ)`,
+ * and search for a series of transitions (a `Trace`), such that we end
+ * at `(pivot, succ, succ)` (see `isReachableFromStartTuple`).
+ *
+ * For example, consider the regular expression `/^\d*5\w*$/`.
+ * The search will start at the tuple `(\d*, \d*, \w*)` and search
+ * for a path to `(\d*, \w*, \w*)`.
+ * This path exists, and consists of a single transition in the product automaton,
+ * where the three corresponding NFA edges all match the character `"5"`.
+ *
+ * The start-state in the NFA has an any-transition to itself, this allows us to
+ * flag regular expressions such as `/a*$/` - which does not have a start anchor -
+ * and can thus start matching anywhere.
+ *
+ * The implementation is not perfect.
+ * It has the same suffix detection issue as the `js/redos` query, which can cause false positives.
+ * It also doesn't find all transitions in the product automaton, which can cause false negatives.
+ */
+
+/**
+ * An instantiaion of `ReDoSConfiguration` for superlinear ReDoS.
+ */
+class SuperLinearReDoSConfiguration extends ReDoSConfiguration {
+ SuperLinearReDoSConfiguration() { this = "SuperLinearReDoSConfiguration" }
+
+ override predicate isReDoSCandidate(State state, string pump) { isPumpable(_, state, pump) }
+}
+
+/**
+ * Gets any root (start) state of a regular expression.
+ */
+private State getRootState() { result = mkMatch(any(RegExpRoot r)) }
+
+private newtype TStateTuple =
+ MkStateTuple(State q1, State q2, State q3) {
+ // starts at (pivot, pivot, succ)
+ isStartLoops(q1, q3) and q1 = q2
+ or
+ step(_, _, _, _, q1, q2, q3) and FeasibleTuple::isFeasibleTuple(q1, q2, q3)
+ }
+
+/**
+ * A state in the product automaton.
+ * The product automaton contains 3-tuples of states.
+ *
+ * We lazily only construct those states that we are actually
+ * going to need.
+ * Either a start state `(pivot, pivot, succ)`, or a state
+ * where there exists a transition from an already existing state.
+ *
+ * The exponential variant of this query (`js/redos`) uses an optimization
+ * trick where `q1 <= q2`. This trick cannot be used here as the order
+ * of the elements matter.
+ */
+class StateTuple extends TStateTuple {
+ State q1;
+ State q2;
+ State q3;
+
+ StateTuple() { this = MkStateTuple(q1, q2, q3) }
+
+ /**
+ * Gest a string repesentation of this tuple.
+ */
+ string toString() { result = "(" + q1 + ", " + q2 + ", " + q3 + ")" }
+
+ /**
+ * Holds if this tuple is `(r1, r2, r3)`.
+ */
+ pragma[noinline]
+ predicate isTuple(State r1, State r2, State r3) { r1 = q1 and r2 = q2 and r3 = q3 }
+}
+
+/**
+ * A module for determining feasible tuples for the product automaton.
+ *
+ * The implementation is split into many predicates for performance reasons.
+ */
+private module FeasibleTuple {
+ /**
+ * Holds if the tuple `(r1, r2, r3)` might be on path from a start-state to an end-state in the product automaton.
+ */
+ pragma[inline]
+ predicate isFeasibleTuple(State r1, State r2, State r3) {
+ // The first element is either inside a repetition (or the start state itself)
+ isRepetitionOrStart(r1) and
+ // The last element is inside a repetition
+ stateInsideRepetition(r3) and
+ // The states are reachable in the NFA in the order r1 -> r2 -> r3
+ delta+(r1) = r2 and
+ delta+(r2) = r3 and
+ // The first element can reach a beginning (the "pivot" state in a `(pivot, succ)` pair).
+ canReachABeginning(r1) and
+ // The last element can reach a target (the "succ" state in a `(pivot, succ)` pair).
+ canReachATarget(r3)
+ }
+
+ /**
+ * Holds if `s` is either inside a repetition, or is the start state (which is a repetition).
+ */
+ pragma[noinline]
+ private predicate isRepetitionOrStart(State s) { stateInsideRepetition(s) or s = getRootState() }
+
+ /**
+ * Holds if state `s` might be inside a backtracking repetition.
+ */
+ pragma[noinline]
+ private predicate stateInsideRepetition(State s) {
+ s.getRepr().getParent*() instanceof InfiniteRepetitionQuantifier
+ }
+
+ /**
+ * Holds if there exists a path in the NFA from `s` to a "pivot" state
+ * (from a `(pivot, succ)` pair that starts the search).
+ */
+ pragma[noinline]
+ private predicate canReachABeginning(State s) {
+ delta+(s) = any(State pivot | isStartLoops(pivot, _))
+ }
+
+ /**
+ * Holds if there exists a path in the NFA from `s` to a "succ" state
+ * (from a `(pivot, succ)` pair that starts the search).
+ */
+ pragma[noinline]
+ private predicate canReachATarget(State s) { delta+(s) = any(State succ | isStartLoops(_, succ)) }
+}
+
+/**
+ * Holds if `pivot` and `succ` are a pair of loops that could be the beginning of a quadratic blowup.
+ *
+ * There is a slight implementation difference compared to the paper: this predicate requires that `pivot != succ`.
+ * The case where `pivot = succ` causes exponential backtracking and is handled by the `js/redos` query.
+ */
+predicate isStartLoops(State pivot, State succ) {
+ pivot != succ and
+ succ.getRepr() instanceof InfiniteRepetitionQuantifier and
+ delta+(pivot) = succ and
+ (
+ pivot.getRepr() instanceof InfiniteRepetitionQuantifier
+ or
+ pivot = mkMatch(any(RegExpRoot root))
+ )
+}
+
+/**
+ * Gets a state for which there exists a transition in the NFA from `s'.
+ */
+State delta(State s) { delta(s, _, result) }
+
+/**
+ * Holds if there are transitions from the components of `q` to the corresponding
+ * components of `r` labelled with `s1`, `s2`, and `s3`, respectively.
+ */
+pragma[noinline]
+predicate step(StateTuple q, InputSymbol s1, InputSymbol s2, InputSymbol s3, StateTuple r) {
+ exists(State r1, State r2, State r3 |
+ step(q, s1, s2, s3, r1, r2, r3) and r = MkStateTuple(r1, r2, r3)
+ )
+}
+
+/**
+ * Holds if there are transitions from the components of `q` to `r1`, `r2`, and `r3
+ * labelled with `s1`, `s2`, and `s3`, respectively.
+ */
+pragma[noopt]
+predicate step(
+ StateTuple q, InputSymbol s1, InputSymbol s2, InputSymbol s3, State r1, State r2, State r3
+) {
+ exists(State q1, State q2, State q3 | q.isTuple(q1, q2, q3) |
+ deltaClosed(q1, s1, r1) and
+ deltaClosed(q2, s2, r2) and
+ deltaClosed(q3, s3, r3) and
+ // use noopt to force the join on `getAThreewayIntersect` to happen last.
+ exists(getAThreewayIntersect(s1, s2, s3))
+ )
+}
+
+/**
+ * Gets a char that is matched by all the edges `s1`, `s2`, and `s3`.
+ *
+ * The result is not complete, and might miss some combination of edges that share some character.
+ */
+pragma[noinline]
+string getAThreewayIntersect(InputSymbol s1, InputSymbol s2, InputSymbol s3) {
+ result = minAndMaxIntersect(s1, s2) and result = [intersect(s2, s3), intersect(s1, s3)]
+ or
+ result = minAndMaxIntersect(s1, s3) and result = [intersect(s2, s3), intersect(s1, s2)]
+ or
+ result = minAndMaxIntersect(s2, s3) and result = [intersect(s1, s2), intersect(s1, s3)]
+}
+
+/**
+ * Gets the minimum and maximum characters that intersect between `a` and `b`.
+ * This predicate is used to limit the size of `getAThreewayIntersect`.
+ */
+pragma[noinline]
+string minAndMaxIntersect(InputSymbol a, InputSymbol b) {
+ result = [min(intersect(a, b)), max(intersect(a, b))]
+}
+
+private newtype TTrace =
+ Nil() or
+ Step(InputSymbol s1, InputSymbol s2, InputSymbol s3, TTrace t) {
+ exists(StateTuple p |
+ isReachableFromStartTuple(_, _, p, t, _) and
+ step(p, s1, s2, s3, _)
+ )
+ or
+ exists(State pivot, State succ | isStartLoops(pivot, succ) |
+ t = Nil() and step(MkStateTuple(pivot, pivot, succ), s1, s2, s3, _)
+ )
+ }
+
+/**
+ * A list of tuples of input symbols that describe a path in the product automaton
+ * starting from some start state.
+ */
+class Trace extends TTrace {
+ /**
+ * Gets a string representation of this Trace that can be used for debug purposes.
+ */
+ string toString() {
+ this = Nil() and result = "Nil()"
+ or
+ exists(InputSymbol s1, InputSymbol s2, InputSymbol s3, Trace t | this = Step(s1, s2, s3, t) |
+ result = "Step(" + s1 + ", " + s2 + ", " + s3 + ", " + t + ")"
+ )
+ }
+}
+
+/**
+ * Gets a string corresponding to the trace `t`.
+ */
+string concretise(Trace t) {
+ t = Nil() and result = ""
+ or
+ exists(InputSymbol s1, InputSymbol s2, InputSymbol s3, Trace rest | t = Step(s1, s2, s3, rest) |
+ result = concretise(rest) + getAThreewayIntersect(s1, s2, s3)
+ )
+}
+
+/**
+ * Holds if there exists a transition from `r` to `q` in the product automaton.
+ * Notice that the arguments are flipped, and thus the direction is backwards.
+ */
+pragma[noinline]
+predicate tupleDeltaBackwards(StateTuple q, StateTuple r) { step(r, _, _, _, q) }
+
+/**
+ * Holds if `tuple` is an end state in our search.
+ * That means there exists a pair of loops `(pivot, succ)` such that `tuple = (pivot, succ, succ)`.
+ */
+predicate isEndTuple(StateTuple tuple) { tuple = getAnEndTuple(_, _) }
+
+/**
+ * Gets the minimum length of a path from `r` to some an end state `end`.
+ *
+ * The implementation searches backwards from the end-tuple.
+ * This approach was chosen because it is way more efficient if the first predicate given to `shortestDistances` is small.
+ * The `end` argument must always be an end state.
+ */
+int distBackFromEnd(StateTuple r, StateTuple end) =
+ shortestDistances(isEndTuple/1, tupleDeltaBackwards/2)(end, r, result)
+
+/**
+ * Holds if there exists a pair of repetitions `(pivot, succ)` in the regular expression such that:
+ * `tuple` is reachable from `(pivot, pivot, succ)` in the product automaton,
+ * and there is a distance of `dist` from `tuple` to the nearest end-tuple `(pivot, succ, succ)`,
+ * and a path from a start-state to `tuple` follows the transitions in `trace`.
+ */
+predicate isReachableFromStartTuple(State pivot, State succ, StateTuple tuple, Trace trace, int dist) {
+ // base case. The first step is inlined to start the search after all possible 1-steps, and not just the ones with the shortest path.
+ exists(InputSymbol s1, InputSymbol s2, InputSymbol s3, State q1, State q2, State q3 |
+ isStartLoops(pivot, succ) and
+ step(MkStateTuple(pivot, pivot, succ), s1, s2, s3, tuple) and
+ tuple = MkStateTuple(q1, q2, q3) and
+ trace = Step(s1, s2, s3, Nil()) and
+ dist = distBackFromEnd(tuple, MkStateTuple(pivot, succ, succ))
+ )
+ or
+ // recursive case
+ exists(StateTuple p, Trace v, InputSymbol s1, InputSymbol s2, InputSymbol s3 |
+ isReachableFromStartTuple(pivot, succ, p, v, dist + 1) and
+ dist = isReachableFromStartTupleHelper(pivot, succ, tuple, p, s1, s2, s3) and
+ trace = Step(s1, s2, s3, v)
+ )
+}
+
+/**
+ * Helper predicate for the recursive case in `isReachableFromStartTuple`.
+ */
+pragma[noinline]
+private int isReachableFromStartTupleHelper(
+ State pivot, State succ, StateTuple r, StateTuple p, InputSymbol s1, InputSymbol s2,
+ InputSymbol s3
+) {
+ result = distBackFromEnd(r, MkStateTuple(pivot, succ, succ)) and
+ step(p, s1, s2, s3, r)
+}
+
+/**
+ * Gets the tuple `(pivot, succ, succ)` from the product automaton.
+ */
+StateTuple getAnEndTuple(State pivot, State succ) {
+ isStartLoops(pivot, succ) and
+ result = MkStateTuple(pivot, succ, succ)
+}
+
+/**
+ * Holds if matching repetitions of `pump` can:
+ * 1) Transition from `pivot` back to `pivot`.
+ * 2) Transition from `pivot` to `succ`.
+ * 3) Transition from `succ` to `succ`.
+ *
+ * From theorem 3 in the paper linked in the top of this file we can therefore conclude that
+ * the regular expression has polynomial backtracking - if a rejecting suffix exists.
+ *
+ * This predicate is used by `SuperLinearReDoSConfiguration`, and the final results are
+ * available in the `hasReDoSResult` predicate.
+ */
+predicate isPumpable(State pivot, State succ, string pump) {
+ exists(StateTuple q, Trace t |
+ isReachableFromStartTuple(pivot, succ, q, t, _) and
+ q = getAnEndTuple(pivot, succ) and
+ pump = concretise(t)
+ )
+}
+
+/**
+ * Holds if repetitions of `pump` at `t` will cause polynomial backtracking.
+ */
+predicate polynimalReDoS(RegExpTerm t, string pump, string prefixMsg, RegExpTerm prev) {
+ exists(State s, State pivot |
+ hasReDoSResult(t, pump, s, prefixMsg) and
+ isPumpable(pivot, s, _) and
+ prev = pivot.getRepr()
+ )
+}
+
+/**
+ * Gets a message for why `term` can cause polynomial backtracking.
+ */
+string getReasonString(RegExpTerm term, string pump, string prefixMsg, RegExpTerm prev) {
+ polynimalReDoS(term, pump, prefixMsg, prev) and
+ result =
+ "Strings " + prefixMsg + "with many repetitions of '" + pump +
+ "' can start matching anywhere after the start of the preceeding " + prev
+}
+
+/**
+ * A term that may cause a regular expression engine to perform a
+ * polynomial number of match attempts, relative to the input length.
+ */
+class PolynomialBackTrackingTerm extends InfiniteRepetitionQuantifier {
+ string reason;
+ string pump;
+ string prefixMsg;
+ RegExpTerm prev;
+
+ PolynomialBackTrackingTerm() {
+ reason = getReasonString(this, pump, prefixMsg, prev) and
+ // there might be many reasons for this term to have polynomial backtracking - we pick the shortest one.
+ reason = min(string msg | msg = getReasonString(this, _, _, _) | msg order by msg.length(), msg)
+ }
+
+ /**
+ * Holds if all non-empty successors to the polynomial backtracking term matches the end of the line.
+ */
+ predicate isAtEndLine() {
+ forall(RegExpTerm succ | this.getSuccessor+() = succ and not matchesEpsilon(succ) |
+ succ instanceof RegExpDollar
+ )
+ }
+
+ /**
+ * Gets the string that should be repeated to cause this regular expression to perform polynomially.
+ */
+ string getPumpString() { result = pump }
+
+ /**
+ * Gets a message for which prefix a matching string must start with for this term to cause polynomial backtracking.
+ */
+ string getPrefixMessage() { result = prefixMsg }
+
+ /**
+ * Gets a predecessor to `this`, which also loops on the pump string, and thereby causes polynomial backtracking.
+ */
+ RegExpTerm getPreviousLoop() { result = prev }
+
+ /**
+ * Gets the reason for the number of match attempts.
+ */
+ string getReason() { result = reason }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll
new file mode 100644
index 00000000000..272e87b4995
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll
@@ -0,0 +1,489 @@
+/** Step Summaries and Type Tracking */
+
+private import TypeTrackerSpecific
+
+/**
+ * Any string that may appear as the name of a piece of content. This will usually include things like:
+ * - Attribute names (in Python)
+ * - Property names (in JavaScript)
+ *
+ * In general, this can also be used to model things like stores to specific list indices. To ensure
+ * correctness, it is important that
+ *
+ * - different types of content do not have overlapping names, and
+ * - the empty string `""` is not a valid piece of content, as it is used to indicate the absence of
+ * content instead.
+ */
+class ContentName extends string {
+ ContentName() { this = getPossibleContentName() }
+}
+
+/** Either a content name, or the empty string (representing no content). */
+class OptionalContentName extends string {
+ OptionalContentName() { this instanceof ContentName or this = "" }
+}
+
+cached
+private module Cached {
+ /**
+ * A description of a step on an inter-procedural data flow path.
+ */
+ cached
+ newtype TStepSummary =
+ LevelStep() or
+ CallStep() or
+ ReturnStep() or
+ StoreStep(ContentName content) or
+ LoadStep(ContentName content)
+
+ /** Gets the summary resulting from appending `step` to type-tracking summary `tt`. */
+ cached
+ TypeTracker append(TypeTracker tt, StepSummary step) {
+ exists(Boolean hasCall, OptionalContentName content | tt = MkTypeTracker(hasCall, content) |
+ step = LevelStep() and result = tt
+ or
+ step = CallStep() and result = MkTypeTracker(true, content)
+ or
+ step = ReturnStep() and hasCall = false and result = tt
+ or
+ step = LoadStep(content) and result = MkTypeTracker(hasCall, "")
+ or
+ exists(string p | step = StoreStep(p) and content = "" and result = MkTypeTracker(hasCall, p))
+ )
+ }
+
+ /** Gets the summary resulting from prepending `step` to this type-tracking summary. */
+ cached
+ TypeBackTracker prepend(TypeBackTracker tbt, StepSummary step) {
+ exists(Boolean hasReturn, string content | tbt = MkTypeBackTracker(hasReturn, content) |
+ step = LevelStep() and result = tbt
+ or
+ step = CallStep() and hasReturn = false and result = tbt
+ or
+ step = ReturnStep() and result = MkTypeBackTracker(true, content)
+ or
+ exists(string p |
+ step = LoadStep(p) and content = "" and result = MkTypeBackTracker(hasReturn, p)
+ )
+ or
+ step = StoreStep(content) and result = MkTypeBackTracker(hasReturn, "")
+ )
+ }
+
+ /**
+ * Gets the summary that corresponds to having taken a forwards
+ * heap and/or intra-procedural step from `nodeFrom` to `nodeTo`.
+ *
+ * Steps contained in this predicate should _not_ depend on the call graph.
+ */
+ cached
+ predicate stepNoCall(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
+ exists(Node mid | nodeFrom.flowsTo(mid) and smallstepNoCall(mid, nodeTo, summary))
+ }
+
+ /**
+ * Gets the summary that corresponds to having taken a forwards
+ * inter-procedural step from `nodeFrom` to `nodeTo`.
+ */
+ cached
+ predicate stepCall(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
+ exists(Node mid | nodeFrom.flowsTo(mid) and smallstepCall(mid, nodeTo, summary))
+ }
+}
+
+private import Cached
+
+/**
+ * INTERNAL: Use `TypeTracker` or `TypeBackTracker` instead.
+ *
+ * A description of a step on an inter-procedural data flow path.
+ */
+class StepSummary extends TStepSummary {
+ /** Gets a textual representation of this step summary. */
+ string toString() {
+ this instanceof LevelStep and result = "level"
+ or
+ this instanceof CallStep and result = "call"
+ or
+ this instanceof ReturnStep and result = "return"
+ or
+ exists(string content | this = StoreStep(content) | result = "store " + content)
+ or
+ exists(string content | this = LoadStep(content) | result = "load " + content)
+ }
+}
+
+pragma[noinline]
+private predicate smallstepNoCall(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
+ jumpStep(nodeFrom, nodeTo) and
+ summary = LevelStep()
+ or
+ exists(string content |
+ StepSummary::localSourceStoreStep(nodeFrom, nodeTo, content) and
+ summary = StoreStep(content)
+ or
+ basicLoadStep(nodeFrom, nodeTo, content) and summary = LoadStep(content)
+ )
+}
+
+pragma[noinline]
+private predicate smallstepCall(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
+ callStep(nodeFrom, nodeTo) and summary = CallStep()
+ or
+ returnStep(nodeFrom, nodeTo) and
+ summary = ReturnStep()
+}
+
+/** Provides predicates for updating step summaries (`StepSummary`s). */
+module StepSummary {
+ /**
+ * Gets the summary that corresponds to having taken a forwards
+ * heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
+ *
+ * This predicate is inlined, which enables better join-orders when
+ * the call graph construction and type tracking are mutually recursive.
+ * In such cases, non-linear recursion involving `step` will be limited
+ * to non-linear recursion for the parts of `step` that involve the
+ * call graph.
+ */
+ pragma[inline]
+ predicate step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
+ stepNoCall(nodeFrom, nodeTo, summary)
+ or
+ stepCall(nodeFrom, nodeTo, summary)
+ }
+
+ /**
+ * Gets the summary that corresponds to having taken a forwards
+ * local, heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
+ *
+ * Unlike `StepSummary::step`, this predicate does not compress
+ * type-preserving steps.
+ */
+ pragma[inline]
+ predicate smallstep(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
+ smallstepNoCall(nodeFrom, nodeTo, summary)
+ or
+ smallstepCall(nodeFrom, nodeTo, summary)
+ }
+
+ /**
+ * Holds if `nodeFrom` is being written to the `content` content of the object in `nodeTo`.
+ *
+ * Note that `nodeTo` will always be a local source node that flows to the place where the content
+ * is written in `basicStoreStep`. This may lead to the flow of information going "back in time"
+ * from the point of view of the execution of the program.
+ *
+ * For instance, if we interpret attribute writes in Python as writing to content with the same
+ * name as the attribute and consider the following snippet
+ *
+ * ```python
+ * def foo(y):
+ * x = Foo()
+ * bar(x)
+ * x.attr = y
+ * baz(x)
+ *
+ * def bar(x):
+ * z = x.attr
+ * ```
+ * for the attribute write `x.attr = y`, we will have `content` being the literal string `"attr"`,
+ * `nodeFrom` will be `y`, and `nodeTo` will be the object `Foo()` created on the first line of the
+ * function. This means we will track the fact that `x.attr` can have the type of `y` into the
+ * assignment to `z` inside `bar`, even though this attribute write happens _after_ `bar` is called.
+ */
+ predicate localSourceStoreStep(Node nodeFrom, TypeTrackingNode nodeTo, string content) {
+ exists(Node obj | nodeTo.flowsTo(obj) and basicStoreStep(nodeFrom, obj, content))
+ }
+}
+
+private newtype TTypeTracker = MkTypeTracker(Boolean hasCall, OptionalContentName content)
+
+/**
+ * Summary of the steps needed to track a value to a given dataflow node.
+ *
+ * This can be used to track objects that implement a certain API in order to
+ * recognize calls to that API. Note that type-tracking does not by itself provide a
+ * source/sink relation, that is, it may determine that a node has a given type,
+ * but it won't determine where that type came from.
+ *
+ * It is recommended that all uses of this type are written in the following form,
+ * for tracking some type `myType`:
+ * ```ql
+ * DataFlow::TypeTrackingNode myType(DataFlow::TypeTracker t) {
+ * t.start() and
+ * result = < source of myType >
+ * or
+ * exists (DataFlow::TypeTracker t2 |
+ * result = myType(t2).track(t2, t)
+ * )
+ * }
+ *
+ * DataFlow::Node myType() { myType(DataFlow::TypeTracker::end()).flowsTo(result) }
+ * ```
+ *
+ * Instead of `result = myType(t2).track(t2, t)`, you can also use the equivalent
+ * `t = t2.step(myType(t2), result)`. If you additionally want to track individual
+ * intra-procedural steps, use `t = t2.smallstep(myCallback(t2), result)`.
+ */
+class TypeTracker extends TTypeTracker {
+ Boolean hasCall;
+ OptionalContentName content;
+
+ TypeTracker() { this = MkTypeTracker(hasCall, content) }
+
+ /** Gets the summary resulting from appending `step` to this type-tracking summary. */
+ TypeTracker append(StepSummary step) { result = append(this, step) }
+
+ /** Gets a textual representation of this summary. */
+ string toString() {
+ exists(string withCall, string withContent |
+ (if hasCall = true then withCall = "with" else withCall = "without") and
+ (if content != "" then withContent = " with content " + content else withContent = "") and
+ result = "type tracker " + withCall + " call steps" + withContent
+ )
+ }
+
+ /**
+ * Holds if this is the starting point of type tracking.
+ */
+ predicate start() { hasCall = false and content = "" }
+
+ /**
+ * Holds if this is the starting point of type tracking, and the value starts in the content named `contentName`.
+ * The type tracking only ends after the content has been loaded.
+ */
+ predicate startInContent(ContentName contentName) { hasCall = false and content = contentName }
+
+ /**
+ * Holds if this is the starting point of type tracking
+ * when tracking a parameter into a call, but not out of it.
+ */
+ predicate call() { hasCall = true and content = "" }
+
+ /**
+ * Holds if this is the end point of type tracking.
+ */
+ predicate end() { content = "" }
+
+ /**
+ * INTERNAL. DO NOT USE.
+ *
+ * Holds if this type has been tracked into a call.
+ */
+ boolean hasCall() { result = hasCall }
+
+ /**
+ * INTERNAL. DO NOT USE.
+ *
+ * Gets the content associated with this type tracker.
+ */
+ string getContent() { result = content }
+
+ /**
+ * Gets a type tracker that starts where this one has left off to allow continued
+ * tracking.
+ *
+ * This predicate is only defined if the type is not associated to a piece of content.
+ */
+ TypeTracker continue() { content = "" and result = this }
+
+ /**
+ * Gets the summary that corresponds to having taken a forwards
+ * heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
+ */
+ pragma[inline]
+ TypeTracker step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
+ exists(StepSummary summary |
+ StepSummary::step(nodeFrom, pragma[only_bind_out](nodeTo), pragma[only_bind_into](summary)) and
+ result = this.append(pragma[only_bind_into](summary))
+ )
+ }
+
+ /**
+ * Gets the summary that corresponds to having taken a forwards
+ * local, heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
+ *
+ * Unlike `TypeTracker::step`, this predicate exposes all edges
+ * in the flow graph, and not just the edges between `Node`s.
+ * It may therefore be less performant.
+ *
+ * Type tracking predicates using small steps typically take the following form:
+ * ```ql
+ * DataFlow::Node myType(DataFlow::TypeTracker t) {
+ * t.start() and
+ * result = < source of myType >
+ * or
+ * exists (DataFlow::TypeTracker t2 |
+ * t = t2.smallstep(myType(t2), result)
+ * )
+ * }
+ *
+ * DataFlow::Node myType() {
+ * result = myType(DataFlow::TypeTracker::end())
+ * }
+ * ```
+ */
+ pragma[inline]
+ TypeTracker smallstep(Node nodeFrom, Node nodeTo) {
+ exists(StepSummary summary |
+ StepSummary::smallstep(nodeFrom, nodeTo, summary) and
+ result = this.append(summary)
+ )
+ or
+ simpleLocalFlowStep(nodeFrom, nodeTo) and
+ result = this
+ }
+}
+
+/** Provides predicates for implementing custom `TypeTracker`s. */
+module TypeTracker {
+ /**
+ * Gets a valid end point of type tracking.
+ */
+ TypeTracker end() { result.end() }
+}
+
+private newtype TTypeBackTracker = MkTypeBackTracker(Boolean hasReturn, OptionalContentName content)
+
+/**
+ * Summary of the steps needed to back-track a use of a value to a given dataflow node.
+ *
+ * This can for example be used to track callbacks that are passed to a certain API,
+ * so we can model specific parameters of that callback as having a certain type.
+ *
+ * Note that type back-tracking does not provide a source/sink relation, that is,
+ * it may determine that a node will be used in an API call somewhere, but it won't
+ * determine exactly where that use was, or the path that led to the use.
+ *
+ * It is recommended that all uses of this type are written in the following form,
+ * for back-tracking some callback type `myCallback`:
+ *
+ * ```ql
+ * DataFlow::TypeTrackingNode myCallback(DataFlow::TypeBackTracker t) {
+ * t.start() and
+ * result = (< some API call >).getArgument(< n >).getALocalSource()
+ * or
+ * exists (DataFlow::TypeBackTracker t2 |
+ * result = myCallback(t2).backtrack(t2, t)
+ * )
+ * }
+ *
+ * DataFlow::TypeTrackingNode myCallback() { result = myCallback(DataFlow::TypeBackTracker::end()) }
+ * ```
+ *
+ * Instead of `result = myCallback(t2).backtrack(t2, t)`, you can also use the equivalent
+ * `t2 = t.step(result, myCallback(t2))`. If you additionally want to track individual
+ * intra-procedural steps, use `t2 = t.smallstep(result, myCallback(t2))`.
+ */
+class TypeBackTracker extends TTypeBackTracker {
+ Boolean hasReturn;
+ string content;
+
+ TypeBackTracker() { this = MkTypeBackTracker(hasReturn, content) }
+
+ /** Gets the summary resulting from prepending `step` to this type-tracking summary. */
+ TypeBackTracker prepend(StepSummary step) { result = prepend(this, step) }
+
+ /** Gets a textual representation of this summary. */
+ string toString() {
+ exists(string withReturn, string withContent |
+ (if hasReturn = true then withReturn = "with" else withReturn = "without") and
+ (if content != "" then withContent = " with content " + content else withContent = "") and
+ result = "type back-tracker " + withReturn + " return steps" + withContent
+ )
+ }
+
+ /**
+ * Holds if this is the starting point of type tracking.
+ */
+ predicate start() { hasReturn = false and content = "" }
+
+ /**
+ * Holds if this is the end point of type tracking.
+ */
+ predicate end() { content = "" }
+
+ /**
+ * INTERNAL. DO NOT USE.
+ *
+ * Holds if this type has been back-tracked into a call through return edge.
+ */
+ boolean hasReturn() { result = hasReturn }
+
+ /**
+ * Gets a type tracker that starts where this one has left off to allow continued
+ * tracking.
+ *
+ * This predicate is only defined if the type has not been tracked into a piece of content.
+ */
+ TypeBackTracker continue() { content = "" and result = this }
+
+ /**
+ * Gets the summary that corresponds to having taken a backwards
+ * heap and/or inter-procedural step from `nodeTo` to `nodeFrom`.
+ */
+ pragma[inline]
+ TypeBackTracker step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
+ exists(StepSummary summary |
+ StepSummary::step(pragma[only_bind_out](nodeFrom), nodeTo, pragma[only_bind_into](summary)) and
+ this = result.prepend(pragma[only_bind_into](summary))
+ )
+ }
+
+ /**
+ * Gets the summary that corresponds to having taken a backwards
+ * local, heap and/or inter-procedural step from `nodeTo` to `nodeFrom`.
+ *
+ * Unlike `TypeBackTracker::step`, this predicate exposes all edges
+ * in the flowgraph, and not just the edges between
+ * `TypeTrackingNode`s. It may therefore be less performant.
+ *
+ * Type tracking predicates using small steps typically take the following form:
+ * ```ql
+ * DataFlow::Node myType(DataFlow::TypeBackTracker t) {
+ * t.start() and
+ * result = < some API call >.getArgument(< n >)
+ * or
+ * exists (DataFlow::TypeBackTracker t2 |
+ * t = t2.smallstep(result, myType(t2))
+ * )
+ * }
+ *
+ * DataFlow::Node myType() {
+ * result = myType(DataFlow::TypeBackTracker::end())
+ * }
+ * ```
+ */
+ pragma[inline]
+ TypeBackTracker smallstep(Node nodeFrom, Node nodeTo) {
+ exists(StepSummary summary |
+ StepSummary::smallstep(nodeFrom, nodeTo, summary) and
+ this = result.prepend(summary)
+ )
+ or
+ simpleLocalFlowStep(nodeFrom, nodeTo) and
+ this = result
+ }
+
+ /**
+ * Gets a forwards summary that is compatible with this backwards summary.
+ * That is, if this summary describes the steps needed to back-track a value
+ * from `sink` to `mid`, and the result is a valid summary of the steps needed
+ * to track a value from `source` to `mid`, then the value from `source` may
+ * also flow to `sink`.
+ */
+ TypeTracker getACompatibleTypeTracker() {
+ exists(boolean hasCall | result = MkTypeTracker(hasCall, content) |
+ hasCall = false or this.hasReturn() = false
+ )
+ }
+}
+
+/** Provides predicates for implementing custom `TypeBackTracker`s. */
+module TypeBackTracker {
+ /**
+ * Gets a valid end point of type back-tracking.
+ */
+ TypeBackTracker end() { result.end() }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll
new file mode 100644
index 00000000000..f71fb1bf6f1
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll
@@ -0,0 +1,146 @@
+private import codeql.ruby.AST as AST
+private import codeql.ruby.CFG as CFG
+private import CFG::CfgNodes
+private import codeql.ruby.dataflow.internal.DataFlowImplCommon as DataFlowImplCommon
+private import codeql.ruby.dataflow.internal.DataFlowPublic as DataFlowPublic
+private import codeql.ruby.dataflow.internal.DataFlowPrivate as DataFlowPrivate
+private import codeql.ruby.dataflow.internal.DataFlowDispatch as DataFlowDispatch
+private import codeql.ruby.dataflow.internal.SsaImpl as SsaImpl
+
+class Node = DataFlowPublic::Node;
+
+class TypeTrackingNode = DataFlowPublic::LocalSourceNode;
+
+predicate simpleLocalFlowStep = DataFlowPrivate::localFlowStepTypeTracker/2;
+
+predicate jumpStep = DataFlowPrivate::jumpStep/2;
+
+/**
+ * Gets the name of a possible piece of content. This will usually include things like
+ *
+ * - Attribute names (in Python)
+ * - Property names (in JavaScript)
+ */
+string getPossibleContentName() { result = getSetterCallAttributeName(_) }
+
+/**
+ * Holds if `nodeFrom` steps to `nodeTo` by being passed as a parameter in a call.
+ *
+ * Flow into summarized library methods is not included, as that will lead to negative
+ * recursion (or, at best, terrible performance), since identifying calls to library
+ * methods is done using API graphs (which uses type tracking).
+ */
+predicate callStep(Node nodeFrom, Node nodeTo) {
+ exists(ExprNodes::CallCfgNode call, CFG::CfgScope callable, int i |
+ DataFlowDispatch::getTarget(call) = callable and
+ nodeFrom.(DataFlowPrivate::ArgumentNode).sourceArgumentOf(call, i) and
+ nodeTo.(DataFlowPrivate::ParameterNodeImpl).isSourceParameterOf(callable, i)
+ )
+ or
+ // In normal data-flow, this will be a local flow step. But for type tracking
+ // we model it as a call step, in order to avoid computing a potential
+ // self-cross product of all calls to a function that returns one of its parameters
+ // (only to later filter that flow out using `TypeTracker::append`).
+ nodeTo =
+ DataFlowPrivate::LocalFlow::getParameterDefNode(nodeFrom
+ .(DataFlowPublic::ParameterNode)
+ .getParameter())
+}
+
+/**
+ * Holds if `nodeFrom` steps to `nodeTo` by being returned from a call.
+ *
+ * Flow out of summarized library methods is not included, as that will lead to negative
+ * recursion (or, at best, terrible performance), since identifying calls to library
+ * methods is done using API graphs (which uses type tracking).
+ */
+predicate returnStep(Node nodeFrom, Node nodeTo) {
+ exists(ExprNodes::CallCfgNode call |
+ nodeFrom instanceof DataFlowPrivate::ReturnNode and
+ nodeFrom.(DataFlowPrivate::NodeImpl).getCfgScope() = DataFlowDispatch::getTarget(call) and
+ nodeTo.asExpr().getNode() = call.getNode()
+ )
+ or
+ // In normal data-flow, this will be a local flow step. But for type tracking
+ // we model it as a returning flow step, in order to avoid computing a potential
+ // self-cross product of all calls to a function that returns one of its parameters
+ // (only to later filter that flow out using `TypeTracker::append`).
+ nodeTo.(DataFlowPrivate::SynthReturnNode).getAnInput() = nodeFrom
+}
+
+/**
+ * Holds if `nodeFrom` is being written to the `content` content of the object
+ * in `nodeTo`.
+ *
+ * Note that the choice of `nodeTo` does not have to make sense
+ * "chronologically". All we care about is whether the `content` content of
+ * `nodeTo` can have a specific type, and the assumption is that if a specific
+ * type appears here, then any access of that particular content can yield
+ * something of that particular type.
+ *
+ * Thus, in an example such as
+ *
+ * ```rb
+ * def foo(y)
+ * x = Foo.new
+ * bar(x)
+ * x.content = y
+ * baz(x)
+ * end
+ *
+ * def bar(x)
+ * z = x.content
+ * end
+ * ```
+ * for the content write `x.content = y`, we will have `content` being the
+ * literal string `"content"`, `nodeFrom` will be `y`, and `nodeTo` will be the
+ * `Foo` object created on the first line of the function. This means we will
+ * track the fact that `x.content` can have the type of `y` into the assignment
+ * to `z` inside `bar`, even though this content write happens _after_ `bar` is
+ * called.
+ */
+predicate basicStoreStep(Node nodeFrom, Node nodeTo, string content) {
+ // TODO: support SetterMethodCall inside TuplePattern
+ exists(ExprNodes::MethodCallCfgNode call |
+ content = getSetterCallAttributeName(call.getExpr()) and
+ nodeTo.(DataFlowPrivate::PostUpdateNode).getPreUpdateNode().asExpr() = call.getReceiver() and
+ call.getExpr() instanceof AST::SetterMethodCall and
+ call.getArgument(call.getNumberOfArguments() - 1) =
+ nodeFrom.(DataFlowPublic::ExprNode).getExprNode()
+ )
+}
+
+/**
+ * Returns the name of the attribute being set by the setter method call, i.e.
+ * the name of the setter method without the trailing `=`. In the following
+ * example, the result is `"bar"`.
+ *
+ * ```rb
+ * foo.bar = 1
+ * ```
+ */
+private string getSetterCallAttributeName(AST::SetterMethodCall call) {
+ // TODO: this should be exposed in `SetterMethodCall`
+ exists(string setterName |
+ setterName = call.getMethodName() and result = setterName.prefix(setterName.length() - 1)
+ )
+}
+
+/**
+ * Holds if `nodeTo` is the result of accessing the `content` content of `nodeFrom`.
+ */
+predicate basicLoadStep(Node nodeFrom, Node nodeTo, string content) {
+ exists(ExprNodes::MethodCallCfgNode call |
+ call.getExpr().getNumberOfArguments() = 0 and
+ content = call.getExpr().getMethodName() and
+ nodeFrom.asExpr() = call.getReceiver() and
+ nodeTo.asExpr() = call
+ )
+}
+
+/**
+ * A utility class that is equivalent to `boolean` but does not require type joining.
+ */
+class Boolean extends boolean {
+ Boolean() { this = true or this = false }
+}
diff --git a/repo-tests/codeql/ruby/ql/lib/qlpack.yml b/repo-tests/codeql/ruby/ql/lib/qlpack.yml
new file mode 100644
index 00000000000..91f40532fc9
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/qlpack.yml
@@ -0,0 +1,6 @@
+name: codeql/ruby-all
+version: 0.0.2
+extractor: ruby
+dbscheme: ruby.dbscheme
+upgrades: upgrades
+library: true
diff --git a/repo-tests/codeql/ruby/ql/lib/ruby.dbscheme b/repo-tests/codeql/ruby/ql/lib/ruby.dbscheme
new file mode 100644
index 00000000000..f36dd8a35ce
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/ruby.dbscheme
@@ -0,0 +1,1318 @@
+// CodeQL database schema for Ruby
+// Automatically generated from the tree-sitter grammar; do not edit
+
+@location = @location_default
+
+locations_default(
+ unique int id: @location_default,
+ int file: @file ref,
+ int start_line: int ref,
+ int start_column: int ref,
+ int end_line: int ref,
+ int end_column: int ref
+);
+
+files(
+ unique int id: @file,
+ string name: string ref
+);
+
+folders(
+ unique int id: @folder,
+ string name: string ref
+);
+
+@container = @file | @folder
+
+containerparent(
+ int parent: @container ref,
+ unique int child: @container ref
+);
+
+sourceLocationPrefix(
+ string prefix: string ref
+);
+
+diagnostics(
+ unique int id: @diagnostic,
+ int severity: int ref,
+ string error_tag: string ref,
+ string error_message: string ref,
+ string full_error_message: string ref,
+ int location: @location_default ref
+);
+
+case @diagnostic.severity of
+ 10 = @diagnostic_debug
+| 20 = @diagnostic_info
+| 30 = @diagnostic_warning
+| 40 = @diagnostic_error
+;
+
+
+@ruby_underscore_arg = @ruby_assignment | @ruby_binary | @ruby_conditional | @ruby_operator_assignment | @ruby_range | @ruby_unary | @ruby_underscore_primary
+
+@ruby_underscore_lhs = @ruby_call | @ruby_element_reference | @ruby_scope_resolution | @ruby_token_false | @ruby_token_nil | @ruby_token_true | @ruby_underscore_variable
+
+@ruby_underscore_method_name = @ruby_delimited_symbol | @ruby_setter | @ruby_token_class_variable | @ruby_token_constant | @ruby_token_global_variable | @ruby_token_identifier | @ruby_token_instance_variable | @ruby_token_operator | @ruby_token_simple_symbol
+
+@ruby_underscore_primary = @ruby_array | @ruby_begin | @ruby_break | @ruby_case__ | @ruby_chained_string | @ruby_class | @ruby_delimited_symbol | @ruby_for | @ruby_hash | @ruby_if | @ruby_lambda | @ruby_method | @ruby_module | @ruby_next | @ruby_parenthesized_statements | @ruby_rational | @ruby_redo | @ruby_regex | @ruby_retry | @ruby_return | @ruby_singleton_class | @ruby_singleton_method | @ruby_string__ | @ruby_string_array | @ruby_subshell | @ruby_symbol_array | @ruby_token_character | @ruby_token_complex | @ruby_token_float | @ruby_token_heredoc_beginning | @ruby_token_integer | @ruby_token_simple_symbol | @ruby_unary | @ruby_underscore_lhs | @ruby_unless | @ruby_until | @ruby_while | @ruby_yield
+
+@ruby_underscore_statement = @ruby_alias | @ruby_assignment | @ruby_begin_block | @ruby_binary | @ruby_break | @ruby_call | @ruby_end_block | @ruby_if_modifier | @ruby_next | @ruby_operator_assignment | @ruby_rescue_modifier | @ruby_return | @ruby_unary | @ruby_undef | @ruby_underscore_arg | @ruby_unless_modifier | @ruby_until_modifier | @ruby_while_modifier | @ruby_yield
+
+@ruby_underscore_variable = @ruby_token_class_variable | @ruby_token_constant | @ruby_token_global_variable | @ruby_token_identifier | @ruby_token_instance_variable | @ruby_token_self | @ruby_token_super
+
+ruby_alias_def(
+ unique int id: @ruby_alias,
+ int alias: @ruby_underscore_method_name ref,
+ int name: @ruby_underscore_method_name ref,
+ int loc: @location ref
+);
+
+@ruby_argument_list_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_token_forward_argument | @ruby_underscore_arg | @ruby_yield
+
+#keyset[ruby_argument_list, index]
+ruby_argument_list_child(
+ int ruby_argument_list: @ruby_argument_list ref,
+ int index: int ref,
+ unique int child: @ruby_argument_list_child_type ref
+);
+
+ruby_argument_list_def(
+ unique int id: @ruby_argument_list,
+ int loc: @location ref
+);
+
+@ruby_array_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_token_forward_argument | @ruby_underscore_arg | @ruby_yield
+
+#keyset[ruby_array, index]
+ruby_array_child(
+ int ruby_array: @ruby_array ref,
+ int index: int ref,
+ unique int child: @ruby_array_child_type ref
+);
+
+ruby_array_def(
+ unique int id: @ruby_array,
+ int loc: @location ref
+);
+
+@ruby_assignment_left_type = @ruby_left_assignment_list | @ruby_underscore_lhs
+
+@ruby_assignment_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_right_assignment_list | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield
+
+ruby_assignment_def(
+ unique int id: @ruby_assignment,
+ int left: @ruby_assignment_left_type ref,
+ int right: @ruby_assignment_right_type ref,
+ int loc: @location ref
+);
+
+@ruby_bare_string_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content
+
+#keyset[ruby_bare_string, index]
+ruby_bare_string_child(
+ int ruby_bare_string: @ruby_bare_string ref,
+ int index: int ref,
+ unique int child: @ruby_bare_string_child_type ref
+);
+
+ruby_bare_string_def(
+ unique int id: @ruby_bare_string,
+ int loc: @location ref
+);
+
+@ruby_bare_symbol_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content
+
+#keyset[ruby_bare_symbol, index]
+ruby_bare_symbol_child(
+ int ruby_bare_symbol: @ruby_bare_symbol ref,
+ int index: int ref,
+ unique int child: @ruby_bare_symbol_child_type ref
+);
+
+ruby_bare_symbol_def(
+ unique int id: @ruby_bare_symbol,
+ int loc: @location ref
+);
+
+@ruby_begin_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement
+
+#keyset[ruby_begin, index]
+ruby_begin_child(
+ int ruby_begin: @ruby_begin ref,
+ int index: int ref,
+ unique int child: @ruby_begin_child_type ref
+);
+
+ruby_begin_def(
+ unique int id: @ruby_begin,
+ int loc: @location ref
+);
+
+@ruby_begin_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement
+
+#keyset[ruby_begin_block, index]
+ruby_begin_block_child(
+ int ruby_begin_block: @ruby_begin_block ref,
+ int index: int ref,
+ unique int child: @ruby_begin_block_child_type ref
+);
+
+ruby_begin_block_def(
+ unique int id: @ruby_begin_block,
+ int loc: @location ref
+);
+
+@ruby_binary_left_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield
+
+case @ruby_binary.operator of
+ 0 = @ruby_binary_bangequal
+| 1 = @ruby_binary_bangtilde
+| 2 = @ruby_binary_percent
+| 3 = @ruby_binary_ampersand
+| 4 = @ruby_binary_ampersandampersand
+| 5 = @ruby_binary_star
+| 6 = @ruby_binary_starstar
+| 7 = @ruby_binary_plus
+| 8 = @ruby_binary_minus
+| 9 = @ruby_binary_slash
+| 10 = @ruby_binary_langle
+| 11 = @ruby_binary_langlelangle
+| 12 = @ruby_binary_langleequal
+| 13 = @ruby_binary_langleequalrangle
+| 14 = @ruby_binary_equalequal
+| 15 = @ruby_binary_equalequalequal
+| 16 = @ruby_binary_equaltilde
+| 17 = @ruby_binary_rangle
+| 18 = @ruby_binary_rangleequal
+| 19 = @ruby_binary_ranglerangle
+| 20 = @ruby_binary_caret
+| 21 = @ruby_binary_and
+| 22 = @ruby_binary_or
+| 23 = @ruby_binary_pipe
+| 24 = @ruby_binary_pipepipe
+;
+
+
+@ruby_binary_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield
+
+ruby_binary_def(
+ unique int id: @ruby_binary,
+ int left: @ruby_binary_left_type ref,
+ int operator: int ref,
+ int right: @ruby_binary_right_type ref,
+ int loc: @location ref
+);
+
+ruby_block_parameters(
+ unique int ruby_block: @ruby_block ref,
+ unique int parameters: @ruby_block_parameters ref
+);
+
+@ruby_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement
+
+#keyset[ruby_block, index]
+ruby_block_child(
+ int ruby_block: @ruby_block ref,
+ int index: int ref,
+ unique int child: @ruby_block_child_type ref
+);
+
+ruby_block_def(
+ unique int id: @ruby_block,
+ int loc: @location ref
+);
+
+ruby_block_argument_def(
+ unique int id: @ruby_block_argument,
+ int child: @ruby_underscore_arg ref,
+ int loc: @location ref
+);
+
+ruby_block_parameter_def(
+ unique int id: @ruby_block_parameter,
+ int name: @ruby_token_identifier ref,
+ int loc: @location ref
+);
+
+@ruby_block_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_forward_parameter | @ruby_token_identifier
+
+#keyset[ruby_block_parameters, index]
+ruby_block_parameters_child(
+ int ruby_block_parameters: @ruby_block_parameters ref,
+ int index: int ref,
+ unique int child: @ruby_block_parameters_child_type ref
+);
+
+ruby_block_parameters_def(
+ unique int id: @ruby_block_parameters,
+ int loc: @location ref
+);
+
+ruby_break_child(
+ unique int ruby_break: @ruby_break ref,
+ unique int child: @ruby_argument_list ref
+);
+
+ruby_break_def(
+ unique int id: @ruby_break,
+ int loc: @location ref
+);
+
+ruby_call_arguments(
+ unique int ruby_call: @ruby_call ref,
+ unique int arguments: @ruby_argument_list ref
+);
+
+@ruby_call_block_type = @ruby_block | @ruby_do_block
+
+ruby_call_block(
+ unique int ruby_call: @ruby_call ref,
+ unique int block: @ruby_call_block_type ref
+);
+
+@ruby_call_method_type = @ruby_argument_list | @ruby_scope_resolution | @ruby_token_operator | @ruby_underscore_variable
+
+@ruby_call_receiver_type = @ruby_call | @ruby_underscore_primary
+
+ruby_call_receiver(
+ unique int ruby_call: @ruby_call ref,
+ unique int receiver: @ruby_call_receiver_type ref
+);
+
+ruby_call_def(
+ unique int id: @ruby_call,
+ int method: @ruby_call_method_type ref,
+ int loc: @location ref
+);
+
+ruby_case_value(
+ unique int ruby_case__: @ruby_case__ ref,
+ unique int value: @ruby_underscore_statement ref
+);
+
+@ruby_case_child_type = @ruby_else | @ruby_when
+
+#keyset[ruby_case__, index]
+ruby_case_child(
+ int ruby_case__: @ruby_case__ ref,
+ int index: int ref,
+ unique int child: @ruby_case_child_type ref
+);
+
+ruby_case_def(
+ unique int id: @ruby_case__,
+ int loc: @location ref
+);
+
+#keyset[ruby_chained_string, index]
+ruby_chained_string_child(
+ int ruby_chained_string: @ruby_chained_string ref,
+ int index: int ref,
+ unique int child: @ruby_string__ ref
+);
+
+ruby_chained_string_def(
+ unique int id: @ruby_chained_string,
+ int loc: @location ref
+);
+
+@ruby_class_name_type = @ruby_scope_resolution | @ruby_token_constant
+
+ruby_class_superclass(
+ unique int ruby_class: @ruby_class ref,
+ unique int superclass: @ruby_superclass ref
+);
+
+@ruby_class_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement
+
+#keyset[ruby_class, index]
+ruby_class_child(
+ int ruby_class: @ruby_class ref,
+ int index: int ref,
+ unique int child: @ruby_class_child_type ref
+);
+
+ruby_class_def(
+ unique int id: @ruby_class,
+ int name: @ruby_class_name_type ref,
+ int loc: @location ref
+);
+
+ruby_conditional_def(
+ unique int id: @ruby_conditional,
+ int alternative: @ruby_underscore_arg ref,
+ int condition: @ruby_underscore_arg ref,
+ int consequence: @ruby_underscore_arg ref,
+ int loc: @location ref
+);
+
+@ruby_delimited_symbol_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content
+
+#keyset[ruby_delimited_symbol, index]
+ruby_delimited_symbol_child(
+ int ruby_delimited_symbol: @ruby_delimited_symbol ref,
+ int index: int ref,
+ unique int child: @ruby_delimited_symbol_child_type ref
+);
+
+ruby_delimited_symbol_def(
+ unique int id: @ruby_delimited_symbol,
+ int loc: @location ref
+);
+
+@ruby_destructured_left_assignment_child_type = @ruby_destructured_left_assignment | @ruby_rest_assignment | @ruby_underscore_lhs
+
+#keyset[ruby_destructured_left_assignment, index]
+ruby_destructured_left_assignment_child(
+ int ruby_destructured_left_assignment: @ruby_destructured_left_assignment ref,
+ int index: int ref,
+ unique int child: @ruby_destructured_left_assignment_child_type ref
+);
+
+ruby_destructured_left_assignment_def(
+ unique int id: @ruby_destructured_left_assignment,
+ int loc: @location ref
+);
+
+@ruby_destructured_parameter_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_forward_parameter | @ruby_token_identifier
+
+#keyset[ruby_destructured_parameter, index]
+ruby_destructured_parameter_child(
+ int ruby_destructured_parameter: @ruby_destructured_parameter ref,
+ int index: int ref,
+ unique int child: @ruby_destructured_parameter_child_type ref
+);
+
+ruby_destructured_parameter_def(
+ unique int id: @ruby_destructured_parameter,
+ int loc: @location ref
+);
+
+@ruby_do_child_type = @ruby_token_empty_statement | @ruby_underscore_statement
+
+#keyset[ruby_do, index]
+ruby_do_child(
+ int ruby_do: @ruby_do ref,
+ int index: int ref,
+ unique int child: @ruby_do_child_type ref
+);
+
+ruby_do_def(
+ unique int id: @ruby_do,
+ int loc: @location ref
+);
+
+ruby_do_block_parameters(
+ unique int ruby_do_block: @ruby_do_block ref,
+ unique int parameters: @ruby_block_parameters ref
+);
+
+@ruby_do_block_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement
+
+#keyset[ruby_do_block, index]
+ruby_do_block_child(
+ int ruby_do_block: @ruby_do_block ref,
+ int index: int ref,
+ unique int child: @ruby_do_block_child_type ref
+);
+
+ruby_do_block_def(
+ unique int id: @ruby_do_block,
+ int loc: @location ref
+);
+
+@ruby_element_reference_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_token_forward_argument | @ruby_underscore_arg | @ruby_yield
+
+#keyset[ruby_element_reference, index]
+ruby_element_reference_child(
+ int ruby_element_reference: @ruby_element_reference ref,
+ int index: int ref,
+ unique int child: @ruby_element_reference_child_type ref
+);
+
+ruby_element_reference_def(
+ unique int id: @ruby_element_reference,
+ int object: @ruby_underscore_primary ref,
+ int loc: @location ref
+);
+
+@ruby_else_child_type = @ruby_token_empty_statement | @ruby_underscore_statement
+
+#keyset[ruby_else, index]
+ruby_else_child(
+ int ruby_else: @ruby_else ref,
+ int index: int ref,
+ unique int child: @ruby_else_child_type ref
+);
+
+ruby_else_def(
+ unique int id: @ruby_else,
+ int loc: @location ref
+);
+
+@ruby_elsif_alternative_type = @ruby_else | @ruby_elsif
+
+ruby_elsif_alternative(
+ unique int ruby_elsif: @ruby_elsif ref,
+ unique int alternative: @ruby_elsif_alternative_type ref
+);
+
+ruby_elsif_consequence(
+ unique int ruby_elsif: @ruby_elsif ref,
+ unique int consequence: @ruby_then ref
+);
+
+ruby_elsif_def(
+ unique int id: @ruby_elsif,
+ int condition: @ruby_underscore_statement ref,
+ int loc: @location ref
+);
+
+@ruby_end_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement
+
+#keyset[ruby_end_block, index]
+ruby_end_block_child(
+ int ruby_end_block: @ruby_end_block ref,
+ int index: int ref,
+ unique int child: @ruby_end_block_child_type ref
+);
+
+ruby_end_block_def(
+ unique int id: @ruby_end_block,
+ int loc: @location ref
+);
+
+@ruby_ensure_child_type = @ruby_token_empty_statement | @ruby_underscore_statement
+
+#keyset[ruby_ensure, index]
+ruby_ensure_child(
+ int ruby_ensure: @ruby_ensure ref,
+ int index: int ref,
+ unique int child: @ruby_ensure_child_type ref
+);
+
+ruby_ensure_def(
+ unique int id: @ruby_ensure,
+ int loc: @location ref
+);
+
+ruby_exception_variable_def(
+ unique int id: @ruby_exception_variable,
+ int child: @ruby_underscore_lhs ref,
+ int loc: @location ref
+);
+
+@ruby_exceptions_child_type = @ruby_splat_argument | @ruby_underscore_arg
+
+#keyset[ruby_exceptions, index]
+ruby_exceptions_child(
+ int ruby_exceptions: @ruby_exceptions ref,
+ int index: int ref,
+ unique int child: @ruby_exceptions_child_type ref
+);
+
+ruby_exceptions_def(
+ unique int id: @ruby_exceptions,
+ int loc: @location ref
+);
+
+@ruby_for_pattern_type = @ruby_left_assignment_list | @ruby_underscore_lhs
+
+ruby_for_def(
+ unique int id: @ruby_for,
+ int body: @ruby_do ref,
+ int pattern: @ruby_for_pattern_type ref,
+ int value: @ruby_in ref,
+ int loc: @location ref
+);
+
+@ruby_hash_child_type = @ruby_hash_splat_argument | @ruby_pair
+
+#keyset[ruby_hash, index]
+ruby_hash_child(
+ int ruby_hash: @ruby_hash ref,
+ int index: int ref,
+ unique int child: @ruby_hash_child_type ref
+);
+
+ruby_hash_def(
+ unique int id: @ruby_hash,
+ int loc: @location ref
+);
+
+ruby_hash_splat_argument_def(
+ unique int id: @ruby_hash_splat_argument,
+ int child: @ruby_underscore_arg ref,
+ int loc: @location ref
+);
+
+ruby_hash_splat_parameter_name(
+ unique int ruby_hash_splat_parameter: @ruby_hash_splat_parameter ref,
+ unique int name: @ruby_token_identifier ref
+);
+
+ruby_hash_splat_parameter_def(
+ unique int id: @ruby_hash_splat_parameter,
+ int loc: @location ref
+);
+
+@ruby_heredoc_body_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_heredoc_content | @ruby_token_heredoc_end
+
+#keyset[ruby_heredoc_body, index]
+ruby_heredoc_body_child(
+ int ruby_heredoc_body: @ruby_heredoc_body ref,
+ int index: int ref,
+ unique int child: @ruby_heredoc_body_child_type ref
+);
+
+ruby_heredoc_body_def(
+ unique int id: @ruby_heredoc_body,
+ int loc: @location ref
+);
+
+@ruby_if_alternative_type = @ruby_else | @ruby_elsif
+
+ruby_if_alternative(
+ unique int ruby_if: @ruby_if ref,
+ unique int alternative: @ruby_if_alternative_type ref
+);
+
+ruby_if_consequence(
+ unique int ruby_if: @ruby_if ref,
+ unique int consequence: @ruby_then ref
+);
+
+ruby_if_def(
+ unique int id: @ruby_if,
+ int condition: @ruby_underscore_statement ref,
+ int loc: @location ref
+);
+
+@ruby_if_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield
+
+ruby_if_modifier_def(
+ unique int id: @ruby_if_modifier,
+ int body: @ruby_underscore_statement ref,
+ int condition: @ruby_if_modifier_condition_type ref,
+ int loc: @location ref
+);
+
+ruby_in_def(
+ unique int id: @ruby_in,
+ int child: @ruby_underscore_arg ref,
+ int loc: @location ref
+);
+
+@ruby_interpolation_child_type = @ruby_token_empty_statement | @ruby_underscore_statement
+
+#keyset[ruby_interpolation, index]
+ruby_interpolation_child(
+ int ruby_interpolation: @ruby_interpolation ref,
+ int index: int ref,
+ unique int child: @ruby_interpolation_child_type ref
+);
+
+ruby_interpolation_def(
+ unique int id: @ruby_interpolation,
+ int loc: @location ref
+);
+
+ruby_keyword_parameter_value(
+ unique int ruby_keyword_parameter: @ruby_keyword_parameter ref,
+ unique int value: @ruby_underscore_arg ref
+);
+
+ruby_keyword_parameter_def(
+ unique int id: @ruby_keyword_parameter,
+ int name: @ruby_token_identifier ref,
+ int loc: @location ref
+);
+
+@ruby_lambda_body_type = @ruby_block | @ruby_do_block
+
+ruby_lambda_parameters(
+ unique int ruby_lambda: @ruby_lambda ref,
+ unique int parameters: @ruby_lambda_parameters ref
+);
+
+ruby_lambda_def(
+ unique int id: @ruby_lambda,
+ int body: @ruby_lambda_body_type ref,
+ int loc: @location ref
+);
+
+@ruby_lambda_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_forward_parameter | @ruby_token_identifier
+
+#keyset[ruby_lambda_parameters, index]
+ruby_lambda_parameters_child(
+ int ruby_lambda_parameters: @ruby_lambda_parameters ref,
+ int index: int ref,
+ unique int child: @ruby_lambda_parameters_child_type ref
+);
+
+ruby_lambda_parameters_def(
+ unique int id: @ruby_lambda_parameters,
+ int loc: @location ref
+);
+
+@ruby_left_assignment_list_child_type = @ruby_destructured_left_assignment | @ruby_rest_assignment | @ruby_underscore_lhs
+
+#keyset[ruby_left_assignment_list, index]
+ruby_left_assignment_list_child(
+ int ruby_left_assignment_list: @ruby_left_assignment_list ref,
+ int index: int ref,
+ unique int child: @ruby_left_assignment_list_child_type ref
+);
+
+ruby_left_assignment_list_def(
+ unique int id: @ruby_left_assignment_list,
+ int loc: @location ref
+);
+
+ruby_method_parameters(
+ unique int ruby_method: @ruby_method ref,
+ unique int parameters: @ruby_method_parameters ref
+);
+
+@ruby_method_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement
+
+#keyset[ruby_method, index]
+ruby_method_child(
+ int ruby_method: @ruby_method ref,
+ int index: int ref,
+ unique int child: @ruby_method_child_type ref
+);
+
+ruby_method_def(
+ unique int id: @ruby_method,
+ int name: @ruby_underscore_method_name ref,
+ int loc: @location ref
+);
+
+@ruby_method_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_forward_parameter | @ruby_token_identifier
+
+#keyset[ruby_method_parameters, index]
+ruby_method_parameters_child(
+ int ruby_method_parameters: @ruby_method_parameters ref,
+ int index: int ref,
+ unique int child: @ruby_method_parameters_child_type ref
+);
+
+ruby_method_parameters_def(
+ unique int id: @ruby_method_parameters,
+ int loc: @location ref
+);
+
+@ruby_module_name_type = @ruby_scope_resolution | @ruby_token_constant
+
+@ruby_module_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement
+
+#keyset[ruby_module, index]
+ruby_module_child(
+ int ruby_module: @ruby_module ref,
+ int index: int ref,
+ unique int child: @ruby_module_child_type ref
+);
+
+ruby_module_def(
+ unique int id: @ruby_module,
+ int name: @ruby_module_name_type ref,
+ int loc: @location ref
+);
+
+ruby_next_child(
+ unique int ruby_next: @ruby_next ref,
+ unique int child: @ruby_argument_list ref
+);
+
+ruby_next_def(
+ unique int id: @ruby_next,
+ int loc: @location ref
+);
+
+case @ruby_operator_assignment.operator of
+ 0 = @ruby_operator_assignment_percentequal
+| 1 = @ruby_operator_assignment_ampersandampersandequal
+| 2 = @ruby_operator_assignment_ampersandequal
+| 3 = @ruby_operator_assignment_starstarequal
+| 4 = @ruby_operator_assignment_starequal
+| 5 = @ruby_operator_assignment_plusequal
+| 6 = @ruby_operator_assignment_minusequal
+| 7 = @ruby_operator_assignment_slashequal
+| 8 = @ruby_operator_assignment_langlelangleequal
+| 9 = @ruby_operator_assignment_ranglerangleequal
+| 10 = @ruby_operator_assignment_caretequal
+| 11 = @ruby_operator_assignment_pipeequal
+| 12 = @ruby_operator_assignment_pipepipeequal
+;
+
+
+@ruby_operator_assignment_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield
+
+ruby_operator_assignment_def(
+ unique int id: @ruby_operator_assignment,
+ int left: @ruby_underscore_lhs ref,
+ int operator: int ref,
+ int right: @ruby_operator_assignment_right_type ref,
+ int loc: @location ref
+);
+
+ruby_optional_parameter_def(
+ unique int id: @ruby_optional_parameter,
+ int name: @ruby_token_identifier ref,
+ int value: @ruby_underscore_arg ref,
+ int loc: @location ref
+);
+
+@ruby_pair_key_type = @ruby_string__ | @ruby_token_hash_key_symbol | @ruby_underscore_arg
+
+ruby_pair_def(
+ unique int id: @ruby_pair,
+ int key__: @ruby_pair_key_type ref,
+ int value: @ruby_underscore_arg ref,
+ int loc: @location ref
+);
+
+@ruby_parenthesized_statements_child_type = @ruby_token_empty_statement | @ruby_underscore_statement
+
+#keyset[ruby_parenthesized_statements, index]
+ruby_parenthesized_statements_child(
+ int ruby_parenthesized_statements: @ruby_parenthesized_statements ref,
+ int index: int ref,
+ unique int child: @ruby_parenthesized_statements_child_type ref
+);
+
+ruby_parenthesized_statements_def(
+ unique int id: @ruby_parenthesized_statements,
+ int loc: @location ref
+);
+
+@ruby_pattern_child_type = @ruby_splat_argument | @ruby_underscore_arg
+
+ruby_pattern_def(
+ unique int id: @ruby_pattern,
+ int child: @ruby_pattern_child_type ref,
+ int loc: @location ref
+);
+
+@ruby_program_child_type = @ruby_token_empty_statement | @ruby_token_uninterpreted | @ruby_underscore_statement
+
+#keyset[ruby_program, index]
+ruby_program_child(
+ int ruby_program: @ruby_program ref,
+ int index: int ref,
+ unique int child: @ruby_program_child_type ref
+);
+
+ruby_program_def(
+ unique int id: @ruby_program,
+ int loc: @location ref
+);
+
+ruby_range_begin(
+ unique int ruby_range: @ruby_range ref,
+ unique int begin: @ruby_underscore_arg ref
+);
+
+ruby_range_end(
+ unique int ruby_range: @ruby_range ref,
+ unique int end: @ruby_underscore_arg ref
+);
+
+case @ruby_range.operator of
+ 0 = @ruby_range_dotdot
+| 1 = @ruby_range_dotdotdot
+;
+
+
+ruby_range_def(
+ unique int id: @ruby_range,
+ int operator: int ref,
+ int loc: @location ref
+);
+
+@ruby_rational_child_type = @ruby_token_float | @ruby_token_integer
+
+ruby_rational_def(
+ unique int id: @ruby_rational,
+ int child: @ruby_rational_child_type ref,
+ int loc: @location ref
+);
+
+ruby_redo_child(
+ unique int ruby_redo: @ruby_redo ref,
+ unique int child: @ruby_argument_list ref
+);
+
+ruby_redo_def(
+ unique int id: @ruby_redo,
+ int loc: @location ref
+);
+
+@ruby_regex_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content
+
+#keyset[ruby_regex, index]
+ruby_regex_child(
+ int ruby_regex: @ruby_regex ref,
+ int index: int ref,
+ unique int child: @ruby_regex_child_type ref
+);
+
+ruby_regex_def(
+ unique int id: @ruby_regex,
+ int loc: @location ref
+);
+
+ruby_rescue_body(
+ unique int ruby_rescue: @ruby_rescue ref,
+ unique int body: @ruby_then ref
+);
+
+ruby_rescue_exceptions(
+ unique int ruby_rescue: @ruby_rescue ref,
+ unique int exceptions: @ruby_exceptions ref
+);
+
+ruby_rescue_variable(
+ unique int ruby_rescue: @ruby_rescue ref,
+ unique int variable: @ruby_exception_variable ref
+);
+
+ruby_rescue_def(
+ unique int id: @ruby_rescue,
+ int loc: @location ref
+);
+
+@ruby_rescue_modifier_handler_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield
+
+ruby_rescue_modifier_def(
+ unique int id: @ruby_rescue_modifier,
+ int body: @ruby_underscore_statement ref,
+ int handler: @ruby_rescue_modifier_handler_type ref,
+ int loc: @location ref
+);
+
+ruby_rest_assignment_child(
+ unique int ruby_rest_assignment: @ruby_rest_assignment ref,
+ unique int child: @ruby_underscore_lhs ref
+);
+
+ruby_rest_assignment_def(
+ unique int id: @ruby_rest_assignment,
+ int loc: @location ref
+);
+
+ruby_retry_child(
+ unique int ruby_retry: @ruby_retry ref,
+ unique int child: @ruby_argument_list ref
+);
+
+ruby_retry_def(
+ unique int id: @ruby_retry,
+ int loc: @location ref
+);
+
+ruby_return_child(
+ unique int ruby_return: @ruby_return ref,
+ unique int child: @ruby_argument_list ref
+);
+
+ruby_return_def(
+ unique int id: @ruby_return,
+ int loc: @location ref
+);
+
+@ruby_right_assignment_list_child_type = @ruby_splat_argument | @ruby_underscore_arg
+
+#keyset[ruby_right_assignment_list, index]
+ruby_right_assignment_list_child(
+ int ruby_right_assignment_list: @ruby_right_assignment_list ref,
+ int index: int ref,
+ unique int child: @ruby_right_assignment_list_child_type ref
+);
+
+ruby_right_assignment_list_def(
+ unique int id: @ruby_right_assignment_list,
+ int loc: @location ref
+);
+
+@ruby_scope_resolution_name_type = @ruby_token_constant | @ruby_token_identifier
+
+ruby_scope_resolution_scope(
+ unique int ruby_scope_resolution: @ruby_scope_resolution ref,
+ unique int scope: @ruby_underscore_primary ref
+);
+
+ruby_scope_resolution_def(
+ unique int id: @ruby_scope_resolution,
+ int name: @ruby_scope_resolution_name_type ref,
+ int loc: @location ref
+);
+
+ruby_setter_def(
+ unique int id: @ruby_setter,
+ int name: @ruby_token_identifier ref,
+ int loc: @location ref
+);
+
+@ruby_singleton_class_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement
+
+#keyset[ruby_singleton_class, index]
+ruby_singleton_class_child(
+ int ruby_singleton_class: @ruby_singleton_class ref,
+ int index: int ref,
+ unique int child: @ruby_singleton_class_child_type ref
+);
+
+ruby_singleton_class_def(
+ unique int id: @ruby_singleton_class,
+ int value: @ruby_underscore_arg ref,
+ int loc: @location ref
+);
+
+@ruby_singleton_method_object_type = @ruby_underscore_arg | @ruby_underscore_variable
+
+ruby_singleton_method_parameters(
+ unique int ruby_singleton_method: @ruby_singleton_method ref,
+ unique int parameters: @ruby_method_parameters ref
+);
+
+@ruby_singleton_method_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement
+
+#keyset[ruby_singleton_method, index]
+ruby_singleton_method_child(
+ int ruby_singleton_method: @ruby_singleton_method ref,
+ int index: int ref,
+ unique int child: @ruby_singleton_method_child_type ref
+);
+
+ruby_singleton_method_def(
+ unique int id: @ruby_singleton_method,
+ int name: @ruby_underscore_method_name ref,
+ int object: @ruby_singleton_method_object_type ref,
+ int loc: @location ref
+);
+
+ruby_splat_argument_def(
+ unique int id: @ruby_splat_argument,
+ int child: @ruby_underscore_arg ref,
+ int loc: @location ref
+);
+
+ruby_splat_parameter_name(
+ unique int ruby_splat_parameter: @ruby_splat_parameter ref,
+ unique int name: @ruby_token_identifier ref
+);
+
+ruby_splat_parameter_def(
+ unique int id: @ruby_splat_parameter,
+ int loc: @location ref
+);
+
+@ruby_string_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content
+
+#keyset[ruby_string__, index]
+ruby_string_child(
+ int ruby_string__: @ruby_string__ ref,
+ int index: int ref,
+ unique int child: @ruby_string_child_type ref
+);
+
+ruby_string_def(
+ unique int id: @ruby_string__,
+ int loc: @location ref
+);
+
+#keyset[ruby_string_array, index]
+ruby_string_array_child(
+ int ruby_string_array: @ruby_string_array ref,
+ int index: int ref,
+ unique int child: @ruby_bare_string ref
+);
+
+ruby_string_array_def(
+ unique int id: @ruby_string_array,
+ int loc: @location ref
+);
+
+@ruby_subshell_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content
+
+#keyset[ruby_subshell, index]
+ruby_subshell_child(
+ int ruby_subshell: @ruby_subshell ref,
+ int index: int ref,
+ unique int child: @ruby_subshell_child_type ref
+);
+
+ruby_subshell_def(
+ unique int id: @ruby_subshell,
+ int loc: @location ref
+);
+
+@ruby_superclass_child_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield
+
+ruby_superclass_def(
+ unique int id: @ruby_superclass,
+ int child: @ruby_superclass_child_type ref,
+ int loc: @location ref
+);
+
+#keyset[ruby_symbol_array, index]
+ruby_symbol_array_child(
+ int ruby_symbol_array: @ruby_symbol_array ref,
+ int index: int ref,
+ unique int child: @ruby_bare_symbol ref
+);
+
+ruby_symbol_array_def(
+ unique int id: @ruby_symbol_array,
+ int loc: @location ref
+);
+
+@ruby_then_child_type = @ruby_token_empty_statement | @ruby_underscore_statement
+
+#keyset[ruby_then, index]
+ruby_then_child(
+ int ruby_then: @ruby_then ref,
+ int index: int ref,
+ unique int child: @ruby_then_child_type ref
+);
+
+ruby_then_def(
+ unique int id: @ruby_then,
+ int loc: @location ref
+);
+
+@ruby_unary_operand_type = @ruby_break | @ruby_call | @ruby_next | @ruby_parenthesized_statements | @ruby_return | @ruby_token_float | @ruby_token_integer | @ruby_underscore_arg | @ruby_yield
+
+case @ruby_unary.operator of
+ 0 = @ruby_unary_bang
+| 1 = @ruby_unary_plus
+| 2 = @ruby_unary_minus
+| 3 = @ruby_unary_definedquestion
+| 4 = @ruby_unary_not
+| 5 = @ruby_unary_tilde
+;
+
+
+ruby_unary_def(
+ unique int id: @ruby_unary,
+ int operand: @ruby_unary_operand_type ref,
+ int operator: int ref,
+ int loc: @location ref
+);
+
+#keyset[ruby_undef, index]
+ruby_undef_child(
+ int ruby_undef: @ruby_undef ref,
+ int index: int ref,
+ unique int child: @ruby_underscore_method_name ref
+);
+
+ruby_undef_def(
+ unique int id: @ruby_undef,
+ int loc: @location ref
+);
+
+@ruby_unless_alternative_type = @ruby_else | @ruby_elsif
+
+ruby_unless_alternative(
+ unique int ruby_unless: @ruby_unless ref,
+ unique int alternative: @ruby_unless_alternative_type ref
+);
+
+ruby_unless_consequence(
+ unique int ruby_unless: @ruby_unless ref,
+ unique int consequence: @ruby_then ref
+);
+
+ruby_unless_def(
+ unique int id: @ruby_unless,
+ int condition: @ruby_underscore_statement ref,
+ int loc: @location ref
+);
+
+@ruby_unless_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield
+
+ruby_unless_modifier_def(
+ unique int id: @ruby_unless_modifier,
+ int body: @ruby_underscore_statement ref,
+ int condition: @ruby_unless_modifier_condition_type ref,
+ int loc: @location ref
+);
+
+ruby_until_def(
+ unique int id: @ruby_until,
+ int body: @ruby_do ref,
+ int condition: @ruby_underscore_statement ref,
+ int loc: @location ref
+);
+
+@ruby_until_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield
+
+ruby_until_modifier_def(
+ unique int id: @ruby_until_modifier,
+ int body: @ruby_underscore_statement ref,
+ int condition: @ruby_until_modifier_condition_type ref,
+ int loc: @location ref
+);
+
+ruby_when_body(
+ unique int ruby_when: @ruby_when ref,
+ unique int body: @ruby_then ref
+);
+
+#keyset[ruby_when, index]
+ruby_when_pattern(
+ int ruby_when: @ruby_when ref,
+ int index: int ref,
+ unique int pattern: @ruby_pattern ref
+);
+
+ruby_when_def(
+ unique int id: @ruby_when,
+ int loc: @location ref
+);
+
+ruby_while_def(
+ unique int id: @ruby_while,
+ int body: @ruby_do ref,
+ int condition: @ruby_underscore_statement ref,
+ int loc: @location ref
+);
+
+@ruby_while_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield
+
+ruby_while_modifier_def(
+ unique int id: @ruby_while_modifier,
+ int body: @ruby_underscore_statement ref,
+ int condition: @ruby_while_modifier_condition_type ref,
+ int loc: @location ref
+);
+
+ruby_yield_child(
+ unique int ruby_yield: @ruby_yield ref,
+ unique int child: @ruby_argument_list ref
+);
+
+ruby_yield_def(
+ unique int id: @ruby_yield,
+ int loc: @location ref
+);
+
+ruby_tokeninfo(
+ unique int id: @ruby_token,
+ int kind: int ref,
+ string value: string ref,
+ int loc: @location ref
+);
+
+case @ruby_token.kind of
+ 0 = @ruby_reserved_word
+| 1 = @ruby_token_character
+| 2 = @ruby_token_class_variable
+| 3 = @ruby_token_comment
+| 4 = @ruby_token_complex
+| 5 = @ruby_token_constant
+| 6 = @ruby_token_empty_statement
+| 7 = @ruby_token_escape_sequence
+| 8 = @ruby_token_false
+| 9 = @ruby_token_float
+| 10 = @ruby_token_forward_argument
+| 11 = @ruby_token_forward_parameter
+| 12 = @ruby_token_global_variable
+| 13 = @ruby_token_hash_key_symbol
+| 14 = @ruby_token_heredoc_beginning
+| 15 = @ruby_token_heredoc_content
+| 16 = @ruby_token_heredoc_end
+| 17 = @ruby_token_identifier
+| 18 = @ruby_token_instance_variable
+| 19 = @ruby_token_integer
+| 20 = @ruby_token_nil
+| 21 = @ruby_token_operator
+| 22 = @ruby_token_self
+| 23 = @ruby_token_simple_symbol
+| 24 = @ruby_token_string_content
+| 25 = @ruby_token_super
+| 26 = @ruby_token_true
+| 27 = @ruby_token_uninterpreted
+;
+
+
+@ruby_ast_node = @ruby_alias | @ruby_argument_list | @ruby_array | @ruby_assignment | @ruby_bare_string | @ruby_bare_symbol | @ruby_begin | @ruby_begin_block | @ruby_binary | @ruby_block | @ruby_block_argument | @ruby_block_parameter | @ruby_block_parameters | @ruby_break | @ruby_call | @ruby_case__ | @ruby_chained_string | @ruby_class | @ruby_conditional | @ruby_delimited_symbol | @ruby_destructured_left_assignment | @ruby_destructured_parameter | @ruby_do | @ruby_do_block | @ruby_element_reference | @ruby_else | @ruby_elsif | @ruby_end_block | @ruby_ensure | @ruby_exception_variable | @ruby_exceptions | @ruby_for | @ruby_hash | @ruby_hash_splat_argument | @ruby_hash_splat_parameter | @ruby_heredoc_body | @ruby_if | @ruby_if_modifier | @ruby_in | @ruby_interpolation | @ruby_keyword_parameter | @ruby_lambda | @ruby_lambda_parameters | @ruby_left_assignment_list | @ruby_method | @ruby_method_parameters | @ruby_module | @ruby_next | @ruby_operator_assignment | @ruby_optional_parameter | @ruby_pair | @ruby_parenthesized_statements | @ruby_pattern | @ruby_program | @ruby_range | @ruby_rational | @ruby_redo | @ruby_regex | @ruby_rescue | @ruby_rescue_modifier | @ruby_rest_assignment | @ruby_retry | @ruby_return | @ruby_right_assignment_list | @ruby_scope_resolution | @ruby_setter | @ruby_singleton_class | @ruby_singleton_method | @ruby_splat_argument | @ruby_splat_parameter | @ruby_string__ | @ruby_string_array | @ruby_subshell | @ruby_superclass | @ruby_symbol_array | @ruby_then | @ruby_token | @ruby_unary | @ruby_undef | @ruby_unless | @ruby_unless_modifier | @ruby_until | @ruby_until_modifier | @ruby_when | @ruby_while | @ruby_while_modifier | @ruby_yield
+
+@ruby_ast_node_parent = @file | @ruby_ast_node
+
+#keyset[parent, parent_index]
+ruby_ast_node_parent(
+ int child: @ruby_ast_node ref,
+ int parent: @ruby_ast_node_parent ref,
+ int parent_index: int ref
+);
+
+erb_comment_directive_def(
+ unique int id: @erb_comment_directive,
+ int child: @erb_token_comment ref,
+ int loc: @location ref
+);
+
+erb_directive_def(
+ unique int id: @erb_directive,
+ int child: @erb_token_code ref,
+ int loc: @location ref
+);
+
+erb_graphql_directive_def(
+ unique int id: @erb_graphql_directive,
+ int child: @erb_token_code ref,
+ int loc: @location ref
+);
+
+erb_output_directive_def(
+ unique int id: @erb_output_directive,
+ int child: @erb_token_code ref,
+ int loc: @location ref
+);
+
+@erb_template_child_type = @erb_comment_directive | @erb_directive | @erb_graphql_directive | @erb_output_directive | @erb_token_content
+
+#keyset[erb_template, index]
+erb_template_child(
+ int erb_template: @erb_template ref,
+ int index: int ref,
+ unique int child: @erb_template_child_type ref
+);
+
+erb_template_def(
+ unique int id: @erb_template,
+ int loc: @location ref
+);
+
+erb_tokeninfo(
+ unique int id: @erb_token,
+ int kind: int ref,
+ string value: string ref,
+ int loc: @location ref
+);
+
+case @erb_token.kind of
+ 0 = @erb_reserved_word
+| 1 = @erb_token_code
+| 2 = @erb_token_comment
+| 3 = @erb_token_content
+;
+
+
+@erb_ast_node = @erb_comment_directive | @erb_directive | @erb_graphql_directive | @erb_output_directive | @erb_template | @erb_token
+
+@erb_ast_node_parent = @erb_ast_node | @file
+
+#keyset[parent, parent_index]
+erb_ast_node_parent(
+ int child: @erb_ast_node ref,
+ int parent: @erb_ast_node_parent ref,
+ int parent_index: int ref
+);
+
diff --git a/repo-tests/codeql/ruby/ql/lib/ruby.qll b/repo-tests/codeql/ruby/ql/lib/ruby.qll
new file mode 100644
index 00000000000..18468c9f8cf
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/ruby.qll
@@ -0,0 +1 @@
+import codeql.ruby.AST
diff --git a/repo-tests/codeql/ruby/ql/lib/tutorial.qll b/repo-tests/codeql/ruby/ql/lib/tutorial.qll
new file mode 100644
index 00000000000..8cb1797a532
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/lib/tutorial.qll
@@ -0,0 +1,1207 @@
+/**
+ * This library is used in the QL detective tutorials.
+ *
+ * Note: Data is usually stored in a separate database and the QL libraries only contain predicates,
+ * but for this tutorial both the data and the predicates are stored in the library.
+ */
+class Person extends string {
+ Person() {
+ this = "Ronil" or
+ this = "Dina" or
+ this = "Ravi" or
+ this = "Bruce" or
+ this = "Jo" or
+ this = "Aida" or
+ this = "Esme" or
+ this = "Charlie" or
+ this = "Fred" or
+ this = "Meera" or
+ this = "Maya" or
+ this = "Chad" or
+ this = "Tiana" or
+ this = "Laura" or
+ this = "George" or
+ this = "Will" or
+ this = "Mary" or
+ this = "Almira" or
+ this = "Susannah" or
+ this = "Rhoda" or
+ this = "Cynthia" or
+ this = "Eunice" or
+ this = "Olive" or
+ this = "Virginia" or
+ this = "Angeline" or
+ this = "Helen" or
+ this = "Cornelia" or
+ this = "Harriet" or
+ this = "Mahala" or
+ this = "Abby" or
+ this = "Margaret" or
+ this = "Deb" or
+ this = "Minerva" or
+ this = "Severus" or
+ this = "Lavina" or
+ this = "Adeline" or
+ this = "Cath" or
+ this = "Elisa" or
+ this = "Lucretia" or
+ this = "Anne" or
+ this = "Eleanor" or
+ this = "Joanna" or
+ this = "Adam" or
+ this = "Agnes" or
+ this = "Rosanna" or
+ this = "Clara" or
+ this = "Melissa" or
+ this = "Amy" or
+ this = "Isabel" or
+ this = "Jemima" or
+ this = "Cordelia" or
+ this = "Melinda" or
+ this = "Delila" or
+ this = "Jeremiah" or
+ this = "Elijah" or
+ this = "Hester" or
+ this = "Walter" or
+ this = "Oliver" or
+ this = "Hugh" or
+ this = "Aaron" or
+ this = "Reuben" or
+ this = "Eli" or
+ this = "Amos" or
+ this = "Augustus" or
+ this = "Theodore" or
+ this = "Ira" or
+ this = "Timothy" or
+ this = "Cyrus" or
+ this = "Horace" or
+ this = "Simon" or
+ this = "Asa" or
+ this = "Frank" or
+ this = "Nelson" or
+ this = "Leonard" or
+ this = "Harrison" or
+ this = "Anthony" or
+ this = "Louis" or
+ this = "Milton" or
+ this = "Noah" or
+ this = "Cornelius" or
+ this = "Abdul" or
+ this = "Warren" or
+ this = "Harvey" or
+ this = "Dennis" or
+ this = "Wesley" or
+ this = "Sylvester" or
+ this = "Gilbert" or
+ this = "Sullivan" or
+ this = "Edmund" or
+ this = "Wilson" or
+ this = "Perry" or
+ this = "Matthew" or
+ this = "Simba" or
+ this = "Nala" or
+ this = "Rafiki" or
+ this = "Shenzi" or
+ this = "Ernest" or
+ this = "Gertrude" or
+ this = "Oscar" or
+ this = "Lilian" or
+ this = "Raymond" or
+ this = "Elgar" or
+ this = "Elmer" or
+ this = "Herbert" or
+ this = "Maude" or
+ this = "Mae" or
+ this = "Otto" or
+ this = "Edwin" or
+ this = "Ophelia" or
+ this = "Parsley" or
+ this = "Sage" or
+ this = "Rosemary" or
+ this = "Thyme" or
+ this = "Garfunkel" or
+ this = "King Basil" or
+ this = "Stephen"
+ }
+
+ /** Gets the hair color of the person. If the person is bald, there is no result. */
+ string getHairColor() {
+ this = "Ronil" and result = "black"
+ or
+ this = "Dina" and result = "black"
+ or
+ this = "Ravi" and result = "black"
+ or
+ this = "Bruce" and result = "brown"
+ or
+ this = "Jo" and result = "red"
+ or
+ this = "Aida" and result = "blond"
+ or
+ this = "Esme" and result = "blond"
+ or
+ this = "Fred" and result = "gray"
+ or
+ this = "Meera" and result = "brown"
+ or
+ this = "Maya" and result = "brown"
+ or
+ this = "Chad" and result = "brown"
+ or
+ this = "Tiana" and result = "black"
+ or
+ this = "Laura" and result = "blond"
+ or
+ this = "George" and result = "blond"
+ or
+ this = "Will" and result = "blond"
+ or
+ this = "Mary" and result = "blond"
+ or
+ this = "Almira" and result = "black"
+ or
+ this = "Susannah" and result = "blond"
+ or
+ this = "Rhoda" and result = "blond"
+ or
+ this = "Cynthia" and result = "gray"
+ or
+ this = "Eunice" and result = "white"
+ or
+ this = "Olive" and result = "brown"
+ or
+ this = "Virginia" and result = "brown"
+ or
+ this = "Angeline" and result = "red"
+ or
+ this = "Helen" and result = "white"
+ or
+ this = "Cornelia" and result = "gray"
+ or
+ this = "Harriet" and result = "white"
+ or
+ this = "Mahala" and result = "black"
+ or
+ this = "Abby" and result = "red"
+ or
+ this = "Margaret" and result = "brown"
+ or
+ this = "Deb" and result = "brown"
+ or
+ this = "Minerva" and result = "brown"
+ or
+ this = "Severus" and result = "black"
+ or
+ this = "Lavina" and result = "brown"
+ or
+ this = "Adeline" and result = "brown"
+ or
+ this = "Cath" and result = "brown"
+ or
+ this = "Elisa" and result = "brown"
+ or
+ this = "Lucretia" and result = "gray"
+ or
+ this = "Anne" and result = "black"
+ or
+ this = "Eleanor" and result = "brown"
+ or
+ this = "Joanna" and result = "brown"
+ or
+ this = "Adam" and result = "black"
+ or
+ this = "Agnes" and result = "black"
+ or
+ this = "Rosanna" and result = "gray"
+ or
+ this = "Clara" and result = "blond"
+ or
+ this = "Melissa" and result = "brown"
+ or
+ this = "Amy" and result = "brown"
+ or
+ this = "Isabel" and result = "black"
+ or
+ this = "Jemima" and result = "red"
+ or
+ this = "Cordelia" and result = "red"
+ or
+ this = "Melinda" and result = "gray"
+ or
+ this = "Delila" and result = "white"
+ or
+ this = "Jeremiah" and result = "gray"
+ or
+ this = "Hester" and result = "black"
+ or
+ this = "Walter" and result = "black"
+ or
+ this = "Aaron" and result = "gray"
+ or
+ this = "Reuben" and result = "gray"
+ or
+ this = "Eli" and result = "gray"
+ or
+ this = "Amos" and result = "white"
+ or
+ this = "Augustus" and result = "white"
+ or
+ this = "Theodore" and result = "white"
+ or
+ this = "Timothy" and result = "brown"
+ or
+ this = "Cyrus" and result = "brown"
+ or
+ this = "Horace" and result = "brown"
+ or
+ this = "Simon" and result = "brown"
+ or
+ this = "Asa" and result = "brown"
+ or
+ this = "Frank" and result = "brown"
+ or
+ this = "Nelson" and result = "black"
+ or
+ this = "Leonard" and result = "black"
+ or
+ this = "Harrison" and result = "black"
+ or
+ this = "Anthony" and result = "black"
+ or
+ this = "Louis" and result = "black"
+ or
+ this = "Milton" and result = "blond"
+ or
+ this = "Noah" and result = "blond"
+ or
+ this = "Cornelius" and result = "red"
+ or
+ this = "Abdul" and result = "brown"
+ or
+ this = "Warren" and result = "red"
+ or
+ this = "Harvey" and result = "blond"
+ or
+ this = "Dennis" and result = "blond"
+ or
+ this = "Wesley" and result = "brown"
+ or
+ this = "Sylvester" and result = "brown"
+ or
+ this = "Gilbert" and result = "brown"
+ or
+ this = "Sullivan" and result = "brown"
+ or
+ this = "Edmund" and result = "brown"
+ or
+ this = "Wilson" and result = "blond"
+ or
+ this = "Perry" and result = "black"
+ or
+ this = "Simba" and result = "brown"
+ or
+ this = "Nala" and result = "brown"
+ or
+ this = "Rafiki" and result = "red"
+ or
+ this = "Shenzi" and result = "gray"
+ or
+ this = "Ernest" and result = "blond"
+ or
+ this = "Gertrude" and result = "brown"
+ or
+ this = "Oscar" and result = "blond"
+ or
+ this = "Lilian" and result = "brown"
+ or
+ this = "Raymond" and result = "brown"
+ or
+ this = "Elgar" and result = "brown"
+ or
+ this = "Elmer" and result = "brown"
+ or
+ this = "Herbert" and result = "brown"
+ or
+ this = "Maude" and result = "brown"
+ or
+ this = "Mae" and result = "brown"
+ or
+ this = "Otto" and result = "black"
+ or
+ this = "Edwin" and result = "black"
+ or
+ this = "Ophelia" and result = "brown"
+ or
+ this = "Parsley" and result = "brown"
+ or
+ this = "Sage" and result = "brown"
+ or
+ this = "Rosemary" and result = "brown"
+ or
+ this = "Thyme" and result = "brown"
+ or
+ this = "Garfunkel" and result = "brown"
+ or
+ this = "King Basil" and result = "brown"
+ or
+ this = "Stephen" and result = "black"
+ or
+ this = "Stephen" and result = "gray"
+ }
+
+ /** Gets the age of the person (in years). If the person is deceased, there is no result. */
+ int getAge() {
+ this = "Ronil" and result = 21
+ or
+ this = "Dina" and result = 53
+ or
+ this = "Ravi" and result = 16
+ or
+ this = "Bruce" and result = 35
+ or
+ this = "Jo" and result = 47
+ or
+ this = "Aida" and result = 26
+ or
+ this = "Esme" and result = 25
+ or
+ this = "Charlie" and result = 31
+ or
+ this = "Fred" and result = 68
+ or
+ this = "Meera" and result = 62
+ or
+ this = "Maya" and result = 29
+ or
+ this = "Chad" and result = 49
+ or
+ this = "Tiana" and result = 18
+ or
+ this = "Laura" and result = 2
+ or
+ this = "George" and result = 3
+ or
+ this = "Will" and result = 41
+ or
+ this = "Mary" and result = 51
+ or
+ this = "Almira" and result = 1
+ or
+ this = "Susannah" and result = 97
+ or
+ this = "Rhoda" and result = 39
+ or
+ this = "Cynthia" and result = 89
+ or
+ this = "Eunice" and result = 83
+ or
+ this = "Olive" and result = 25
+ or
+ this = "Virginia" and result = 52
+ or
+ this = "Angeline" and result = 22
+ or
+ this = "Helen" and result = 79
+ or
+ this = "Cornelia" and result = 59
+ or
+ this = "Harriet" and result = 57
+ or
+ this = "Mahala" and result = 61
+ or
+ this = "Abby" and result = 24
+ or
+ this = "Margaret" and result = 59
+ or
+ this = "Deb" and result = 31
+ or
+ this = "Minerva" and result = 72
+ or
+ this = "Severus" and result = 61
+ or
+ this = "Lavina" and result = 33
+ or
+ this = "Adeline" and result = 17
+ or
+ this = "Cath" and result = 22
+ or
+ this = "Elisa" and result = 9
+ or
+ this = "Lucretia" and result = 56
+ or
+ this = "Anne" and result = 11
+ or
+ this = "Eleanor" and result = 80
+ or
+ this = "Joanna" and result = 43
+ or
+ this = "Adam" and result = 37
+ or
+ this = "Agnes" and result = 47
+ or
+ this = "Rosanna" and result = 61
+ or
+ this = "Clara" and result = 31
+ or
+ this = "Melissa" and result = 37
+ or
+ this = "Amy" and result = 12
+ or
+ this = "Isabel" and result = 6
+ or
+ this = "Jemima" and result = 16
+ or
+ this = "Cordelia" and result = 21
+ or
+ this = "Melinda" and result = 55
+ or
+ this = "Delila" and result = 66
+ or
+ this = "Jeremiah" and result = 54
+ or
+ this = "Elijah" and result = 42
+ or
+ this = "Hester" and result = 68
+ or
+ this = "Walter" and result = 66
+ or
+ this = "Oliver" and result = 33
+ or
+ this = "Hugh" and result = 51
+ or
+ this = "Aaron" and result = 49
+ or
+ this = "Reuben" and result = 58
+ or
+ this = "Eli" and result = 70
+ or
+ this = "Amos" and result = 65
+ or
+ this = "Augustus" and result = 56
+ or
+ this = "Theodore" and result = 69
+ or
+ this = "Ira" and result = 1
+ or
+ this = "Timothy" and result = 54
+ or
+ this = "Cyrus" and result = 78
+ or
+ this = "Horace" and result = 34
+ or
+ this = "Simon" and result = 23
+ or
+ this = "Asa" and result = 28
+ or
+ this = "Frank" and result = 59
+ or
+ this = "Nelson" and result = 38
+ or
+ this = "Leonard" and result = 58
+ or
+ this = "Harrison" and result = 7
+ or
+ this = "Anthony" and result = 2
+ or
+ this = "Louis" and result = 34
+ or
+ this = "Milton" and result = 36
+ or
+ this = "Noah" and result = 48
+ or
+ this = "Cornelius" and result = 41
+ or
+ this = "Abdul" and result = 67
+ or
+ this = "Warren" and result = 47
+ or
+ this = "Harvey" and result = 31
+ or
+ this = "Dennis" and result = 39
+ or
+ this = "Wesley" and result = 13
+ or
+ this = "Sylvester" and result = 19
+ or
+ this = "Gilbert" and result = 16
+ or
+ this = "Sullivan" and result = 17
+ or
+ this = "Edmund" and result = 29
+ or
+ this = "Wilson" and result = 27
+ or
+ this = "Perry" and result = 31
+ or
+ this = "Matthew" and result = 55
+ or
+ this = "Simba" and result = 8
+ or
+ this = "Nala" and result = 7
+ or
+ this = "Rafiki" and result = 76
+ or
+ this = "Shenzi" and result = 67
+ }
+
+ /** Gets the height of the person (in cm). If the person is deceased, there is no result. */
+ float getHeight() {
+ this = "Ronil" and result = 183.0
+ or
+ this = "Dina" and result = 155.1
+ or
+ this = "Ravi" and result = 175.2
+ or
+ this = "Bruce" and result = 191.3
+ or
+ this = "Jo" and result = 163.4
+ or
+ this = "Aida" and result = 182.6
+ or
+ this = "Esme" and result = 176.9
+ or
+ this = "Charlie" and result = 189.7
+ or
+ this = "Fred" and result = 179.4
+ or
+ this = "Meera" and result = 160.1
+ or
+ this = "Maya" and result = 153.0
+ or
+ this = "Chad" and result = 168.5
+ or
+ this = "Tiana" and result = 149.7
+ or
+ this = "Laura" and result = 87.5
+ or
+ this = "George" and result = 96.4
+ or
+ this = "Will" and result = 167.1
+ or
+ this = "Mary" and result = 159.8
+ or
+ this = "Almira" and result = 62.1
+ or
+ this = "Susannah" and result = 145.8
+ or
+ this = "Rhoda" and result = 180.1
+ or
+ this = "Cynthia" and result = 161.8
+ or
+ this = "Eunice" and result = 153.2
+ or
+ this = "Olive" and result = 179.9
+ or
+ this = "Virginia" and result = 165.1
+ or
+ this = "Angeline" and result = 172.3
+ or
+ this = "Helen" and result = 163.1
+ or
+ this = "Cornelia" and result = 160.8
+ or
+ this = "Harriet" and result = 163.2
+ or
+ this = "Mahala" and result = 157.7
+ or
+ this = "Abby" and result = 174.5
+ or
+ this = "Margaret" and result = 165.6
+ or
+ this = "Deb" and result = 171.6
+ or
+ this = "Minerva" and result = 168.7
+ or
+ this = "Severus" and result = 188.8
+ or
+ this = "Lavina" and result = 155.1
+ or
+ this = "Adeline" and result = 165.5
+ or
+ this = "Cath" and result = 147.8
+ or
+ this = "Elisa" and result = 129.4
+ or
+ this = "Lucretia" and result = 153.6
+ or
+ this = "Anne" and result = 140.4
+ or
+ this = "Eleanor" and result = 151.1
+ or
+ this = "Joanna" and result = 167.2
+ or
+ this = "Adam" and result = 155.5
+ or
+ this = "Agnes" and result = 156.8
+ or
+ this = "Rosanna" and result = 162.4
+ or
+ this = "Clara" and result = 158.6
+ or
+ this = "Melissa" and result = 182.3
+ or
+ this = "Amy" and result = 147.1
+ or
+ this = "Isabel" and result = 121.4
+ or
+ this = "Jemima" and result = 149.8
+ or
+ this = "Cordelia" and result = 151.7
+ or
+ this = "Melinda" and result = 154.4
+ or
+ this = "Delila" and result = 163.4
+ or
+ this = "Jeremiah" and result = 167.5
+ or
+ this = "Elijah" and result = 184.5
+ or
+ this = "Hester" and result = 152.7
+ or
+ this = "Walter" and result = 159.6
+ or
+ this = "Oliver" and result = 192.4
+ or
+ this = "Hugh" and result = 173.1
+ or
+ this = "Aaron" and result = 176.6
+ or
+ this = "Reuben" and result = 169.9
+ or
+ this = "Eli" and result = 180.4
+ or
+ this = "Amos" and result = 167.4
+ or
+ this = "Augustus" and result = 156.5
+ or
+ this = "Theodore" and result = 176.6
+ or
+ this = "Ira" and result = 54.1
+ or
+ this = "Timothy" and result = 172.2
+ or
+ this = "Cyrus" and result = 157.9
+ or
+ this = "Horace" and result = 169.3
+ or
+ this = "Simon" and result = 157.1
+ or
+ this = "Asa" and result = 149.4
+ or
+ this = "Frank" and result = 167.2
+ or
+ this = "Nelson" and result = 173.0
+ or
+ this = "Leonard" and result = 172.0
+ or
+ this = "Harrison" and result = 126.0
+ or
+ this = "Anthony" and result = 98.4
+ or
+ this = "Louis" and result = 186.8
+ or
+ this = "Milton" and result = 157.8
+ or
+ this = "Noah" and result = 190.5
+ or
+ this = "Cornelius" and result = 183.1
+ or
+ this = "Abdul" and result = 182.0
+ or
+ this = "Warren" and result = 175.0
+ or
+ this = "Harvey" and result = 169.3
+ or
+ this = "Dennis" and result = 160.4
+ or
+ this = "Wesley" and result = 139.8
+ or
+ this = "Sylvester" and result = 188.2
+ or
+ this = "Gilbert" and result = 177.6
+ or
+ this = "Sullivan" and result = 168.3
+ or
+ this = "Edmund" and result = 159.2
+ or
+ this = "Wilson" and result = 167.6
+ or
+ this = "Perry" and result = 189.1
+ or
+ this = "Matthew" and result = 167.2
+ or
+ this = "Simba" and result = 140.1
+ or
+ this = "Nala" and result = 138.0
+ or
+ this = "Rafiki" and result = 139.3
+ or
+ this = "Shenzi" and result = 171.1
+ }
+
+ /** Gets the location of the person's home ("north", "south", "east", or "west"). If the person is deceased, there is no result. */
+ string getLocation() {
+ this = "Ronil" and result = "north"
+ or
+ this = "Dina" and result = "north"
+ or
+ this = "Ravi" and result = "north"
+ or
+ this = "Bruce" and result = "south"
+ or
+ this = "Jo" and result = "west"
+ or
+ this = "Aida" and result = "east"
+ or
+ this = "Esme" and result = "east"
+ or
+ this = "Charlie" and result = "south"
+ or
+ this = "Fred" and result = "west"
+ or
+ this = "Meera" and result = "south"
+ or
+ this = "Maya" and result = "south"
+ or
+ this = "Chad" and result = "south"
+ or
+ this = "Tiana" and result = "west"
+ or
+ this = "Laura" and result = "south"
+ or
+ this = "George" and result = "south"
+ or
+ this = "Will" and result = "south"
+ or
+ this = "Mary" and result = "south"
+ or
+ this = "Almira" and result = "south"
+ or
+ this = "Susannah" and result = "north"
+ or
+ this = "Rhoda" and result = "north"
+ or
+ this = "Cynthia" and result = "north"
+ or
+ this = "Eunice" and result = "north"
+ or
+ this = "Olive" and result = "west"
+ or
+ this = "Virginia" and result = "west"
+ or
+ this = "Angeline" and result = "west"
+ or
+ this = "Helen" and result = "west"
+ or
+ this = "Cornelia" and result = "east"
+ or
+ this = "Harriet" and result = "east"
+ or
+ this = "Mahala" and result = "east"
+ or
+ this = "Abby" and result = "east"
+ or
+ this = "Margaret" and result = "east"
+ or
+ this = "Deb" and result = "east"
+ or
+ this = "Minerva" and result = "south"
+ or
+ this = "Severus" and result = "north"
+ or
+ this = "Lavina" and result = "east"
+ or
+ this = "Adeline" and result = "west"
+ or
+ this = "Cath" and result = "east"
+ or
+ this = "Elisa" and result = "east"
+ or
+ this = "Lucretia" and result = "north"
+ or
+ this = "Anne" and result = "north"
+ or
+ this = "Eleanor" and result = "south"
+ or
+ this = "Joanna" and result = "south"
+ or
+ this = "Adam" and result = "east"
+ or
+ this = "Agnes" and result = "east"
+ or
+ this = "Rosanna" and result = "east"
+ or
+ this = "Clara" and result = "east"
+ or
+ this = "Melissa" and result = "west"
+ or
+ this = "Amy" and result = "west"
+ or
+ this = "Isabel" and result = "west"
+ or
+ this = "Jemima" and result = "west"
+ or
+ this = "Cordelia" and result = "west"
+ or
+ this = "Melinda" and result = "west"
+ or
+ this = "Delila" and result = "south"
+ or
+ this = "Jeremiah" and result = "north"
+ or
+ this = "Elijah" and result = "north"
+ or
+ this = "Hester" and result = "east"
+ or
+ this = "Walter" and result = "east"
+ or
+ this = "Oliver" and result = "east"
+ or
+ this = "Hugh" and result = "south"
+ or
+ this = "Aaron" and result = "south"
+ or
+ this = "Reuben" and result = "west"
+ or
+ this = "Eli" and result = "west"
+ or
+ this = "Amos" and result = "east"
+ or
+ this = "Augustus" and result = "south"
+ or
+ this = "Theodore" and result = "west"
+ or
+ this = "Ira" and result = "south"
+ or
+ this = "Timothy" and result = "north"
+ or
+ this = "Cyrus" and result = "north"
+ or
+ this = "Horace" and result = "east"
+ or
+ this = "Simon" and result = "east"
+ or
+ this = "Asa" and result = "east"
+ or
+ this = "Frank" and result = "west"
+ or
+ this = "Nelson" and result = "west"
+ or
+ this = "Leonard" and result = "west"
+ or
+ this = "Harrison" and result = "north"
+ or
+ this = "Anthony" and result = "north"
+ or
+ this = "Louis" and result = "north"
+ or
+ this = "Milton" and result = "south"
+ or
+ this = "Noah" and result = "south"
+ or
+ this = "Cornelius" and result = "east"
+ or
+ this = "Abdul" and result = "east"
+ or
+ this = "Warren" and result = "west"
+ or
+ this = "Harvey" and result = "west"
+ or
+ this = "Dennis" and result = "west"
+ or
+ this = "Wesley" and result = "west"
+ or
+ this = "Sylvester" and result = "south"
+ or
+ this = "Gilbert" and result = "east"
+ or
+ this = "Sullivan" and result = "east"
+ or
+ this = "Edmund" and result = "north"
+ or
+ this = "Wilson" and result = "north"
+ or
+ this = "Perry" and result = "west"
+ or
+ this = "Matthew" and result = "east"
+ or
+ this = "Simba" and result = "south"
+ or
+ this = "Nala" and result = "south"
+ or
+ this = "Rafiki" and result = "north"
+ or
+ this = "Shenzi" and result = "west"
+ }
+
+ /** Holds if the person is deceased. */
+ predicate isDeceased() {
+ this = "Ernest" or
+ this = "Gertrude" or
+ this = "Oscar" or
+ this = "Lilian" or
+ this = "Edwin" or
+ this = "Raymond" or
+ this = "Elgar" or
+ this = "Elmer" or
+ this = "Herbert" or
+ this = "Maude" or
+ this = "Mae" or
+ this = "Otto" or
+ this = "Ophelia" or
+ this = "Parsley" or
+ this = "Sage" or
+ this = "Rosemary" or
+ this = "Thyme" or
+ this = "Garfunkel" or
+ this = "King Basil"
+ }
+
+ /** Gets a parent of the person (alive or deceased). */
+ Person getAParent() {
+ this = "Stephen" and result = "Edmund"
+ or
+ this = "Edmund" and result = "Augustus"
+ or
+ this = "Augustus" and result = "Stephen"
+ or
+ this = "Abby" and result = "Cornelia"
+ or
+ this = "Abby" and result = "Amos"
+ or
+ this = "Abdul" and result = "Susannah"
+ or
+ this = "Adam" and result = "Amos"
+ or
+ this = "Adeline" and result = "Melinda"
+ or
+ this = "Adeline" and result = "Frank"
+ or
+ this = "Agnes" and result = "Abdul"
+ or
+ this = "Aida" and result = "Agnes"
+ or
+ this = "Almira" and result = "Sylvester"
+ or
+ this = "Amos" and result = "Eunice"
+ or
+ this = "Amy" and result = "Noah"
+ or
+ this = "Amy" and result = "Chad"
+ or
+ this = "Angeline" and result = "Reuben"
+ or
+ this = "Angeline" and result = "Lucretia"
+ or
+ this = "Anne" and result = "Rhoda"
+ or
+ this = "Anne" and result = "Louis"
+ or
+ this = "Anthony" and result = "Lavina"
+ or
+ this = "Anthony" and result = "Asa"
+ or
+ this = "Asa" and result = "Cornelia"
+ or
+ this = "Cath" and result = "Harriet"
+ or
+ this = "Charlie" and result = "Matthew"
+ or
+ this = "Clara" and result = "Ernest"
+ or
+ this = "Cornelia" and result = "Cynthia"
+ or
+ this = "Cornelius" and result = "Eli"
+ or
+ this = "Deb" and result = "Margaret"
+ or
+ this = "Dennis" and result = "Fred"
+ or
+ this = "Eli" and result = "Susannah"
+ or
+ this = "Elijah" and result = "Delila"
+ or
+ this = "Elisa" and result = "Deb"
+ or
+ this = "Elisa" and result = "Horace"
+ or
+ this = "Esme" and result = "Margaret"
+ or
+ this = "Frank" and result = "Eleanor"
+ or
+ this = "Frank" and result = "Cyrus"
+ or
+ this = "George" and result = "Maya"
+ or
+ this = "George" and result = "Wilson"
+ or
+ this = "Gilbert" and result = "Cornelius"
+ or
+ this = "Harriet" and result = "Cynthia"
+ or
+ this = "Harrison" and result = "Louis"
+ or
+ this = "Harvey" and result = "Fred"
+ or
+ this = "Helen" and result = "Susannah"
+ or
+ this = "Hester" and result = "Edwin"
+ or
+ this = "Hugh" and result = "Cyrus"
+ or
+ this = "Hugh" and result = "Helen"
+ or
+ this = "Ira" and result = "Maya"
+ or
+ this = "Ira" and result = "Wilson"
+ or
+ this = "Isabel" and result = "Perry"
+ or
+ this = "Isabel" and result = "Harvey"
+ or
+ this = "Jemima" and result = "Melinda"
+ or
+ this = "Jemima" and result = "Frank"
+ or
+ this = "Ernest" and result = "Lilian"
+ or
+ this = "Ernest" and result = "Oscar"
+ or
+ this = "Gertrude" and result = "Ophelia"
+ or
+ this = "Gertrude" and result = "Raymond"
+ or
+ this = "Lilian" and result = "Elgar"
+ or
+ this = "Lilian" and result = "Mae"
+ or
+ this = "Raymond" and result = "Elgar"
+ or
+ this = "Raymond" and result = "Mae"
+ or
+ this = "Elmer" and result = "Ophelia"
+ or
+ this = "Elmer" and result = "Raymond"
+ or
+ this = "Herbert" and result = "Ophelia"
+ or
+ this = "Herbert" and result = "Raymond"
+ or
+ this = "Maude" and result = "Ophelia"
+ or
+ this = "Maude" and result = "Raymond"
+ or
+ this = "Otto" and result = "Elgar"
+ or
+ this = "Otto" and result = "Mae"
+ or
+ this = "Edwin" and result = "Otto"
+ or
+ this = "Parsley" and result = "Simon"
+ or
+ this = "Parsley" and result = "Garfunkel"
+ or
+ this = "Sage" and result = "Simon"
+ or
+ this = "Sage" and result = "Garfunkel"
+ or
+ this = "Rosemary" and result = "Simon"
+ or
+ this = "Rosemary" and result = "Garfunkel"
+ or
+ this = "Thyme" and result = "Simon"
+ or
+ this = "Thyme" and result = "Garfunkel"
+ or
+ this = "King Basil" and result = "Ophelia"
+ or
+ this = "King Basil" and result = "Raymond"
+ or
+ this = "Jo" and result = "Theodore"
+ or
+ this = "Joanna" and result = "Shenzi"
+ or
+ this = "Laura" and result = "Maya"
+ or
+ this = "Laura" and result = "Wilson"
+ or
+ this = "Lavina" and result = "Mahala"
+ or
+ this = "Lavina" and result = "Walter"
+ or
+ this = "Leonard" and result = "Cyrus"
+ or
+ this = "Leonard" and result = "Helen"
+ or
+ this = "Lucretia" and result = "Eleanor"
+ or
+ this = "Lucretia" and result = "Cyrus"
+ or
+ this = "Mahala" and result = "Eunice"
+ or
+ this = "Margaret" and result = "Cynthia"
+ or
+ this = "Matthew" and result = "Cyrus"
+ or
+ this = "Matthew" and result = "Helen"
+ or
+ this = "Maya" and result = "Meera"
+ or
+ this = "Melinda" and result = "Rafiki"
+ or
+ this = "Melissa" and result = "Mahala"
+ or
+ this = "Melissa" and result = "Walter"
+ or
+ this = "Nala" and result = "Bruce"
+ or
+ this = "Nelson" and result = "Mahala"
+ or
+ this = "Nelson" and result = "Walter"
+ or
+ this = "Noah" and result = "Eli"
+ or
+ this = "Olive" and result = "Reuben"
+ or
+ this = "Olive" and result = "Lucretia"
+ or
+ this = "Oliver" and result = "Matthew"
+ or
+ this = "Perry" and result = "Leonard"
+ or
+ this = "Ravi" and result = "Dina"
+ or
+ this = "Simba" and result = "Will"
+ or
+ this = "Simon" and result = "Margaret"
+ or
+ this = "Sullivan" and result = "Cornelius"
+ or
+ this = "Sylvester" and result = "Timothy"
+ or
+ this = "Theodore" and result = "Susannah"
+ or
+ this = "Tiana" and result = "Jo"
+ or
+ this = "Virginia" and result = "Helen"
+ or
+ this = "Warren" and result = "Shenzi"
+ or
+ this = "Wesley" and result = "Warren"
+ or
+ this = "Wesley" and result = "Jo"
+ or
+ this = "Will" and result = "Eli"
+ }
+
+ /** Holds if the person is allowed in the region. Initially, all villagers are allowed in every region. */
+ predicate isAllowedIn(string region) {
+ region = "north" or
+ region = "south" or
+ region = "east" or
+ region = "west"
+ }
+}
+
+/** Returns a parent of the person. */
+Person parentOf(Person p) { result = p.getAParent() }
diff --git a/repo-tests/codeql/ruby/ql/src/AlertSuppression.ql b/repo-tests/codeql/ruby/ql/src/AlertSuppression.ql
new file mode 100644
index 00000000000..b10c4ecbb45
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/AlertSuppression.ql
@@ -0,0 +1,82 @@
+/**
+ * @name Alert suppression
+ * @description Generates information about alert suppressions.
+ * @kind alert-suppression
+ * @id rb/alert-suppression
+ */
+
+import ruby
+import codeql.ruby.ast.internal.TreeSitter
+
+/**
+ * An alert suppression comment.
+ */
+class SuppressionComment extends Ruby::Comment {
+ string annotation;
+
+ SuppressionComment() {
+ // suppression comments must be single-line
+ this.getLocation().getStartLine() = this.getLocation().getEndLine() and
+ exists(string text | text = commentText(this) |
+ // match `lgtm[...]` anywhere in the comment
+ annotation = text.regexpFind("(?i)\\blgtm\\s*\\[[^\\]]*\\]", _, _)
+ or
+ // match `lgtm` at the start of the comment and after semicolon
+ annotation = text.regexpFind("(?i)(?<=^|;)\\s*lgtm(?!\\B|\\s*\\[)", _, _).trim()
+ )
+ }
+
+ /**
+ * Gets the text of this suppression comment.
+ */
+ string getText() { result = commentText(this) }
+
+ /** Gets the suppression annotation in this comment. */
+ string getAnnotation() { result = annotation }
+
+ /**
+ * Holds if this comment applies to the range from column `startcolumn` of line `startline`
+ * to column `endcolumn` of line `endline` in file `filepath`.
+ */
+ predicate covers(string filepath, int startline, int startcolumn, int endline, int endcolumn) {
+ this.getLocation().hasLocationInfo(filepath, startline, _, endline, endcolumn) and
+ startcolumn = 1
+ }
+
+ /** Gets the scope of this suppression. */
+ SuppressionScope getScope() { this = result.getSuppressionComment() }
+}
+
+private string commentText(Ruby::Comment comment) { result = comment.getValue().suffix(1) }
+
+/**
+ * The scope of an alert suppression comment.
+ */
+class SuppressionScope extends @ruby_token_comment {
+ SuppressionScope() { this instanceof SuppressionComment }
+
+ /** Gets a suppression comment with this scope. */
+ SuppressionComment getSuppressionComment() { result = this }
+
+ /**
+ * Holds if this element is at the specified location.
+ * The location spans column `startcolumn` of line `startline` to
+ * column `endcolumn` of line `endline` in file `filepath`.
+ * For more information, see
+ * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
+ */
+ predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
+ this.(SuppressionComment).covers(filepath, startline, startcolumn, endline, endcolumn)
+ }
+
+ /** Gets a textual representation of this element. */
+ string toString() { result = "suppression range" }
+}
+
+from SuppressionComment c
+select c, // suppression comment
+ c.getText(), // text of suppression comment (excluding delimiters)
+ c.getAnnotation(), // text of suppression annotation
+ c.getScope() // scope of suppression
diff --git a/repo-tests/codeql/ruby/ql/src/experimental/performance/UseDetect.ql b/repo-tests/codeql/ruby/ql/src/experimental/performance/UseDetect.ql
new file mode 100644
index 00000000000..f5fcf6df4fb
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/experimental/performance/UseDetect.ql
@@ -0,0 +1,64 @@
+/**
+ * @name Use detect
+ * @description Use 'detect' instead of 'select' followed by 'first' or 'last'.
+ * @kind problem
+ * @problem.severity warning
+ * @id rb/use-detect
+ * @tags performance rubocop
+ * @precision high
+ */
+
+// This is an implementation of the Rubocop rule
+// https://github.com/rubocop/rubocop-performance/blob/master/lib/rubocop/cop/performance/detect.rb
+import ruby
+import codeql.ruby.dataflow.SSA
+
+/** A call that extracts the first or last element of a list. */
+class EndCall extends MethodCall {
+ string detect;
+
+ EndCall() {
+ detect = "detect" and
+ (
+ this.getMethodName() = "first" and
+ this.getNumberOfArguments() = 0
+ or
+ this.getNumberOfArguments() = 1 and
+ this.getArgument(0).(IntegerLiteral).getValueText() = "0"
+ )
+ or
+ detect = "reverse_detect" and
+ (
+ this.getMethodName() = "last" and
+ this.getNumberOfArguments() = 0
+ or
+ this.getNumberOfArguments() = 1 and
+ this.getArgument(0).(UnaryMinusExpr).getOperand().(IntegerLiteral).getValueText() = "1"
+ )
+ }
+
+ string detectCall() { result = detect }
+}
+
+Expr getUniqueRead(Expr e) {
+ exists(AssignExpr ae |
+ e = ae.getRightOperand() and
+ forex(Ssa::WriteDefinition def | def.getWriteAccess() = ae.getLeftOperand() |
+ strictcount(def.getARead()) = 1 and
+ not def = any(Ssa::PhiNode phi).getAnInput() and
+ def.getARead() = result.getAControlFlowNode()
+ )
+ )
+}
+
+class SelectBlock extends MethodCall {
+ SelectBlock() {
+ this.getMethodName() in ["select", "filter", "find_all"] and
+ exists(this.getBlock())
+ }
+}
+
+from EndCall call, SelectBlock selectBlock
+where getUniqueRead*(selectBlock) = call.getReceiver()
+select call, "Replace this call and $@ with '" + call.detectCall() + "'.", selectBlock,
+ "'select' call"
diff --git a/repo-tests/codeql/ruby/ql/src/filters/ClassifyFiles.ql b/repo-tests/codeql/ruby/ql/src/filters/ClassifyFiles.ql
new file mode 100644
index 00000000000..d194523e09d
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/filters/ClassifyFiles.ql
@@ -0,0 +1,20 @@
+/**
+ * @name Classify files
+ * @description This query produces a list of all files in a database
+ * that are classified as generated code or test code.
+ *
+ * Used by LGTM.
+ * @kind file-classifier
+ * @id rb/file-classifier
+ */
+
+import ruby
+import codeql.ruby.filters.GeneratedCode
+
+predicate classify(File f, string category) {
+ f instanceof GeneratedCodeFile and category = "generated"
+}
+
+from File f, string category
+where classify(f, category)
+select f, category
diff --git a/repo-tests/codeql/ruby/ql/src/ide-contextual-queries/localDefinitions.ql b/repo-tests/codeql/ruby/ql/src/ide-contextual-queries/localDefinitions.ql
new file mode 100644
index 00000000000..5c71105600c
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/ide-contextual-queries/localDefinitions.ql
@@ -0,0 +1,20 @@
+/**
+ * @name Jump-to-definition links
+ * @description Generates use-definition pairs that provide the data
+ * for jump-to-definition in the code viewer.
+ * @kind definitions
+ * @id rb/ide-jump-to-definition
+ * @tags ide-contextual-queries/local-definitions
+ */
+
+import codeql.IDEContextual
+import codeql.ruby.AST
+
+external string selectedSourceFile();
+
+from AstNode e, Variable def, string kind
+where
+ e = def.getAnAccess() and
+ kind = "local variable" and
+ e.getLocation().getFile() = getFileBySourceArchiveName(selectedSourceFile())
+select e, def, kind
diff --git a/repo-tests/codeql/ruby/ql/src/ide-contextual-queries/localReferences.ql b/repo-tests/codeql/ruby/ql/src/ide-contextual-queries/localReferences.ql
new file mode 100644
index 00000000000..8e39377ec42
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/ide-contextual-queries/localReferences.ql
@@ -0,0 +1,21 @@
+/**
+ * @name Find-references links
+ * @description Generates use-definition pairs that provide the data
+ * for find-references in the code viewer.
+ * @kind definitions
+ * @id rb/ide-find-references
+ * @tags ide-contextual-queries/local-references
+ */
+
+import codeql.IDEContextual
+import codeql.ruby.AST
+import codeql.ruby.ast.Variable
+
+external string selectedSourceFile();
+
+from AstNode e, Variable def, string kind
+where
+ e = def.getAnAccess() and
+ kind = "local variable" and
+ def.getLocation().getFile() = getFileBySourceArchiveName(selectedSourceFile())
+select e, def, kind
diff --git a/repo-tests/codeql/ruby/ql/src/ide-contextual-queries/printAst.ql b/repo-tests/codeql/ruby/ql/src/ide-contextual-queries/printAst.ql
new file mode 100644
index 00000000000..84294e6fdeb
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/ide-contextual-queries/printAst.ql
@@ -0,0 +1,27 @@
+/**
+ * @name Print AST
+ * @description Produces a representation of a file's Abstract Syntax Tree.
+ * This query is used by the VS Code extension.
+ * @id rb/print-ast
+ * @kind graph
+ * @tags ide-contextual-queries/print-ast
+ */
+
+private import codeql.IDEContextual
+private import codeql.ruby.AST
+private import codeql.ruby.printAst
+
+/**
+ * The source file to generate an AST from.
+ */
+external string selectedSourceFile();
+
+/**
+ * Overrides the configuration to print only nodes in the selected source file.
+ */
+class Cfg extends PrintAstConfiguration {
+ override predicate shouldPrintNode(AstNode n) {
+ super.shouldPrintNode(n) and
+ n.getLocation().getFile() = getFileBySourceArchiveName(selectedSourceFile())
+ }
+}
diff --git a/repo-tests/codeql/ruby/ql/src/qlpack.yml b/repo-tests/codeql/ruby/ql/src/qlpack.yml
new file mode 100644
index 00000000000..1c346968c43
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/qlpack.yml
@@ -0,0 +1,7 @@
+name: codeql/ruby-queries
+version: 0.0.2
+suites: codeql-suites
+defaultSuiteFile: codeql-suites/ruby-code-scanning.qls
+dependencies:
+ codeql/ruby-all: "*"
+ codeql/suite-helpers: "*"
diff --git a/repo-tests/codeql/ruby/ql/src/queries/analysis/Definitions.ql b/repo-tests/codeql/ruby/ql/src/queries/analysis/Definitions.ql
new file mode 100644
index 00000000000..efa01dc3388
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/analysis/Definitions.ql
@@ -0,0 +1,88 @@
+/**
+ * @name Definitions
+ * @description Jump to definition helper query.
+ * @kind definitions
+ * @id rb/jump-to-definition
+ */
+
+/*
+ * TODO:
+ * - should `Foo.new` point to `Foo#initialize`?
+ */
+
+import ruby
+import codeql.ruby.ast.internal.Module
+import codeql.ruby.dataflow.SSA
+
+from DefLoc loc, Expr src, Expr target, string kind
+where
+ ConstantDefLoc(src, target) = loc and kind = "constant"
+ or
+ MethodLoc(src, target) = loc and kind = "method"
+ or
+ LocalVariableLoc(src, target) = loc and kind = "variable"
+ or
+ InstanceVariableLoc(src, target) = loc and kind = "instance variable"
+ or
+ ClassVariableLoc(src, target) = loc and kind = "class variable"
+select src, target, kind
+
+/**
+ * Definition location info for different identifiers.
+ * Each branch holds two values that are subclasses of `Expr`.
+ * The first is the "source" - some usage of an identifier.
+ * The second is the "target" - the definition of that identifier.
+ */
+newtype DefLoc =
+ /** A constant, module or class. */
+ ConstantDefLoc(ConstantReadAccess read, ConstantWriteAccess write) {
+ write = definitionOf(resolveConstant(read))
+ } or
+ /** A method call. */
+ MethodLoc(MethodCall call, Method meth) { meth = call.getATarget() } or
+ /** A local variable. */
+ LocalVariableLoc(VariableReadAccess read, VariableWriteAccess write) {
+ exists(Ssa::WriteDefinition w |
+ write = w.getWriteAccess() and
+ read = w.getARead().getExpr() and
+ not read.isSynthesized()
+ )
+ } or
+ /** An instance variable */
+ InstanceVariableLoc(InstanceVariableReadAccess read, InstanceVariableWriteAccess write) {
+ /*
+ * We consider instance variables to be "defined" in the initialize method of their enclosing class.
+ * If that method doesn't exist, we won't provide any jump-to-def information for the instance variable.
+ */
+
+ exists(Method m |
+ m.getAChild+() = write and
+ m.getName() = "initialize" and
+ write.getVariable() = read.getVariable()
+ )
+ } or
+ /** A class variable */
+ ClassVariableLoc(ClassVariableReadAccess read, ClassVariableWriteAccess write) {
+ read.getVariable() = write.getVariable() and
+ not exists(MethodBase m | m.getAChild+() = write)
+ }
+
+/**
+ * Gets the constant write that defines the given constant.
+ * Modules often don't have a unique definition, as they are opened multiple times in different
+ * files. In these cases we arbitrarily pick the definition with the lexicographically least
+ * location.
+ */
+pragma[noinline]
+ConstantWriteAccess definitionOf(string fqn) {
+ fqn = resolveConstant(_) and
+ result =
+ min(ConstantWriteAccess w, Location l |
+ w.getQualifiedName() = fqn and l = w.getLocation()
+ |
+ w
+ order by
+ l.getFile().getAbsolutePath(), l.getStartLine(), l.getStartColumn(), l.getEndLine(),
+ l.getEndColumn()
+ )
+}
diff --git a/repo-tests/codeql/ruby/ql/src/queries/diagnostics/ExtractionErrors.ql b/repo-tests/codeql/ruby/ql/src/queries/diagnostics/ExtractionErrors.ql
new file mode 100644
index 00000000000..5c55d984337
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/diagnostics/ExtractionErrors.ql
@@ -0,0 +1,18 @@
+/**
+ * @name Extraction errors
+ * @description List all extraction errors for files in the source code directory.
+ * @kind diagnostic
+ * @id rb/diagnostics/extraction-errors
+ */
+
+import ruby
+import codeql.ruby.Diagnostics
+
+/** Gets the SARIF severity to associate an error. */
+int getSeverity() { result = 2 }
+
+from ExtractionError error, File f
+where
+ f = error.getLocation().getFile() and
+ exists(f.getRelativePath())
+select error, "Extraction failed in " + f + " with error " + error.getMessage(), getSeverity()
diff --git a/repo-tests/codeql/ruby/ql/src/queries/diagnostics/SuccessfullyExtractedFiles.ql b/repo-tests/codeql/ruby/ql/src/queries/diagnostics/SuccessfullyExtractedFiles.ql
new file mode 100644
index 00000000000..74f95763d8a
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/diagnostics/SuccessfullyExtractedFiles.ql
@@ -0,0 +1,16 @@
+/**
+ * @name Successfully extracted files
+ * @description Lists all files in the source code directory that were extracted
+ * without encountering an error in the file.
+ * @kind diagnostic
+ * @id rb/diagnostics/successfully-extracted-files
+ */
+
+import ruby
+import codeql.ruby.Diagnostics
+
+from File f
+where
+ not exists(ExtractionError e | e.getLocation().getFile() = f) and
+ exists(f.getRelativePath())
+select f, ""
diff --git a/repo-tests/codeql/ruby/ql/src/queries/metrics/FLines.ql b/repo-tests/codeql/ruby/ql/src/queries/metrics/FLines.ql
new file mode 100644
index 00000000000..97c319fbf73
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/metrics/FLines.ql
@@ -0,0 +1,13 @@
+/**
+ * @name Number of lines
+ * @kind metric
+ * @description The number of lines in each file.
+ * @metricType file
+ * @id rb/lines-per-file
+ */
+
+import ruby
+
+from RubyFile f, int n
+where n = f.getNumberOfLines()
+select f, n order by n desc
diff --git a/repo-tests/codeql/ruby/ql/src/queries/metrics/FLinesOfCode.ql b/repo-tests/codeql/ruby/ql/src/queries/metrics/FLinesOfCode.ql
new file mode 100644
index 00000000000..0c1d15960cc
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/metrics/FLinesOfCode.ql
@@ -0,0 +1,14 @@
+/**
+ * @name Lines of code in files
+ * @kind metric
+ * @description Measures the number of lines of code in each file, ignoring lines that
+ * contain only comments or whitespace.
+ * @metricType file
+ * @id rb/lines-of-code-in-files
+ */
+
+import ruby
+
+from RubyFile f, int n
+where n = f.getNumberOfLinesOfCode()
+select f, n order by n desc
diff --git a/repo-tests/codeql/ruby/ql/src/queries/metrics/FLinesOfComments.ql b/repo-tests/codeql/ruby/ql/src/queries/metrics/FLinesOfComments.ql
new file mode 100644
index 00000000000..8af882f13d1
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/metrics/FLinesOfComments.ql
@@ -0,0 +1,13 @@
+/**
+ * @name Lines of comments in files
+ * @kind metric
+ * @description Measures the number of lines of comments in each file.
+ * @metricType file
+ * @id rb/lines-of-comments-in-files
+ */
+
+import ruby
+
+from RubyFile f, int n
+where n = f.getNumberOfLinesOfComments()
+select f, n order by n desc
diff --git a/repo-tests/codeql/ruby/ql/src/queries/security/cwe-022/PathInjection.ql b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-022/PathInjection.ql
new file mode 100644
index 00000000000..eb52f8e4531
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-022/PathInjection.ql
@@ -0,0 +1,26 @@
+/**
+ * @name Uncontrolled data used in path expression
+ * @description Accessing paths influenced by users can allow an attacker to access
+ * unexpected resources.
+ * @kind path-problem
+ * @problem.severity error
+ * @security-severity 7.5
+ * @precision high
+ * @id rb/path-injection
+ * @tags security
+ * external/cwe/cwe-022
+ * external/cwe/cwe-023
+ * external/cwe/cwe-036
+ * external/cwe/cwe-073
+ * external/cwe/cwe-099
+ */
+
+import ruby
+import codeql.ruby.security.PathInjectionQuery
+import codeql.ruby.DataFlow
+import DataFlow::PathGraph
+
+from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
+where cfg.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "This path depends on $@.", source.getNode(),
+ "a user-provided value"
diff --git a/repo-tests/codeql/ruby/ql/src/queries/security/cwe-078/CommandInjection.ql b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-078/CommandInjection.ql
new file mode 100644
index 00000000000..4c2dda966b9
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-078/CommandInjection.ql
@@ -0,0 +1,25 @@
+/**
+ * @name Uncontrolled command line
+ * @description Using externally controlled strings in a command line may allow a malicious
+ * user to change the meaning of the command.
+ * @kind path-problem
+ * @problem.severity error
+ * @security-severity 9.8
+ * @precision high
+ * @id rb/command-line-injection
+ * @tags correctness
+ * security
+ * external/cwe/cwe-078
+ * external/cwe/cwe-088
+ */
+
+import ruby
+import codeql.ruby.security.CommandInjectionQuery
+import DataFlow::PathGraph
+
+from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, Source sourceNode
+where
+ config.hasFlowPath(source, sink) and
+ sourceNode = source.getNode()
+select sink.getNode(), source, sink, "This command depends on $@.", sourceNode,
+ sourceNode.getSourceType()
diff --git a/repo-tests/codeql/ruby/ql/src/queries/security/cwe-078/KernelOpen.ql b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-078/KernelOpen.ql
new file mode 100644
index 00000000000..5bb02183915
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-078/KernelOpen.ql
@@ -0,0 +1,76 @@
+/**
+ * @name Use of `Kernel.open` or `IO.read`
+ * @description Using `Kernel.open` or `IO.read` may allow a malicious
+ * user to execute arbitrary system commands.
+ * @kind path-problem
+ * @problem.severity error
+ * @security-severity 9.8
+ * @precision high
+ * @id rb/kernel-open
+ * @tags correctness
+ * security
+ * external/cwe/cwe-078
+ * external/cwe/cwe-088
+ * external/cwe/cwe-073
+ */
+
+import ruby
+import codeql.ruby.ApiGraphs
+import codeql.ruby.frameworks.StandardLibrary
+import codeql.ruby.TaintTracking
+import codeql.ruby.dataflow.BarrierGuards
+import codeql.ruby.dataflow.RemoteFlowSources
+import DataFlow::PathGraph
+
+/**
+ * Method calls that have a suggested replacement.
+ */
+abstract class Replacement extends DataFlow::CallNode {
+ abstract string getFrom();
+
+ abstract string getTo();
+}
+
+class KernelOpenCall extends KernelMethodCall, Replacement {
+ KernelOpenCall() { this.getMethodName() = "open" }
+
+ override string getFrom() { result = "Kernel.open" }
+
+ override string getTo() { result = "File.open" }
+}
+
+class IOReadCall extends DataFlow::CallNode, Replacement {
+ IOReadCall() { this = API::getTopLevelMember("IO").getAMethodCall("read") }
+
+ override string getFrom() { result = "IO.read" }
+
+ override string getTo() { result = "File.read" }
+}
+
+class Configuration extends TaintTracking::Configuration {
+ Configuration() { this = "KernelOpen" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(KernelOpenCall c | c.getArgument(0) = sink)
+ or
+ exists(IOReadCall c | c.getArgument(0) = sink)
+ }
+
+ override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
+ guard instanceof StringConstCompare or
+ guard instanceof StringConstArrayInclusionCall
+ }
+}
+
+from
+ Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink,
+ DataFlow::Node sourceNode, DataFlow::CallNode call
+where
+ config.hasFlowPath(source, sink) and
+ sourceNode = source.getNode() and
+ call.asExpr().getExpr().(MethodCall).getArgument(0) = sink.getNode().asExpr().getExpr()
+select sink.getNode(), source, sink,
+ "This call to " + call.(Replacement).getFrom() +
+ " depends on a user-provided value. Replace it with " + call.(Replacement).getTo() + "."
diff --git a/repo-tests/codeql/ruby/ql/src/queries/security/cwe-079/ReflectedXSS.ql b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-079/ReflectedXSS.ql
new file mode 100644
index 00000000000..d3f95f69fea
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-079/ReflectedXSS.ql
@@ -0,0 +1,24 @@
+/**
+ * @name Reflected server-side cross-site scripting
+ * @description Writing user input directly to a web page
+ * allows for a cross-site scripting vulnerability.
+ * @kind path-problem
+ * @problem.severity error
+ * @security-severity 6.1
+ * @sub-severity high
+ * @precision high
+ * @id rb/reflected-xss
+ * @tags security
+ * external/cwe/cwe-079
+ * external/cwe/cwe-116
+ */
+
+import ruby
+import codeql.ruby.security.ReflectedXSSQuery
+import codeql.ruby.DataFlow
+import DataFlow::PathGraph
+
+from ReflectedXSS::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
+where config.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "Cross-site scripting vulnerability due to $@.",
+ source.getNode(), "a user-provided value"
diff --git a/repo-tests/codeql/ruby/ql/src/queries/security/cwe-079/StoredXSS.ql b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-079/StoredXSS.ql
new file mode 100644
index 00000000000..e473d5c31e9
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-079/StoredXSS.ql
@@ -0,0 +1,23 @@
+/**
+ * @name Stored cross-site scripting
+ * @description Using uncontrolled stored values in HTML allows for
+ * a stored cross-site scripting vulnerability.
+ * @kind path-problem
+ * @problem.severity error
+ * @security-severity 6.1
+ * @precision high
+ * @id rb/stored-xss
+ * @tags security
+ * external/cwe/cwe-079
+ * external/cwe/cwe-116
+ */
+
+import ruby
+import codeql.ruby.security.StoredXSSQuery
+import codeql.ruby.DataFlow
+import DataFlow::PathGraph
+
+from StoredXSS::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
+where config.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "Cross-site scripting vulnerability due to $@",
+ source.getNode(), "stored value"
diff --git a/repo-tests/codeql/ruby/ql/src/queries/security/cwe-089/SqlInjection.ql b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-089/SqlInjection.ql
new file mode 100644
index 00000000000..de795e34e71
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-089/SqlInjection.ql
@@ -0,0 +1,39 @@
+/**
+ * @name SQL query built from user-controlled sources
+ * @description Building a SQL query from user-controlled sources is vulnerable to insertion of
+ * malicious SQL code by the user.
+ * @kind path-problem
+ * @problem.severity error
+ * @security-severity 8.8
+ * @precision high
+ * @id rb/sql-injection
+ * @tags security
+ * external/cwe/cwe-089
+ * external/owasp/owasp-a1
+ */
+
+import ruby
+import codeql.ruby.Concepts
+import codeql.ruby.DataFlow
+import codeql.ruby.dataflow.BarrierGuards
+import codeql.ruby.dataflow.RemoteFlowSources
+import codeql.ruby.TaintTracking
+import DataFlow::PathGraph
+
+class SQLInjectionConfiguration extends TaintTracking::Configuration {
+ SQLInjectionConfiguration() { this = "SQLInjectionConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof SqlExecution }
+
+ override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
+ guard instanceof StringConstCompare or
+ guard instanceof StringConstArrayInclusionCall
+ }
+}
+
+from SQLInjectionConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
+where config.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "This SQL query depends on $@.", source.getNode(),
+ "a user-provided value"
diff --git a/repo-tests/codeql/ruby/ql/src/queries/security/cwe-094/CodeInjection.ql b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-094/CodeInjection.ql
new file mode 100644
index 00000000000..60e8e32c2f6
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-094/CodeInjection.ql
@@ -0,0 +1,27 @@
+/**
+ * @name Code injection
+ * @description Interpreting unsanitized user input as code allows a malicious user to perform arbitrary
+ * code execution.
+ * @kind path-problem
+ * @problem.severity error
+ * @security-severity 9.3
+ * @sub-severity high
+ * @precision high
+ * @id rb/code-injection
+ * @tags security
+ * external/owasp/owasp-a1
+ * external/cwe/cwe-094
+ * external/cwe/cwe-095
+ * external/cwe/cwe-116
+ */
+
+import ruby
+import codeql.ruby.security.CodeInjectionQuery
+import DataFlow::PathGraph
+
+from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, Source sourceNode
+where
+ config.hasFlowPath(source, sink) and
+ sourceNode = source.getNode()
+select sink.getNode(), source, sink, "This code execution depends on $@.", sourceNode,
+ "a user-provided value"
diff --git a/repo-tests/codeql/ruby/ql/src/queries/security/cwe-116/BadTagFilter.ql b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-116/BadTagFilter.ql
new file mode 100644
index 00000000000..066c5f86cf8
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-116/BadTagFilter.ql
@@ -0,0 +1,19 @@
+/**
+ * @name Bad HTML filtering regexp
+ * @description Matching HTML tags using regular expressions is hard to do right, and can easily lead to security issues.
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity 7.8
+ * @precision high
+ * @id rb/bad-tag-filter
+ * @tags correctness
+ * security
+ * external/cwe/cwe-116
+ * external/cwe/cwe-020
+ */
+
+import codeql.ruby.security.BadTagFilterQuery
+
+from HTMLMatchingRegExp regexp, string msg
+where msg = min(string m | isBadRegexpFilter(regexp, m) | m order by m.length(), m) // there might be multiple, we arbitrarily pick the shortest one
+select regexp, msg
diff --git a/repo-tests/codeql/ruby/ql/src/queries/security/cwe-1333/PolynomialReDoS.ql b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-1333/PolynomialReDoS.ql
new file mode 100644
index 00000000000..dd3899625af
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-1333/PolynomialReDoS.ql
@@ -0,0 +1,31 @@
+/**
+ * @name Polynomial regular expression used on uncontrolled data
+ * @description A regular expression that can require polynomial time
+ * to match may be vulnerable to denial-of-service attacks.
+ * @kind path-problem
+ * @problem.severity warning
+ * @security-severity 7.5
+ * @precision high
+ * @id rb/polynomial-redos
+ * @tags security
+ * external/cwe/cwe-1333
+ * external/cwe/cwe-730
+ * external/cwe/cwe-400
+ */
+
+import DataFlow::PathGraph
+import codeql.ruby.DataFlow
+import codeql.ruby.security.performance.PolynomialReDoSQuery
+import codeql.ruby.security.performance.SuperlinearBackTracking
+
+from
+ PolynomialReDoS::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink,
+ PolynomialReDoS::Sink sinkNode, PolynomialBackTrackingTerm regexp
+where
+ config.hasFlowPath(source, sink) and
+ sinkNode = sink.getNode() and
+ regexp = sinkNode.getRegExp()
+select sinkNode.getHighlight(), source, sink,
+ "This $@ that depends on $@ may run slow on strings " + regexp.getPrefixMessage() +
+ "with many repetitions of '" + regexp.getPumpString() + "'.", regexp, "regular expression",
+ source.getNode(), "a user-provided value"
diff --git a/repo-tests/codeql/ruby/ql/src/queries/security/cwe-1333/ReDoS.ql b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-1333/ReDoS.ql
new file mode 100644
index 00000000000..153be77110d
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-1333/ReDoS.ql
@@ -0,0 +1,25 @@
+/**
+ * @name Inefficient regular expression
+ * @description A regular expression that requires exponential time to match certain inputs
+ * can be a performance bottleneck, and may be vulnerable to denial-of-service
+ * attacks.
+ * @kind problem
+ * @problem.severity error
+ * @security-severity 7.5
+ * @precision high
+ * @id rb/redos
+ * @tags security
+ * external/cwe/cwe-1333
+ * external/cwe/cwe-730
+ * external/cwe/cwe-400
+ */
+
+import codeql.ruby.security.performance.ExponentialBackTracking
+import codeql.ruby.security.performance.ReDoSUtil
+import codeql.ruby.security.performance.RegExpTreeView
+
+from RegExpTerm t, string pump, State s, string prefixMsg
+where hasReDoSResult(t, pump, s, prefixMsg)
+select t,
+ "This part of the regular expression may cause exponential backtracking on strings " + prefixMsg +
+ "containing many repetitions of '" + pump + "'."
diff --git a/repo-tests/codeql/ruby/ql/src/queries/security/cwe-295/RequestWithoutValidation.ql b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-295/RequestWithoutValidation.ql
new file mode 100644
index 00000000000..e9b236897bc
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-295/RequestWithoutValidation.ql
@@ -0,0 +1,20 @@
+/**
+ * @name Request without certificate validation
+ * @description Making a request without certificate validation can allow
+ * man-in-the-middle attacks.
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity 7.5
+ * @precision medium
+ * @id rb/request-without-cert-validation
+ * @tags security
+ * external/cwe/cwe-295
+ */
+
+import ruby
+import codeql.ruby.Concepts
+import codeql.ruby.DataFlow
+
+from HTTP::Client::Request request, DataFlow::Node disablingNode
+where request.disablesCertificateValidation(disablingNode)
+select request, "This request may run with $@.", disablingNode, "certificate validation disabled"
diff --git a/repo-tests/codeql/ruby/ql/src/queries/security/cwe-502/UnsafeDeserialization.ql b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-502/UnsafeDeserialization.ql
new file mode 100644
index 00000000000..0df3b7c8d67
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-502/UnsafeDeserialization.ql
@@ -0,0 +1,21 @@
+/**
+ * @name Deserialization of user-controlled data
+ * @description Deserializing user-controlled data may allow attackers to
+ * execute arbitrary code.
+ * @kind path-problem
+ * @problem.severity warning
+ * @security-severity 9.8
+ * @precision high
+ * @id rb/unsafe-deserialization
+ * @tags security
+ * external/cwe/cwe-502
+ */
+
+import ruby
+import DataFlow::PathGraph
+import codeql.ruby.DataFlow
+import codeql.ruby.security.UnsafeDeserializationQuery
+
+from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
+where cfg.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "Unsafe deserialization of $@.", source.getNode(), "user input"
diff --git a/repo-tests/codeql/ruby/ql/src/queries/security/cwe-601/UrlRedirect.ql b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-601/UrlRedirect.ql
new file mode 100644
index 00000000000..aeaa4c29dc5
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-601/UrlRedirect.ql
@@ -0,0 +1,22 @@
+/**
+ * @name URL redirection from remote source
+ * @description URL redirection based on unvalidated user input
+ * may cause redirection to malicious web sites.
+ * @kind path-problem
+ * @problem.severity error
+ * @security-severity 6.1
+ * @sub-severity low
+ * @id rb/url-redirection
+ * @tags security
+ * external/cwe/cwe-601
+ * @precision high
+ */
+
+import ruby
+import codeql.ruby.security.UrlRedirectQuery
+import codeql.ruby.DataFlow::DataFlow::PathGraph
+
+from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
+where config.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "Untrusted URL redirection due to $@.", source.getNode(),
+ "a user-provided value"
diff --git a/repo-tests/codeql/ruby/ql/src/queries/security/cwe-611/Xxe.ql b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-611/Xxe.ql
new file mode 100644
index 00000000000..c7eae21333e
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-611/Xxe.ql
@@ -0,0 +1,43 @@
+/**
+ * @name XML external entity expansion
+ * @description Parsing user input as an XML document with external
+ * entity expansion is vulnerable to XXE attacks.
+ * @kind path-problem
+ * @problem.severity error
+ * @security-severity 9.1
+ * @precision high
+ * @id rb/xxe
+ * @tags security
+ * external/cwe/cwe-611
+ * external/cwe/cwe-776
+ * external/cwe/cwe-827
+ */
+
+import ruby
+import codeql.ruby.dataflow.RemoteFlowSources
+import codeql.ruby.TaintTracking
+import codeql.ruby.Concepts
+import codeql.ruby.DataFlow
+import DataFlow::PathGraph
+
+class UnsafeXxeSink extends DataFlow::ExprNode {
+ UnsafeXxeSink() {
+ exists(XmlParserCall parse |
+ parse.getInput() = this and
+ parse.externalEntitiesEnabled()
+ )
+ }
+}
+
+class XxeConfig extends TaintTracking::Configuration {
+ XxeConfig() { this = "XXE.ql::XxeConfig" }
+
+ override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof UnsafeXxeSink }
+}
+
+from DataFlow::PathNode source, DataFlow::PathNode sink, XxeConfig conf
+where conf.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "Unsafe parsing of XML file from $@.", source.getNode(),
+ "user input"
diff --git a/repo-tests/codeql/ruby/ql/src/queries/security/cwe-732/WeakFilePermissions.ql b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-732/WeakFilePermissions.ql
new file mode 100644
index 00000000000..d2bc837ac02
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-732/WeakFilePermissions.ql
@@ -0,0 +1,64 @@
+/**
+ * @name Overly permissive file permissions
+ * @description Allowing files to be readable or writable by users other than the owner may allow sensitive information to be accessed.
+ * @kind path-problem
+ * @problem.severity warning
+ * @security-severity 7.8
+ * @id rb/overly-permissive-file
+ * @tags external/cwe/cwe-732
+ * security
+ * @precision low
+ */
+
+import ruby
+import codeql.ruby.Concepts
+import codeql.ruby.DataFlow
+import DataFlow::PathGraph
+import codeql.ruby.ApiGraphs
+
+bindingset[p]
+int world_permission(int p) { result = p.bitAnd(7) }
+
+// 70 oct = 56 dec
+bindingset[p]
+int group_permission(int p) { result = p.bitAnd(56) }
+
+bindingset[p]
+string access(int p) {
+ p.bitAnd(2) != 0 and result = "writable"
+ or
+ p.bitAnd(4) != 0 and result = "readable"
+}
+
+/** An expression specifying a file permission that allows group/others read or write access */
+class PermissivePermissionsExpr extends Expr {
+ // TODO: non-literal expressions?
+ PermissivePermissionsExpr() {
+ exists(int perm, string acc |
+ perm = this.(IntegerLiteral).getValue() and
+ (acc = access(world_permission(perm)) or acc = access(group_permission(perm)))
+ )
+ or
+ // adding/setting read or write permissions for all/group/other
+ this.(StringLiteral).getValueText().regexpMatch(".*[ago][^-=+]*[+=][xXst]*[rw].*")
+ }
+}
+
+class PermissivePermissionsConfig extends DataFlow::Configuration {
+ PermissivePermissionsConfig() { this = "PermissivePermissionsConfig" }
+
+ override predicate isSource(DataFlow::Node source) {
+ exists(PermissivePermissionsExpr ppe | source.asExpr().getExpr() = ppe)
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(FileSystemPermissionModification mod | mod.getAPermissionNode() = sink)
+ }
+}
+
+from
+ DataFlow::PathNode source, DataFlow::PathNode sink, PermissivePermissionsConfig conf,
+ FileSystemPermissionModification mod
+where conf.hasFlowPath(source, sink) and mod.getAPermissionNode() = sink.getNode()
+select source.getNode(), source, sink, "Overly permissive mask in $@ sets file to $@.", mod,
+ mod.toString(), source.getNode(), source.getNode().toString()
diff --git a/repo-tests/codeql/ruby/ql/src/queries/security/cwe-798/HardcodedCredentials.ql b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-798/HardcodedCredentials.ql
new file mode 100644
index 00000000000..0f7fd35f7f7
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/security/cwe-798/HardcodedCredentials.ql
@@ -0,0 +1,155 @@
+/**
+ * @name Hard-coded credentials
+ * @description Credentials are hard coded in the source code of the application.
+ * @kind path-problem
+ * @problem.severity error
+ * @security-severity 9.8
+ * @precision medium
+ * @id rb/hardcoded-credentials
+ * @tags security
+ * external/cwe/cwe-259
+ * external/cwe/cwe-321
+ * external/cwe/cwe-798
+ */
+
+import ruby
+import codeql.ruby.DataFlow
+import DataFlow::PathGraph
+import codeql.ruby.TaintTracking
+import codeql.ruby.controlflow.CfgNodes
+
+bindingset[char, fraction]
+predicate fewer_characters_than(StringLiteral str, string char, float fraction) {
+ exists(string text, int chars |
+ text = str.getValueText() and
+ chars = count(int i | text.charAt(i) = char)
+ |
+ /* Allow one character */
+ chars = 1 or
+ chars < text.length() * fraction
+ )
+}
+
+predicate possible_reflective_name(string name) {
+ // TODO: implement this?
+ none()
+}
+
+int char_count(StringLiteral str) { result = count(string c | c = str.getValueText().charAt(_)) }
+
+predicate capitalized_word(StringLiteral str) { str.getValueText().regexpMatch("[A-Z][a-z]+") }
+
+predicate format_string(StringLiteral str) { str.getValueText().matches("%{%}%") }
+
+predicate maybeCredential(Expr e) {
+ /* A string that is not too short and unlikely to be text or an identifier. */
+ exists(StringLiteral str | str = e |
+ /* At least 10 characters */
+ str.getValueText().length() > 9 and
+ /* Not too much whitespace */
+ fewer_characters_than(str, " ", 0.05) and
+ /* or underscores */
+ fewer_characters_than(str, "_", 0.2) and
+ /* Not too repetitive */
+ exists(int chars | chars = char_count(str) |
+ chars > 15 or
+ chars * 3 > str.getValueText().length() * 2
+ ) and
+ not possible_reflective_name(str.getValueText()) and
+ not capitalized_word(str) and
+ not format_string(str)
+ )
+ or
+ /* Or, an integer with over 32 bits */
+ exists(IntegerLiteral lit | lit = e |
+ not exists(lit.getValue()) and
+ /* Not a set of flags or round number */
+ not lit.getValueText().matches("%00%")
+ )
+}
+
+class HardcodedValueSource extends DataFlow::Node {
+ HardcodedValueSource() { maybeCredential(this.asExpr().getExpr()) }
+}
+
+/**
+ * Gets a regular expression for matching names of locations (variables, parameters, keys) that
+ * indicate the value being held is a credential.
+ */
+private string getACredentialRegExp() {
+ result = "(?i).*pass(wd|word|code|phrase)(?!.*question).*" or
+ result = "(?i).*(puid|username|userid).*" or
+ result = "(?i).*(cert)(?!.*(format|name)).*"
+}
+
+bindingset[name]
+private predicate maybeCredentialName(string name) {
+ name.regexpMatch(getACredentialRegExp()) and
+ not name.suffix(name.length() - 4) = "file"
+}
+
+// Positional parameter
+private DataFlow::Node credentialParameter() {
+ exists(Method m, NamedParameter p, int idx |
+ result.asParameter() = p and
+ p = m.getParameter(idx) and
+ maybeCredentialName(p.getName())
+ )
+}
+
+// Keyword argument
+private Expr credentialKeywordArgument() {
+ exists(MethodCall mc, string argKey |
+ result = mc.getKeywordArgument(argKey) and
+ maybeCredentialName(argKey)
+ )
+}
+
+// An equality check against a credential value
+private Expr credentialComparison() {
+ exists(EqualityOperation op, VariableReadAccess vra |
+ maybeCredentialName(vra.getVariable().getName()) and
+ (
+ op.getLeftOperand() = result and
+ op.getRightOperand() = vra
+ or
+ op.getLeftOperand() = vra and op.getRightOperand() = result
+ )
+ )
+}
+
+private predicate isCredentialSink(DataFlow::Node node) {
+ node = credentialParameter()
+ or
+ node.asExpr().getExpr() = credentialKeywordArgument()
+ or
+ node.asExpr().getExpr() = credentialComparison()
+}
+
+class CredentialSink extends DataFlow::Node {
+ CredentialSink() { isCredentialSink(this) }
+}
+
+class HardcodedCredentialsConfiguration extends DataFlow::Configuration {
+ HardcodedCredentialsConfiguration() { this = "HardcodedCredentialsConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof HardcodedValueSource }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof CredentialSink }
+
+ override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
+ exists(ExprNodes::BinaryOperationCfgNode binop |
+ (
+ binop.getLeftOperand() = node1.asExpr() or
+ binop.getRightOperand() = node1.asExpr()
+ ) and
+ binop = node2.asExpr() and
+ // string concatenation
+ binop.getExpr() instanceof AddExpr
+ )
+ }
+}
+
+from DataFlow::PathNode source, DataFlow::PathNode sink, HardcodedCredentialsConfiguration conf
+where conf.hasFlowPath(source, sink)
+select source.getNode(), source, sink, "Use of $@.", source.getNode(), "hardcoded credentials"
diff --git a/repo-tests/codeql/ruby/ql/src/queries/summary/LinesOfCode.ql b/repo-tests/codeql/ruby/ql/src/queries/summary/LinesOfCode.ql
new file mode 100644
index 00000000000..f727cf504d9
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/summary/LinesOfCode.ql
@@ -0,0 +1,15 @@
+/**
+ * @id rb/summary/lines-of-code
+ * @name Total lines of Ruby code in the database
+ * @description The total number of lines of Ruby code from the source code
+ * directory, including external libraries and auto-generated files. This is a
+ * useful metric of the size of a database. This query counts the lines of
+ * code, excluding whitespace or comments.
+ * @kind metric
+ * @tags summary
+ * lines-of-code
+ */
+
+import ruby
+
+select sum(RubyFile f | exists(f.getRelativePath()) | f.getNumberOfLinesOfCode())
diff --git a/repo-tests/codeql/ruby/ql/src/queries/summary/LinesOfUserCode.ql b/repo-tests/codeql/ruby/ql/src/queries/summary/LinesOfUserCode.ql
new file mode 100644
index 00000000000..19f4f46fb8d
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/summary/LinesOfUserCode.ql
@@ -0,0 +1,19 @@
+/**
+ * @id rb/summary/lines-of-user-code
+ * @name Total Lines of user written Ruby code in the database
+ * @description The total number of lines of Ruby code from the source code
+ * directory, excluding external library and auto-generated files. This
+ * query counts the lines of code, excluding whitespace or comments.
+ * @kind metric
+ * @tags summary
+ */
+
+import ruby
+
+select sum(RubyFile f |
+ f.fromSource() and
+ exists(f.getRelativePath()) and
+ not f.getAbsolutePath().matches("%/vendor/%")
+ |
+ f.getNumberOfLinesOfCode()
+ )
diff --git a/repo-tests/codeql/ruby/ql/src/queries/summary/NumberOfFilesExtractedWithErrors.ql b/repo-tests/codeql/ruby/ql/src/queries/summary/NumberOfFilesExtractedWithErrors.ql
new file mode 100644
index 00000000000..1a68d2c57e6
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/summary/NumberOfFilesExtractedWithErrors.ql
@@ -0,0 +1,15 @@
+/**
+ * @id rb/summary/number-of-files-extracted-with-errors
+ * @name Total number of files that were extracted with errors
+ * @description The total number of Ruby code files that we extracted, but where
+ * at least one extraction error occurred in the process.
+ * @kind metric
+ * @tags summary
+ */
+
+import ruby
+import codeql.ruby.Diagnostics
+
+select count(File f |
+ exists(ExtractionError e | e.getLocation().getFile() = f) and exists(f.getRelativePath())
+ )
diff --git a/repo-tests/codeql/ruby/ql/src/queries/summary/NumberOfSuccessfullyExtractedFiles.ql b/repo-tests/codeql/ruby/ql/src/queries/summary/NumberOfSuccessfullyExtractedFiles.ql
new file mode 100644
index 00000000000..356989935e1
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/summary/NumberOfSuccessfullyExtractedFiles.ql
@@ -0,0 +1,15 @@
+/**
+ * @id rb/summary/number-of-successfully-extracted-files
+ * @name Total number of files that were extracted without error
+ * @description The total number of Ruby code files that we extracted without
+ * encountering any extraction errors
+ * @kind metric
+ * @tags summary
+ */
+
+import ruby
+import codeql.ruby.Diagnostics
+
+select count(File f |
+ not exists(ExtractionError e | e.getLocation().getFile() = f) and exists(f.getRelativePath())
+ )
diff --git a/repo-tests/codeql/ruby/ql/src/queries/variables/DeadStoreOfLocal.ql b/repo-tests/codeql/ruby/ql/src/queries/variables/DeadStoreOfLocal.ql
new file mode 100644
index 00000000000..5ce06a0c182
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/variables/DeadStoreOfLocal.ql
@@ -0,0 +1,28 @@
+/**
+ * @name Useless assignment to local variable
+ * @description An assignment to a local variable that is not used later on, or whose value is always
+ * overwritten, has no effect.
+ * @kind problem
+ * @problem.severity warning
+ * @id rb/useless-assignment-to-local
+ * @tags maintainability
+ * external/cwe/cwe-563
+ * @precision low
+ */
+
+import ruby
+import codeql.ruby.dataflow.SSA
+
+class RelevantLocalVariableWriteAccess extends LocalVariableWriteAccess {
+ RelevantLocalVariableWriteAccess() {
+ not this.getVariable().getName().charAt(0) = "_" and
+ not this = any(Parameter p).getAVariable().getDefiningAccess()
+ }
+}
+
+from RelevantLocalVariableWriteAccess write, LocalVariable v
+where
+ v = write.getVariable() and
+ exists(write.getAControlFlowNode()) and
+ not exists(Ssa::WriteDefinition def | def.getWriteAccess() = write)
+select write, "This assignment to $@ is useless, since its value is never read.", v, v.getName()
diff --git a/repo-tests/codeql/ruby/ql/src/queries/variables/UninitializedLocal.ql b/repo-tests/codeql/ruby/ql/src/queries/variables/UninitializedLocal.ql
new file mode 100644
index 00000000000..ef134eddd70
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/variables/UninitializedLocal.ql
@@ -0,0 +1,32 @@
+/**
+ * @name Potentially uninitialized local variable
+ * @description Using a local variable before it is initialized gives the variable a default
+ * 'nil' value.
+ * @kind problem
+ * @problem.severity error
+ * @id rb/uninitialized-local-variable
+ * @tags reliability
+ * correctness
+ * @precision low
+ */
+
+import ruby
+import codeql.ruby.dataflow.SSA
+
+class RelevantLocalVariableReadAccess extends LocalVariableReadAccess {
+ RelevantLocalVariableReadAccess() {
+ not exists(MethodCall c |
+ c.getReceiver() = this and
+ c.getMethodName() = "nil?"
+ )
+ }
+}
+
+from RelevantLocalVariableReadAccess read, LocalVariable v
+where
+ v = read.getVariable() and
+ exists(Ssa::Definition def |
+ def.getAnUltimateDefinition() instanceof Ssa::UninitializedDefinition and
+ read = def.getARead().getExpr()
+ )
+select read, "Local variable $@ may be used before it is initialized.", v, v.getName()
diff --git a/repo-tests/codeql/ruby/ql/src/queries/variables/UnusedParameter.ql b/repo-tests/codeql/ruby/ql/src/queries/variables/UnusedParameter.ql
new file mode 100644
index 00000000000..1aa1a6bc462
--- /dev/null
+++ b/repo-tests/codeql/ruby/ql/src/queries/variables/UnusedParameter.ql
@@ -0,0 +1,27 @@
+/**
+ * @name Unused parameter.
+ * @description A parameter that is not used later on, or whose value is always overwritten,
+ * can be removed.
+ * @kind problem
+ * @problem.severity warning
+ * @id rb/unused-parameter
+ * @tags maintainability
+ * external/cwe/cwe-563
+ * @precision low
+ */
+
+import ruby
+import codeql.ruby.dataflow.SSA
+
+class RelevantParameterVariable extends LocalVariable {
+ RelevantParameterVariable() {
+ exists(Parameter p |
+ this = p.getAVariable() and
+ not this.getName().charAt(0) = "_"
+ )
+ }
+}
+
+from RelevantParameterVariable v
+where not exists(Ssa::WriteDefinition def | def.getWriteAccess() = v.getDefiningAccess())
+select v, "Unused parameter."