QL: Merge branch 'main' into use-set-literal

This commit is contained in:
Geoffrey White
2021-10-14 13:55:40 +01:00
4693 changed files with 716944 additions and 386 deletions

View File

@@ -12,6 +12,30 @@ on:
jobs:
build_query_pack:
runs-on: ubuntu-latest-xl
steps:
- uses: actions/checkout@v2
- name: Find codeql
id: find-codeql
uses: github/codeql-action/init@esbena/ql
with:
languages: javascript # does not matter
- name: Build query pack
run: |
cd ql/src
"${CODEQL}" pack create
cd .codeql/pack/codeql/ql-all/0.0.0
zip "${PACKZIP}" -r .
env:
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
PACKZIP: ${{ runner.temp }}/query-pack.zip
- name: Upload query pack
uses: actions/upload-artifact@v2
with:
name: query-pack
path: ${{ runner.temp }}/query-pack.zip
# XXX this is mostly an inlined copy of the 'build' job in build.yml
build_extractor_pack:
strategy:
@@ -79,9 +103,11 @@ jobs:
analyze:
name: Analyze
needs: build_extractor_pack
needs:
- build_query_pack
- build_extractor_pack
runs-on: ubuntu-latest
runs-on: ubuntu-latest-xl
permissions:
actions: read
@@ -89,61 +115,55 @@ jobs:
security-events: write
steps:
- name: Download pack
- name: Download query pack
uses: actions/download-artifact@v2
with:
name: query-pack
path: ${{ runner.temp }}/query-pack-artifact
- name: Download extractor pack
uses: actions/download-artifact@v2
with:
name: extractor-pack
path: ${{ runner.temp }}/extractor-pack-artifact
- name: Unzip pack
- name: Prepare packs
id: prepare-packs
run: |
set -x
mkdir "${PACKTMP}"
cd "${PACKTMP}"
unzip "${PACKARTIFACT}/*.zip" -d unzipped
cp -r unzipped/ql "${PACK}"
mkdir -p "${COMPLETE_PACK}" "${PACKS_TMP}"
cd "${PACKS_TMP}"
unzip "${QUERY_PACK_ARTIFACT}/*.zip" -d query-pack-artifact-unzipped
cp -r query-pack-artifact-unzipped/. "${COMPLETE_PACK}"
unzip "${EXTRACTOR_PACK_ARTIFACT}/*.zip" -d extractor-pack-artifact-unzipped
cp -r extractor-pack-artifact-unzipped/ql/. "${COMPLETE_PACK}"
cd "${COMPLETE_PACK}"
zip "${COMPLETE_PACK_ZIP}" -r .
env:
PACKTMP: ${{ runner.temp }}/extractor-pack-artifact.tmp
PACKARTIFACT: ${{ runner.temp }}/extractor-pack-artifact
PACK: ${{ runner.temp }}/extractor-pack
- name: Checkout repository
uses: actions/checkout@v2
- name: Make config file
run: |
set -x
echo "name: CodeQL config for QL" >> "${CONFIG_FILE}"
echo "" >> "${CONFIG_FILE}"
echo "disable-default-queries: true" >> "${CONFIG_FILE}"
echo "" >> "${CONFIG_FILE}"
echo "queries: " >> "${CONFIG_FILE}"
echo " - name: Standard queries" >> "${CONFIG_FILE}"
echo " uses: ${SUITE}" >> "${CONFIG_FILE}"
cat "${CONFIG_FILE}"
env:
SUITE: ./ql/src/codeql-suites/ql-code-scanning.qls
CONFIG_FILE: ./.custom-codeql-actions-config.yml
PACKS_TMP: ${{ runner.temp }}/pack-artifacts.tmp
QUERY_PACK_ARTIFACT: ${{ runner.temp }}/query-pack-artifact
EXTRACTOR_PACK_ARTIFACT: ${{ runner.temp }}/extractor-pack-artifact
COMPLETE_PACK: ${{ runner.temp }}/pack
COMPLETE_PACK_ZIP: ${{ runner.temp }}/pack.zip
- name: Hack codeql-action options
run: |
JSON=$(jq -nc --arg pack "${PACK}" '.resolve.extractor=["--search-path", $pack] | .database.init=["--search-path", $pack]')
JSON=$(jq -nc --arg pack "${COMPLETE_PACK}" '.resolve.queries=["--search-path", $pack] | .resolve.extractor=["--search-path", $pack] | .database.init=["--search-path", $pack]')
echo "CODEQL_ACTION_EXTRA_OPTIONS=${JSON}" >> ${GITHUB_ENV}
env:
PACK: ${{ runner.temp }}/extractor-pack
COMPLETE_PACK: ${{ runner.temp }}/pack
- name: Checkout repository
uses: actions/checkout@v2
- name: Initialize CodeQL
uses: github/codeql-action/init@esbena/ql
with:
languages: ql
db-location: ${{ runner.temp }}/db
config-file: ./.custom-codeql-actions-config.yml
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@esbena/ql
with:
results: ${{ runner.temp }}/results
add-snippets: true
- name: Upload db
uses: actions/upload-artifact@v2
@@ -152,9 +172,9 @@ jobs:
path: ${{ runner.temp }}/db
retention-days: 1
- name: Upload results
- name: Upload complete pack
uses: actions/upload-artifact@v2
with:
name: results
path: ${{ runner.temp }}/results
name: complete-pack
path: ${{ runner.temp }}/pack.zip
retention-days: 1

2
Cargo.lock generated
View File

@@ -589,7 +589,7 @@ dependencies = [
[[package]]
name = "tree-sitter-ql"
version = "0.19.0"
source = "git+https://github.com/tausbn/tree-sitter-ql.git?rev=577c43d96c93915bd7ae9c2765d11be8db102952#577c43d96c93915bd7ae9c2765d11be8db102952"
source = "git+https://github.com/tausbn/tree-sitter-ql.git?rev=36bdc0eae196f9833182ce3f8932be63534121b3#36bdc0eae196f9833182ce3f8932be63534121b3"
dependencies = [
"cc",
"tree-sitter",

View File

@@ -1,14 +0,0 @@
{
"folders": [
{
"path": "."
}
],
"settings": {
"editor.formatOnSave": true,
"files.eol": "\n",
"files.exclude": {
"codeql": true
}
}
}

View File

@@ -10,7 +10,7 @@ edition = "2018"
flate2 = "1.0"
node-types = { path = "../node-types" }
tree-sitter = "0.19"
tree-sitter-ql = { git = "https://github.com/tausbn/tree-sitter-ql.git", rev = "577c43d96c93915bd7ae9c2765d11be8db102952" }
tree-sitter-ql = { git = "https://github.com/tausbn/tree-sitter-ql.git", rev = "36bdc0eae196f9833182ce3f8932be63534121b3" }
clap = "2.33"
tracing = "0.1"
tracing-subscriber = { version = "0.2", features = ["env-filter"] }

View File

@@ -10,4 +10,4 @@ edition = "2018"
node-types = { path = "../node-types" }
tracing = "0.1"
tracing-subscriber = { version = "0.2", features = ["env-filter"] }
tree-sitter-ql = { git = "https://github.com/tausbn/tree-sitter-ql.git", rev = "577c43d96c93915bd7ae9c2765d11be8db102952" }
tree-sitter-ql = { git = "https://github.com/tausbn/tree-sitter-ql.git", rev = "36bdc0eae196f9833182ce3f8932be63534121b3" }

View File

@@ -0,0 +1,8 @@
- description: All Code Scanning queries for QL
- queries: .
- include:
kind:
- problem
- path-problem
- alert
- path-alert

View File

@@ -6,3 +6,13 @@
- path-problem
- alert
- path-alert
precision:
- high
- very-high
problem.severity:
- error
- warning
- exclude:
deprecated: //
- exclude:
query path: /^experimental\/.*/

View File

@@ -0,0 +1,244 @@
private import ql
private import codeql_ql.ast.internal.Predicate
private import codeql_ql.ast.internal.Type
private import codeql_ql.ast.internal.Builtins
private newtype TValueNumber =
TVariableValueNumber(VarDecl var) { variableAccessValueNumber(_, var) } or
TFieldValueNumber(VarDecl var) { fieldAccessValueNumber(_, var) } or
TThisValueNumber(Predicate pred) { thisAccessValueNumber(_, pred) } or
TPredicateValueNumber(PredicateOrBuiltin pred, ValueNumberArgumentList args) {
predicateCallValueNumber(_, pred, args)
} or
TClassPredicateValueNumber(PredicateOrBuiltin pred, ValueNumber base, ValueNumberArgumentList args) {
classPredicateCallValueNumber(_, pred, base, args)
} or
TLiteralValueNumber(string value, Type t) { literalValueNumber(_, value, t) } or
TBinaryOpValueNumber(FunctionSymbol symbol, ValueNumber leftOperand, ValueNumber rightOperand) {
binaryOperandValueNumber(_, symbol, leftOperand, rightOperand)
} or
TUnaryOpValueNumber(FunctionSymbol symbol, ValueNumber operand) {
unaryOperandValueNumber(_, symbol, operand)
} or
TInlineCastValueNumber(ValueNumber operand, Type t) { inlineCastValueNumber(_, operand, t) } or
TDontCareValueNumber() or
TRangeValueNumber(ValueNumber lower, ValueNumber high) { rangeValueNumber(_, lower, high) } or
TSetValueNumber(ValueNumberElementList elements) { setValueNumber(_, elements) } or
TUniqueValueNumber(Expr e) { uniqueValueNumber(e) }
private newtype ValueNumberArgumentList =
MkArgsNil() or
MkArgsCons(ValueNumber head, ValueNumberArgumentList tail) {
argumentValueNumbers(_, _, head, tail)
}
private newtype ValueNumberElementList =
MkElementsNil() or
MkElementsCons(ValueNumber head, ValueNumberElementList tail) {
setValueNumbers(_, _, head, tail)
}
private ValueNumberArgumentList argumentValueNumbers(Call call, int start) {
start = call.getNumberOfArguments() and
result = MkArgsNil()
or
exists(ValueNumber head, ValueNumberArgumentList tail |
argumentValueNumbers(call, start, head, tail) and
result = MkArgsCons(head, tail)
)
}
private predicate argumentValueNumbers(
Call call, int start, ValueNumber head, ValueNumberArgumentList tail
) {
head = valueNumber(call.getArgument(start)) and
tail = argumentValueNumbers(call, start + 1)
}
private ValueNumberElementList setValueNumbers(Set set, int start) {
start = set.getNumberOfElements() and
result = MkElementsNil()
or
exists(ValueNumber head, ValueNumberElementList tail |
setValueNumbers(set, start, head, tail) and
result = MkElementsCons(head, tail)
)
}
private predicate setValueNumbers(Set set, int start, ValueNumber head, ValueNumberElementList tail) {
head = valueNumber(set.getElement(start)) and
tail = setValueNumbers(set, start + 1)
}
/**
* A value number. A value number represents a collection of expressions that compute to the same value
* at runtime.
*/
class ValueNumber extends TValueNumber {
string toString() { result = "GVN" }
/** Gets an expression that has this value number. */
final Expr getAnExpr() { this = valueNumber(result) }
}
private predicate uniqueValueNumber(Expr e) { not numberable(e) }
private predicate numberable(Expr e) {
e instanceof VarAccess or
e instanceof FieldAccess or
e instanceof ThisAccess or
e instanceof Call or
e instanceof Literal or
e instanceof BinOpExpr or
e instanceof UnaryExpr or
e instanceof InlineCast or
e instanceof ExprAnnotation or
e instanceof DontCare or
e instanceof Range or
e instanceof Set or
e instanceof AsExpr
}
private predicate variableAccessValueNumber(VarAccess access, VarDef var) {
access.getDeclaration() = var
}
private predicate fieldAccessValueNumber(FieldAccess access, VarDef var) {
access.getDeclaration() = var
}
private predicate thisAccessValueNumber(ThisAccess access, Predicate pred) {
access.getEnclosingPredicate() = pred
}
private predicate predicateCallValueNumber(
Call call, PredicateOrBuiltin pred, ValueNumberArgumentList args
) {
call.getTarget() = pred and
not exists(call.(MemberCall).getBase()) and
args = argumentValueNumbers(call, 0)
}
private predicate classPredicateCallValueNumber(
MemberCall call, PredicateOrBuiltin pred, ValueNumber base, ValueNumberArgumentList args
) {
call.getTarget() = pred and
valueNumber(call.getBase()) = base and
args = argumentValueNumbers(call, 0)
}
private predicate literalValueNumber(Literal lit, string value, Type t) {
lit.(String).getValue() = value and
t instanceof StringClass
or
lit.(Integer).getValue().toString() = value and
t instanceof IntClass
or
lit.(Float).getValue().toString() = value and
t instanceof FloatClass
or
lit.(Boolean).isFalse() and
value = "false" and
t instanceof BooleanClass
or
lit.(Boolean).isTrue() and
value = "true" and
t instanceof BooleanClass
}
private predicate binaryOperandValueNumber(
BinOpExpr e, FunctionSymbol symbol, ValueNumber leftOperand, ValueNumber rightOperand
) {
e.getOperator() = symbol and
valueNumber(e.getLeftOperand()) = leftOperand and
valueNumber(e.getRightOperand()) = rightOperand
}
private predicate unaryOperandValueNumber(UnaryExpr e, FunctionSymbol symbol, ValueNumber operand) {
e.getOperator() = symbol and
valueNumber(e.getOperand()) = operand
}
private predicate inlineCastValueNumber(InlineCast cast, ValueNumber operand, Type t) {
valueNumber(cast.getBase()) = operand and
cast.getTypeExpr().getResolvedType() = t
}
private predicate rangeValueNumber(Range range, ValueNumber lower, ValueNumber high) {
valueNumber(range.getLowEndpoint()) = lower and
valueNumber(range.getHighEndpoint()) = high
}
private predicate setValueNumber(Set set, ValueNumberElementList elements) {
elements = setValueNumbers(set, 0)
}
private TValueNumber nonUniqueValueNumber(Expr e) {
exists(VarDecl var |
variableAccessValueNumber(e, var) and
result = TVariableValueNumber(var)
)
or
exists(VarDecl var |
fieldAccessValueNumber(e, var) and
result = TFieldValueNumber(var)
)
or
exists(Predicate pred |
thisAccessValueNumber(e, pred) and
result = TThisValueNumber(pred)
)
or
exists(PredicateOrBuiltin pred, ValueNumberArgumentList args |
predicateCallValueNumber(e, pred, args) and
result = TPredicateValueNumber(pred, args)
)
or
exists(PredicateOrBuiltin pred, ValueNumber base, ValueNumberArgumentList args |
classPredicateCallValueNumber(e, pred, base, args) and
result = TClassPredicateValueNumber(pred, base, args)
)
or
exists(string value, Type t |
literalValueNumber(e, value, t) and
result = TLiteralValueNumber(value, t)
)
or
exists(FunctionSymbol symbol, ValueNumber leftOperand, ValueNumber rightOperand |
binaryOperandValueNumber(e, symbol, leftOperand, rightOperand) and
result = TBinaryOpValueNumber(symbol, leftOperand, rightOperand)
)
or
exists(FunctionSymbol symbol, ValueNumber operand |
unaryOperandValueNumber(e, symbol, operand) and
result = TUnaryOpValueNumber(symbol, operand)
)
or
exists(ValueNumber operand, Type t |
inlineCastValueNumber(e, operand, t) and
result = TInlineCastValueNumber(operand, t)
)
or
result = valueNumber([e.(ExprAnnotation).getExpression(), e.(AsExpr).getInnerExpr()])
or
e instanceof DontCare and result = TDontCareValueNumber()
or
exists(ValueNumber lower, ValueNumber high |
rangeValueNumber(e, lower, high) and
result = TRangeValueNumber(lower, high)
)
or
exists(ValueNumberElementList elements |
setValueNumber(e, elements) and
result = TSetValueNumber(elements)
)
}
/** Gets the value number of an expression `e`. */
cached
TValueNumber valueNumber(Expr e) {
result = nonUniqueValueNumber(e)
or
uniqueValueNumber(e) and
result = TUniqueValueNumber(e)
}

View File

@@ -4,6 +4,7 @@ private import codeql_ql.ast.internal.Module
private import codeql_ql.ast.internal.Predicate
import codeql_ql.ast.internal.Type
private import codeql_ql.ast.internal.Variable
private import codeql_ql.ast.internal.Builtins
bindingset[name]
private string directMember(string name) { result = name + "()" }
@@ -16,15 +17,6 @@ private string stringIndexedMember(string name, string index) {
result = name + "(_)" and exists(index)
}
/**
* Holds if `node` has an annotation with `name`.
*/
private predicate hasAnnotation(AstNode node, string name) {
exists(Generated::Annotation annotation | annotation.getName().getValue() = name |
toGenerated(node).getParent() = annotation.getParent()
)
}
/** An AST node of a QL program */
class AstNode extends TAstNode {
string toString() { result = getAPrimaryQlClass() }
@@ -40,6 +32,20 @@ class AstNode extends TAstNode {
)
}
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
if exists(getLocation())
then getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
else (
filepath = "" and
startline = 0 and
startcolumn = 0 and
endline = 0 and
endcolumn = 0
)
}
/**
* Gets the parent in the AST for this node.
*/
@@ -60,6 +66,12 @@ class AstNode extends TAstNode {
/** Gets the QLDoc comment for this AST node, if any. */
QLDoc getQLDoc() { none() }
/** Holds if `node` has an annotation with `name`. */
predicate hasAnnotation(string name) { this.getAnAnnotation().getName() = name }
/** Gets an annotation of this AST node. */
Annotation getAnAnnotation() { toGenerated(this).getParent() = toGenerated(result).getParent() }
/**
* Gets the predicate that contains this AST node.
*/
@@ -180,11 +192,62 @@ class Select extends TSelect, AstNode {
override string getAPrimaryQlClass() { result = "Select" }
}
class PredicateOrBuiltin extends TPredOrBuiltin, AstNode {
string getName() { none() }
Type getDeclaringType() { none() }
Type getParameterType(int i) { none() }
Type getReturnType() { none() }
int getArity() { result = count(getParameterType(_)) }
predicate isPrivate() { none() }
}
class BuiltinPredicate extends PredicateOrBuiltin, TBuiltin {
override string toString() { result = getName() }
override string getAPrimaryQlClass() { result = "BuiltinPredicate" }
}
private class BuiltinClassless extends BuiltinPredicate, TBuiltinClassless {
string name;
string ret;
string args;
BuiltinClassless() { this = TBuiltinClassless(ret, name, args) }
override string getName() { result = name }
override PrimitiveType getReturnType() { result.getName() = ret }
override PrimitiveType getParameterType(int i) { result.getName() = getArgType(args, i) }
}
private class BuiltinMember extends BuiltinPredicate, TBuiltinMember {
string name;
string qual;
string ret;
string args;
BuiltinMember() { this = TBuiltinMember(qual, ret, name, args) }
override string getName() { result = name }
override PrimitiveType getReturnType() { result.getName() = ret }
override PrimitiveType getParameterType(int i) { result.getName() = getArgType(args, i) }
override PrimitiveType getDeclaringType() { result.getName() = qual }
}
/**
* A QL predicate.
* Either a classless predicate, a class predicate, or a characteristic predicate.
*/
class Predicate extends TPredicate, AstNode, Declaration {
class Predicate extends TPredicate, AstNode, PredicateOrBuiltin, Declaration {
/**
* Gets the body of the predicate.
*/
@@ -203,7 +266,7 @@ class Predicate extends TPredicate, AstNode, Declaration {
/**
* Gets the number of parameters.
*/
int getArity() {
override int getArity() {
not this.(ClasslessPredicate).getAlias() instanceof PredicateExpr and
result = count(getParameter(_))
or
@@ -212,12 +275,19 @@ class Predicate extends TPredicate, AstNode, Declaration {
)
}
/**
* Holds if this predicate is private.
*/
override predicate isPrivate() { hasAnnotation("private") }
/**
* Gets the return type (if any) of the predicate.
*/
TypeExpr getReturnTypeExpr() { none() }
Type getReturnType() { result = this.getReturnTypeExpr().getResolvedType() }
override Type getReturnType() { result = this.getReturnTypeExpr().getResolvedType() }
override Type getParameterType(int i) { result = this.getParameter(i).getType() }
override AstNode getAChild(string pred) {
result = super.getAChild(pred)
@@ -232,6 +302,45 @@ class Predicate extends TPredicate, AstNode, Declaration {
override string getAPrimaryQlClass() { result = "Predicate" }
}
/**
* A relation in the database.
*/
class Relation extends TDBRelation, AstNode, Declaration {
Generated::DbTable table;
Relation() { this = TDBRelation(table) }
/**
* Gets the name of the relation.
*/
override string getName() { result = table.getTableName().getChild().getValue() }
private Generated::DbColumn getColumn(int i) {
result =
rank[i + 1](Generated::DbColumn column, int child |
table.getChild(child) = column
|
column order by child
)
}
/** Gets the `i`th parameter name */
string getParameterName(int i) { result = getColumn(i).getColName().getValue() }
/** Gets the `i`th parameter type */
string getParameterType(int i) {
// TODO: This is just using the name of the type, not the actual type. Checkout Type.qll
result = getColumn(i).getColType().getChild().(Generated::Token).getValue()
}
/**
* Gets the number of parameters.
*/
int getArity() { result = count(getColumn(_)) }
override string getAPrimaryQlClass() { result = "Relation" }
}
/**
* An expression that refers to a predicate, e.g. `BasicBlock::succ/2`.
*/
@@ -340,6 +449,8 @@ class ClasslessPredicate extends TClasslessPredicate, Predicate, ModuleDeclarati
or
pred_name = directMember("getReturnTypeExpr") and result = this.getReturnTypeExpr()
}
override predicate isPrivate() { Predicate.super.isPrivate() }
}
/**
@@ -358,15 +469,10 @@ class ClassPredicate extends TClassPredicate, Predicate {
override Class getParent() { result.getAClassPredicate() = this }
/**
* Holds if this predicate is private.
*/
predicate isPrivate() { hasAnnotation(this, "private") }
/**
* Holds if this predicate is annotated as overriding another predicate.
*/
predicate isOverride() { hasAnnotation(this, "override") }
predicate isOverride() { hasAnnotation("override") }
override VarDecl getParameter(int i) {
toGenerated(result) =
@@ -380,7 +486,7 @@ class ClassPredicate extends TClassPredicate, Predicate {
/**
* Gets the type representing this class.
*/
ClassType getDeclaringType() { result.getDeclaration() = getParent() }
override ClassType getDeclaringType() { result.getDeclaration() = getParent() }
predicate overrides(ClassPredicate other) { predOverrides(this, other) }
@@ -417,7 +523,7 @@ class CharPred extends TCharPred, Predicate {
pred_name = directMember("getBody") and result = this.getBody()
}
ClassType getDeclaringType() { result.getDeclaration() = getParent() }
override ClassType getDeclaringType() { result.getDeclaration() = getParent() }
}
/**
@@ -579,7 +685,7 @@ class Module extends TModule, ModuleDeclaration {
*/
class ModuleMember extends TModuleMember, AstNode {
/** Holds if this member is declared as `private`. */
predicate isPrivate() { hasAnnotation(this, "private") }
predicate isPrivate() { hasAnnotation("private") }
}
/** A declaration. E.g. a class, type, predicate, newtype... */
@@ -665,7 +771,7 @@ class Class extends TClass, TypeDeclaration, ModuleDeclaration {
/**
* Gets a super-type referenced in the `extends` part of the class declaration.
*/
TypeExpr getASuperType() { toGenerated(result) in [cls.getExtends(_), cls.getInstanceof(_)] }
TypeExpr getASuperType() { toGenerated(result) = cls.getExtends(_) }
/** Gets the type that this class is defined to be an alias of. */
TypeExpr getAliasType() {
@@ -728,7 +834,7 @@ class NewType extends TNewType, TypeDeclaration, ModuleDeclaration {
* A branch in a `newtype`.
* E.g. `Bar()` or `Baz()` in `newtype Foo = Bar() or Baz()`.
*/
class NewTypeBranch extends TNewTypeBranch, TypeDeclaration {
class NewTypeBranch extends TNewTypeBranch, PredicateOrBuiltin, TypeDeclaration {
Generated::DatatypeBranch branch;
NewTypeBranch() { this = TNewTypeBranch(branch) }
@@ -750,6 +856,16 @@ class NewTypeBranch extends TNewTypeBranch, TypeDeclaration {
/** Gets the body of this branch. */
Formula getBody() { toGenerated(result) = branch.getChild(_).(Generated::Body).getChild() }
override NewTypeBranchType getReturnType() { result.getDeclaration() = this }
override Type getParameterType(int i) { result = this.getField(i).getType() }
override int getArity() { result = count(this.getField(_)) }
override Type getDeclaringType() { none() }
override predicate isPrivate() { this.getNewType().isPrivate() }
override QLDoc getQLDoc() { toGenerated(result) = branch.getChild(_) }
NewType getNewType() { result.getABranch() = this }
@@ -777,6 +893,9 @@ class Call extends TCall, Expr, Formula {
none() // overriden in sublcasses.
}
/** Gets an argument of this call, if any. */
final Expr getAnArgument() { result = getArgument(_) }
PredicateOrBuiltin getTarget() { resolveCall(this, result) }
override Type getType() { result = this.getTarget().getReturnType() }
@@ -1752,7 +1871,19 @@ class FunctionSymbol extends string {
/**
* A binary operation expression, such as `x + 3` or `y / 2`.
*/
class BinOpExpr extends TBinOpExpr, Expr { }
class BinOpExpr extends TBinOpExpr, Expr {
/** Gets the left operand of the binary expression. */
Expr getLeftOperand() { none() } // overriden in subclasses
/* Gets the right operand of the binary expression. */
Expr getRightOperand() { none() } // overriden in subclasses
/** Gets the operator of the binary expression. */
FunctionSymbol getOperator() { none() } // overriden in subclasses
/* Gets an operand of the binary expression. */
final Expr getAnOperand() { result = getLeftOperand() or result = getRightOperand() }
}
/**
* An addition or subtraction expression.
@@ -1763,17 +1894,11 @@ class AddSubExpr extends TAddSubExpr, BinOpExpr {
AddSubExpr() { this = TAddSubExpr(expr) and operator = expr.getChild().getValue() }
/** Gets the left operand of the binary expression. */
Expr getLeftOperand() { toGenerated(result) = expr.getLeft() }
override Expr getLeftOperand() { toGenerated(result) = expr.getLeft() }
/* Gets the right operand of the binary expression. */
Expr getRightOperand() { toGenerated(result) = expr.getRight() }
override Expr getRightOperand() { toGenerated(result) = expr.getRight() }
/* Gets an operand of the binary expression. */
Expr getAnOperand() { result = getLeftOperand() or result = getRightOperand() }
/** Gets the operator of the binary expression. */
FunctionSymbol getOperator() { result = operator }
override FunctionSymbol getOperator() { result = operator }
override PrimitiveType getType() {
// Both operands are the same type
@@ -1833,16 +1958,12 @@ class MulDivModExpr extends TMulDivModExpr, BinOpExpr {
MulDivModExpr() { this = TMulDivModExpr(expr) and operator = expr.getChild().getValue() }
/** Gets the left operand of the binary expression. */
Expr getLeftOperand() { toGenerated(result) = expr.getLeft() }
override Expr getLeftOperand() { toGenerated(result) = expr.getLeft() }
/** Gets the right operand of the binary expression. */
Expr getRightOperand() { toGenerated(result) = expr.getRight() }
override Expr getRightOperand() { toGenerated(result) = expr.getRight() }
/** Gets an operand of the binary expression. */
Expr getAnOperand() { result = getLeftOperand() or result = getRightOperand() }
/** Gets the operator of the binary expression. */
FunctionSymbol getOperator() { result = operator }
override FunctionSymbol getOperator() { result = operator }
override PrimitiveType getType() {
// Both operands are of the same type
@@ -1915,6 +2036,11 @@ class Range extends TRange, Expr {
*/
Expr getHighEndpoint() { toGenerated(result) = range.getUpper() }
/**
* Gets the lower and upper bounds of the range.
*/
Expr getAnEndpoint() { result = [getLowEndpoint(), getHighEndpoint()] }
override PrimitiveType getType() { result.getName() = "int" }
override string getAPrimaryQlClass() { result = "Range" }
@@ -1941,6 +2067,16 @@ class Set extends TSet, Expr {
*/
Expr getElement(int i) { toGenerated(result) = set.getChild(i) }
/**
* Gets an element in this set literal expression, if any.
*/
Expr getAnElement() { result = getElement(_) }
/**
* Gets the number of elements in this set literal expression.
*/
int getNumberOfElements() { result = count(getAnElement()) }
override Type getType() { result = this.getElement(0).getType() }
override string getAPrimaryQlClass() { result = "Set" }
@@ -2030,3 +2166,310 @@ class ModuleExpr extends TModuleExpr, ModuleRef {
pred = directMember("getQualifier") and result = this.getQualifier()
}
}
/** An argument to an annotation. */
private class AnnotationArg extends TAnnotationArg, AstNode {
Generated::AnnotArg arg;
AnnotationArg() { this = TAnnotationArg(arg) }
/** Gets the name of this argument. */
string getValue() {
result =
[
arg.getChild().(Generated::SimpleId).getValue(),
arg.getChild().(Generated::Result).getValue(), arg.getChild().(Generated::This).getValue()
]
}
override string toString() { result = this.getValue() }
}
private class NoInlineArg extends AnnotationArg {
NoInlineArg() { this.getValue() = "noinline" }
}
private class NoMagicArg extends AnnotationArg {
NoMagicArg() { this.getValue() = "nomagic" }
}
private class InlineArg extends AnnotationArg {
InlineArg() { this.getValue() = "inline" }
}
private class NoOptArg extends AnnotationArg {
NoOptArg() { this.getValue() = "noopt" }
}
private class MonotonicAggregatesArg extends AnnotationArg {
MonotonicAggregatesArg() { this.getValue() = "monotonicAggregates" }
}
/** An annotation on an element. */
class Annotation extends TAnnotation, AstNode {
Generated::Annotation annot;
Annotation() { this = TAnnotation(annot) }
override string toString() { result = "annotation" }
override string getAPrimaryQlClass() { result = "Annotation" }
override Location getLocation() { result = annot.getLocation() }
/** Gets the node corresponding to the field `args`. */
AnnotationArg getArgs(int i) { toGenerated(result) = annot.getArgs(i) }
/** Gets the node corresponding to the field `name`. */
string getName() { result = annot.getName().getValue() }
}
/** A `pragma[noinline]` annotation. */
class NoInline extends Annotation {
NoInline() { this.getArgs(0) instanceof NoInlineArg }
override string toString() { result = "noinline" }
}
/** A `pragma[inline]` annotation. */
class Inline extends Annotation {
Inline() { this.getArgs(0) instanceof InlineArg }
override string toString() { result = "inline" }
}
/** A `pragma[nomagic]` annotation. */
class NoMagic extends Annotation {
NoMagic() { this.getArgs(0) instanceof NoMagicArg }
override string toString() { result = "nomagic" }
}
/** A `pragma[noopt]` annotation. */
class NoOpt extends Annotation {
NoOpt() { this.getArgs(0) instanceof NoOptArg }
override string toString() { result = "noopt" }
}
/** A `language[monotonicAggregates]` annotation. */
class MonotonicAggregates extends Annotation {
MonotonicAggregates() { this.getArgs(0) instanceof MonotonicAggregatesArg }
override string toString() { result = "monotonicaggregates" }
}
/** A `bindingset` annotation. */
class BindingSet extends Annotation {
BindingSet() { this.getName() = "bindingset" }
/** Gets the `index`'th bound name in this bindingset. */
string getBoundName(int index) { result = this.getArgs(index).getValue() }
/** Gets a name bound by this bindingset, if any. */
string getABoundName() { result = getBoundName(_) }
/** Gets the number of names bound by this bindingset. */
int getNumberOfBoundNames() { result = count(getABoundName()) }
}
/**
* Classes modelling YAML AST nodes.
*/
module YAML {
/** A node in a YAML file */
class YAMLNode extends TYAMLNode, AstNode {
/** Holds if the predicate is a root node (has no parent) */
predicate isRoot() { not exists(getParent()) }
}
/** A YAML comment. */
class YAMLComment extends TYamlCommemt, YAMLNode {
Generated::YamlComment yamlcomment;
YAMLComment() { this = TYamlCommemt(yamlcomment) }
override string getAPrimaryQlClass() { result = "YAMLComment" }
}
/** A YAML entry. */
class YAMLEntry extends TYamlEntry, YAMLNode {
Generated::YamlEntry yamle;
YAMLEntry() { this = TYamlEntry(yamle) }
/** Gets the key of this YAML entry. */
YAMLKey getKey() {
exists(Generated::YamlKeyvaluepair pair |
pair.getParent() = yamle and
result = TYamlKey(pair.getKey())
)
}
/** Gets the value of this YAML entry. */
YAMLValue getValue() {
exists(Generated::YamlKeyvaluepair pair |
pair.getParent() = yamle and
result = TYamlValue(pair.getValue())
)
}
override string getAPrimaryQlClass() { result = "YAMLEntry" }
}
/** A YAML key. */
class YAMLKey extends TYamlKey, YAMLNode {
Generated::YamlKey yamlkey;
YAMLKey() { this = TYamlKey(yamlkey) }
/**
* Gets the value of this YAML key.
*/
YAMLValue getValue() {
exists(Generated::YamlKeyvaluepair pair |
pair.getKey() = yamlkey and result = TYamlValue(pair.getValue())
)
}
override string getAPrimaryQlClass() { result = "YAMLKey" }
/** Gets the value of this YAML value. */
string getNamePart(int i) {
i = 0 and result = yamlkey.getChild(0).(Generated::SimpleId).getValue()
or
exists(YAMLKey child |
child = TYamlKey(yamlkey.getChild(1)) and
result = child.getNamePart(i - 1)
)
}
/**
* Gets all the name parts of this YAML key concatenated with `/`.
* Dashes are replaced with `/` (because we don't have that information in the generated AST).
*/
string getQualifiedName() {
result = concat(string part, int i | part = getNamePart(i) | part, "/" order by i)
}
}
/** A YAML list item. */
class YAMLListItem extends TYamlListitem, YAMLNode {
Generated::YamlListitem yamllistitem;
YAMLListItem() { this = TYamlListitem(yamllistitem) }
/**
* Gets the value of this YAML list item.
*/
YAMLValue getValue() { result = TYamlValue(yamllistitem.getChild()) }
override string getAPrimaryQlClass() { result = "YAMLListItem" }
}
/** A YAML value. */
class YAMLValue extends TYamlValue, YAMLNode {
Generated::YamlValue yamlvalue;
YAMLValue() { this = TYamlValue(yamlvalue) }
override string getAPrimaryQlClass() { result = "YAMLValue" }
/** Gets the value of this YAML value. */
string getValue() { result = yamlvalue.getValue() }
}
// to not expose the entire `File` API on `QlPack`.
private newtype TQLPack = MKQlPack(File file) { file.getBaseName() = "qlpack.yml" }
YAMLEntry test() { not result.isRoot() }
/**
* A `qlpack.yml` file.
*/
class QLPack extends MKQlPack {
File file;
QLPack() { this = MKQlPack(file) }
private string getProperty(string name) {
exists(YAMLEntry entry |
entry.isRoot() and
entry.getKey().getQualifiedName() = name and
result = entry.getValue().getValue().trim() and
entry.getLocation().getFile() = file
)
}
/** Gets the name of this qlpack */
string getName() { result = getProperty("name") }
/** Gets the version of this qlpack */
string getVersion() { result = getProperty("version") }
/** Gets the extractor of this qlpack */
string getExtractor() { result = getProperty("extractor") }
string toString() { result = getName() }
/** Gets the file that this `QLPack` represents. */
File getFile() { result = file }
private predicate isADependency(YAMLEntry entry) {
exists(YAMLEntry deps |
deps.getLocation().getFile() = file and entry.getLocation().getFile() = file
|
deps.isRoot() and
deps.getKey().getQualifiedName() = "dependencies" and
entry.getLocation().getStartLine() = 1 + deps.getLocation().getStartLine() and
entry.getLocation().getStartColumn() > deps.getLocation().getStartColumn()
)
or
exists(YAMLEntry prev | isADependency(prev) |
prev.getLocation().getFile() = file and
entry.getLocation().getFile() = file and
entry.getLocation().getStartLine() = 1 + prev.getLocation().getStartLine() and
entry.getLocation().getStartColumn() = prev.getLocation().getStartColumn()
)
}
predicate hasDependency(string name, string version) {
exists(YAMLEntry entry | isADependency(entry) |
entry.getKey().getQualifiedName() = name and
entry.getValue().getValue() = version
)
}
/** Gets the database scheme of this qlpack */
File getDBScheme() {
result.getBaseName() = getProperty("dbscheme") and
result = file.getParentContainer().getFile(any(string s | s.matches("%.dbscheme")))
}
pragma[noinline]
Container getAFileInPack() {
result.getParentContainer() = file.getParentContainer()
or
result = getAFileInPack().(Folder).getAChildContainer()
}
/**
* Gets a QLPack that this QLPack depends on.
*/
QLPack getADependency() {
exists(string name | hasDependency(name, _) | result.getName().replaceAll("-", "/") = name)
}
Location getLocation() {
// hacky, just pick the first node in the file.
result =
min(YAMLNode entry, Location l, File f |
entry.getLocation().getFile() = file and
f = file and
l = entry.getLocation()
|
entry order by l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine()
).getLocation()
}
}
}

View File

@@ -1,5 +1,6 @@
import codeql_ql.ast.Ast as AST
import TreeSitter
private import Builtins
cached
newtype TAstNode =
@@ -10,6 +11,7 @@ newtype TAstNode =
TClass(Generated::Dataclass dc) or
TCharPred(Generated::Charpred pred) or
TClassPredicate(Generated::MemberPredicate pred) or
TDBRelation(Generated::DbTable table) or
TSelect(Generated::Select sel) or
TModule(Generated::Module mod) or
TNewType(Generated::Datatype dt) or
@@ -57,7 +59,18 @@ newtype TAstNode =
TUnaryExpr(Generated::UnaryExpr unaryexpr) or
TDontCare(Generated::Underscore dontcare) or
TModuleExpr(Generated::ModuleExpr me) or
TPredicateExpr(Generated::PredicateExpr pe)
TPredicateExpr(Generated::PredicateExpr pe) or
TAnnotation(Generated::Annotation annot) or
TAnnotationArg(Generated::AnnotArg arg) or
TYamlCommemt(Generated::YamlComment yc) or
TYamlEntry(Generated::YamlEntry ye) or
TYamlKey(Generated::YamlKey yk) or
TYamlListitem(Generated::YamlListitem yli) or
TYamlValue(Generated::YamlValue yv) or
TBuiltinClassless(string ret, string name, string args) { isBuiltinClassless(ret, name, args) } or
TBuiltinMember(string qual, string ret, string name, string args) {
isBuiltinMember(qual, ret, name, args)
}
class TFormula =
TDisjunction or TConjunction or TComparisonFormula or TQuantifier or TNegation or TIfFormula or
@@ -75,6 +88,8 @@ class TCall = TPredicateCall or TMemberCall or TNoneCall or TAnyCall;
class TModuleRef = TImport or TModuleExpr;
class TYAMLNode = TYamlCommemt or TYamlEntry or TYamlKey or TYamlListitem or TYamlValue;
private Generated::AstNode toGeneratedFormula(AST::AstNode n) {
n = TConjunction(result) or
n = TDisjunction(result) or
@@ -105,6 +120,14 @@ private Generated::AstNode toGeneratedExpr(AST::AstNode n) {
n = TDontCare(result)
}
private Generated::AstNode toGenerateYAML(AST::AstNode n) {
n = TYamlCommemt(result) or
n = TYamlEntry(result) or
n = TYamlKey(result) or
n = TYamlListitem(result) or
n = TYamlValue(result)
}
/**
* Gets the underlying TreeSitter entity for a given AST node.
*/
@@ -113,6 +136,8 @@ Generated::AstNode toGenerated(AST::AstNode n) {
or
result = toGeneratedFormula(n)
or
result = toGenerateYAML(n)
or
result.(Generated::ParExpr).getChild() = toGenerated(n)
or
result =
@@ -135,6 +160,8 @@ Generated::AstNode toGenerated(AST::AstNode n) {
or
n = TClassPredicate(result)
or
n = TDBRelation(result)
or
n = TSelect(result)
or
n = TModule(result)
@@ -164,9 +191,17 @@ Generated::AstNode toGenerated(AST::AstNode n) {
n = TAnyCall(result)
or
n = TSuper(result)
or
n = TAnnotation(result)
or
n = TAnnotationArg(result)
}
class TPredicate = TCharPred or TClasslessPredicate or TClassPredicate;
class TPredicate = TCharPred or TClasslessPredicate or TClassPredicate or TDBRelation;
class TPredOrBuiltin = TPredicate or TNewTypeBranch or TBuiltin;
class TBuiltin = TBuiltinClassless or TBuiltinMember;
class TModuleMember = TModuleDeclaration or TImport or TSelect or TQLDoc;

View File

@@ -65,3 +65,18 @@ string getArgType(string args, int i) { result = args.splitAt(",", i).trim() }
class StringClass extends PrimitiveType {
StringClass() { this.getName() = "string" }
}
/** The primitive 'int' class. */
class IntClass extends PrimitiveType {
IntClass() { this.getName() = "int" }
}
/** The primitive 'float' class. */
class FloatClass extends PrimitiveType {
FloatClass() { this.getName() = "float" }
}
/** The primitive 'boolean' class. */
class BooleanClass extends PrimitiveType {
BooleanClass() { this.getName() = "boolean" }
}

View File

@@ -100,8 +100,10 @@ private predicate resolveQualifiedName(Import imp, ContainerOrModule m, int i) {
exists(Container c, Container parent |
// should ideally look at `qlpack.yml` files
parent = imp.getLocation().getFile().getParentContainer+() and
exists(parent.getFile("qlpack.yml")) and
c.getParentContainer() = parent and
exists(YAML::QLPack pack |
pack.getFile().getParentContainer() = parent and
c.getParentContainer() = pack.getADependency*().getFile().getParentContainer()
) and
q = m.getName()
|
m = TFile(c)

View File

@@ -1,12 +1,11 @@
import ql
private import Builtins
private import codeql_ql.ast.internal.Module
private import codeql_ql.ast.internal.AstNodes as AstNodes
private import codeql_ql.ast.internal.AstNodes
private class TClasslessPredicateOrNewTypeBranch =
AstNodes::TClasslessPredicate or AstNodes::TNewTypeBranch;
private class TClasslessPredicateOrNewTypeBranch = TClasslessPredicate or TNewTypeBranch;
string getPredicateName(TClasslessPredicateOrNewTypeBranch p) {
private string getPredicateName(TClasslessPredicateOrNewTypeBranch p) {
result = p.(ClasslessPredicate).getName() or
result = p.(NewTypeBranch).getName()
}
@@ -69,8 +68,7 @@ private module Cached {
m = pc.getQualifier().getResolvedModule() and
public = true
|
definesPredicate(m, pc.getPredicateName(), pc.getNumberOfArguments(), p.getDeclaration(),
public)
definesPredicate(m, pc.getPredicateName(), pc.getNumberOfArguments(), p, public)
)
}
@@ -88,148 +86,41 @@ private module Cached {
)
}
pragma[noinline]
private predicate candidate(Relation rel, PredicateCall pc) {
rel.getName() = pc.getPredicateName()
}
private predicate resolveDBRelation(PredicateCall pc, Predicate p) {
exists(Relation rel | p = rel |
candidate(rel, pc) and
rel.getArity() = pc.getNumberOfArguments() and
(
exists(YAML::QLPack libPack, YAML::QLPack qlPack |
rel.getLocation().getFile() = libPack.getDBScheme() and
qlPack.getADependency*() = libPack and
qlPack.getAFileInPack() = pc.getLocation().getFile()
)
or
// upgrade scripts don't have a qlpack
rel.getLocation().getFile().getParentContainer() =
pc.getLocation().getFile().getParentContainer()
)
)
}
cached
predicate resolveCall(Call c, PredicateOrBuiltin p) {
resolvePredicateCall(c, p)
or
resolveMemberCall(c, p)
}
cached
module NewTypeDef {
cached
newtype TPredOrBuiltin =
TPred(Predicate p) or
TNewTypeBranch(NewTypeBranch b) or
TBuiltinClassless(string ret, string name, string args) {
isBuiltinClassless(ret, name, args)
} or
TBuiltinMember(string qual, string ret, string name, string args) {
isBuiltinMember(qual, ret, name, args)
}
or
not resolvePredicateCall(c, _) and
resolveDBRelation(c, p)
}
}
import Cached
private import NewTypeDef
class PredicateOrBuiltin extends TPredOrBuiltin {
string getName() { none() }
string toString() { result = getName() }
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
if exists(getDeclaration())
then
getDeclaration()
.getLocation()
.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
else (
filepath = "" and
startline = 0 and
startcolumn = 0 and
endline = 0 and
endcolumn = 0
)
}
AstNode getDeclaration() { none() }
Type getDeclaringType() { none() }
Type getParameterType(int i) { none() }
Type getReturnType() { none() }
int getArity() { result = count(getParameterType(_)) }
predicate isPrivate() { none() }
}
private class DefinedPredicate extends PredicateOrBuiltin, TPred {
Predicate decl;
DefinedPredicate() { this = TPred(decl) }
override Predicate getDeclaration() { result = decl }
override string getName() { result = decl.getName() }
override Type getReturnType() { result = decl.getReturnType() }
override Type getParameterType(int i) { result = decl.getParameter(i).getType() }
// Can be removed when all types can be resolved
override int getArity() { result = decl.getArity() }
override Type getDeclaringType() {
result = decl.(ClassPredicate).getDeclaringType()
or
result = decl.(CharPred).getDeclaringType()
}
override predicate isPrivate() {
decl.(ClassPredicate).isPrivate() or decl.(ClassPredicate).isPrivate()
}
}
private class DefinedNewTypeBranch extends PredicateOrBuiltin, TNewTypeBranch {
NewTypeBranch b;
DefinedNewTypeBranch() { this = TNewTypeBranch(b) }
override NewTypeBranch getDeclaration() { result = b }
override string getName() { result = b.getName() }
override NewTypeBranchType getReturnType() { result.getDeclaration() = b }
override Type getParameterType(int i) { result = b.getField(i).getType() }
// Can be removed when all types can be resolved
override int getArity() { result = count(b.getField(_)) }
override Type getDeclaringType() { none() }
override predicate isPrivate() { b.getNewType().isPrivate() }
}
private class TBuiltin = TBuiltinClassless or TBuiltinMember;
class BuiltinPredicate extends PredicateOrBuiltin, TBuiltin { }
private class BuiltinClassless extends BuiltinPredicate, TBuiltinClassless {
string name;
string ret;
string args;
BuiltinClassless() { this = TBuiltinClassless(ret, name, args) }
override string getName() { result = name }
override PrimitiveType getReturnType() { result.getName() = ret }
override PrimitiveType getParameterType(int i) { result.getName() = getArgType(args, i) }
}
private class BuiltinMember extends BuiltinPredicate, TBuiltinMember {
string name;
string qual;
string ret;
string args;
BuiltinMember() { this = TBuiltinMember(qual, ret, name, args) }
override string getName() { result = name }
override PrimitiveType getReturnType() { result.getName() = ret }
override PrimitiveType getParameterType(int i) { result.getName() = getArgType(args, i) }
override PrimitiveType getDeclaringType() { result.getName() = qual }
}
module PredConsistency {
query predicate noResolvePredicateExpr(PredicateExpr pe) {
@@ -261,7 +152,7 @@ module PredConsistency {
strictcount(PredicateOrBuiltin p0 |
resolveCall(call, p0) and
// aliases are expected to resolve to multiple.
not exists(p0.getDeclaration().(ClasslessPredicate).getAlias())
not exists(p0.(ClasslessPredicate).getAlias())
) and
c > 1 and
resolveCall(call, p)

View File

@@ -114,6 +114,7 @@ private PredicateOrBuiltin declaredPred(Type ty, string name, int arity) {
result.getArity() = arity
}
pragma[nomagic]
private PredicateOrBuiltin classPredCandidate(Type ty, string name, int arity) {
result = declaredPred(ty, name, arity)
or
@@ -127,8 +128,7 @@ private PredicateOrBuiltin inherClassPredCandidate(Type ty, string name, int ari
}
predicate predOverrides(ClassPredicate sub, ClassPredicate sup) {
sup =
inherClassPredCandidate(sub.getDeclaringType(), sub.getName(), sub.getArity()).getDeclaration()
sup = inherClassPredCandidate(sub.getDeclaringType(), sub.getName(), sub.getArity())
}
private VarDecl declaredField(ClassType ty, string name) {
@@ -283,6 +283,7 @@ private predicate qualifier(TypeExpr te, FileOrModule m, boolean public, string
)
}
pragma[nomagic]
private predicate defines(FileOrModule m, string name, Type t, boolean public) {
exists(Class ty | t = TClass(ty) |
getEnclosingModule(ty) = m and

View File

@@ -54,7 +54,7 @@ private predicate resolveField(FieldAccess va, VarDecl decl, string kind) {
}
private predicate resolveCall(Call c, Predicate p, string kind) {
p = c.getTarget().getDeclaration() and
p = c.getTarget() and
kind = "call"
}

View File

@@ -2,4 +2,5 @@ name: codeql-ql
version: 0.0.0
dbscheme: ql.dbscheme
suites: codeql-suites
defaultSuiteFile: codeql-suites/ql-code-scanning.qls
extractor: ql

View File

@@ -0,0 +1,86 @@
/**
* @name Class predicate doesn't mention `this`
* @description A class predicate that doesn't use `this` (or a field) could instead be a classless predicate, and may cause a cartesian product.
* @kind problem
* @problem.severity warning
* @id ql/class-predicate-doesnt-use-this
* @tags performance
* @precision medium
*/
import ql
predicate usesThis(ClassPredicate pred) {
exists(ThisAccess th | th.getEnclosingPredicate() = pred)
or
exists(Super sup | sup.getEnclosingPredicate() = pred)
or
exists(FieldAccess f | f.getEnclosingPredicate() = pred)
or
// implicit this
exists(PredicateCall pc | pc.getEnclosingPredicate() = pred |
pc.getTarget() instanceof ClassPredicate
)
}
predicate isLiteralComparison(ComparisonFormula eq) {
exists(Expr lhs, Expr rhs |
eq.getSymbol() = "=" and
eq.getAnOperand() = lhs and
eq.getAnOperand() = rhs and
(
lhs instanceof ResultAccess
or
lhs instanceof ThisAccess
or
lhs instanceof VarAccess
) and
(
rhs instanceof Literal
or
exists(NewTypeBranch nt |
rhs.(Call).getTarget() = nt and
count(nt.getField(_)) = 0
)
)
)
}
predicate conjParent(Formula par, Formula child) { child = par.(Conjunction).getAnOperand() }
predicate isLiteralComparisons(Formula f) {
forex(ComparisonFormula child | conjParent*(f, child) | isLiteralComparison(child))
}
predicate isTrivialImplementation(Predicate pred) {
not exists(pred.getBody())
or
exists(Formula bod | bod = pred.getBody() |
bod instanceof AnyCall
or
bod instanceof NoneCall
or
isLiteralComparisons(bod)
)
}
predicate isSingleton(Type ty) {
isTrivialImplementation(ty.(ClassType).getDeclaration().getCharPred())
or
isSingleton(ty.getASuperType())
or
exists(NewTypeBranch br | count(br.getField(_)) = 0 |
ty.(NewTypeBranchType).getDeclaration() = br
or
br = unique(NewTypeBranch br2 | br2 = ty.(NewTypeType).getDeclaration().getABranch())
)
}
from ClassPredicate pred
where
not usesThis(pred) and
not isTrivialImplementation(pred) and
not isSingleton(pred.getDeclaringType()) and
not exists(ClassPredicate other | pred.overrides(other) or other.overrides(pred)) and
not pred.isOverride()
select pred, "This predicate could be a classless predicate, as it doesn't depend on `this`."

View File

@@ -0,0 +1,22 @@
/**
* @name Don't use getAQlClass.
* @description Any use of getAQlClass causes both compile-time and runtime to be significantly slower.
* @kind problem
* @problem.severity warning
* @id ql/dont-use-getaqlclass
* @tags performance
* @precision very-high
*/
import ql
from Call call
where
(
call.(PredicateCall).getPredicateName() = "getAQlClass" or
call.(MemberCall).getMemberName() = "getAQlClass"
) and
not call.getLocation().getFile().getAbsolutePath().matches("%/" + ["meta", "test"] + "/%") and
not call.getLocation().getFile().getBaseName().toLowerCase() =
["consistency.ql", "test.ql", "tst.ql", "tests.ql"]
select call, "Don't use .getAQlClass"

View File

@@ -0,0 +1,23 @@
/**
* @name Missing `noinline` or `nomagic` annotation
* @description When a predicate is factored out to improve join-ordering, it should be marked as `noinline` or `nomagic`.
* @kind problem
* @problem.severity error
* @id ql/missing-noinline
* @tags performance
* @precision high
*/
import ql
from QLDoc doc, Predicate decl
where
doc.getContents().matches(["%join order%", "%join-order%"]) and
decl.getQLDoc() = doc and
not decl.getAnAnnotation() instanceof NoInline and
not decl.getAnAnnotation() instanceof NoMagic and
not decl.getAnAnnotation() instanceof NoOpt and
// If it's marked as inline it's probably because the QLDoc says something like
// "this predicate is inlined because it gives a better join-order".
not decl.getAnAnnotation() instanceof Inline
select decl, "This predicate might be inlined."

View File

@@ -0,0 +1,34 @@
/**
* @name Using implicit `this`
* @description Writing member predicate calls with an implicit `this` can be confusing
* @kind problem
* @problem.severity recommendation
* @precision very-high
* @id ql/implicit-this
* @tags maintainability
*/
import ql
MemberCall explicitThisCallInFile(File f) {
result.getLocation().getFile() = f and
result.getBase() instanceof ThisAccess and
// Exclude `this.(Type).whatever(...)`, as some files have that as their only instance of `this`.
not result = any(InlineCast c).getBase()
}
PredicateCall implicitThisCallInFile(File f) {
result.getLocation().getFile() = f and
exists(result.getTarget().getDeclaringType().getASuperType()) and
// Exclude `SomeModule::whatever(...)`
not exists(result.getQualifier())
}
PredicateCall confusingImplicitThisCall(File f) {
result = implicitThisCallInFile(f) and
exists(explicitThisCallInFile(f))
}
from PredicateCall c
where c = confusingImplicitThisCall(_)
select c, "Use of implicit `this`."

View File

@@ -0,0 +1,96 @@
/**
* @name Superfluous 'exists' conjunct.
* @description Writing 'exists(x)' when the existence of X is implied by another conjunct is bad practice.
* @kind problem
* @problem.severity warning
* @precision high
* @id ql/superfluous-exists
* @tags maintainability
*/
import ql
import codeql.GlobalValueNumbering
/**
* Gets an operand of this conjunction (we need the restriction
* to `Conjunction` to get the correct transitive closure).
*/
Formula getAConjOperand(Conjunction conj) { result = conj.getAnOperand() }
/** A conjunction that is not a operand of another conjunction. */
class TopLevelConjunction extends Conjunction {
TopLevelConjunction() { not this = getAConjOperand(_) }
/** Gets a formula within this conjunction that is not itself a conjunction. */
Formula getAnAtom() {
not result instanceof Conjunction and
result = getAConjOperand*(this)
}
}
/**
* Holds if the existence of `e` implies the existence of `vn`. For instance, the existence of
* `1 + x` implies the existence of a value number `vn` such that `vn.getAnExpr() = x`.
*/
predicate exprImpliesExists(ValueNumber vn, Expr e) {
vn.getAnExpr() = e
or
exprImpliesExists(vn, e.(BinOpExpr).getAnOperand())
or
exprImpliesExists(vn, e.(InlineCast).getBase())
or
exprImpliesExists(vn, e.(PredicateCall).getAnArgument())
or
exprImpliesExists(vn, [e.(MemberCall).getAnArgument(), e.(MemberCall).getBase()])
or
exprImpliesExists(vn, e.(UnaryExpr).getOperand())
or
exprImpliesExists(vn, e.(ExprAnnotation).getExpression())
or
forex(Formula child | child = e.(Set).getAnElement() | exprImpliesExists(vn, child))
or
exprImpliesExists(vn, e.(AsExpr).getInnerExpr())
or
exists(ExprAggregate agg |
agg = e and
agg.getKind().matches(["strict%", "unique"]) and
exprImpliesExists(vn, agg.getExpr(0))
)
}
/**
* Holds if the satisfiability of `f` implies the existence of `vn`. For instance, if `x.foo()` is
* satisfied, the value number `vn` such that `vn.getAnExpr() = x` exists.
*/
predicate formulaImpliesExists(ValueNumber vn, Formula f) {
forex(Formula child | child = f.(Disjunction).getAnOperand() | formulaImpliesExists(vn, child))
or
formulaImpliesExists(vn, f.(Conjunction).getAnOperand())
or
exprImpliesExists(vn, f.(ComparisonFormula).getAnOperand())
or
exists(IfFormula ifFormula |
ifFormula = f and
formulaImpliesExists(vn, ifFormula.getThenPart()) and
formulaImpliesExists(vn, ifFormula.getElsePart())
)
or
exprImpliesExists(vn, f.(InstanceOf).getExpr())
or
exprImpliesExists(vn, f.(PredicateCall).getAnArgument())
or
exprImpliesExists(vn, [f.(MemberCall).getAnArgument(), f.(MemberCall).getBase()])
or
exists(InFormula inFormula | inFormula = f |
exprImpliesExists(vn, [inFormula.getExpr(), inFormula.getRange()])
)
}
from TopLevelConjunction toplevel, Exists existsFormula, ValueNumber vn, Formula conjunct
where
existsFormula = toplevel.getAnAtom() and
vn.getAnExpr() = existsFormula.getExpr() and
conjunct = toplevel.getAnAtom() and
formulaImpliesExists(vn, conjunct)
select existsFormula, "This conjunct is superfluous as the existence is implied by $@.", conjunct,
"this conjunct"

View File

@@ -0,0 +1,26 @@
/**
* @name Class QLDoc style.
* @description The QLDoc for a class should start with "A", "An", or "The".
* @kind problem
* @problem.severity warning
* @id ql/class-doc-style
* @tags maintainability
* @precision very-high
*/
import ql
bindingset[s]
predicate badStyle(string s) {
not s.replaceAll("/**", "")
.replaceAll("*", "")
.splitAt("\n")
.trim()
.matches(["A %", "An %", "The %", "INTERNAL%", "DEPRECATED%"])
}
from Class c
where
badStyle(c.getQLDoc().getContents()) and
not c.isPrivate()
select c.getQLDoc(), "The QLDoc for a class should start with 'A', 'An', or 'The'."

View File

@@ -0,0 +1,38 @@
/**
* @name Non US spelling
* @description QLDocs shold use US spelling.
* @kind problem
* @problem.severity warning
* @id ql/non-us-spelling
* @tags maintainability
* @precision very-high
*/
import ql
predicate non_us_word(string wrong, string right) {
exists(string s |
wrong = s.splitAt("/", 0) and
right = s.splitAt("/", 1) and
s = ["colour/color", "authorise/authorize", "analyse/analyze"]
)
}
bindingset[s]
predicate contains_non_us_spelling(string s, string wrong, string right) {
non_us_word(wrong, right) and
(
s.matches("%" + wrong + "%") and
wrong != "analyse"
or
// analyses (as a noun) is fine
s.regexpMatch(".*analyse[^s].*") and
wrong = "analyse"
)
}
from QLDoc doc, string wrong, string right
where contains_non_us_spelling(doc.getContents().toLowerCase(), wrong, right)
select doc,
"This QLDoc comment contains the non-US spelling '" + wrong + "', which should instead be '" +
right + "'."

View File

@@ -1,3 +1,3 @@
import ql
query AstNode getTarget(Call call) { result = call.getTarget().getDeclaration() }
query AstNode getTarget(Call call) { result = call.getTarget() }

View File

@@ -29,159 +29,239 @@ nodes
| Foo.qll:6:30:6:30 | ComparisonOp | semmle.order | 14 |
| Foo.qll:6:32:6:36 | String | semmle.label | [String] String |
| Foo.qll:6:32:6:36 | String | semmle.order | 15 |
| Foo.qll:9:1:9:5 | annotation | semmle.label | [Annotation] annotation |
| Foo.qll:9:1:9:5 | annotation | semmle.order | 16 |
| Foo.qll:9:7:11:1 | ClasslessPredicate foo | semmle.label | [ClasslessPredicate] ClasslessPredicate foo |
| Foo.qll:9:7:11:1 | ClasslessPredicate foo | semmle.order | 16 |
| Foo.qll:9:7:11:1 | ClasslessPredicate foo | semmle.order | 17 |
| Foo.qll:9:21:9:23 | TypeExpr | semmle.label | [TypeExpr] TypeExpr |
| Foo.qll:9:21:9:23 | TypeExpr | semmle.order | 17 |
| Foo.qll:9:21:9:23 | TypeExpr | semmle.order | 18 |
| Foo.qll:9:21:9:25 | f | semmle.label | [VarDecl] f |
| Foo.qll:9:21:9:25 | f | semmle.order | 17 |
| Foo.qll:9:21:9:25 | f | semmle.order | 18 |
| Foo.qll:10:3:10:3 | f | semmle.label | [VarAccess] f |
| Foo.qll:10:3:10:3 | f | semmle.order | 19 |
| Foo.qll:10:3:10:3 | f | semmle.order | 20 |
| Foo.qll:10:3:10:85 | ComparisonFormula | semmle.label | [ComparisonFormula] ComparisonFormula |
| Foo.qll:10:3:10:85 | ComparisonFormula | semmle.order | 19 |
| Foo.qll:10:3:10:85 | ComparisonFormula | semmle.order | 20 |
| Foo.qll:10:5:10:5 | ComparisonOp | semmle.label | [ComparisonOp] ComparisonOp |
| Foo.qll:10:5:10:5 | ComparisonOp | semmle.order | 21 |
| Foo.qll:10:5:10:5 | ComparisonOp | semmle.order | 22 |
| Foo.qll:10:7:10:85 | Rank | semmle.label | [Rank] Rank |
| Foo.qll:10:7:10:85 | Rank | semmle.order | 22 |
| Foo.qll:10:7:10:85 | Rank | semmle.order | 23 |
| Foo.qll:10:12:10:12 | Integer | semmle.label | [Integer] Integer |
| Foo.qll:10:12:10:12 | Integer | semmle.order | 23 |
| Foo.qll:10:12:10:12 | Integer | semmle.order | 24 |
| Foo.qll:10:15:10:17 | TypeExpr | semmle.label | [TypeExpr] TypeExpr |
| Foo.qll:10:15:10:17 | TypeExpr | semmle.order | 24 |
| Foo.qll:10:15:10:17 | TypeExpr | semmle.order | 25 |
| Foo.qll:10:15:10:23 | inner | semmle.label | [VarDecl] inner |
| Foo.qll:10:15:10:23 | inner | semmle.order | 24 |
| Foo.qll:10:15:10:23 | inner | semmle.order | 25 |
| Foo.qll:10:27:10:31 | inner | semmle.label | [VarAccess] inner |
| Foo.qll:10:27:10:31 | inner | semmle.order | 26 |
| Foo.qll:10:27:10:31 | inner | semmle.order | 27 |
| Foo.qll:10:27:10:42 | MemberCall | semmle.label | [MemberCall] MemberCall |
| Foo.qll:10:27:10:42 | MemberCall | semmle.order | 26 |
| Foo.qll:10:27:10:42 | MemberCall | semmle.order | 27 |
| Foo.qll:10:27:10:50 | ComparisonFormula | semmle.label | [ComparisonFormula] ComparisonFormula |
| Foo.qll:10:27:10:50 | ComparisonFormula | semmle.order | 26 |
| Foo.qll:10:27:10:50 | ComparisonFormula | semmle.order | 27 |
| Foo.qll:10:44:10:44 | ComparisonOp | semmle.label | [ComparisonOp] ComparisonOp |
| Foo.qll:10:44:10:44 | ComparisonOp | semmle.order | 29 |
| Foo.qll:10:44:10:44 | ComparisonOp | semmle.order | 30 |
| Foo.qll:10:46:10:50 | String | semmle.label | [String] String |
| Foo.qll:10:46:10:50 | String | semmle.order | 30 |
| Foo.qll:10:46:10:50 | String | semmle.order | 31 |
| Foo.qll:10:54:10:58 | inner | semmle.label | [VarAccess] inner |
| Foo.qll:10:54:10:58 | inner | semmle.order | 31 |
| Foo.qll:10:54:10:58 | inner | semmle.order | 32 |
| Foo.qll:10:69:10:73 | inner | semmle.label | [VarAccess] inner |
| Foo.qll:10:69:10:73 | inner | semmle.order | 32 |
| Foo.qll:10:69:10:73 | inner | semmle.order | 33 |
| Foo.qll:10:69:10:84 | MemberCall | semmle.label | [MemberCall] MemberCall |
| Foo.qll:10:69:10:84 | MemberCall | semmle.order | 32 |
| Foo.qll:10:69:10:84 | MemberCall | semmle.order | 33 |
| Foo.qll:13:1:27:1 | ClasslessPredicate calls | semmle.label | [ClasslessPredicate] ClasslessPredicate calls |
| Foo.qll:13:1:27:1 | ClasslessPredicate calls | semmle.order | 34 |
| Foo.qll:13:1:27:1 | ClasslessPredicate calls | semmle.order | 35 |
| Foo.qll:13:17:13:19 | TypeExpr | semmle.label | [TypeExpr] TypeExpr |
| Foo.qll:13:17:13:19 | TypeExpr | semmle.order | 35 |
| Foo.qll:13:17:13:19 | TypeExpr | semmle.order | 36 |
| Foo.qll:13:17:13:21 | f | semmle.label | [VarDecl] f |
| Foo.qll:13:17:13:21 | f | semmle.order | 35 |
| Foo.qll:13:17:13:21 | f | semmle.order | 36 |
| Foo.qll:14:3:14:10 | PredicateCall | semmle.label | [PredicateCall] PredicateCall |
| Foo.qll:14:3:14:10 | PredicateCall | semmle.order | 37 |
| Foo.qll:14:3:14:10 | PredicateCall | semmle.order | 38 |
| Foo.qll:14:3:16:29 | Disjunction | semmle.label | [Disjunction] Disjunction |
| Foo.qll:14:3:16:29 | Disjunction | semmle.order | 37 |
| Foo.qll:14:3:16:29 | Disjunction | semmle.order | 38 |
| Foo.qll:14:3:18:28 | Disjunction | semmle.label | [Disjunction] Disjunction |
| Foo.qll:14:3:18:28 | Disjunction | semmle.order | 37 |
| Foo.qll:14:3:18:28 | Disjunction | semmle.order | 38 |
| Foo.qll:14:3:20:13 | Disjunction | semmle.label | [Disjunction] Disjunction |
| Foo.qll:14:3:20:13 | Disjunction | semmle.order | 37 |
| Foo.qll:14:3:20:13 | Disjunction | semmle.order | 38 |
| Foo.qll:14:3:22:16 | Disjunction | semmle.label | [Disjunction] Disjunction |
| Foo.qll:14:3:22:16 | Disjunction | semmle.order | 37 |
| Foo.qll:14:3:22:16 | Disjunction | semmle.order | 38 |
| Foo.qll:14:3:24:23 | Disjunction | semmle.label | [Disjunction] Disjunction |
| Foo.qll:14:3:24:23 | Disjunction | semmle.order | 37 |
| Foo.qll:14:3:24:23 | Disjunction | semmle.order | 38 |
| Foo.qll:14:3:26:14 | Disjunction | semmle.label | [Disjunction] Disjunction |
| Foo.qll:14:3:26:14 | Disjunction | semmle.order | 37 |
| Foo.qll:14:3:26:14 | Disjunction | semmle.order | 38 |
| Foo.qll:14:9:14:9 | f | semmle.label | [VarAccess] f |
| Foo.qll:14:9:14:9 | f | semmle.order | 44 |
| Foo.qll:14:9:14:9 | f | semmle.order | 45 |
| Foo.qll:16:3:16:7 | String | semmle.label | [String] String |
| Foo.qll:16:3:16:7 | String | semmle.order | 45 |
| Foo.qll:16:3:16:7 | String | semmle.order | 46 |
| Foo.qll:16:3:16:29 | ComparisonFormula | semmle.label | [ComparisonFormula] ComparisonFormula |
| Foo.qll:16:3:16:29 | ComparisonFormula | semmle.order | 45 |
| Foo.qll:16:3:16:29 | ComparisonFormula | semmle.order | 46 |
| Foo.qll:16:9:16:9 | ComparisonOp | semmle.label | [ComparisonOp] ComparisonOp |
| Foo.qll:16:9:16:9 | ComparisonOp | semmle.order | 47 |
| Foo.qll:16:9:16:9 | ComparisonOp | semmle.order | 48 |
| Foo.qll:16:11:16:11 | f | semmle.label | [VarAccess] f |
| Foo.qll:16:11:16:11 | f | semmle.order | 48 |
| Foo.qll:16:11:16:11 | f | semmle.order | 49 |
| Foo.qll:16:11:16:29 | MemberCall | semmle.label | [MemberCall] MemberCall |
| Foo.qll:16:11:16:29 | MemberCall | semmle.order | 48 |
| Foo.qll:16:11:16:29 | MemberCall | semmle.order | 49 |
| Foo.qll:16:22:16:22 | Integer | semmle.label | [Integer] Integer |
| Foo.qll:16:22:16:22 | Integer | semmle.order | 50 |
| Foo.qll:16:22:16:22 | Integer | semmle.order | 51 |
| Foo.qll:16:25:16:25 | Integer | semmle.label | [Integer] Integer |
| Foo.qll:16:25:16:25 | Integer | semmle.order | 51 |
| Foo.qll:16:25:16:25 | Integer | semmle.order | 52 |
| Foo.qll:16:28:16:28 | Integer | semmle.label | [Integer] Integer |
| Foo.qll:16:28:16:28 | Integer | semmle.order | 52 |
| Foo.qll:16:28:16:28 | Integer | semmle.order | 53 |
| Foo.qll:18:3:18:3 | f | semmle.label | [VarAccess] f |
| Foo.qll:18:3:18:3 | f | semmle.order | 53 |
| Foo.qll:18:3:18:3 | f | semmle.order | 54 |
| Foo.qll:18:3:18:9 | InlineCast | semmle.label | [InlineCast] InlineCast |
| Foo.qll:18:3:18:9 | InlineCast | semmle.order | 53 |
| Foo.qll:18:3:18:9 | InlineCast | semmle.order | 54 |
| Foo.qll:18:3:18:20 | MemberCall | semmle.label | [MemberCall] MemberCall |
| Foo.qll:18:3:18:20 | MemberCall | semmle.order | 53 |
| Foo.qll:18:3:18:20 | MemberCall | semmle.order | 54 |
| Foo.qll:18:3:18:28 | ComparisonFormula | semmle.label | [ComparisonFormula] ComparisonFormula |
| Foo.qll:18:3:18:28 | ComparisonFormula | semmle.order | 53 |
| Foo.qll:18:3:18:28 | ComparisonFormula | semmle.order | 54 |
| Foo.qll:18:6:18:8 | TypeExpr | semmle.label | [TypeExpr] TypeExpr |
| Foo.qll:18:6:18:8 | TypeExpr | semmle.order | 57 |
| Foo.qll:18:6:18:8 | TypeExpr | semmle.order | 58 |
| Foo.qll:18:22:18:22 | ComparisonOp | semmle.label | [ComparisonOp] ComparisonOp |
| Foo.qll:18:22:18:22 | ComparisonOp | semmle.order | 58 |
| Foo.qll:18:22:18:22 | ComparisonOp | semmle.order | 59 |
| Foo.qll:18:24:18:28 | String | semmle.label | [String] String |
| Foo.qll:18:24:18:28 | String | semmle.order | 59 |
| Foo.qll:18:24:18:28 | String | semmle.order | 60 |
| Foo.qll:20:3:20:3 | f | semmle.label | [VarAccess] f |
| Foo.qll:20:3:20:3 | f | semmle.order | 60 |
| Foo.qll:20:3:20:3 | f | semmle.order | 61 |
| Foo.qll:20:3:20:9 | InlineCast | semmle.label | [InlineCast] InlineCast |
| Foo.qll:20:3:20:9 | InlineCast | semmle.order | 60 |
| Foo.qll:20:3:20:9 | InlineCast | semmle.order | 61 |
| Foo.qll:20:3:20:13 | ComparisonFormula | semmle.label | [ComparisonFormula] ComparisonFormula |
| Foo.qll:20:3:20:13 | ComparisonFormula | semmle.order | 60 |
| Foo.qll:20:3:20:13 | ComparisonFormula | semmle.order | 61 |
| Foo.qll:20:6:20:8 | TypeExpr | semmle.label | [TypeExpr] TypeExpr |
| Foo.qll:20:6:20:8 | TypeExpr | semmle.order | 63 |
| Foo.qll:20:6:20:8 | TypeExpr | semmle.order | 64 |
| Foo.qll:20:11:20:11 | ComparisonOp | semmle.label | [ComparisonOp] ComparisonOp |
| Foo.qll:20:11:20:11 | ComparisonOp | semmle.order | 64 |
| Foo.qll:20:11:20:11 | ComparisonOp | semmle.order | 65 |
| Foo.qll:20:13:20:13 | f | semmle.label | [VarAccess] f |
| Foo.qll:20:13:20:13 | f | semmle.order | 65 |
| Foo.qll:20:13:20:13 | f | semmle.order | 66 |
| Foo.qll:22:3:22:3 | f | semmle.label | [VarAccess] f |
| Foo.qll:22:3:22:3 | f | semmle.order | 66 |
| Foo.qll:22:3:22:3 | f | semmle.order | 67 |
| Foo.qll:22:3:22:16 | ComparisonFormula | semmle.label | [ComparisonFormula] ComparisonFormula |
| Foo.qll:22:3:22:16 | ComparisonFormula | semmle.order | 66 |
| Foo.qll:22:3:22:16 | ComparisonFormula | semmle.order | 67 |
| Foo.qll:22:5:22:5 | ComparisonOp | semmle.label | [ComparisonOp] ComparisonOp |
| Foo.qll:22:5:22:5 | ComparisonOp | semmle.order | 68 |
| Foo.qll:22:5:22:5 | ComparisonOp | semmle.order | 69 |
| Foo.qll:22:7:22:16 | FullAggregate[any] | semmle.label | [FullAggregate[any]] FullAggregate[any] |
| Foo.qll:22:7:22:16 | FullAggregate[any] | semmle.order | 69 |
| Foo.qll:22:7:22:16 | FullAggregate[any] | semmle.order | 70 |
| Foo.qll:22:11:22:13 | TypeExpr | semmle.label | [TypeExpr] TypeExpr |
| Foo.qll:22:11:22:13 | TypeExpr | semmle.order | 70 |
| Foo.qll:22:11:22:13 | TypeExpr | semmle.order | 71 |
| Foo.qll:22:11:22:15 | f | semmle.label | [VarDecl] f |
| Foo.qll:22:11:22:15 | f | semmle.order | 70 |
| Foo.qll:22:11:22:15 | f | semmle.order | 71 |
| Foo.qll:24:3:24:3 | Integer | semmle.label | [Integer] Integer |
| Foo.qll:24:3:24:3 | Integer | semmle.order | 72 |
| Foo.qll:24:3:24:3 | Integer | semmle.order | 73 |
| Foo.qll:24:3:24:23 | ComparisonFormula | semmle.label | [ComparisonFormula] ComparisonFormula |
| Foo.qll:24:3:24:23 | ComparisonFormula | semmle.order | 72 |
| Foo.qll:24:3:24:23 | ComparisonFormula | semmle.order | 73 |
| Foo.qll:24:5:24:5 | ComparisonOp | semmle.label | [ComparisonOp] ComparisonOp |
| Foo.qll:24:5:24:5 | ComparisonOp | semmle.order | 74 |
| Foo.qll:24:5:24:5 | ComparisonOp | semmle.order | 75 |
| Foo.qll:24:7:24:7 | Integer | semmle.label | [Integer] Integer |
| Foo.qll:24:7:24:7 | Integer | semmle.order | 75 |
| Foo.qll:24:7:24:7 | Integer | semmle.order | 76 |
| Foo.qll:24:7:24:23 | AddExpr | semmle.label | [AddExpr] AddExpr |
| Foo.qll:24:7:24:23 | AddExpr | semmle.order | 75 |
| Foo.qll:24:7:24:23 | AddExpr | semmle.order | 76 |
| Foo.qll:24:12:24:12 | Integer | semmle.label | [Integer] Integer |
| Foo.qll:24:12:24:12 | Integer | semmle.order | 77 |
| Foo.qll:24:12:24:12 | Integer | semmle.order | 78 |
| Foo.qll:24:12:24:22 | AddExpr | semmle.label | [AddExpr] AddExpr |
| Foo.qll:24:12:24:22 | AddExpr | semmle.order | 77 |
| Foo.qll:24:12:24:22 | AddExpr | semmle.order | 78 |
| Foo.qll:24:17:24:17 | Integer | semmle.label | [Integer] Integer |
| Foo.qll:24:17:24:17 | Integer | semmle.order | 79 |
| Foo.qll:24:17:24:17 | Integer | semmle.order | 80 |
| Foo.qll:24:17:24:21 | AddExpr | semmle.label | [AddExpr] AddExpr |
| Foo.qll:24:17:24:21 | AddExpr | semmle.order | 79 |
| Foo.qll:24:17:24:21 | AddExpr | semmle.order | 80 |
| Foo.qll:24:21:24:21 | Integer | semmle.label | [Integer] Integer |
| Foo.qll:24:21:24:21 | Integer | semmle.order | 81 |
| Foo.qll:24:21:24:21 | Integer | semmle.order | 82 |
| Foo.qll:26:3:26:6 | Boolean | semmle.label | [Boolean] Boolean |
| Foo.qll:26:3:26:6 | Boolean | semmle.order | 82 |
| Foo.qll:26:3:26:6 | Boolean | semmle.order | 83 |
| Foo.qll:26:3:26:14 | ComparisonFormula | semmle.label | [ComparisonFormula] ComparisonFormula |
| Foo.qll:26:3:26:14 | ComparisonFormula | semmle.order | 82 |
| Foo.qll:26:3:26:14 | ComparisonFormula | semmle.order | 83 |
| Foo.qll:26:8:26:8 | ComparisonOp | semmle.label | [ComparisonOp] ComparisonOp |
| Foo.qll:26:8:26:8 | ComparisonOp | semmle.order | 84 |
| Foo.qll:26:8:26:8 | ComparisonOp | semmle.order | 85 |
| Foo.qll:26:10:26:14 | Boolean | semmle.label | [Boolean] Boolean |
| Foo.qll:26:10:26:14 | Boolean | semmle.order | 85 |
| Foo.qll:26:10:26:14 | Boolean | semmle.order | 86 |
| file://:0:0:0:0 | abs | semmle.label | [BuiltinPredicate] abs |
| file://:0:0:0:0 | abs | semmle.label | [BuiltinPredicate] abs |
| file://:0:0:0:0 | acos | semmle.label | [BuiltinPredicate] acos |
| file://:0:0:0:0 | any | semmle.label | [BuiltinPredicate] any |
| file://:0:0:0:0 | atan | semmle.label | [BuiltinPredicate] atan |
| file://:0:0:0:0 | bitAnd | semmle.label | [BuiltinPredicate] bitAnd |
| file://:0:0:0:0 | bitNot | semmle.label | [BuiltinPredicate] bitNot |
| file://:0:0:0:0 | bitOr | semmle.label | [BuiltinPredicate] bitOr |
| file://:0:0:0:0 | bitShiftLeft | semmle.label | [BuiltinPredicate] bitShiftLeft |
| file://:0:0:0:0 | bitShiftRight | semmle.label | [BuiltinPredicate] bitShiftRight |
| file://:0:0:0:0 | bitShiftRightSigned | semmle.label | [BuiltinPredicate] bitShiftRightSigned |
| file://:0:0:0:0 | bitXor | semmle.label | [BuiltinPredicate] bitXor |
| file://:0:0:0:0 | booleanAnd | semmle.label | [BuiltinPredicate] booleanAnd |
| file://:0:0:0:0 | booleanNot | semmle.label | [BuiltinPredicate] booleanNot |
| file://:0:0:0:0 | booleanOr | semmle.label | [BuiltinPredicate] booleanOr |
| file://:0:0:0:0 | booleanXor | semmle.label | [BuiltinPredicate] booleanXor |
| file://:0:0:0:0 | ceil | semmle.label | [BuiltinPredicate] ceil |
| file://:0:0:0:0 | charAt | semmle.label | [BuiltinPredicate] charAt |
| file://:0:0:0:0 | copySign | semmle.label | [BuiltinPredicate] copySign |
| file://:0:0:0:0 | cos | semmle.label | [BuiltinPredicate] cos |
| file://:0:0:0:0 | cosh | semmle.label | [BuiltinPredicate] cosh |
| file://:0:0:0:0 | daysTo | semmle.label | [BuiltinPredicate] daysTo |
| file://:0:0:0:0 | exp | semmle.label | [BuiltinPredicate] exp |
| file://:0:0:0:0 | floor | semmle.label | [BuiltinPredicate] floor |
| file://:0:0:0:0 | gcd | semmle.label | [BuiltinPredicate] gcd |
| file://:0:0:0:0 | getDay | semmle.label | [BuiltinPredicate] getDay |
| file://:0:0:0:0 | getHours | semmle.label | [BuiltinPredicate] getHours |
| file://:0:0:0:0 | getMinutes | semmle.label | [BuiltinPredicate] getMinutes |
| file://:0:0:0:0 | getMonth | semmle.label | [BuiltinPredicate] getMonth |
| file://:0:0:0:0 | getSeconds | semmle.label | [BuiltinPredicate] getSeconds |
| file://:0:0:0:0 | getYear | semmle.label | [BuiltinPredicate] getYear |
| file://:0:0:0:0 | indexOf | semmle.label | [BuiltinPredicate] indexOf |
| file://:0:0:0:0 | indexOf | semmle.label | [BuiltinPredicate] indexOf |
| file://:0:0:0:0 | isLowercase | semmle.label | [BuiltinPredicate] isLowercase |
| file://:0:0:0:0 | isUppercase | semmle.label | [BuiltinPredicate] isUppercase |
| file://:0:0:0:0 | length | semmle.label | [BuiltinPredicate] length |
| file://:0:0:0:0 | log | semmle.label | [BuiltinPredicate] log |
| file://:0:0:0:0 | log | semmle.label | [BuiltinPredicate] log |
| file://:0:0:0:0 | log2 | semmle.label | [BuiltinPredicate] log2 |
| file://:0:0:0:0 | log10 | semmle.label | [BuiltinPredicate] log10 |
| file://:0:0:0:0 | matches | semmle.label | [BuiltinPredicate] matches |
| file://:0:0:0:0 | maximum | semmle.label | [BuiltinPredicate] maximum |
| file://:0:0:0:0 | minimum | semmle.label | [BuiltinPredicate] minimum |
| file://:0:0:0:0 | nextAfter | semmle.label | [BuiltinPredicate] nextAfter |
| file://:0:0:0:0 | nextDown | semmle.label | [BuiltinPredicate] nextDown |
| file://:0:0:0:0 | nextUp | semmle.label | [BuiltinPredicate] nextUp |
| file://:0:0:0:0 | none | semmle.label | [BuiltinPredicate] none |
| file://:0:0:0:0 | pow | semmle.label | [BuiltinPredicate] pow |
| file://:0:0:0:0 | prefix | semmle.label | [BuiltinPredicate] prefix |
| file://:0:0:0:0 | regexpMatch | semmle.label | [BuiltinPredicate] regexpMatch |
| file://:0:0:0:0 | regexpReplaceAll | semmle.label | [BuiltinPredicate] regexpReplaceAll |
| file://:0:0:0:0 | replaceAll | semmle.label | [BuiltinPredicate] replaceAll |
| file://:0:0:0:0 | signum | semmle.label | [BuiltinPredicate] signum |
| file://:0:0:0:0 | sin | semmle.label | [BuiltinPredicate] sin |
| file://:0:0:0:0 | sinh | semmle.label | [BuiltinPredicate] sinh |
| file://:0:0:0:0 | splitAt | semmle.label | [BuiltinPredicate] splitAt |
| file://:0:0:0:0 | splitAt | semmle.label | [BuiltinPredicate] splitAt |
| file://:0:0:0:0 | sqrt | semmle.label | [BuiltinPredicate] sqrt |
| file://:0:0:0:0 | substring | semmle.label | [BuiltinPredicate] substring |
| file://:0:0:0:0 | suffix | semmle.label | [BuiltinPredicate] suffix |
| file://:0:0:0:0 | tan | semmle.label | [BuiltinPredicate] tan |
| file://:0:0:0:0 | tanh | semmle.label | [BuiltinPredicate] tanh |
| file://:0:0:0:0 | toDate | semmle.label | [BuiltinPredicate] toDate |
| file://:0:0:0:0 | toFloat | semmle.label | [BuiltinPredicate] toFloat |
| file://:0:0:0:0 | toISO | semmle.label | [BuiltinPredicate] toISO |
| file://:0:0:0:0 | toInt | semmle.label | [BuiltinPredicate] toInt |
| file://:0:0:0:0 | toLowerCase | semmle.label | [BuiltinPredicate] toLowerCase |
| file://:0:0:0:0 | toString | semmle.label | [BuiltinPredicate] toString |
| file://:0:0:0:0 | toString | semmle.label | [BuiltinPredicate] toString |
| file://:0:0:0:0 | toString | semmle.label | [BuiltinPredicate] toString |
| file://:0:0:0:0 | toString | semmle.label | [BuiltinPredicate] toString |
| file://:0:0:0:0 | toString | semmle.label | [BuiltinPredicate] toString |
| file://:0:0:0:0 | toUnicode | semmle.label | [BuiltinPredicate] toUnicode |
| file://:0:0:0:0 | toUpperCase | semmle.label | [BuiltinPredicate] toUpperCase |
| file://:0:0:0:0 | toUrl | semmle.label | [BuiltinPredicate] toUrl |
| file://:0:0:0:0 | toUrl | semmle.label | [BuiltinPredicate] toUrl |
| file://:0:0:0:0 | trim | semmle.label | [BuiltinPredicate] trim |
| file://:0:0:0:0 | ulp | semmle.label | [BuiltinPredicate] ulp |
| printAst.ql:1:1:1:28 | Import | semmle.label | [Import] Import |
| printAst.ql:1:1:1:28 | Import | semmle.order | 86 |
| printAst.ql:1:1:1:28 | Import | semmle.order | 87 |
| printAst.ql:1:1:1:29 | TopLevel | semmle.label | [TopLevel] TopLevel |
| printAst.ql:1:1:1:29 | TopLevel | semmle.order | 86 |
| printAst.ql:1:1:1:29 | TopLevel | semmle.order | 87 |
edges
| Foo.qll:1:1:27:2 | TopLevel | Foo.qll:1:1:1:17 | Import | semmle.label | getAnImport() |
| Foo.qll:1:1:27:2 | TopLevel | Foo.qll:1:1:1:17 | Import | semmle.order | 1 |
| Foo.qll:1:1:27:2 | TopLevel | Foo.qll:3:1:7:1 | Class Foo | semmle.label | getAClass() |
| Foo.qll:1:1:27:2 | TopLevel | Foo.qll:3:1:7:1 | Class Foo | semmle.order | 3 |
| Foo.qll:1:1:27:2 | TopLevel | Foo.qll:9:7:11:1 | ClasslessPredicate foo | semmle.label | getAPredicate() |
| Foo.qll:1:1:27:2 | TopLevel | Foo.qll:9:7:11:1 | ClasslessPredicate foo | semmle.order | 16 |
| Foo.qll:1:1:27:2 | TopLevel | Foo.qll:9:7:11:1 | ClasslessPredicate foo | semmle.order | 17 |
| Foo.qll:1:1:27:2 | TopLevel | Foo.qll:13:1:27:1 | ClasslessPredicate calls | semmle.label | getAPredicate() |
| Foo.qll:1:1:27:2 | TopLevel | Foo.qll:13:1:27:1 | ClasslessPredicate calls | semmle.order | 34 |
| Foo.qll:1:1:27:2 | TopLevel | Foo.qll:13:1:27:1 | ClasslessPredicate calls | semmle.order | 35 |
| Foo.qll:3:1:7:1 | Class Foo | Foo.qll:3:19:3:22 | TypeExpr | semmle.label | getASuperType() |
| Foo.qll:3:1:7:1 | Class Foo | Foo.qll:3:19:3:22 | TypeExpr | semmle.order | 4 |
| Foo.qll:3:1:7:1 | Class Foo | Foo.qll:4:3:4:17 | CharPred Foo | semmle.label | getCharPred() |
@@ -207,142 +287,142 @@ edges
| Foo.qll:6:23:6:36 | ComparisonFormula | Foo.qll:6:32:6:36 | String | semmle.label | getRightOperand() |
| Foo.qll:6:23:6:36 | ComparisonFormula | Foo.qll:6:32:6:36 | String | semmle.order | 15 |
| Foo.qll:9:7:11:1 | ClasslessPredicate foo | Foo.qll:9:21:9:25 | f | semmle.label | getParameter(_) |
| Foo.qll:9:7:11:1 | ClasslessPredicate foo | Foo.qll:9:21:9:25 | f | semmle.order | 17 |
| Foo.qll:9:7:11:1 | ClasslessPredicate foo | Foo.qll:9:21:9:25 | f | semmle.order | 18 |
| Foo.qll:9:7:11:1 | ClasslessPredicate foo | Foo.qll:10:3:10:85 | ComparisonFormula | semmle.label | getBody() |
| Foo.qll:9:7:11:1 | ClasslessPredicate foo | Foo.qll:10:3:10:85 | ComparisonFormula | semmle.order | 19 |
| Foo.qll:9:7:11:1 | ClasslessPredicate foo | Foo.qll:10:3:10:85 | ComparisonFormula | semmle.order | 20 |
| Foo.qll:9:21:9:25 | f | Foo.qll:9:21:9:23 | TypeExpr | semmle.label | getTypeExpr() |
| Foo.qll:9:21:9:25 | f | Foo.qll:9:21:9:23 | TypeExpr | semmle.order | 17 |
| Foo.qll:9:21:9:25 | f | Foo.qll:9:21:9:23 | TypeExpr | semmle.order | 18 |
| Foo.qll:10:3:10:85 | ComparisonFormula | Foo.qll:10:3:10:3 | f | semmle.label | getLeftOperand() |
| Foo.qll:10:3:10:85 | ComparisonFormula | Foo.qll:10:3:10:3 | f | semmle.order | 19 |
| Foo.qll:10:3:10:85 | ComparisonFormula | Foo.qll:10:3:10:3 | f | semmle.order | 20 |
| Foo.qll:10:3:10:85 | ComparisonFormula | Foo.qll:10:5:10:5 | ComparisonOp | semmle.label | getOperator() |
| Foo.qll:10:3:10:85 | ComparisonFormula | Foo.qll:10:5:10:5 | ComparisonOp | semmle.order | 21 |
| Foo.qll:10:3:10:85 | ComparisonFormula | Foo.qll:10:5:10:5 | ComparisonOp | semmle.order | 22 |
| Foo.qll:10:3:10:85 | ComparisonFormula | Foo.qll:10:7:10:85 | Rank | semmle.label | getRightOperand() |
| Foo.qll:10:3:10:85 | ComparisonFormula | Foo.qll:10:7:10:85 | Rank | semmle.order | 22 |
| Foo.qll:10:3:10:85 | ComparisonFormula | Foo.qll:10:7:10:85 | Rank | semmle.order | 23 |
| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:12:10:12 | Integer | semmle.label | getRankExpr() |
| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:12:10:12 | Integer | semmle.order | 23 |
| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:12:10:12 | Integer | semmle.order | 24 |
| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:15:10:23 | inner | semmle.label | getArgument(_) |
| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:15:10:23 | inner | semmle.order | 24 |
| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:15:10:23 | inner | semmle.order | 25 |
| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:27:10:50 | ComparisonFormula | semmle.label | getRange() |
| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:27:10:50 | ComparisonFormula | semmle.order | 26 |
| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:27:10:50 | ComparisonFormula | semmle.order | 27 |
| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:54:10:58 | inner | semmle.label | getExpr(_) |
| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:54:10:58 | inner | semmle.order | 31 |
| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:54:10:58 | inner | semmle.order | 32 |
| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:69:10:84 | MemberCall | semmle.label | getOrderBy(_) |
| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:69:10:84 | MemberCall | semmle.order | 32 |
| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:69:10:84 | MemberCall | semmle.order | 33 |
| Foo.qll:10:15:10:23 | inner | Foo.qll:10:15:10:17 | TypeExpr | semmle.label | getTypeExpr() |
| Foo.qll:10:15:10:23 | inner | Foo.qll:10:15:10:17 | TypeExpr | semmle.order | 24 |
| Foo.qll:10:15:10:23 | inner | Foo.qll:10:15:10:17 | TypeExpr | semmle.order | 25 |
| Foo.qll:10:27:10:42 | MemberCall | Foo.qll:10:27:10:31 | inner | semmle.label | getBase() |
| Foo.qll:10:27:10:42 | MemberCall | Foo.qll:10:27:10:31 | inner | semmle.order | 26 |
| Foo.qll:10:27:10:42 | MemberCall | Foo.qll:10:27:10:31 | inner | semmle.order | 27 |
| Foo.qll:10:27:10:50 | ComparisonFormula | Foo.qll:10:27:10:42 | MemberCall | semmle.label | getLeftOperand() |
| Foo.qll:10:27:10:50 | ComparisonFormula | Foo.qll:10:27:10:42 | MemberCall | semmle.order | 26 |
| Foo.qll:10:27:10:50 | ComparisonFormula | Foo.qll:10:27:10:42 | MemberCall | semmle.order | 27 |
| Foo.qll:10:27:10:50 | ComparisonFormula | Foo.qll:10:44:10:44 | ComparisonOp | semmle.label | getOperator() |
| Foo.qll:10:27:10:50 | ComparisonFormula | Foo.qll:10:44:10:44 | ComparisonOp | semmle.order | 29 |
| Foo.qll:10:27:10:50 | ComparisonFormula | Foo.qll:10:44:10:44 | ComparisonOp | semmle.order | 30 |
| Foo.qll:10:27:10:50 | ComparisonFormula | Foo.qll:10:46:10:50 | String | semmle.label | getRightOperand() |
| Foo.qll:10:27:10:50 | ComparisonFormula | Foo.qll:10:46:10:50 | String | semmle.order | 30 |
| Foo.qll:10:27:10:50 | ComparisonFormula | Foo.qll:10:46:10:50 | String | semmle.order | 31 |
| Foo.qll:10:69:10:84 | MemberCall | Foo.qll:10:69:10:73 | inner | semmle.label | getBase() |
| Foo.qll:10:69:10:84 | MemberCall | Foo.qll:10:69:10:73 | inner | semmle.order | 32 |
| Foo.qll:10:69:10:84 | MemberCall | Foo.qll:10:69:10:73 | inner | semmle.order | 33 |
| Foo.qll:13:1:27:1 | ClasslessPredicate calls | Foo.qll:13:17:13:21 | f | semmle.label | getParameter(_) |
| Foo.qll:13:1:27:1 | ClasslessPredicate calls | Foo.qll:13:17:13:21 | f | semmle.order | 35 |
| Foo.qll:13:1:27:1 | ClasslessPredicate calls | Foo.qll:13:17:13:21 | f | semmle.order | 36 |
| Foo.qll:13:1:27:1 | ClasslessPredicate calls | Foo.qll:14:3:26:14 | Disjunction | semmle.label | getBody() |
| Foo.qll:13:1:27:1 | ClasslessPredicate calls | Foo.qll:14:3:26:14 | Disjunction | semmle.order | 37 |
| Foo.qll:13:1:27:1 | ClasslessPredicate calls | Foo.qll:14:3:26:14 | Disjunction | semmle.order | 38 |
| Foo.qll:13:17:13:21 | f | Foo.qll:13:17:13:19 | TypeExpr | semmle.label | getTypeExpr() |
| Foo.qll:13:17:13:21 | f | Foo.qll:13:17:13:19 | TypeExpr | semmle.order | 35 |
| Foo.qll:13:17:13:21 | f | Foo.qll:13:17:13:19 | TypeExpr | semmle.order | 36 |
| Foo.qll:14:3:14:10 | PredicateCall | Foo.qll:14:9:14:9 | f | semmle.label | getArgument(_) |
| Foo.qll:14:3:14:10 | PredicateCall | Foo.qll:14:9:14:9 | f | semmle.order | 44 |
| Foo.qll:14:3:14:10 | PredicateCall | Foo.qll:14:9:14:9 | f | semmle.order | 45 |
| Foo.qll:14:3:16:29 | Disjunction | Foo.qll:14:3:14:10 | PredicateCall | semmle.label | getAnOperand() |
| Foo.qll:14:3:16:29 | Disjunction | Foo.qll:14:3:14:10 | PredicateCall | semmle.order | 37 |
| Foo.qll:14:3:16:29 | Disjunction | Foo.qll:14:3:14:10 | PredicateCall | semmle.order | 38 |
| Foo.qll:14:3:16:29 | Disjunction | Foo.qll:16:3:16:29 | ComparisonFormula | semmle.label | getAnOperand() |
| Foo.qll:14:3:16:29 | Disjunction | Foo.qll:16:3:16:29 | ComparisonFormula | semmle.order | 45 |
| Foo.qll:14:3:16:29 | Disjunction | Foo.qll:16:3:16:29 | ComparisonFormula | semmle.order | 46 |
| Foo.qll:14:3:18:28 | Disjunction | Foo.qll:14:3:16:29 | Disjunction | semmle.label | getAnOperand() |
| Foo.qll:14:3:18:28 | Disjunction | Foo.qll:14:3:16:29 | Disjunction | semmle.order | 37 |
| Foo.qll:14:3:18:28 | Disjunction | Foo.qll:14:3:16:29 | Disjunction | semmle.order | 38 |
| Foo.qll:14:3:18:28 | Disjunction | Foo.qll:18:3:18:28 | ComparisonFormula | semmle.label | getAnOperand() |
| Foo.qll:14:3:18:28 | Disjunction | Foo.qll:18:3:18:28 | ComparisonFormula | semmle.order | 53 |
| Foo.qll:14:3:18:28 | Disjunction | Foo.qll:18:3:18:28 | ComparisonFormula | semmle.order | 54 |
| Foo.qll:14:3:20:13 | Disjunction | Foo.qll:14:3:18:28 | Disjunction | semmle.label | getAnOperand() |
| Foo.qll:14:3:20:13 | Disjunction | Foo.qll:14:3:18:28 | Disjunction | semmle.order | 37 |
| Foo.qll:14:3:20:13 | Disjunction | Foo.qll:14:3:18:28 | Disjunction | semmle.order | 38 |
| Foo.qll:14:3:20:13 | Disjunction | Foo.qll:20:3:20:13 | ComparisonFormula | semmle.label | getAnOperand() |
| Foo.qll:14:3:20:13 | Disjunction | Foo.qll:20:3:20:13 | ComparisonFormula | semmle.order | 60 |
| Foo.qll:14:3:20:13 | Disjunction | Foo.qll:20:3:20:13 | ComparisonFormula | semmle.order | 61 |
| Foo.qll:14:3:22:16 | Disjunction | Foo.qll:14:3:20:13 | Disjunction | semmle.label | getAnOperand() |
| Foo.qll:14:3:22:16 | Disjunction | Foo.qll:14:3:20:13 | Disjunction | semmle.order | 37 |
| Foo.qll:14:3:22:16 | Disjunction | Foo.qll:14:3:20:13 | Disjunction | semmle.order | 38 |
| Foo.qll:14:3:22:16 | Disjunction | Foo.qll:22:3:22:16 | ComparisonFormula | semmle.label | getAnOperand() |
| Foo.qll:14:3:22:16 | Disjunction | Foo.qll:22:3:22:16 | ComparisonFormula | semmle.order | 66 |
| Foo.qll:14:3:22:16 | Disjunction | Foo.qll:22:3:22:16 | ComparisonFormula | semmle.order | 67 |
| Foo.qll:14:3:24:23 | Disjunction | Foo.qll:14:3:22:16 | Disjunction | semmle.label | getAnOperand() |
| Foo.qll:14:3:24:23 | Disjunction | Foo.qll:14:3:22:16 | Disjunction | semmle.order | 37 |
| Foo.qll:14:3:24:23 | Disjunction | Foo.qll:14:3:22:16 | Disjunction | semmle.order | 38 |
| Foo.qll:14:3:24:23 | Disjunction | Foo.qll:24:3:24:23 | ComparisonFormula | semmle.label | getAnOperand() |
| Foo.qll:14:3:24:23 | Disjunction | Foo.qll:24:3:24:23 | ComparisonFormula | semmle.order | 72 |
| Foo.qll:14:3:24:23 | Disjunction | Foo.qll:24:3:24:23 | ComparisonFormula | semmle.order | 73 |
| Foo.qll:14:3:26:14 | Disjunction | Foo.qll:14:3:24:23 | Disjunction | semmle.label | getAnOperand() |
| Foo.qll:14:3:26:14 | Disjunction | Foo.qll:14:3:24:23 | Disjunction | semmle.order | 37 |
| Foo.qll:14:3:26:14 | Disjunction | Foo.qll:14:3:24:23 | Disjunction | semmle.order | 38 |
| Foo.qll:14:3:26:14 | Disjunction | Foo.qll:26:3:26:14 | ComparisonFormula | semmle.label | getAnOperand() |
| Foo.qll:14:3:26:14 | Disjunction | Foo.qll:26:3:26:14 | ComparisonFormula | semmle.order | 82 |
| Foo.qll:14:3:26:14 | Disjunction | Foo.qll:26:3:26:14 | ComparisonFormula | semmle.order | 83 |
| Foo.qll:16:3:16:29 | ComparisonFormula | Foo.qll:16:3:16:7 | String | semmle.label | getLeftOperand() |
| Foo.qll:16:3:16:29 | ComparisonFormula | Foo.qll:16:3:16:7 | String | semmle.order | 45 |
| Foo.qll:16:3:16:29 | ComparisonFormula | Foo.qll:16:3:16:7 | String | semmle.order | 46 |
| Foo.qll:16:3:16:29 | ComparisonFormula | Foo.qll:16:9:16:9 | ComparisonOp | semmle.label | getOperator() |
| Foo.qll:16:3:16:29 | ComparisonFormula | Foo.qll:16:9:16:9 | ComparisonOp | semmle.order | 47 |
| Foo.qll:16:3:16:29 | ComparisonFormula | Foo.qll:16:9:16:9 | ComparisonOp | semmle.order | 48 |
| Foo.qll:16:3:16:29 | ComparisonFormula | Foo.qll:16:11:16:29 | MemberCall | semmle.label | getRightOperand() |
| Foo.qll:16:3:16:29 | ComparisonFormula | Foo.qll:16:11:16:29 | MemberCall | semmle.order | 48 |
| Foo.qll:16:3:16:29 | ComparisonFormula | Foo.qll:16:11:16:29 | MemberCall | semmle.order | 49 |
| Foo.qll:16:11:16:29 | MemberCall | Foo.qll:16:11:16:11 | f | semmle.label | getBase() |
| Foo.qll:16:11:16:29 | MemberCall | Foo.qll:16:11:16:11 | f | semmle.order | 48 |
| Foo.qll:16:11:16:29 | MemberCall | Foo.qll:16:11:16:11 | f | semmle.order | 49 |
| Foo.qll:16:11:16:29 | MemberCall | Foo.qll:16:22:16:22 | Integer | semmle.label | getArgument(_) |
| Foo.qll:16:11:16:29 | MemberCall | Foo.qll:16:22:16:22 | Integer | semmle.order | 50 |
| Foo.qll:16:11:16:29 | MemberCall | Foo.qll:16:22:16:22 | Integer | semmle.order | 51 |
| Foo.qll:16:11:16:29 | MemberCall | Foo.qll:16:25:16:25 | Integer | semmle.label | getArgument(_) |
| Foo.qll:16:11:16:29 | MemberCall | Foo.qll:16:25:16:25 | Integer | semmle.order | 51 |
| Foo.qll:16:11:16:29 | MemberCall | Foo.qll:16:25:16:25 | Integer | semmle.order | 52 |
| Foo.qll:16:11:16:29 | MemberCall | Foo.qll:16:28:16:28 | Integer | semmle.label | getArgument(_) |
| Foo.qll:16:11:16:29 | MemberCall | Foo.qll:16:28:16:28 | Integer | semmle.order | 52 |
| Foo.qll:16:11:16:29 | MemberCall | Foo.qll:16:28:16:28 | Integer | semmle.order | 53 |
| Foo.qll:18:3:18:9 | InlineCast | Foo.qll:18:3:18:3 | f | semmle.label | getBase() |
| Foo.qll:18:3:18:9 | InlineCast | Foo.qll:18:3:18:3 | f | semmle.order | 53 |
| Foo.qll:18:3:18:9 | InlineCast | Foo.qll:18:3:18:3 | f | semmle.order | 54 |
| Foo.qll:18:3:18:9 | InlineCast | Foo.qll:18:6:18:8 | TypeExpr | semmle.label | getTypeExpr() |
| Foo.qll:18:3:18:9 | InlineCast | Foo.qll:18:6:18:8 | TypeExpr | semmle.order | 57 |
| Foo.qll:18:3:18:9 | InlineCast | Foo.qll:18:6:18:8 | TypeExpr | semmle.order | 58 |
| Foo.qll:18:3:18:20 | MemberCall | Foo.qll:18:3:18:9 | InlineCast | semmle.label | getBase() |
| Foo.qll:18:3:18:20 | MemberCall | Foo.qll:18:3:18:9 | InlineCast | semmle.order | 53 |
| Foo.qll:18:3:18:20 | MemberCall | Foo.qll:18:3:18:9 | InlineCast | semmle.order | 54 |
| Foo.qll:18:3:18:28 | ComparisonFormula | Foo.qll:18:3:18:20 | MemberCall | semmle.label | getLeftOperand() |
| Foo.qll:18:3:18:28 | ComparisonFormula | Foo.qll:18:3:18:20 | MemberCall | semmle.order | 53 |
| Foo.qll:18:3:18:28 | ComparisonFormula | Foo.qll:18:3:18:20 | MemberCall | semmle.order | 54 |
| Foo.qll:18:3:18:28 | ComparisonFormula | Foo.qll:18:22:18:22 | ComparisonOp | semmle.label | getOperator() |
| Foo.qll:18:3:18:28 | ComparisonFormula | Foo.qll:18:22:18:22 | ComparisonOp | semmle.order | 58 |
| Foo.qll:18:3:18:28 | ComparisonFormula | Foo.qll:18:22:18:22 | ComparisonOp | semmle.order | 59 |
| Foo.qll:18:3:18:28 | ComparisonFormula | Foo.qll:18:24:18:28 | String | semmle.label | getRightOperand() |
| Foo.qll:18:3:18:28 | ComparisonFormula | Foo.qll:18:24:18:28 | String | semmle.order | 59 |
| Foo.qll:18:3:18:28 | ComparisonFormula | Foo.qll:18:24:18:28 | String | semmle.order | 60 |
| Foo.qll:20:3:20:9 | InlineCast | Foo.qll:20:3:20:3 | f | semmle.label | getBase() |
| Foo.qll:20:3:20:9 | InlineCast | Foo.qll:20:3:20:3 | f | semmle.order | 60 |
| Foo.qll:20:3:20:9 | InlineCast | Foo.qll:20:3:20:3 | f | semmle.order | 61 |
| Foo.qll:20:3:20:9 | InlineCast | Foo.qll:20:6:20:8 | TypeExpr | semmle.label | getTypeExpr() |
| Foo.qll:20:3:20:9 | InlineCast | Foo.qll:20:6:20:8 | TypeExpr | semmle.order | 63 |
| Foo.qll:20:3:20:9 | InlineCast | Foo.qll:20:6:20:8 | TypeExpr | semmle.order | 64 |
| Foo.qll:20:3:20:13 | ComparisonFormula | Foo.qll:20:3:20:9 | InlineCast | semmle.label | getLeftOperand() |
| Foo.qll:20:3:20:13 | ComparisonFormula | Foo.qll:20:3:20:9 | InlineCast | semmle.order | 60 |
| Foo.qll:20:3:20:13 | ComparisonFormula | Foo.qll:20:3:20:9 | InlineCast | semmle.order | 61 |
| Foo.qll:20:3:20:13 | ComparisonFormula | Foo.qll:20:11:20:11 | ComparisonOp | semmle.label | getOperator() |
| Foo.qll:20:3:20:13 | ComparisonFormula | Foo.qll:20:11:20:11 | ComparisonOp | semmle.order | 64 |
| Foo.qll:20:3:20:13 | ComparisonFormula | Foo.qll:20:11:20:11 | ComparisonOp | semmle.order | 65 |
| Foo.qll:20:3:20:13 | ComparisonFormula | Foo.qll:20:13:20:13 | f | semmle.label | getRightOperand() |
| Foo.qll:20:3:20:13 | ComparisonFormula | Foo.qll:20:13:20:13 | f | semmle.order | 65 |
| Foo.qll:20:3:20:13 | ComparisonFormula | Foo.qll:20:13:20:13 | f | semmle.order | 66 |
| Foo.qll:22:3:22:16 | ComparisonFormula | Foo.qll:22:3:22:3 | f | semmle.label | getLeftOperand() |
| Foo.qll:22:3:22:16 | ComparisonFormula | Foo.qll:22:3:22:3 | f | semmle.order | 66 |
| Foo.qll:22:3:22:16 | ComparisonFormula | Foo.qll:22:3:22:3 | f | semmle.order | 67 |
| Foo.qll:22:3:22:16 | ComparisonFormula | Foo.qll:22:5:22:5 | ComparisonOp | semmle.label | getOperator() |
| Foo.qll:22:3:22:16 | ComparisonFormula | Foo.qll:22:5:22:5 | ComparisonOp | semmle.order | 68 |
| Foo.qll:22:3:22:16 | ComparisonFormula | Foo.qll:22:5:22:5 | ComparisonOp | semmle.order | 69 |
| Foo.qll:22:3:22:16 | ComparisonFormula | Foo.qll:22:7:22:16 | FullAggregate[any] | semmle.label | getRightOperand() |
| Foo.qll:22:3:22:16 | ComparisonFormula | Foo.qll:22:7:22:16 | FullAggregate[any] | semmle.order | 69 |
| Foo.qll:22:3:22:16 | ComparisonFormula | Foo.qll:22:7:22:16 | FullAggregate[any] | semmle.order | 70 |
| Foo.qll:22:7:22:16 | FullAggregate[any] | Foo.qll:22:11:22:15 | f | semmle.label | getArgument(_) |
| Foo.qll:22:7:22:16 | FullAggregate[any] | Foo.qll:22:11:22:15 | f | semmle.order | 70 |
| Foo.qll:22:7:22:16 | FullAggregate[any] | Foo.qll:22:11:22:15 | f | semmle.order | 71 |
| Foo.qll:22:11:22:15 | f | Foo.qll:22:11:22:13 | TypeExpr | semmle.label | getTypeExpr() |
| Foo.qll:22:11:22:15 | f | Foo.qll:22:11:22:13 | TypeExpr | semmle.order | 70 |
| Foo.qll:22:11:22:15 | f | Foo.qll:22:11:22:13 | TypeExpr | semmle.order | 71 |
| Foo.qll:24:3:24:23 | ComparisonFormula | Foo.qll:24:3:24:3 | Integer | semmle.label | getLeftOperand() |
| Foo.qll:24:3:24:23 | ComparisonFormula | Foo.qll:24:3:24:3 | Integer | semmle.order | 72 |
| Foo.qll:24:3:24:23 | ComparisonFormula | Foo.qll:24:3:24:3 | Integer | semmle.order | 73 |
| Foo.qll:24:3:24:23 | ComparisonFormula | Foo.qll:24:5:24:5 | ComparisonOp | semmle.label | getOperator() |
| Foo.qll:24:3:24:23 | ComparisonFormula | Foo.qll:24:5:24:5 | ComparisonOp | semmle.order | 74 |
| Foo.qll:24:3:24:23 | ComparisonFormula | Foo.qll:24:5:24:5 | ComparisonOp | semmle.order | 75 |
| Foo.qll:24:3:24:23 | ComparisonFormula | Foo.qll:24:7:24:23 | AddExpr | semmle.label | getRightOperand() |
| Foo.qll:24:3:24:23 | ComparisonFormula | Foo.qll:24:7:24:23 | AddExpr | semmle.order | 75 |
| Foo.qll:24:3:24:23 | ComparisonFormula | Foo.qll:24:7:24:23 | AddExpr | semmle.order | 76 |
| Foo.qll:24:7:24:23 | AddExpr | Foo.qll:24:7:24:7 | Integer | semmle.label | getLeftOperand() |
| Foo.qll:24:7:24:23 | AddExpr | Foo.qll:24:7:24:7 | Integer | semmle.order | 75 |
| Foo.qll:24:7:24:23 | AddExpr | Foo.qll:24:7:24:7 | Integer | semmle.order | 76 |
| Foo.qll:24:7:24:23 | AddExpr | Foo.qll:24:12:24:22 | AddExpr | semmle.label | getRightOperand() |
| Foo.qll:24:7:24:23 | AddExpr | Foo.qll:24:12:24:22 | AddExpr | semmle.order | 77 |
| Foo.qll:24:7:24:23 | AddExpr | Foo.qll:24:12:24:22 | AddExpr | semmle.order | 78 |
| Foo.qll:24:12:24:22 | AddExpr | Foo.qll:24:12:24:12 | Integer | semmle.label | getLeftOperand() |
| Foo.qll:24:12:24:22 | AddExpr | Foo.qll:24:12:24:12 | Integer | semmle.order | 77 |
| Foo.qll:24:12:24:22 | AddExpr | Foo.qll:24:12:24:12 | Integer | semmle.order | 78 |
| Foo.qll:24:12:24:22 | AddExpr | Foo.qll:24:17:24:21 | AddExpr | semmle.label | getRightOperand() |
| Foo.qll:24:12:24:22 | AddExpr | Foo.qll:24:17:24:21 | AddExpr | semmle.order | 79 |
| Foo.qll:24:12:24:22 | AddExpr | Foo.qll:24:17:24:21 | AddExpr | semmle.order | 80 |
| Foo.qll:24:17:24:21 | AddExpr | Foo.qll:24:17:24:17 | Integer | semmle.label | getLeftOperand() |
| Foo.qll:24:17:24:21 | AddExpr | Foo.qll:24:17:24:17 | Integer | semmle.order | 79 |
| Foo.qll:24:17:24:21 | AddExpr | Foo.qll:24:17:24:17 | Integer | semmle.order | 80 |
| Foo.qll:24:17:24:21 | AddExpr | Foo.qll:24:21:24:21 | Integer | semmle.label | getRightOperand() |
| Foo.qll:24:17:24:21 | AddExpr | Foo.qll:24:21:24:21 | Integer | semmle.order | 81 |
| Foo.qll:24:17:24:21 | AddExpr | Foo.qll:24:21:24:21 | Integer | semmle.order | 82 |
| Foo.qll:26:3:26:14 | ComparisonFormula | Foo.qll:26:3:26:6 | Boolean | semmle.label | getLeftOperand() |
| Foo.qll:26:3:26:14 | ComparisonFormula | Foo.qll:26:3:26:6 | Boolean | semmle.order | 82 |
| Foo.qll:26:3:26:14 | ComparisonFormula | Foo.qll:26:3:26:6 | Boolean | semmle.order | 83 |
| Foo.qll:26:3:26:14 | ComparisonFormula | Foo.qll:26:8:26:8 | ComparisonOp | semmle.label | getOperator() |
| Foo.qll:26:3:26:14 | ComparisonFormula | Foo.qll:26:8:26:8 | ComparisonOp | semmle.order | 84 |
| Foo.qll:26:3:26:14 | ComparisonFormula | Foo.qll:26:8:26:8 | ComparisonOp | semmle.order | 85 |
| Foo.qll:26:3:26:14 | ComparisonFormula | Foo.qll:26:10:26:14 | Boolean | semmle.label | getRightOperand() |
| Foo.qll:26:3:26:14 | ComparisonFormula | Foo.qll:26:10:26:14 | Boolean | semmle.order | 85 |
| Foo.qll:26:3:26:14 | ComparisonFormula | Foo.qll:26:10:26:14 | Boolean | semmle.order | 86 |
| printAst.ql:1:1:1:29 | TopLevel | printAst.ql:1:1:1:28 | Import | semmle.label | getAnImport() |
| printAst.ql:1:1:1:29 | TopLevel | printAst.ql:1:1:1:28 | Import | semmle.order | 86 |
| printAst.ql:1:1:1:29 | TopLevel | printAst.ql:1:1:1:28 | Import | semmle.order | 87 |
graphProperties
| semmle.graphKind | tree |

View File

@@ -0,0 +1,11 @@
import ql
class Foo extends string {
Foo() { this = "hello" }
string getBar() { result = "bar" }
string getBarWithThis() { result = this.getBar() }
string getBarWithoutThis() { result = getBar() }
}

View File

@@ -0,0 +1,21 @@
import ql
class Foo extends string {
Foo() { this = "hello" }
string getBar() { result = "bar" }
string getBarWithThis() { result = this.getBar() }
/* Okay because not a member predicate. */
string getBaz() { result = Baz::baz() }
/* Okay because not a member predicate. */
string getOuterQuux() { result = getQuux() }
}
string getQuux() { result = "quux" }
module Baz {
string baz() { result = "baz" }
}

View File

@@ -0,0 +1 @@
| Bad.qll:10:41:10:48 | PredicateCall | Use of implicit `this`. |

View File

@@ -0,0 +1 @@
queries/style/ImplicitThis.ql

View File

@@ -0,0 +1,13 @@
import ql
class Foo extends string {
Foo() { this = "hello" }
string getBar() { result = "bar" }
/* Okay, because we don't write `this.some_method` anywhere */
string getBarWithoutThis() { result = getBar() }
/* Okay, because this is the only way to cast `this`. */
string useThisWithInlineCast() { result = this.(string).toUpperCase() }
}

1
repo-tests/codeql-go.txt Normal file
View File

@@ -0,0 +1 @@
abe3f2148b92b1a94a0a3676cb4dab7d9211076f

View File

@@ -0,0 +1,3 @@
name: legacy-libraries-go
version: 0.0.0
libraryPathDependencies: codeql-go

View File

@@ -0,0 +1,4 @@
name: codeql/go-examples
version: 0.0.2
dependencies:
codeql/go-all: ^0.0.2

View File

@@ -0,0 +1,15 @@
/**
* @name Call to built-in function
* @description Finds calls to the built-in `len` function.
* @id go/examples/calltolen
* @tags call
* function
* len
* built-in
*/
import go
from DataFlow::CallNode call
where call = Builtin::len().getACall()
select call

View File

@@ -0,0 +1,16 @@
/**
* @name Call to library function
* @description Finds calls to "fmt.Println".
* @id go/examples/calltoprintln
* @tags call
* function
* println
*/
import go
from Function println, DataFlow::CallNode call
where
println.hasQualifiedName("fmt", "Println") and
call = println.getACall()
select call

View File

@@ -0,0 +1,18 @@
/**
* @name Call to method
* @description Finds calls to the `Get` method of type `Header` from the `net/http` package.
* @id go/examples/calltoheaderget
* @tags call
* function
* net/http
* Header
* strings
*/
import go
from Method get, DataFlow::CallNode call
where
get.hasQualifiedName("net/http", "Header", "Get") and
call = get.getACall()
select call

View File

@@ -0,0 +1,14 @@
/**
* @name Compile-time constant
* @description Finds compile-time constants with value zero.
* @id go/examples/zeroconstant
* @tags expression
* numeric value
* constant
*/
import go
from DataFlow::Node zero
where zero.getNumericValue() = 0
select zero

View File

@@ -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 go/examples/emptythen
* @tags if
* then
* empty
* conditional
* branch
* statement
*/
import go
from IfStmt i
where i.getThen().getNumStmt() = 0
select i

View File

@@ -0,0 +1,15 @@
/**
* @name Field read
* @description Finds code that reads `Request.Method`.
* @id go/examples/readofrequestmethod
* @tags field
* read
*/
import go
from Field reqm, Read read
where
reqm.hasQualifiedName("net/http", "Request", "Method") and
read = reqm.getARead()
select read

View File

@@ -0,0 +1,15 @@
/**
* @name Field write
* @description Finds assignments to field `Status` of type `Response` from package `net/http`.
* @id go/examples/responsestatus
* @tags net/http
* field write
*/
import go
from Field status, Write write
where
status.hasQualifiedName("net/http", "Response", "Status") and
write = status.getAWrite()
select write, write.getRhs()

View File

@@ -0,0 +1,13 @@
/**
* @name Function
* @description Finds functions called "main".
* @id go/examples/mainfunction
* @tags function
* main
*/
import go
from Function main
where main.getName() = "main"
select main

View File

@@ -0,0 +1,15 @@
/**
* @name Comparison with nil
* @description Finds comparisons with nil.
* @id go/examples/nilcheck
* @tags comparison
* nil
*/
import go
from DataFlow::EqualityTestNode eq, DataFlow::Node nd, DataFlow::Node nil
where
nil = Builtin::nil().getARead() and
eq.eq(_, nd, nil)
select eq

View File

@@ -0,0 +1,12 @@
/**
* @name Parameter
* @description Finds parameters of type "ResponseWriter" from package "net/http".
* @id go/examples/responseparam
* @tags parameter
*/
import go
from Parameter req
where req.getType().hasQualifiedName("net/http", "ResponseWriter")
select req

View File

@@ -0,0 +1,15 @@
/**
* @name Type
* @description Finds pointer type `*Request` from package `net/http`.
* @id go/examples/requestptrtype
* @tags net/http
* type
*/
import go
from Type reqtp, PointerType reqptrtp
where
reqtp.hasQualifiedName("net/http", "Request") and
reqptrtp.getBaseType() = reqtp
select reqptrtp

View File

@@ -0,0 +1,12 @@
/**
* @name Receiver variable
* @description Finds receiver variables of pointer type.
* @id go/examples/pointerreceiver
* @tags receiver variable
*/
import go
from ReceiverVariable recv
where recv.getType() instanceof PointerType
select recv

View File

@@ -0,0 +1,12 @@
/**
* @name Result variable
* @description Finds result variables of type "error".
* @id go/examples/errresult
* @tags result variable
*/
import go
from ResultVariable err
where err.getType() = Builtin::error().getType()
select err

View File

@@ -0,0 +1,13 @@
/**
* @name Type
* @description Finds type `Request` from package `net/http`.
* @id go/examples/requesttype
* @tags net/http
* type
*/
import go
from Type request
where request.hasQualifiedName("net/http", "Request")
select request

View File

@@ -0,0 +1,16 @@
/**
* @name Type information
* @description Finds code elements of type `*Request` from package `net/http`.
* @id go/examples/requests
* @tags net/http
* types
*/
import go
from Type reqtp, PointerType reqptrtp, DataFlow::Node req
where
reqtp.hasQualifiedName("net/http", "Request") and
reqptrtp.getBaseType() = reqtp and
req.getType() = reqptrtp
select req

View File

@@ -0,0 +1,13 @@
/**
* @name Increment statements in loops
* @description Finds increment statements that are nested in a loop
* @id go/examples/updateinloop
* @tags nesting
* increment
*/
import go
from IncStmt s, LoopStmt l
where s.getParent+() = l
select s, l

View File

@@ -0,0 +1,13 @@
/**
* @name Variable
* @description Finds variables called "err".
* @id go/examples/errvariable
* @tags variable
* err
*/
import go
from Variable err
where err.getName() = "err"
select err, err.getDeclaration()

View File

@@ -0,0 +1,14 @@
/**
* @name Variable read
* @description Finds code that reads a variable called `err`.
* @id go/examples/readoferr
* @tags variable read
*/
import go
from Variable err, Read read
where
err.getName() = "err" and
read = err.getARead()
select read

View File

@@ -0,0 +1,14 @@
/**
* @name Variable write
* @description Finds assignments to variables named "err".
* @id go/examples/errwrite
* @tags variable write
*/
import go
from Variable err, Write write
where
err.getName() = "err" and
write = err.getAWrite()
select write, write.getRhs()

View File

@@ -0,0 +1,16 @@
/**
* @name Comparison with zero
* @description Finds comparisons between an unsigned value and zero.
* @id go/examples/unsignedgez
* @tags comparison
* unsigned
*/
import go
from DataFlow::RelationalComparisonNode cmp, DataFlow::Node unsigned, DataFlow::Node zero
where
zero.getNumericValue() = 0 and
unsigned.getType().getUnderlyingType() instanceof UnsignedIntegerType and
cmp.leq(_, zero, unsigned, 0)
select cmp, unsigned

View File

@@ -0,0 +1,12 @@
/**
* Contains customizations to the standard library.
*
* This module is imported by `go.qll`, so any customizations defined here automatically
* apply to all queries.
*
* Typical examples of customizations include adding new subclasses of abstract classes such as
* `FileSystemAccess`, or the `Source` and `Sink` classes associated with the security queries
* to model frameworks that are not covered by the standard library.
*/
import go

View File

@@ -0,0 +1,15 @@
/**
* @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 go/jump-to-definition
*/
import go
from Ident def, Ident use, Entity e
where
use.uses(e) and
def.declares(e)
select use, def, "V"

View File

@@ -0,0 +1,528 @@
/** Auto-generated dbscheme; do not edit. */
/** Duplicate code **/
duplicateCode(
unique int id : @duplication,
varchar(900) relativePath : string ref,
int equivClass : int ref);
similarCode(
unique int id : @similarity,
varchar(900) relativePath : string ref,
int equivClass : int ref);
@duplication_or_similarity = @duplication | @similarity;
tokens(
int id : @duplication_or_similarity ref,
int offset : int ref,
int beginLine : int ref,
int beginColumn : int ref,
int endLine : int ref,
int endColumn : int ref);
/** External data **/
externalData(
int id : @externalDataElement,
varchar(900) path : string ref,
int column: int ref,
varchar(900) value : string ref
);
snapshotDate(unique date snapshotDate : date ref);
sourceLocationPrefix(varchar(900) prefix : string ref);
/*
* XML Files
*/
xmlEncoding(
unique int id: @file ref,
string encoding: string ref
);
xmlDTDs(
unique int id: @xmldtd,
string root: string ref,
string publicId: string ref,
string systemId: string ref,
int fileid: @file ref
);
xmlElements(
unique int id: @xmlelement,
string name: string ref,
int parentid: @xmlparent ref,
int idx: int ref,
int fileid: @file ref
);
xmlAttrs(
unique int id: @xmlattribute,
int elementid: @xmlelement ref,
string name: string ref,
string value: string ref,
int idx: int ref,
int fileid: @file ref
);
xmlNs(
int id: @xmlnamespace,
string prefixName: string ref,
string URI: string ref,
int fileid: @file ref
);
xmlHasNs(
int elementId: @xmlnamespaceable ref,
int nsId: @xmlnamespace ref,
int fileid: @file ref
);
xmlComments(
unique int id: @xmlcomment,
string text: string ref,
int parentid: @xmlparent ref,
int fileid: @file ref
);
xmlChars(
unique int id: @xmlcharacters,
string text: string ref,
int parentid: @xmlparent ref,
int idx: int ref,
int isCDATA: int ref,
int fileid: @file ref
);
@xmlparent = @file | @xmlelement;
@xmlnamespaceable = @xmlelement | @xmlattribute;
xmllocations(
int xmlElement: @xmllocatable ref,
int location: @location_default ref
);
@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace;
compilations(unique int id: @compilation, string cwd: string ref);
#keyset[id, num]
compilation_args(int id: @compilation ref, int num: int ref, string arg: string ref);
#keyset[id, num, kind]
compilation_time(int id: @compilation ref, int num: int ref, int kind: int ref, float secs: float ref);
diagnostic_for(unique int diagnostic: @diagnostic ref, int compilation: @compilation ref, int file_number: int ref, int file_number_diagnostic_number: int ref);
compilation_finished(unique int id: @compilation ref, float cpu_seconds: float ref, float elapsed_seconds: float ref);
#keyset[id, num]
compilation_compiling_files(int id: @compilation ref, int num: int ref, int file: @file 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 ref);
locations_default(unique int id: @location_default, int file: @file ref, int beginLine: int ref, int beginColumn: int ref,
int endLine: int ref, int endColumn: int ref);
numlines(int element_id: @sourceline ref, int num_lines: int ref, int num_code: int ref, int num_comment: int ref);
files(unique int id: @file, string name: string ref);
folders(unique int id: @folder, string name: string ref);
containerparent(int parent: @container ref, unique int child: @container ref);
has_location(unique int locatable: @locatable ref, int location: @location ref);
#keyset[parent, idx]
comment_groups(unique int id: @comment_group, int parent: @file ref, int idx: int ref);
comments(unique int id: @comment, int kind: int ref, int parent: @comment_group ref, int idx: int ref, string text: string ref);
doc_comments(unique int node: @documentable ref, int comment: @comment_group ref);
#keyset[parent, idx]
exprs(unique int id: @expr, int kind: int ref, int parent: @exprparent ref, int idx: int ref);
literals(unique int expr: @expr ref, string value: string ref, string raw: string ref);
constvalues(unique int expr: @expr ref, string value: string ref, string exact: string ref);
fields(unique int id: @field, int parent: @fieldparent ref, int idx: int ref);
#keyset[parent, idx]
stmts(unique int id: @stmt, int kind: int ref, int parent: @stmtparent ref, int idx: int ref);
#keyset[parent, idx]
decls(unique int id: @decl, int kind: int ref, int parent: @declparent ref, int idx: int ref);
#keyset[parent, idx]
specs(unique int id: @spec, int kind: int ref, int parent: @gendecl ref, int idx: int ref);
scopes(unique int id: @scope, int kind: int ref);
scopenesting(unique int inner: @scope ref, int outer: @scope ref);
scopenodes(unique int node: @scopenode ref, int scope: @localscope ref);
objects(unique int id: @object, int kind: int ref, string name: string ref);
objectscopes(unique int object: @object ref, int scope: @scope ref);
objecttypes(unique int object: @object ref, int tp: @type ref);
methodreceivers(unique int method: @object ref, int receiver: @object ref);
fieldstructs(unique int field: @object ref, int struct: @structtype ref);
methodhosts(int method: @object ref, int host: @namedtype ref);
defs(int ident: @ident ref, int object: @object ref);
uses(int ident: @ident ref, int object: @object ref);
types(unique int id: @type, int kind: int ref);
type_of(unique int expr: @expr ref, int tp: @type ref);
typename(unique int tp: @type ref, string name: string ref);
key_type(unique int map: @maptype ref, int tp: @type ref);
element_type(unique int container: @containertype ref, int tp: @type ref);
base_type(unique int ptr: @pointertype ref, int tp: @type ref);
underlying_type(unique int named: @namedtype ref, int tp: @type ref);
#keyset[parent, index]
component_types(int parent: @compositetype ref, int index: int ref, string name: string ref, int tp: @type ref);
array_length(unique int tp: @arraytype ref, string len: string ref);
type_objects(unique int tp: @type ref, int object: @object ref);
packages(unique int id: @package, string name: string ref, string path: string ref, int scope: @packagescope ref);
#keyset[parent, idx]
modexprs(unique int id: @modexpr, int kind: int ref, int parent: @modexprparent ref, int idx: int ref);
#keyset[parent, idx]
modtokens(string token: string ref, int parent: @modexpr ref, int idx: int ref);
#keyset[package, idx]
errors(unique int id: @error, int kind: int ref, string msg: string ref, string rawpos: string ref,
string file: string ref, int line: int ref, int col: int ref, int package: @package ref, int idx: int ref);
has_ellipsis(int id: @callorconversionexpr ref);
@container = @file | @folder;
@locatable = @xmllocatable | @node | @localscope;
@node = @documentable | @exprparent | @modexprparent | @fieldparent | @stmtparent | @declparent | @scopenode
| @comment_group | @comment;
@documentable = @file | @field | @spec | @gendecl | @funcdecl | @modexpr;
@exprparent = @funcdef | @file | @expr | @field | @stmt | @decl | @spec;
@modexprparent = @file | @modexpr;
@fieldparent = @decl | @structtypeexpr | @functypeexpr | @interfacetypeexpr;
@stmtparent = @funcdef | @stmt | @decl;
@declparent = @file | @declstmt;
@funcdef = @funclit | @funcdecl;
@scopenode = @file | @functypeexpr | @blockstmt | @ifstmt | @caseclause | @switchstmt | @commclause | @loopstmt;
@location = @location_default;
@sourceline = @locatable;
case @comment.kind of
0 = @slashslashcomment
| 1 = @slashstarcomment;
case @expr.kind of
0 = @badexpr
| 1 = @ident
| 2 = @ellipsis
| 3 = @intlit
| 4 = @floatlit
| 5 = @imaglit
| 6 = @charlit
| 7 = @stringlit
| 8 = @funclit
| 9 = @compositelit
| 10 = @parenexpr
| 11 = @selectorexpr
| 12 = @indexexpr
| 13 = @sliceexpr
| 14 = @typeassertexpr
| 15 = @callorconversionexpr
| 16 = @starexpr
| 17 = @keyvalueexpr
| 18 = @arraytypeexpr
| 19 = @structtypeexpr
| 20 = @functypeexpr
| 21 = @interfacetypeexpr
| 22 = @maptypeexpr
| 23 = @plusexpr
| 24 = @minusexpr
| 25 = @notexpr
| 26 = @complementexpr
| 27 = @derefexpr
| 28 = @addressexpr
| 29 = @arrowexpr
| 30 = @lorexpr
| 31 = @landexpr
| 32 = @eqlexpr
| 33 = @neqexpr
| 34 = @lssexpr
| 35 = @leqexpr
| 36 = @gtrexpr
| 37 = @geqexpr
| 38 = @addexpr
| 39 = @subexpr
| 40 = @orexpr
| 41 = @xorexpr
| 42 = @mulexpr
| 43 = @quoexpr
| 44 = @remexpr
| 45 = @shlexpr
| 46 = @shrexpr
| 47 = @andexpr
| 48 = @andnotexpr
| 49 = @sendchantypeexpr
| 50 = @recvchantypeexpr
| 51 = @sendrcvchantypeexpr;
@basiclit = @intlit | @floatlit | @imaglit | @charlit | @stringlit;
@operatorexpr = @logicalexpr | @arithmeticexpr | @bitwiseexpr | @unaryexpr | @binaryexpr;
@logicalexpr = @logicalunaryexpr | @logicalbinaryexpr;
@arithmeticexpr = @arithmeticunaryexpr | @arithmeticbinaryexpr;
@bitwiseexpr = @bitwiseunaryexpr | @bitwisebinaryexpr;
@unaryexpr = @logicalunaryexpr | @bitwiseunaryexpr | @arithmeticunaryexpr | @derefexpr | @addressexpr | @arrowexpr;
@logicalunaryexpr = @notexpr;
@bitwiseunaryexpr = @complementexpr;
@arithmeticunaryexpr = @plusexpr | @minusexpr;
@binaryexpr = @logicalbinaryexpr | @bitwisebinaryexpr | @arithmeticbinaryexpr | @comparison;
@logicalbinaryexpr = @lorexpr | @landexpr;
@bitwisebinaryexpr = @shiftexpr | @orexpr | @xorexpr | @andexpr | @andnotexpr;
@arithmeticbinaryexpr = @addexpr | @subexpr | @mulexpr | @quoexpr | @remexpr;
@shiftexpr = @shlexpr | @shrexpr;
@comparison = @equalitytest | @relationalcomparison;
@equalitytest = @eqlexpr | @neqexpr;
@relationalcomparison = @lssexpr | @leqexpr | @gtrexpr | @geqexpr;
@chantypeexpr = @sendchantypeexpr | @recvchantypeexpr | @sendrcvchantypeexpr;
case @stmt.kind of
0 = @badstmt
| 1 = @declstmt
| 2 = @emptystmt
| 3 = @labeledstmt
| 4 = @exprstmt
| 5 = @sendstmt
| 6 = @incstmt
| 7 = @decstmt
| 8 = @gostmt
| 9 = @deferstmt
| 10 = @returnstmt
| 11 = @breakstmt
| 12 = @continuestmt
| 13 = @gotostmt
| 14 = @fallthroughstmt
| 15 = @blockstmt
| 16 = @ifstmt
| 17 = @caseclause
| 18 = @exprswitchstmt
| 19 = @typeswitchstmt
| 20 = @commclause
| 21 = @selectstmt
| 22 = @forstmt
| 23 = @rangestmt
| 24 = @assignstmt
| 25 = @definestmt
| 26 = @addassignstmt
| 27 = @subassignstmt
| 28 = @mulassignstmt
| 29 = @quoassignstmt
| 30 = @remassignstmt
| 31 = @andassignstmt
| 32 = @orassignstmt
| 33 = @xorassignstmt
| 34 = @shlassignstmt
| 35 = @shrassignstmt
| 36 = @andnotassignstmt;
@incdecstmt = @incstmt | @decstmt;
@assignment = @simpleassignstmt | @compoundassignstmt;
@simpleassignstmt = @assignstmt | @definestmt;
@compoundassignstmt = @addassignstmt | @subassignstmt | @mulassignstmt | @quoassignstmt | @remassignstmt
| @andassignstmt | @orassignstmt | @xorassignstmt | @shlassignstmt | @shrassignstmt | @andnotassignstmt;
@branchstmt = @breakstmt | @continuestmt | @gotostmt | @fallthroughstmt;
@switchstmt = @exprswitchstmt | @typeswitchstmt;
@loopstmt = @forstmt | @rangestmt;
case @decl.kind of
0 = @baddecl
| 1 = @importdecl
| 2 = @constdecl
| 3 = @typedecl
| 4 = @vardecl
| 5 = @funcdecl;
@gendecl = @importdecl | @constdecl | @typedecl | @vardecl;
case @spec.kind of
0 = @importspec
| 1 = @valuespec
| 2 = @typedefspec
| 3 = @aliasspec;
@typespec = @typedefspec | @aliasspec;
case @object.kind of
0 = @pkgobject
| 1 = @decltypeobject
| 2 = @builtintypeobject
| 3 = @declconstobject
| 4 = @builtinconstobject
| 5 = @declvarobject
| 6 = @declfunctionobject
| 7 = @builtinfunctionobject
| 8 = @labelobject;
@declobject = @decltypeobject | @declconstobject | @declvarobject | @declfunctionobject;
@builtinobject = @builtintypeobject | @builtinconstobject | @builtinfunctionobject;
@typeobject = @decltypeobject | @builtintypeobject;
@valueobject = @constobject | @varobject | @functionobject;
@constobject = @declconstobject | @builtinconstobject;
@varobject = @declvarobject;
@functionobject = @declfunctionobject | @builtinfunctionobject;
case @scope.kind of
0 = @universescope
| 1 = @packagescope
| 2 = @localscope;
case @type.kind of
0 = @invalidtype
| 1 = @boolexprtype
| 2 = @inttype
| 3 = @int8type
| 4 = @int16type
| 5 = @int32type
| 6 = @int64type
| 7 = @uinttype
| 8 = @uint8type
| 9 = @uint16type
| 10 = @uint32type
| 11 = @uint64type
| 12 = @uintptrtype
| 13 = @float32type
| 14 = @float64type
| 15 = @complex64type
| 16 = @complex128type
| 17 = @stringexprtype
| 18 = @unsafepointertype
| 19 = @boolliteraltype
| 20 = @intliteraltype
| 21 = @runeliteraltype
| 22 = @floatliteraltype
| 23 = @complexliteraltype
| 24 = @stringliteraltype
| 25 = @nilliteraltype
| 26 = @arraytype
| 27 = @slicetype
| 28 = @structtype
| 29 = @pointertype
| 30 = @interfacetype
| 31 = @tupletype
| 32 = @signaturetype
| 33 = @maptype
| 34 = @sendchantype
| 35 = @recvchantype
| 36 = @sendrcvchantype
| 37 = @namedtype;
@basictype = @booltype | @numerictype | @stringtype | @literaltype | @invalidtype | @unsafepointertype;
@booltype = @boolexprtype | @boolliteraltype;
@numerictype = @integertype | @floattype | @complextype;
@integertype = @signedintegertype | @unsignedintegertype;
@signedintegertype = @inttype | @int8type | @int16type | @int32type | @int64type | @intliteraltype | @runeliteraltype;
@unsignedintegertype = @uinttype | @uint8type | @uint16type | @uint32type | @uint64type | @uintptrtype;
@floattype = @float32type | @float64type | @floatliteraltype;
@complextype = @complex64type | @complex128type | @complexliteraltype;
@stringtype = @stringexprtype | @stringliteraltype;
@literaltype = @boolliteraltype | @intliteraltype | @runeliteraltype | @floatliteraltype | @complexliteraltype
| @stringliteraltype | @nilliteraltype;
@compositetype = @containertype | @structtype | @pointertype | @interfacetype | @tupletype | @signaturetype | @namedtype;
@containertype = @arraytype | @slicetype | @maptype | @chantype;
@chantype = @sendchantype | @recvchantype | @sendrcvchantype;
case @modexpr.kind of
0 = @modcommentblock
| 1 = @modline
| 2 = @modlineblock
| 3 = @modlparen
| 4 = @modrparen;
case @error.kind of
0 = @unknownerror
| 1 = @listerror
| 2 = @parseerror
| 3 = @typeerror;

View File

@@ -0,0 +1,64 @@
/**
* Provides classes for working with Go programs.
*/
import Customizations
import semmle.go.Architectures
import semmle.go.AST
import semmle.go.Comments
import semmle.go.Concepts
import semmle.go.Decls
import semmle.go.Errors
import semmle.go.Expr
import semmle.go.Files
import semmle.go.GoMod
import semmle.go.HTML
import semmle.go.Locations
import semmle.go.Packages
import semmle.go.Scopes
import semmle.go.Stmt
import semmle.go.StringOps
import semmle.go.Types
import semmle.go.Util
import semmle.go.VariableWithFields
import semmle.go.controlflow.BasicBlocks
import semmle.go.controlflow.ControlFlowGraph
import semmle.go.controlflow.IR
import semmle.go.dataflow.DataFlow
import semmle.go.dataflow.DataFlow2
import semmle.go.dataflow.GlobalValueNumbering
import semmle.go.dataflow.SSA
import semmle.go.dataflow.TaintTracking
import semmle.go.dataflow.TaintTracking2
import semmle.go.frameworks.Beego
import semmle.go.frameworks.BeegoOrm
import semmle.go.frameworks.Chi
import semmle.go.frameworks.Couchbase
import semmle.go.frameworks.Echo
import semmle.go.frameworks.ElazarlGoproxy
import semmle.go.frameworks.Email
import semmle.go.frameworks.Encoding
import semmle.go.frameworks.EvanphxJsonPatch
import semmle.go.frameworks.Gin
import semmle.go.frameworks.Glog
import semmle.go.frameworks.GoRestfulHttp
import semmle.go.frameworks.K8sIoApimachineryPkgRuntime
import semmle.go.frameworks.K8sIoApiCoreV1
import semmle.go.frameworks.K8sIoClientGo
import semmle.go.frameworks.Logrus
import semmle.go.frameworks.Macaron
import semmle.go.frameworks.Mux
import semmle.go.frameworks.NoSQL
import semmle.go.frameworks.Protobuf
import semmle.go.frameworks.Revel
import semmle.go.frameworks.Spew
import semmle.go.frameworks.SQL
import semmle.go.frameworks.Stdlib
import semmle.go.frameworks.SystemCommandExecutors
import semmle.go.frameworks.Testing
import semmle.go.frameworks.WebSocket
import semmle.go.frameworks.XNetHtml
import semmle.go.frameworks.XPath
import semmle.go.frameworks.Yaml
import semmle.go.frameworks.Zap
import semmle.go.security.FlowSources

View File

@@ -0,0 +1,23 @@
/**
* Provides classes and predicates related to contextual queries
* in the code viewer.
*/
import go
/**
* Returns the `File` matching the given source file name as encoded by the VS
* Code extension.
*/
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("//", "/")
}

View File

@@ -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 go/ide-jump-to-definition
* @tags ide-contextual-queries/local-definitions
*/
import go
import ideContextual
external string selectedSourceFile();
from Ident def, Ident use, Entity e
where
use.uses(e) and
def.declares(e) and
use.getFile() = getFileBySourceArchiveName(selectedSourceFile())
select use, def, "V"

View File

@@ -0,0 +1,20 @@
/**
* @name Find-references links
* @description Generates use-definition pairs that provide the data
* for find-references in the code viewer.
* @kind definitions
* @id go/ide-find-references
* @tags ide-contextual-queries/local-references
*/
import go
import ideContextual
external string selectedSourceFile();
from Ident def, Ident use, Entity e
where
use.uses(e) and
def.declares(e) and
def.getFile() = getFileBySourceArchiveName(selectedSourceFile())
select use, def, "V"

View File

@@ -0,0 +1,30 @@
/**
* @name Print AST
* @description Outputs a representation of a file's Abstract Syntax Tree. This
* query is used by the VS Code extension.
* @id go/print-ast
* @kind graph
* @tags ide-contextual-queries/print-ast
*/
import go
import semmle.go.PrintAst
import ideContextual
/**
* The source file to generate an AST from.
*/
external string selectedSourceFile();
/**
* Hook to customize the functions printed by this query.
*/
class Cfg extends PrintAstConfiguration {
override predicate shouldPrintFunction(FuncDecl func) { shouldPrintFile(func.getFile()) }
override predicate shouldPrintFile(File file) {
file = getFileBySourceArchiveName(selectedSourceFile())
}
override predicate shouldPrintComments(File file) { none() }
}

View File

@@ -0,0 +1,7 @@
name: codeql/go-all
version: 0.0.2
dbscheme: go.dbscheme
extractor: go
library: true
dependencies:
codeql/go-upgrades: ^0.0.2

View File

@@ -0,0 +1,234 @@
/**
* Provides classes for working with AST nodes.
*/
import go
/**
* An AST node.
*/
class AstNode extends @node, Locatable {
/**
* Gets the `i`th child node of this node.
*
* Note that the precise indices of child nodes are considered an implementation detail
* and are subject to change without notice.
*/
AstNode getChild(int i) {
result = this.(ExprParent).getChildExpr(i) or
result = this.(GoModExprParent).getChildGoModExpr(i) or
result = this.(StmtParent).getChildStmt(i) or
result = this.(DeclParent).getDecl(i) or
result = this.(GenDecl).getSpec(i) or
result = this.(FieldParent).getField(i) or
result = this.(File).getCommentGroup(i) or
result = this.(CommentGroup).getComment(i)
}
/**
* Gets a child node of this node.
*/
AstNode getAChild() { result = getChild(_) }
/**
* Gets the number of child nodes of this node.
*/
int getNumChild() { result = count(getAChild()) }
/**
* Gets a child with the given index and of the given kind, if one exists.
* Note that a given parent can have multiple children with the same index but differing kind.
*/
private AstNode getChildOfKind(string kind, int i) {
kind = "expr" and result = this.(ExprParent).getChildExpr(i)
or
kind = "gomodexpr" and result = this.(GoModExprParent).getChildGoModExpr(i)
or
kind = "stmt" and result = this.(StmtParent).getChildStmt(i)
or
kind = "decl" and result = this.(DeclParent).getDecl(i)
or
kind = "spec" and result = this.(GenDecl).getSpec(i)
or
kind = "field" and result = this.(FieldParent).getField(i)
or
kind = "commentgroup" and result = this.(File).getCommentGroup(i)
or
kind = "comment" and result = this.(CommentGroup).getComment(i)
}
/**
* Get an AstNode child, ordered by child kind and then by index.
*/
AstNode getUniquelyNumberedChild(int index) {
result =
rank[index + 1](AstNode child, string kind, int i |
child = getChildOfKind(kind, i)
|
child order by kind, i
)
}
/** Gets the parent node of this AST node, if any. */
AstNode getParent() { this = result.getAChild() }
/** Gets the parent node of this AST node, but without crossing function boundaries. */
private AstNode parentInSameFunction() {
result = getParent() and
not this instanceof FuncDef
}
/** Gets the innermost function definition to which this AST node belongs, if any. */
FuncDef getEnclosingFunction() { result = getParent().parentInSameFunction*() }
/**
* Gets a comma-separated list of the names of the primary CodeQL classes to which this element belongs.
*/
final string getPrimaryQlClasses() { result = concat(getAPrimaryQlClass(), ",") }
/**
* Gets the name of a primary CodeQL class to which this node belongs.
*
* For most nodes, this is simply the most precise syntactic category to which they belong;
* for example, `AddExpr` is a primary class, but `BinaryExpr` is not.
*
* For identifiers and selector expressions, the class describing what kind of entity they refer
* to (for example `FunctionName` or `TypeName`) is also considered primary. For such nodes,
* this predicate has multiple values.
*/
string getAPrimaryQlClass() { result = "???" }
override string toString() { result = "AST node" }
}
/**
* An AST node whose children include expressions.
*/
class ExprParent extends @exprparent, AstNode {
/**
* Gets the `i`th child expression of this node.
*
* Note that the precise indices of child expressions are considered an implementation detail
* and are subject to change without notice.
*/
Expr getChildExpr(int i) { exprs(result, _, this, i) }
/**
* Gets an expression that is a child node of this node in the AST.
*/
Expr getAChildExpr() { result = getChildExpr(_) }
/**
* Gets the number of child expressions of this node.
*/
int getNumChildExpr() { result = count(getAChildExpr()) }
}
/**
* An AST node whose children include go.mod expressions.
*/
class GoModExprParent extends @modexprparent, AstNode {
/**
* Gets the `i`th child expression of this node.
*
* Note that the precise indices of child expressions are considered an implementation detail
* and are subject to change without notice.
*/
GoModExpr getChildGoModExpr(int i) { modexprs(result, _, this, i) }
/**
* Gets an expression that is a child node of this node in the AST.
*/
GoModExpr getAChildGoModExpr() { result = getChildGoModExpr(_) }
/**
* Gets the number of child expressions of this node.
*/
int getNumChildGoModExpr() { result = count(getAChildGoModExpr()) }
}
/**
* An AST node whose children include statements.
*/
class StmtParent extends @stmtparent, AstNode {
/**
* Gets the `i`th child statement of this node.
*
* Note that the precise indices of child statements are considered an implementation detail
* and are subject to change without notice.
*/
Stmt getChildStmt(int i) { stmts(result, _, this, i) }
/**
* Gets a statement that is a child node of this node in the AST.
*/
Stmt getAChildStmt() { result = getChildStmt(_) }
/**
* Gets the number of child statements of this node.
*/
int getNumChildStmt() { result = count(getAChildStmt()) }
}
/**
* An AST node whose children include declarations.
*/
class DeclParent extends @declparent, AstNode {
/**
* Gets the `i`th child declaration of this node.
*
* Note that the precise indices of declarations are considered an implementation detail
* and are subject to change without notice.
*/
Decl getDecl(int i) { decls(result, _, this, i) }
/**
* Gets a child declaration of this node in the AST.
*/
Decl getADecl() { result = getDecl(_) }
/**
* Gets the number of child declarations of this node.
*/
int getNumDecl() { result = count(getADecl()) }
}
/**
* An AST node whose children include fields.
*/
class FieldParent extends @fieldparent, AstNode {
/**
* Gets the `i`th field of this node.
*
* Note that the precise indices of fields are considered an implementation detail
* and are subject to change without notice.
*/
FieldBase getField(int i) { fields(result, this, i) }
/**
* Gets a child field of this node in the AST.
*/
FieldBase getAField() { result = getField(_) }
/**
* Gets the number of child fields of this node.
*/
int getNumFields() { result = count(getAField()) }
}
/**
* An AST node which may induce a scope.
*
* The following nodes may induce scopes:
*
* - files
* - block statements, `if` statements, `switch` statements, `case` clauses, comm clauses, loops
* - function type expressions
*
* Note that functions themselves do not induce a scope, it is their type declaration that induces
* the scope.
*/
class ScopeNode extends @scopenode, AstNode {
/** Gets the scope induced by this node, if any. */
LocalScope getScope() { scopenodes(this, result) }
}

View File

@@ -0,0 +1,37 @@
/** Provides classes for working with architectures. */
import go
/**
* An architecture that is valid in a build constraint.
*
* Information obtained from
* https://github.com/golang/go/blob/e125ccd10ea191101dbc31f0dd39a98f9d3ab929/src/go/types/gccgosizes.go
* where the first field of the struct is 4 for 32-bit architectures
* and 8 for 64-bit architectures.
*/
class Architecture extends string {
int bitSize;
Architecture() {
this in [
"386", "amd64p32", "arm", "armbe", "m64k", "mips", "mipsle", "mips64p32", "mips64p32le",
"nios2", "ppc", "riscv", "s390", "sh", "shbe", "sparc"
] and
bitSize = 32
or
this in [
"alpha", "amd64", "arm64", "arm64be", "ia64", "mips64", "mips64le", "ppc64", "ppc64le",
"riscv64", "s390x", "sparc64", "wasm"
] and
bitSize = 64
}
/**
* Gets the integer and pointer type width for this architecture.
*
* As of the time of writing, this appears to always be identical -- there aren't
* Go architectures with 64-bit pointers but 32-bit ints, for example.
*/
int getBitSize() { result = bitSize }
}

View File

@@ -0,0 +1,226 @@
/**
* Provides classes for working with code comments.
*/
import go
/**
* A code comment.
*
* Examples:
*
* <pre>
* // a line comment
* /* a block
* comment *&#47
* </pre>
*/
class Comment extends @comment, AstNode {
/**
* Gets the text of this comment, not including delimiters.
*/
string getText() { comments(this, _, _, _, result) }
/**
* Gets the comment group to which this comment belongs.
*/
CommentGroup getGroup() { this = result.getAComment() }
override string toString() { result = "comment" }
override string getAPrimaryQlClass() { result = "Comment" }
}
/**
* A comment group, that is, a sequence of comments without any intervening tokens or
* empty lines.
*
* Examples:
*
* <pre>
* // a line comment
* // another line comment
*
* // a line comment
* /* a block
* comment *&#47
*
* /* a block
* comment *&#47
* /* another block comment *&#47
* </pre>
*/
class CommentGroup extends @comment_group, AstNode {
/**
* Gets the file to which this comment group belongs.
*/
override File getParent() { this = result.getACommentGroup() }
/** Gets the `i`th comment in this group (0-based indexing). */
Comment getComment(int i) { comments(result, _, this, i, _) }
/** Gets a comment in this group. */
Comment getAComment() { result = getComment(_) }
/** Gets the number of comments in this group. */
int getNumComment() { result = count(getAComment()) }
override string toString() { result = "comment group" }
override string getAPrimaryQlClass() { result = "CommentGroup" }
}
/**
* A program element to which a documentation comment group may be attached:
* a file, a field, a specifier, a generic declaration, a function declaration
* or a go.mod expression.
*
* Examples:
*
* ```go
* // function documentation
* func double(x int) int { return 2 * x }
*
* // generic declaration documentation
* const (
* // specifier documentation
* size int64 = 1024
* eof = -1 // not specifier documentation
* )
* ```
*/
class Documentable extends AstNode, @documentable {
/** Gets the documentation comment group attached to this element, if any. */
DocComment getDocumentation() { this = result.getDocumentedElement() }
}
/**
* A comment group that is attached to a program element as documentation.
*
* Examples:
*
* ```go
* // function documentation
* func double(x int) int { return 2 * x }
*
* // generic declaration documentation
* const (
* // specifier documentation
* size int64 = 1024
* eof = -1 // not specifier documentation
* )
* ```
*/
class DocComment extends CommentGroup {
Documentable node;
DocComment() { doc_comments(node, this) }
/** Gets the program element documented by this comment group. */
Documentable getDocumentedElement() { result = node }
override string getAPrimaryQlClass() { result = "DocComment" }
}
/**
* A single-line comment starting with `//`.
*
* Examples:
*
* ```go
* // Single line comment
* ```
*/
class SlashSlashComment extends @slashslashcomment, Comment {
override string getAPrimaryQlClass() { result = "SlashSlashComment" }
}
/**
* A block comment starting with `/*` and ending with <code>*&#47;</code>.
*
* Examples:
*
* <pre>
* /* a block
* comment *&#47
* </pre>
*/
class SlashStarComment extends @slashstarcomment, Comment {
override string getAPrimaryQlClass() { result = "SlashStarComment" }
}
/**
* A single-line comment starting with `//`.
*
* Examples:
*
* ```go
* // Single line comment
* ```
*/
class LineComment = SlashSlashComment;
/**
* A block comment starting with `/*` and ending with <code>*&#47;</code>.
*
* Examples:
*
* <pre>
* /* a block
* comment *&#47
* </pre>
*/
class BlockComment = SlashStarComment;
/** Holds if `c` starts at `line`, `col` in `f`, and precedes the package declaration. */
private predicate isInitialComment(Comment c, File f, int line, int col) {
c.hasLocationInfo(f.getAbsolutePath(), line, col, _, _) and
line < f.getPackageNameExpr().getLocation().getStartLine()
}
/** Gets the `i`th initial comment in `f` (0-based). */
private Comment getInitialComment(File f, int i) {
result =
rank[i + 1](Comment c, int line, int col |
isInitialComment(c, f, line, col)
|
c order by line, col
)
}
/**
* A build constraint comment of the form `// +build ...` or `//go:build ...`.
*
* Examples:
*
* ```go
* // +build darwin freebsd netbsd openbsd
* // +build !linux
* ```
*/
class BuildConstraintComment extends LineComment {
BuildConstraintComment() {
// a line comment preceding the package declaration, itself only preceded by
// line comments
exists(File f, int i |
// correctness of the placement of the build constraint is not checked here;
// this is more lax than the actual rules for build constraints
this = getInitialComment(f, i) and
not getInitialComment(f, [0 .. i - 1]) instanceof BlockComment
) and
(
// comment text starts with `+build` or `go:build`
this.getText().regexpMatch("\\s*\\+build.*")
or
this.getText().regexpMatch("\\s*go:build.*")
)
}
override string getAPrimaryQlClass() { result = "BuildConstraintComment" }
/** Gets the body of this build constraint. */
string getConstraintBody() { result = getText().splitAt("build ", 1) }
/** Gets a disjunct of this build constraint. */
string getADisjunct() { result = getConstraintBody().splitAt(" ") }
}

View File

@@ -0,0 +1,475 @@
/**
* Provides abstract classes representing generic concepts such as file system
* access or system command execution, for which individual framework libraries
* provide concrete subclasses.
*/
import go
import semmle.go.dataflow.FunctionInputsAndOutputs
import semmle.go.concepts.HTTP
import semmle.go.concepts.GeneratedFile
/**
* A data-flow node that executes an operating system command,
* for instance by spawning a new process.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `SystemCommandExecution::Range` instead.
*/
class SystemCommandExecution extends DataFlow::Node {
SystemCommandExecution::Range self;
SystemCommandExecution() { this = self }
/** Gets the argument that specifies the command to be executed. */
DataFlow::Node getCommandName() { result = self.getCommandName() }
/** Holds if this node is sanitized whenever it follows `--` in an argument list. */
predicate doubleDashIsSanitizing() { self.doubleDashIsSanitizing() }
}
/** Provides a class for modeling new system-command execution 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 the argument that specifies the command to be executed. */
abstract DataFlow::Node getCommandName();
/** Holds if this node is sanitized whenever it follows `--` in an argument list. */
predicate doubleDashIsSanitizing() { none() }
}
}
/**
* An instantiation of a template; that is, a call which fills out a template with data.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `TemplateInstantiation::Range` instead.
*/
class TemplateInstantiation extends DataFlow::Node {
TemplateInstantiation::Range self;
TemplateInstantiation() { this = self }
/**
* Gets the argument to this template instantiation that is the template being
* instantiated.
*/
DataFlow::Node getTemplateArgument() { result = self.getTemplateArgument() }
/**
* Gets an argument to this template instantiation that is data being inserted
* into the template.
*/
DataFlow::Node getADataArgument() { result = self.getADataArgument() }
}
/** Provides a class for modeling new template-instantiation APIs. */
module TemplateInstantiation {
/**
* An instantiation of a template; that is, a call which fills out a template with data.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `TemplateInstantiation` instead.
*/
abstract class Range extends DataFlow::Node {
/**
* Gets the argument to this template instantiation that is the template being
* instantiated.
*/
abstract DataFlow::Node getTemplateArgument();
/**
* Gets an argument to this template instantiation that is data being inserted
* into the template.
*/
abstract DataFlow::Node getADataArgument();
}
}
/**
* 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 {
FileSystemAccess::Range self;
FileSystemAccess() { this = self }
/** Gets an argument to this file system access that is interpreted as a path. */
DataFlow::Node getAPathArgument() { result = self.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 function that escapes meta-characters to prevent injection attacks. */
class EscapeFunction extends Function {
EscapeFunction::Range self;
EscapeFunction() { this = self }
/**
* The context that this function escapes for.
*
* Currently, this can be "js", "html", or "url".
*/
string kind() { result = self.kind() }
}
/** Provides a class for modeling new escape-function APIs. */
module EscapeFunction {
/**
* A function that escapes meta-characters to prevent injection attacks.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `EscapeFunction' instead.
*/
abstract class Range extends Function {
/**
* The context that this function escapes for.
*
* Currently, this can be `js', `html', or `url'.
*/
abstract string kind();
}
}
/**
* A function that escapes a string so it can be safely included in a
* JavaScript string literal.
*/
class JsEscapeFunction extends EscapeFunction {
JsEscapeFunction() { self.kind() = "js" }
}
/**
* A function that escapes a string so it can be safely included in an
* the body of an HTML element, for example, replacing `{}` in
* `<p>{}</p>`.
*/
class HtmlEscapeFunction extends EscapeFunction {
HtmlEscapeFunction() { self.kind() = "html" }
}
/**
* A function that escapes a string so it can be safely included as part
* of a URL.
*/
class UrlEscapeFunction extends EscapeFunction {
UrlEscapeFunction() { self.kind() = "url" }
}
/**
* A node whose value is interpreted as a part of a regular expression.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `RegexpPattern::Range` instead.
*/
class RegexpPattern extends DataFlow::Node {
RegexpPattern::Range self;
RegexpPattern() { this = self }
/**
* Gets the node where this pattern is parsed as a part of a regular
* expression.
*/
DataFlow::Node getAParse() { result = self.getAParse() }
/**
* Gets this regexp pattern as a string.
*/
string getPattern() { result = self.getPattern() }
/**
* Gets a use of this pattern, either as itself in an argument to a function or as a compiled
* regexp object.
*/
DataFlow::Node getAUse() { result = self.getAUse() }
}
/** Provides a class for modeling new regular-expression APIs. */
module RegexpPattern {
/**
* A node whose value is interpreted as a part of a regular expression.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `RegexpPattern' instead.
*/
abstract class Range extends DataFlow::Node {
/**
* Gets a node where the pattern of this node is parsed as a part of
* a regular expression.
*/
abstract DataFlow::Node getAParse();
/**
* Gets this regexp pattern as a string.
*/
abstract string getPattern();
/**
* Gets a use of this pattern, either as itself in an argument to a function or as a compiled
* regexp object.
*/
abstract DataFlow::Node getAUse();
}
}
/**
* A function that matches a regexp with a string or byte slice.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `RegexpMatchFunction::Range` instead.
*/
class RegexpMatchFunction extends Function {
RegexpMatchFunction::Range self;
RegexpMatchFunction() { this = self }
/**
* Gets the function input that is the regexp being matched.
*/
FunctionInput getRegexpArg() { result = self.getRegexpArg() }
/**
* Gets the regexp pattern that is used in the call to this function `call`.
*/
RegexpPattern getRegexp(DataFlow::CallNode call) {
result.getAUse() = this.getRegexpArg().getNode(call)
}
/**
* Gets the function input that is the string being matched against.
*/
FunctionInput getValue() { result = self.getValue() }
/**
* Gets the function output that is the Boolean result of the match function.
*/
FunctionOutput getResult() { result = self.getResult() }
}
/** Provides a class for modeling new regular-expression matcher APIs. */
module RegexpMatchFunction {
/**
* A function that matches a regexp with a string or byte slice.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `RegexpPattern' instead.
*/
abstract class Range extends Function {
/**
* Gets the function input that is the regexp being matched.
*/
abstract FunctionInput getRegexpArg();
/**
* Gets the function input that is the string being matched against.
*/
abstract FunctionInput getValue();
/**
* Gets the Boolean result of the match function.
*/
abstract FunctionOutput getResult();
}
}
/**
* A function that uses a regexp to replace parts of a string or byte slice.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `RegexpReplaceFunction::Range` instead.
*/
class RegexpReplaceFunction extends Function {
RegexpReplaceFunction::Range self;
RegexpReplaceFunction() { this = self }
/**
* Gets the function input that is the regexp that matches text to replace.
*/
FunctionInput getRegexpArg() { result = self.getRegexpArg() }
/**
* Gets the regexp pattern that is used to match patterns to replace in the call to this function
* `call`.
*/
RegexpPattern getRegexp(DataFlow::CallNode call) {
result.getAUse() = call.(DataFlow::MethodCallNode).getReceiver()
}
/**
* Gets the function input corresponding to the source value, that is, the value that is having
* its contents replaced.
*/
FunctionInput getSource() { result = self.getSource() }
/**
* Gets the function output corresponding to the result, that is, the value after replacement has
* occurred.
*/
FunctionOutput getResult() { result = self.getResult() }
}
/** Provides a class for modeling new regular-expression replacer APIs. */
module RegexpReplaceFunction {
/**
* A function that uses a regexp to replace parts of a string or byte slice.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `RegexpReplaceFunction' instead.
*/
abstract class Range extends Function {
/**
* Gets the function input that is the regexp that matches text to replace.
*/
abstract FunctionInput getRegexpArg();
/**
* Gets the function input corresponding to the source value, that is, the value that is having
* its contents replaced.
*/
abstract FunctionInput getSource();
/**
* Gets the function output corresponding to the result, that is, the value after replacement
* has occurred.
*/
abstract FunctionOutput getResult();
}
}
/**
* A call to a logging mechanism.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `LoggerCall::Range` instead.
*/
class LoggerCall extends DataFlow::Node {
LoggerCall::Range self;
LoggerCall() { this = self }
/** Gets a node that is a part of the logged message. */
DataFlow::Node getAMessageComponent() { result = self.getAMessageComponent() }
}
/** Provides a class for modeling new logging APIs. */
module LoggerCall {
/**
* A call to a logging mechanism.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `LoggerCall` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets a node that is a part of the logged message. */
abstract DataFlow::Node getAMessageComponent();
}
}
/**
* A function that encodes data into a binary or textual format.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `MarshalingFunction::Range` instead.
*/
class MarshalingFunction extends Function {
MarshalingFunction::Range self;
MarshalingFunction() { this = self }
/** Gets an input that is encoded by this function. */
FunctionInput getAnInput() { result = self.getAnInput() }
/** Gets the output that contains the encoded data produced by this function. */
FunctionOutput getOutput() { result = self.getOutput() }
/** Gets an identifier for the format this function encodes into, such as "JSON". */
string getFormat() { result = self.getFormat() }
}
/** Provides a class for modeling new marshaling APIs. */
module MarshalingFunction {
/**
* A function that encodes data into a binary or textual format.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `MarshalingFunction` instead.
*/
abstract class Range extends Function {
/** Gets an input that is encoded by this function. */
abstract FunctionInput getAnInput();
/** Gets the output that contains the encoded data produced by this function. */
abstract FunctionOutput getOutput();
/** Gets an identifier for the format this function encodes into, such as "JSON". */
abstract string getFormat();
}
}
/**
* A function that decodes data from a binary or textual format.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `UnmarshalingFunction::Range` instead.
*/
class UnmarshalingFunction extends Function {
UnmarshalingFunction::Range self;
UnmarshalingFunction() { this = self }
/** Gets an input that is decoded by this function. */
FunctionInput getAnInput() { result = self.getAnInput() }
/** Gets the output that contains the decoded data produced by this function. */
FunctionOutput getOutput() { result = self.getOutput() }
/** Gets an identifier for the format this function decodes from, such as "JSON". */
string getFormat() { result = self.getFormat() }
}
/** Provides a class for modeling new unmarshaling APIs. */
module UnmarshalingFunction {
/**
* A function that decodes data from a binary or textual format.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `UnmarshalingFunction` instead.
*/
abstract class Range extends Function {
/** Gets an input that is decoded by this function. */
abstract FunctionInput getAnInput();
/** Gets the output that contains the decoded data produced by this function. */
abstract FunctionOutput getOutput();
/** Gets an identifier for the format this function decodes from, such as "JSON". */
abstract string getFormat();
}
}

View File

@@ -0,0 +1,610 @@
/**
* Provides classes for working with declarations.
*/
import go
/**
* A declaration.
*/
class Decl extends @decl, ExprParent, StmtParent, FieldParent {
/**
* Gets the kind of this declaration, which is an integer value representing the declaration's
* node type.
*
* Note that the mapping from node types to integer kinds is considered an implementation detail
* and subject to change without notice.
*/
int getKind() { decls(this, result, _, _) }
/**
* Holds if the execution of this statement may produce observable side effects.
*
* Memory allocation is not considered an observable side effect.
*/
predicate mayHaveSideEffects() { none() }
}
/**
* A bad declaration, that is, a declaration that cannot be parsed.
*/
class BadDecl extends @baddecl, Decl {
override string toString() { result = "bad declaration" }
override string getAPrimaryQlClass() { result = "BadDecl" }
}
/**
* A generic declaration.
*/
class GenDecl extends @gendecl, Decl, Documentable {
/** Gets the `i`th declaration specifier in this declaration (0-based). */
Spec getSpec(int i) { specs(result, _, this, i) }
/** Gets a declaration specifier in this declaration. */
Spec getASpec() { result = getSpec(_) }
/** Gets the number of declaration specifiers in this declaration. */
int getNumSpec() { result = count(getASpec()) }
override predicate mayHaveSideEffects() { getASpec().mayHaveSideEffects() }
override string getAPrimaryQlClass() { result = "GenDecl" }
}
/**
* An import declaration.
*/
class ImportDecl extends @importdecl, GenDecl {
override string toString() { result = "import declaration" }
override string getAPrimaryQlClass() { result = "ImportDecl" }
}
/**
* A constant declaration.
*/
class ConstDecl extends @constdecl, GenDecl {
override string toString() { result = "constant declaration" }
override string getAPrimaryQlClass() { result = "ConstDecl" }
}
/**
* A type declaration.
*/
class TypeDecl extends @typedecl, GenDecl {
override string toString() { result = "type declaration" }
override string getAPrimaryQlClass() { result = "TypeDecl" }
}
/**
* A variable declaration.
*/
class VarDecl extends @vardecl, GenDecl {
override string toString() { result = "variable declaration" }
override string getAPrimaryQlClass() { result = "VarDecl" }
}
/**
* A function definition, that is, either a function declaration or
* a function literal.
*/
class FuncDef extends @funcdef, StmtParent, ExprParent {
/** Gets the body of the defined function, if any. */
BlockStmt getBody() { none() }
/** Gets the name of the defined function, if any. */
string getName() { none() }
/** Gets the expression denoting the type of this function. */
FuncTypeExpr getTypeExpr() { none() }
/** Gets the type of this function. */
SignatureType getType() { none() }
/** Gets the scope induced by this function. */
FunctionScope getScope() { result.getFunction() = this }
/** Gets a `defer` statement in this function. */
DeferStmt getADeferStmt() { result.getEnclosingFunction() = this }
/** Gets the `i`th result variable of this function. */
ResultVariable getResultVar(int i) { result.isResultOf(this, i) }
/** Gets a result variable of this function. */
ResultVariable getAResultVar() { result.getFunction() = this }
/**
* Gets the `i`th parameter of this function.
*
* The receiver variable, if any, is considered to be the -1st parameter.
*/
Parameter getParameter(int i) { result.isParameterOf(this, i) }
/** Gets a parameter of this function. */
Parameter getAParameter() { result.getFunction() = this }
/**
* Gets the number of parameters of this function.
*/
int getNumParameter() { result = count(getAParameter()) }
/**
* Gets a call to this function.
*/
DataFlow::CallNode getACall() { result.getACallee() = this }
override string getAPrimaryQlClass() { result = "FuncDef" }
}
/**
* A function declaration.
*/
class FuncDecl extends @funcdecl, Decl, Documentable, FuncDef {
/** Gets the identifier denoting the name of this function. */
Ident getNameExpr() { result = getChildExpr(0) }
override string getName() { result = getNameExpr().getName() }
override FuncTypeExpr getTypeExpr() { result = getChildExpr(1) }
override SignatureType getType() { result = getNameExpr().getType() }
/** Gets the body of this function, if any. */
override BlockStmt getBody() { result = getChildStmt(2) }
/** Gets the function declared by this function declaration. */
DeclaredFunction getFunction() { this = result.getFuncDecl() }
override string toString() { result = "function declaration" }
override string getAPrimaryQlClass() { result = "FuncDecl" }
}
/**
* A method declaration.
*/
class MethodDecl extends FuncDecl {
ReceiverDecl recv;
MethodDecl() { recv.getFunction() = this }
/**
* Gets the receiver declaration of this method.
*
* For example, the receiver declaration of
*
* ```
* func (p *Rectangle) Area() float64 { ... }
* ```
*
* is `p *Rectangle`.
*/
ReceiverDecl getReceiverDecl() { result = recv }
/**
* Gets the receiver type of this method.
*
* For example, the receiver type of
*
* ```
* func (p *Rectangle) Area() float64 { ... }
* ```
*
* is `*Rectangle`.
*/
Type getReceiverType() { result = getReceiverDecl().getType() }
/**
* Gets the receiver base type of this method.
*
* For example, the receiver base type of
*
* ```
* func (p *Rectangle) Area() float64 { ... }
* ```
*
* is `Rectangle`.
*/
NamedType getReceiverBaseType() {
result = getReceiverType() or
result = getReceiverType().(PointerType).getBaseType()
}
/**
* Gets the receiver variable of this method.
*
* For example, the receiver variable of
*
* ```
* func (p *Rectangle) Area() float64 { ... }
* ```
*
* is the variable `p`.
*/
ReceiverVariable getReceiver() { result.getFunction() = this }
override string getAPrimaryQlClass() { result = "MethodDecl" }
}
/**
* A declaration specifier.
*/
class Spec extends @spec, ExprParent, Documentable {
/** Gets the declaration to which this specifier belongs */
Decl getParentDecl() { specs(this, _, result, _) }
/**
* Gets the kind of this specifier, which is an integer value representing the specifier's
* node type.
*
* Note that the mapping from node types to integer kinds is considered an implementation detail
* and subject to change without notice.
*/
int getKind() { specs(this, result, _, _) }
/**
* Holds if the execution of this specifier may produce observable side effects.
*
* Memory allocation is not considered an observable side effect.
*/
predicate mayHaveSideEffects() { none() }
override string getAPrimaryQlClass() { result = "Spec" }
}
/**
* An import specifier.
*/
class ImportSpec extends @importspec, Spec {
/** Gets the identifier denoting the imported name. */
Ident getNameExpr() { result = getChildExpr(0) }
/** Gets the imported name. */
string getName() { result = getNameExpr().getName() }
/** Gets the string literal denoting the imported path. */
StringLit getPathExpr() { result = getChildExpr(1) }
/** Gets the imported path. */
string getPath() { result = getPathExpr().getValue() }
override string toString() { result = "import specifier" }
override string getAPrimaryQlClass() { result = "ImportSpec" }
}
/**
* A constant or variable declaration specifier.
*/
class ValueSpec extends @valuespec, Spec {
/** Gets the identifier denoting the `i`th name declared by this specifier (0-based). */
Ident getNameExpr(int i) {
i >= 0 and
result = getChildExpr(-(i + 1))
}
/** Holds if this specifier is a part of a constant declaration. */
predicate isConstSpec() { this.getParentDecl() instanceof ConstDecl }
/** Gets an identifier denoting a name declared by this specifier. */
Ident getANameExpr() { result = getNameExpr(_) }
/** Gets the `i`th name declared by this specifier (0-based). */
string getName(int i) { result = getNameExpr(i).getName() }
/** Gets a name declared by this specifier. */
string getAName() { result = getName(_) }
/** Gets the number of names declared by this specifier. */
int getNumName() { result = count(getANameExpr()) }
/** Gets the expression denoting the type of the symbols declared by this specifier. */
Expr getTypeExpr() { result = getChildExpr(0) }
/** Gets the `i`th initializer of this specifier (0-based). */
Expr getInit(int i) {
i >= 0 and
result = getChildExpr(i + 1)
}
/** Gets an initializer of this specifier. */
Expr getAnInit() { result = getInit(_) }
/** Gets the number of initializers of this specifier. */
int getNumInit() { result = count(getAnInit()) }
/** Gets the unique initializer of this specifier, if there is only one. */
Expr getInit() { getNumInit() = 1 and result = getInit(0) }
/**
* Gets the specifier that contains the initializers for this specifier.
* If this valuespec has initializers, the result is itself. Otherwise, it is the
* last specifier declared before this one that has initializers.
*/
private ValueSpec getEffectiveSpec() {
(exists(this.getAnInit()) or not this.isConstSpec()) and
result = this
or
not exists(this.getAnInit()) and
exists(ConstDecl decl, int idx |
decl = this.getParentDecl() and
decl.getSpec(idx) = this
|
result = decl.getSpec(idx - 1).(ValueSpec).getEffectiveSpec()
)
}
/**
* Gets the `i`th effective initializer of this specifier, that is, the expression
* that the `i`th name will get initialized to. This is the same as `getInit`
* if it exists, or `getInit` on the last specifier in the declaration that this
* is a child of.
*/
private Expr getEffectiveInit(int i) { result = this.getEffectiveSpec().getInit(i) }
/** Holds if this specifier initializes `name` to the value of `init`. */
predicate initializes(string name, Expr init) {
exists(int i |
name = getName(i) and
init = getEffectiveInit(i)
)
}
override predicate mayHaveSideEffects() { getAnInit().mayHaveSideEffects() }
override string toString() { result = "value declaration specifier" }
override string getAPrimaryQlClass() { result = "ValueSpec" }
}
/**
* A type declaration specifier, which is either a type definition or an alias declaration.
*
* Examples:
*
* ```
* type (
* status int
* intlist = []int
* )
* ```
*/
class TypeSpec extends @typespec, Spec {
/** Gets the identifier denoting the name of the declared type. */
Ident getNameExpr() { result = getChildExpr(0) }
/** Gets the name of the declared type. */
string getName() { result = getNameExpr().getName() }
/**
* Gets the expression denoting the underlying type to which the newly declared type is bound.
*/
Expr getTypeExpr() { result = getChildExpr(1) }
override string toString() { result = "type declaration specifier" }
override string getAPrimaryQlClass() { result = "TypeSpec" }
}
/**
* An alias declaration specifier.
*
* Examples:
*
* ```
* type intlist = []int
* ```
*/
class AliasSpec extends @aliasspec, TypeSpec { }
/**
* A type definition specifier.
*
* Examples:
*
* ```
* type status int
* ```
*/
class TypeDefSpec extends @typedefspec, TypeSpec { }
/**
* A field declaration, of a struct, a function (in which case this is a parameter or result variable),
* or an interface (in which case this is a method or embedding spec).
*/
class FieldBase extends @field, ExprParent {
/**
* Gets the expression representing the type of the fields declared in this declaration.
*/
Expr getTypeExpr() { result = getChildExpr(0) }
/**
* Gets the type of the fields declared in this declaration.
*/
Type getType() { result = getTypeExpr().getType() }
}
/**
* A field declaration in a struct type.
*/
class FieldDecl extends FieldBase, Documentable, ExprParent {
StructTypeExpr st;
FieldDecl() { this = st.getField(_) }
/**
* Gets the expression representing the name of the `i`th field declared in this declaration
* (0-based).
*/
Expr getNameExpr(int i) {
i >= 0 and
result = getChildExpr(i + 1)
}
/** Gets the tag expression of this field declaration, if any. */
Expr getTag() { result = getChildExpr(-1) }
/** Gets the struct type expression to which this field declaration belongs. */
StructTypeExpr getDeclaringStructTypeExpr() { result = st }
/** Gets the struct type to which this field declaration belongs. */
StructType getDeclaringType() { result = getDeclaringStructTypeExpr().getType() }
override string toString() { result = "field declaration" }
override string getAPrimaryQlClass() { result = "FieldDecl" }
}
/**
* An embedded field declaration in a struct.
*/
class EmbeddedFieldDecl extends FieldDecl {
EmbeddedFieldDecl() { not exists(this.getNameExpr(_)) }
override string getAPrimaryQlClass() { result = "EmbeddedFieldDecl" }
}
/**
* A function parameter or result variable declaration.
*/
class ParameterOrResultDecl extends FieldBase, Documentable, ExprParent {
int rawIndex;
FuncTypeExpr ft;
ParameterOrResultDecl() { this = ft.getField(rawIndex) }
/**
* Gets the function type expression to which this declaration belongs.
*/
FuncTypeExpr getFunctionTypeExpr() { result = ft }
/**
* Gets the function to which this declaration belongs.
*/
FuncDef getFunction() { result.getTypeExpr() = getFunctionTypeExpr() }
/**
* Gets the expression representing the name of the `i`th variable declared in this declaration
* (0-based).
*/
Expr getNameExpr(int i) {
i >= 0 and
result = getChildExpr(i + 1)
}
/**
* Gets an expression representing the name of a variable declared in this declaration.
*/
Expr getANameExpr() { result = getNameExpr(_) }
}
/**
* A parameter declaration.
*/
class ParameterDecl extends ParameterOrResultDecl {
ParameterDecl() { rawIndex >= 0 }
/**
* Gets the index of this parameter declarations among all parameter declarations of
* its associated function type.
*/
int getIndex() { result = rawIndex }
override string toString() { result = "parameter declaration" }
override string getAPrimaryQlClass() { result = "ParameterDecl" }
}
/**
* A receiver declaration in a function declaration.
*/
class ReceiverDecl extends FieldBase, Documentable, ExprParent {
FuncDecl fd;
ReceiverDecl() { fd.getField(-1) = this }
/**
* Gets the function declaration to which this receiver belongs.
*/
FuncDecl getFunction() { result = fd }
/**
* Gets the expression representing the name of the receiver declared in this declaration.
*/
Expr getNameExpr() { result = getChildExpr(1) }
override string toString() { result = "receiver declaration" }
override string getAPrimaryQlClass() { result = "ReceiverDecl" }
}
/**
* A result variable declaration.
*/
class ResultVariableDecl extends ParameterOrResultDecl {
ResultVariableDecl() { rawIndex < 0 }
/**
* Gets the index of this result variable declaration among all result variable declarations of
* its associated function type.
*/
int getIndex() { result = -(rawIndex + 1) }
override string toString() { result = "result variable declaration" }
override string getAPrimaryQlClass() { result = "ResultVariableDecl" }
}
/**
* A method or embedding specification in an interface type expression.
*/
class InterfaceMemberSpec extends FieldBase, Documentable, ExprParent {
InterfaceTypeExpr ite;
int idx;
InterfaceMemberSpec() { this = ite.getField(idx) }
/**
* Gets the interface type expression to which this member specification belongs.
*/
InterfaceTypeExpr getInterfaceTypeExpr() { result = ite }
/**
* Gets the index of this member specification among all member specifications of
* its associated interface type expression.
*/
int getIndex() { result = idx }
}
/**
* A method specification in an interface.
*/
class MethodSpec extends InterfaceMemberSpec {
Expr name;
MethodSpec() { name = getChildExpr(1) }
/**
* Gets the expression representing the name of the method declared in this specification.
*/
Expr getNameExpr() { result = name }
override string toString() { result = "method declaration" }
override string getAPrimaryQlClass() { result = "MethodSpec" }
}
/**
* An embedding specification in an interface.
*/
class EmbeddingSpec extends InterfaceMemberSpec {
EmbeddingSpec() { not exists(getChildExpr(1)) }
override string toString() { result = "interface embedding" }
override string getAPrimaryQlClass() { result = "EmbeddingSpec" }
}

View File

@@ -0,0 +1,53 @@
/** Provides classes for working with Go frontend errors recorded during extraction. */
import go
/**
* An error reported by the Go frontend during extraction.
*/
class Error extends @error {
/** Gets the message associated with this error. */
string getMessage() { errors(this, _, result, _, _, _, _, _, _) }
/** Gets the raw position reported by the frontend for this error. */
string getRawPosition() { errors(this, _, _, result, _, _, _, _, _) }
/** Gets the package in which this error was reported. */
Package getPackage() { errors(this, _, _, _, _, _, _, result, _) }
/** Gets the index of this error among all errors reported for the same package. */
int getIndex() { errors(this, _, _, _, _, _, _, _, result) }
/** Gets the file in which this error was reported, if it can be determined. */
ExtractedOrExternalFile getFile() { hasLocationInfo(result.getAbsolutePath(), _, _, _, _) }
/**
* 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://lgtm.com/help/ql/locations).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
errors(this, _, _, _, filepath, startline, startcolumn, _, _) and
endline = startline and
endcolumn = startcolumn
}
/** Gets a textual representation of this error. */
string toString() { result = getMessage() }
}
/** An error reported by an unknown part of the Go frontend. */
class UnknownError extends Error, @unknownerror { }
/** An error reported by the Go frontend driver. */
class ListError extends Error, @listerror { }
/** An error reported by the Go parser. */
class ParseError extends Error, @parseerror { }
/** An error reported by the Go type checker. */
class TypeError extends Error, @typeerror { }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,278 @@
/** Provides classes for working with files and folders. */
import go
/** A file or folder. */
abstract class Container extends @container {
/**
* 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 a URL representing the location of this container.
*
* For more information see https://lgtm.com/help/ql/locations#providing-urls.
*/
abstract string getURL();
/**
* 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 = getAbsolutePath() and sourceLocationPrefix(pref)
|
absPath = pref and result = ""
or
absPath = pref.regexpReplaceAll("/$", "") + "/" + result and
not result.matches("/%")
)
}
/**
* 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):
*
* <table border="1">
* <tr><th>Absolute path</th><th>Base name</th></tr>
* <tr><td>"/tmp/tst.go"</td><td>"tst.go"</td></tr>
* <tr><td>"C:/Program Files (x86)"</td><td>"Program Files (x86)"</td></tr>
* <tr><td>"/"</td><td>""</td></tr>
* <tr><td>"C:/"</td><td>""</td></tr>
* <tr><td>"D:/"</td><td>""</td></tr>
* <tr><td>"//FileServer/"</td><td>""</td></tr>
* </table>
*/
string getBaseName() {
result = 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):
*
* <table border="1">
* <tr><th>Absolute path</th><th>Extension</th></tr>
* <tr><td>"/tmp/tst.go"</td><td>"go"</td></tr>
* <tr><td>"/tmp/.classpath"</td><td>"classpath"</td></tr>
* <tr><td>"/bin/bash"</td><td>not defined</td></tr>
* <tr><td>"/tmp/tst2."</td><td>""</td></tr>
* <tr><td>"/tmp/x.tar.gz"</td><td>"gz"</td></tr>
* </table>
*/
string getExtension() { result = getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3) }
/**
* 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):
*
* <table border="1">
* <tr><th>Absolute path</th><th>Stem</th></tr>
* <tr><td>"/tmp/tst.go"</td><td>"tst"</td></tr>
* <tr><td>"/tmp/.classpath"</td><td>""</td></tr>
* <tr><td>"/bin/bash"</td><td>"bash"</td></tr>
* <tr><td>"/tmp/tst2."</td><td>"tst2"</td></tr>
* <tr><td>"/tmp/x.tar.gz"</td><td>"x.tar"</td></tr>
* </table>
*/
string getStem() { result = getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1) }
/** Gets the parent container of this file or folder, if any. */
Container getParentContainer() { containerparent(result, this) }
/** Gets a file or sub-folder in this container. */
Container getAChildContainer() { this = result.getParentContainer() }
/** Gets a file in this container. */
File getAFile() { result = getAChildContainer() }
/** Gets the file in this container that has the given `baseName`, if any. */
File getFile(string baseName) {
result = getAFile() and
result.getBaseName() = baseName
}
/** Gets a sub-folder in this container. */
Folder getAFolder() { result = getAChildContainer() }
/** Gets the sub-folder in this container that has the given `baseName`, if any. */
Folder getFolder(string baseName) {
result = getAFolder() and
result.getBaseName() = baseName
}
/**
* Gets a textual representation of the path of this container.
*
* This is the absolute path of the container.
*/
string toString() { result = getAbsolutePath() }
}
/** A folder. */
class Folder extends Container, @folder {
override string getAbsolutePath() { folders(this, result) }
/** Gets the file or subfolder in this folder that has the given `name`, if any. */
Container getChildContainer(string name) {
result = getAChildContainer() and
result.getBaseName() = name
}
/** Gets the file in this folder that has the given `stem` and `extension`, if any. */
File getFile(string stem, string extension) {
result = getAChildContainer() and
result.getStem() = stem and
result.getExtension() = extension
}
/** Gets a subfolder contained in this folder. */
Folder getASubFolder() { result = getAChildContainer() }
/** Gets the URL of this folder. */
override string getURL() { result = "folder://" + getAbsolutePath() }
}
/** Any file, including files that have not been extracted but are referred to as locations for errors. */
class ExtractedOrExternalFile extends Container, @file, Documentable, ExprParent, GoModExprParent,
DeclParent, ScopeNode {
override Location getLocation() { has_location(this, result) }
override string getAbsolutePath() { files(this, result) }
/** Gets the number of lines in this file. */
int getNumberOfLines() { numlines(this, result, _, _) }
/** Gets the number of lines containing code in this file. */
int getNumberOfLinesOfCode() { numlines(this, _, result, _) }
/** Gets the number of lines containing comments in this file. */
int getNumberOfLinesOfComments() { numlines(this, _, _, result) }
/** Gets the package name as specified in the package clause of this file. */
Ident getPackageNameExpr() { result = getChildExpr(0) }
/** Gets the name of the package to which this file belongs. */
string getPackageName() { result = getPackageNameExpr().getName() }
/** Holds if this file contains at least one build constraint. */
pragma[noinline]
predicate hasBuildConstraints() { exists(BuildConstraintComment bc | this = bc.getFile()) }
/**
* Holds if this file contains build constraints that ensure that it
* is only built on architectures of bit size `bitSize`, which can be
* 32 or 64.
*/
predicate constrainsIntBitSize(int bitSize) {
explicitlyConstrainsIntBitSize(bitSize) or
implicitlyConstrainsIntBitSize(bitSize)
}
/**
* Holds if this file contains explicit build constraints that ensure
* that it is only built on an architecture of bit size `bitSize`,
* which can be 32 or 64.
*/
predicate explicitlyConstrainsIntBitSize(int bitSize) {
exists(BuildConstraintComment bcc | this = bcc.getFile() |
forex(string disjunct | disjunct = bcc.getADisjunct() |
disjunct.splitAt(",").(Architecture).getBitSize() = bitSize
or
disjunct.splitAt("/").(Architecture).getBitSize() = bitSize
)
)
}
/**
* Holds if this file has a name which acts as an implicit build
* constraint that ensures that it is only built on an
* architecture of bit size `bitSize`, which can be 32 or 64.
*/
predicate implicitlyConstrainsIntBitSize(int bitSize) {
exists(Architecture arch | arch.getBitSize() = bitSize |
this.getStem().regexpMatch("(?i).*_\\Q" + arch + "\\E(_test)?")
)
}
override string toString() { result = Container.super.toString() }
/** Gets the URL of this file. */
override string getURL() { result = "file://" + this.getAbsolutePath() + ":0:0:0:0" }
/** Gets the `i`th child comment group. */
CommentGroup getCommentGroup(int i) { comment_groups(result, this, i) }
/** Gets a child comment group. */
CommentGroup getACommentGroup() { result = getCommentGroup(_) }
/** Gets the number of child comment groups of this file. */
int getNumCommentGroups() { result = count(getACommentGroup()) }
override string getAPrimaryQlClass() { result = "File" }
}
/** A file that has been extracted. */
class File extends ExtractedOrExternalFile {
File() {
// getAChild is specifically for the Go AST and so does not apply to non-go files
// we care about all non-go extracted files, as only go files can have `@file` entries due to requiring a file entry for diagnostic errors
not this.getExtension() = "go"
or
exists(this.getAChild())
}
}
/** A Go file. */
class GoFile extends File {
GoFile() { this.getExtension() = "go" }
}
/** An HTML file. */
class HtmlFile extends File {
HtmlFile() { this.getExtension().regexpMatch("x?html?") }
}

View File

@@ -0,0 +1,231 @@
/**
* Provides classes for working with go.mod files.
*/
import go
/** A go.mod file. */
class GoModFile extends File {
GoModFile() { this.getBaseName() = "go.mod" }
/**
* Gets the module declaration of this file, that is, the line declaring the path of this module.
*/
GoModModuleLine getModuleDeclaration() { result.getFile() = this }
override string getAPrimaryQlClass() { result = "GoModFile" }
}
/**
* An expression in a go.mod file, which is used to declare dependencies.
*/
class GoModExpr extends @modexpr, GoModExprParent {
/**
* Gets the kind of this expression, which is an integer value representing the expression's
* node type.
*
* Note that the mapping from node types to integer kinds is considered an implementation detail
* and subject to change without notice.
*/
int getKind() { modexprs(this, result, _, _) }
/**
* Get the comment group associated with this expression.
*/
DocComment getComments() { result.getDocumentedElement() = this }
override GoModFile getFile() { result = GoModExprParent.super.getFile() }
/** Gets path of the module of this go.mod expression. */
string getModulePath() { result = this.getFile().getModuleDeclaration().getPath() }
override string toString() { result = "go.mod expression" }
override string getAPrimaryQlClass() { result = "GoModExpr" }
}
/**
* A top-level block of comments separate from any rule.
*/
class GoModCommentBlock extends @modcommentblock, GoModExpr {
override string getAPrimaryQlClass() { result = "GoModCommentBlock" }
}
/**
* A single line of tokens.
*/
class GoModLine extends @modline, GoModExpr {
/**
* Gets the `i`th token on this line, 0-based.
*
* Generally, one should use `getToken`, as that accounts for lines inside of line blocks.
*/
string getRawToken(int i) { modtokens(result, this, i) }
/**
* Gets the `i`th token of `line`, including the token in the line block declaration, if it there is
* one, 0-based.
*
* This compensates for the fact that lines in line blocks have their 0th token in the line block
* declaration, and makes dealing with lines more uniform.
*
* For example, `.getToken(1)` will result in the dependency path (`github.com/github/codeql-go`)
* for both lines for normal require lines like `require "github.com/github/codeql-go" v1.2.3` and
* in a line block like
*
* ```
* require (
* "github.com/github/codeql-go" v1.2.3
* ...
* )
* ```
*
* As a special case, when `i` is `0` and the line is in a line block, the result will be the
* token from the line block.
*/
string getToken(int i) {
i = 0 and result = this.getParent().(GoModLineBlock).getRawToken(0)
or
if this.getParent() instanceof GoModLineBlock
then result = this.getRawToken(i - 1)
else result = this.getRawToken(i)
}
override string toString() { result = "go.mod line" }
override string getAPrimaryQlClass() { result = "GoModLine" }
}
/**
* A factored block of lines, for example:
* ```
* require (
* "github.com/github/codeql-go" v1.2.3
* "golang.org/x/tools" v3.2.1
* )
* ```
*/
class GoModLineBlock extends @modlineblock, GoModExpr {
/**
* Gets the `i`th token of this line block, 0-based.
*
* Usually one should not have to use this, as `GoModLine.getToken(0)` will get the token from its
* parent line block, if any.
*/
string getRawToken(int i) { modtokens(result, this, i) }
override string toString() { result = "go.mod line block" }
override string getAPrimaryQlClass() { result = "GoModLineBlock" }
}
/**
* A line that contains the module's package path, for example `module github.com/github/codeql-go`.
*/
class GoModModuleLine extends GoModLine {
GoModModuleLine() { this.getToken(0) = "module" }
/**
* Get the path of the module being declared.
*/
string getPath() { result = this.getToken(1) }
override string toString() { result = "go.mod module line" }
override string getAPrimaryQlClass() { result = "GoModModuleLine" }
}
/**
* A line that declares the Go version to be used, for example `go 1.14`.
*/
class GoModGoLine extends GoModLine {
GoModGoLine() { this.getToken(0) = "go" }
/** Gets the Go version declared. */
string getVersion() { result = this.getToken(1) }
override string toString() { result = "go.mod go line" }
override string getAPrimaryQlClass() { result = "GoModGoLine" }
}
/**
* A line that declares a requirement, for example `require "github.com/github/codeql-go" v1.2.3`.
*/
class GoModRequireLine extends GoModLine {
GoModRequireLine() { this.getToken(0) = "require" }
/** Gets the path of the dependency. */
string getPath() { result = this.getToken(1) }
/** Gets the version of the dependency. */
string getVersion() { result = this.getToken(2) }
override string toString() { result = "go.mod require line" }
override string getAPrimaryQlClass() { result = "GoModRequireLine" }
}
/**
* A line that declares a dependency version to exclude, for example
* `exclude "github.com/github/codeql-go" v1.2.3`.
*/
class GoModExcludeLine extends GoModLine {
GoModExcludeLine() { this.getToken(0) = "exclude" }
/** Gets the path of the dependency to exclude a version of. */
string getPath() { result = this.getToken(1) }
/** Gets the excluded version. */
string getVersion() { result = this.getToken(2) }
override string toString() { result = "go.mod exclude line" }
override string getAPrimaryQlClass() { result = "GoModExcludeLine" }
}
/**
* A line that specifies a dependency to use instead of another one, for example
* `replace "golang.org/x/tools" => "github.com/golang/tools" v1.2.3`.
*/
class GoModReplaceLine extends GoModLine {
GoModReplaceLine() { this.getToken(0) = "replace" }
/** Gets the path of the dependency to be replaced. */
string getOriginalPath() { result = this.getToken(1) }
/** Gets the path of the dependency to be replaced, if any. */
string getOriginalVersion() { result = this.getToken(2) and not result = "=>" }
/** Gets the path of the replacement dependency. */
string getReplacementPath() {
if exists(this.getOriginalVersion())
then result = this.getToken(4)
else result = this.getToken(3)
}
/** Gets the version of the replacement dependency. */
string getReplacementVersion() {
if exists(this.getOriginalVersion())
then result = this.getToken(5)
else result = this.getToken(4)
}
override string toString() { result = "go.mod replace line" }
override string getAPrimaryQlClass() { result = "GoModReplaceLine" }
}
/** A left parenthesis for a line block. */
class GoModLParen extends @modlparen, GoModExpr {
override string toString() { result = "go.mod (" }
override string getAPrimaryQlClass() { result = "GoModLParen" }
}
/** A right parenthesis for a line block. */
class GoModRParen extends @modrparen, GoModExpr {
override string toString() { result = "go.mod )" }
override string getAPrimaryQlClass() { result = "GoModRParen" }
}

View File

@@ -0,0 +1,207 @@
/** Provides classes for working with HTML documents. */
import go
module HTML {
/**
* An HTML element.
*
* Example:
*
* ```
* <a href="semmle.com">Semmle</a>
* ```
*/
class Element extends Locatable, @xmlelement {
Element() { exists(HtmlFile f | xmlElements(this, _, _, _, f)) }
override Location getLocation() { xmllocations(this, result) }
/**
* Gets the name of this HTML element.
*
* For example, the name of `<br>` is `br`.
*/
string getName() { xmlElements(this, result, _, _, _) }
/**
* Gets the parent element of this element, if any.
*/
Element getParent() { xmlElements(this, _, result, _, _) }
/**
* Holds if this is a toplevel element, that is, if it does not have a parent element.
*/
predicate isTopLevel() { not exists(getParent()) }
/**
* Gets the root HTML document element in which this element is contained.
*/
DocumentElement getDocument() { result = getRoot() }
/**
* Gets the root element in which this element is contained.
*/
Element getRoot() { if isTopLevel() then result = this else result = getParent().getRoot() }
/**
* Gets the `i`th child element (0-based) of this element.
*/
Element getChild(int i) { xmlElements(result, _, this, i, _) }
/**
* Gets a child element of this element.
*/
Element getChild() { result = getChild(_) }
/**
* Gets the `i`th attribute (0-based) of this element.
*/
Attribute getAttribute(int i) { xmlAttrs(result, this, _, _, i, _) }
/**
* Gets an attribute of this element.
*/
Attribute getAnAttribute() { result = getAttribute(_) }
/**
* Gets an attribute of this element that has the given name.
*/
Attribute getAttributeByName(string name) {
result = getAnAttribute() and
result.getName() = name
}
/**
* Gets the text node associated with this element.
*/
TextNode getTextNode() { result.getParent() = this }
override string toString() { result = "<" + getName() + ">...</>" }
}
/**
* An attribute of an HTML element.
*
* Examples:
*
* ```
* <a
* href ="semmle.com" <!-- an attribute -->
* target=_blank <!-- also an attribute -->
* >Semmle</a>
* ```
*/
class Attribute extends Locatable, @xmlattribute {
Attribute() { xmlAttrs(this, _, _, _, _, any(HtmlFile f)) }
override Location getLocation() { xmllocations(this, result) }
/**
* Gets the element to which this attribute belongs.
*/
Element getElement() { xmlAttrs(this, result, _, _, _, _) }
/**
* Gets the root element in which the element to which this attribute
* belongs is contained.
*/
Element getRoot() { result = getElement().getRoot() }
/**
* Gets the name of this attribute.
*/
string getName() { xmlAttrs(this, _, result, _, _, _) }
/**
* Gets the value of this attribute.
*
* For attributes without an explicitly specified value, the
* result is the empty string.
*/
string getValue() { xmlAttrs(this, _, _, result, _, _) }
override string toString() { result = getName() + "=" + getValue() }
}
/**
* An HTML `<html>` element.
*
* Example:
*
* ```
* <html>
* <body>
* This is a test.
* </body>
* </html>
* ```
*/
class DocumentElement extends Element {
DocumentElement() { getName() = "html" }
}
/**
* An HTML text node.
*
* Example:
*
* ```
* <div>
* This text is represented as a text node.
* </div>
* ```
*/
class TextNode extends Locatable, @xmlcharacters {
TextNode() { exists(HtmlFile f | xmlChars(this, _, _, _, _, f)) }
override string toString() { result = getText() }
/**
* Gets the content of this text node.
*
* Note that entity expansion has been performed already.
*/
string getText() { xmlChars(this, result, _, _, _, _) }
/**
* Gets the parent this text.
*/
Element getParent() { xmlChars(this, _, result, _, _, _) }
/**
* Gets the child index number of this text node.
*/
int getIndex() { xmlChars(this, _, _, result, _, _) }
/**
* Holds if this text node is inside a `CDATA` tag.
*/
predicate isCData() { xmlChars(this, _, _, _, 1, _) }
override Location getLocation() { xmllocations(this, result) }
}
/**
* An HTML comment.
*
* Example:
*
* ```
* <!-- this is a comment -->
* ```
*/
class CommentNode extends Locatable, @xmlcomment {
CommentNode() { exists(HtmlFile f | xmlComments(this, _, _, f)) }
/** Gets the element in which this comment occurs. */
Element getParent() { xmlComments(this, _, result, _) }
/** Gets the text of this comment, not including delimiters. */
string getText() { result = toString().regexpCapture("(?s)<!--(.*)-->", 1) }
override string toString() { xmlComments(this, result, _, _) }
override Location getLocation() { xmllocations(this, result) }
}
}

View File

@@ -0,0 +1,81 @@
/** Provides classes for working with locations and program elements that have locations. */
import go
/**
* 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://lgtm.com/help/ql/locations).
*/
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 = getEndLine() - getStartLine() + 1 }
/** Gets a textual representation of this element. */
string toString() {
exists(string filepath, int startline, int startcolumn, int endline, int endcolumn |
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://lgtm.com/help/ql/locations).
*/
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()
)
}
}
/** A program element with a location. */
class Locatable extends @locatable {
/** Gets the file this program element comes from. */
File getFile() { result = getLocation().getFile() }
/** Gets this element's location. */
Location getLocation() { has_location(this, result) }
/** Gets the number of lines covered by this element. */
int getNumLines() { result = getLocation().getNumLines() }
/**
* 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://lgtm.com/help/ql/locations).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
/** Gets a textual representation of this element. */
string toString() { result = "locatable element" }
}

View File

@@ -0,0 +1,41 @@
/**
* Provides classes for working with packages.
*/
import go
/**
* A package.
*/
class Package extends @package {
/** Gets the name of this package. */
string getName() { packages(this, result, _, _) }
/** Gets the path of this package. */
string getPath() {
exists(string fullPath | packages(this, _, fullPath, _) |
result = fullPath.regexpReplaceAll("^.*/vendor/", "")
)
}
/** Gets the scope of this package. */
PackageScope getScope() { packages(this, _, _, result) }
/** Gets a textual representation of this element. */
string toString() { result = "package " + getPath() }
}
/**
* Gets an import path that identifies a package in module `mod` with the given path,
* possibly modulo [semantic import versioning](https://github.com/golang/go/wiki/Modules#semantic-import-versioning).
*
* For example, `package("github.com/go-pg/pg", "types")` gets an import path that can
* refer to `"github.com/go-pg/pg/types"`, but also to `"github.com/go-pg/pg/v10/types"`.
*/
bindingset[mod, path]
string package(string mod, string path) {
// "\Q" and "\E" start and end a quoted section of a regular expression. Anything like "." or "*" that
// "*" that comes between them is not interpreted as it would normally be in a regular expression.
result.regexpMatch("\\Q" + mod + "\\E([/.]v[^/]+)?($|/)\\Q" + path + "\\E") and
result = any(Package p).getPath()
}

View File

@@ -0,0 +1,20 @@
/**
* @name Print AST
* @description Outputs a representation of the Abstract Syntax Tree.
* @id go/print-ast
* @kind graph
*/
import go
import PrintAst
/**
* Hook to customize the functions printed by this query.
*/
class Cfg extends PrintAstConfiguration {
override predicate shouldPrintFunction(FuncDecl func) { any() }
override predicate shouldPrintFile(File file) { any() }
override predicate shouldPrintComments(File file) { any() }
}

View File

@@ -0,0 +1,271 @@
/**
* Provides queries to pretty-print a Go AST as a graph.
*/
import go
/**
* Hook to customize the files and functions printed by this module.
*
* For an AstNode to be printed, it always requires `shouldPrintFile(f)` to hold
* for its containing file `f`, and additionally requires `shouldPrintFunction(fun)`
* to hold if it is, or is a child of, function `fun`.
*/
class PrintAstConfiguration extends string {
/**
* Restrict to a single string, making this a singleton type.
*/
PrintAstConfiguration() { this = "PrintAstConfiguration" }
/**
* Holds if the AST for `func` should be printed. By default, holds for all
* functions.
*/
predicate shouldPrintFunction(FuncDecl func) { any() }
/**
* Holds if the AST for `file` should be printed. By default, holds for all
* files.
*/
predicate shouldPrintFile(File file) { any() }
/**
* Holds if the AST for `file` should include comments. By default, holds for all
* files.
*/
predicate shouldPrintComments(File file) { any() }
}
private predicate shouldPrintFunction(FuncDef func) {
exists(PrintAstConfiguration config | config.shouldPrintFunction(func))
}
private predicate shouldPrintFile(File file) {
exists(PrintAstConfiguration config | config.shouldPrintFile(file))
}
private predicate shouldPrintComments(File file) {
exists(PrintAstConfiguration config | config.shouldPrintComments(file))
}
private FuncDecl getEnclosingFunctionDecl(AstNode n) { result = n.getParent*() }
/**
* An AST node that should be printed.
*/
private newtype TPrintAstNode =
TAstNode(AstNode ast) {
shouldPrintFile(ast.getFile()) and
// Do print ast nodes without an enclosing function, e.g. file headers, that are not otherwise excluded
forall(FuncDecl f | f = getEnclosingFunctionDecl(ast) | shouldPrintFunction(f)) and
(
shouldPrintComments(ast.getFile())
or
not ast instanceof Comment and not ast instanceof CommentGroup
)
}
/**
* A node in the output tree.
*/
class PrintAstNode extends TPrintAstNode {
/**
* Gets a textual representation of this node.
*/
abstract string toString();
/**
* Gets the child node at index `childIndex`. Child indices must be unique,
* but need not be contiguous.
*/
abstract PrintAstNode getChild(int childIndex);
/**
* Holds if this node should be printed in the output. By default, all nodes
* within a function are printed, but the query can override
* `PrintAstConfiguration.shouldPrintFunction` to filter the output.
*/
predicate shouldPrint() { exists(getLocation()) }
/**
* Gets a child of this node.
*/
PrintAstNode getAChild() { result = getChild(_) }
/**
* Gets the location of this node in the source code.
*/
abstract Location getLocation();
/**
* Gets the value of the property of this node, where the name of the property
* is `key`.
*/
string getProperty(string key) {
key = "semmle.label" and
result = toString()
}
/**
* Gets the label for the edge from this node to the specified child. By
* default, this is just the index of the child, but subclasses can override
* this.
*/
string getChildEdgeLabel(int childIndex) {
exists(getChild(childIndex)) and
result = childIndex.toString()
}
/**
* Gets the `FuncDef` that contains this node.
*/
abstract FuncDef getEnclosingFunction();
}
/**
* Gets a pretty-printed representation of the QL class(es) for entity `el`.
*/
private string qlClass(AstNode el) {
// This version shows all non-overridden QL classes:
// result = "[" + concat(el.getAQlClass(), ", ") + "] "
// Normally we prefer to show just the canonical class:
result = "[" + concat(el.getAPrimaryQlClass(), ", ") + "] "
}
/**
* A graph node representing a real AST node.
*/
class BaseAstNode extends PrintAstNode, TAstNode {
AstNode ast;
BaseAstNode() { this = TAstNode(ast) }
override BaseAstNode getChild(int childIndex) {
// Note a node can have several results for getChild(n) because some
// nodes have multiple different types of child (e.g. a File has a
// child expression, the package name, and child declarations whose
// indices may clash), so we renumber them:
result = TAstNode(ast.getUniquelyNumberedChild(childIndex))
}
override string toString() { result = qlClass(ast) + ast }
final override Location getLocation() { result = ast.getLocation() }
final override FuncDef getEnclosingFunction() {
result = ast or result = ast.getEnclosingFunction()
}
}
/**
* A node representing an `Expr`.
*/
class ExprNode extends BaseAstNode {
override Expr ast;
override string getProperty(string key) {
result = super.getProperty(key)
or
key = "Value" and
result = qlClass(ast) + ast.getExactValue()
or
key = "Type" and
not ast.getType() instanceof InvalidType and
result = ast.getType().pp()
}
}
/**
* A node representing a `File`
*/
class FileNode extends BaseAstNode {
override File ast;
private string getRelativePath() { result = ast.getRelativePath() }
private int getSortOrder() {
rank[result](FileNode fn | any() | fn order by fn.getRelativePath()) = this
}
override string getProperty(string key) {
result = super.getProperty(key)
or
key = "semmle.order" and
result = getSortOrder().toString()
}
/**
* Gets a child of this node, renumbering `packageNode`, our parent's
* `oldPackageIndex`th child, as the first child and moving others accordingly.
*/
private BaseAstNode getChildPackageFirst(
int childIndex, BaseAstNode packageNode, int oldPackageIndex
) {
super.getChild(oldPackageIndex) = packageNode and
(
childIndex = 0 and result = packageNode
or
result =
rank[childIndex](BaseAstNode node, int i |
node = super.getChild(i) and i != oldPackageIndex
|
node order by i
)
)
}
/**
* Gets a child of this node, moving the package-name expression to the front
* of the list if one exists.
*/
override BaseAstNode getChild(int childIndex) {
if exists(ast.getPackageNameExpr())
then result = getChildPackageFirst(childIndex, TAstNode(ast.getPackageNameExpr()), _)
else result = super.getChild(childIndex)
}
/**
* Gets the label for the edge from this node to the specified child. The package name
* expression is named 'package'; others are numbered as per our parent's implementation
* of this method.
*/
override string getChildEdgeLabel(int childIndex) {
if getChild(childIndex) = TAstNode(ast.getPackageNameExpr())
then result = "package"
else result = super.getChildEdgeLabel(childIndex)
}
/**
* Gets the string representation of this File. Note explicitly using a relative path
* like this rather than absolute as per default for the File class is a workaround for
* a bug with codeql run test, which should replace absolute paths but currently does not.
*/
override string toString() { result = qlClass(ast) + ast.getRelativePath() }
}
/** 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) {
node.shouldPrint() and
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) {
exists(int childIndex |
source.shouldPrint() and
target.shouldPrint() and
target = source.getChild(childIndex)
|
key = "semmle.label" and value = source.getChildEdgeLabel(childIndex)
or
key = "semmle.order" and value = childIndex.toString()
)
}
/** Holds if property `key` of the graph has the given `value`. */
query predicate graphProperties(string key, string value) {
key = "semmle.graphKind" and value = "tree"
}

View File

@@ -0,0 +1,755 @@
/**
* Provides classes for working with scopes and declared objects.
*/
import go
/**
* A scope.
*/
class Scope extends @scope {
/** Gets the enclosing scope of this scope, if any. */
Scope getOuterScope() { scopenesting(this, result) }
/** Gets a scope nested inside this scope. */
Scope getAnInnerScope() { this = result.getOuterScope() }
/** Looks up the entity with the given name in this scope. */
Entity getEntity(string name) {
result.getName() = name and
result.getScope() = this
}
/** Gets a textual representation of this scope. */
string toString() { result = "scope" }
}
/** Provides helper predicates for working with scopes. */
module Scope {
/** Gets the universe scope. */
UniverseScope universe() { any() }
}
/**
* The universe scope.
*/
class UniverseScope extends @universescope, Scope {
override string toString() { result = "universe scope" }
}
/** A package scope. */
class PackageScope extends @packagescope, Scope {
/** Gets the package whose scope this is. */
Package getPackage() { this = result.getScope() }
override string toString() { result = "package scope" }
}
/** A local scope. */
class LocalScope extends @localscope, Scope, Locatable {
/** Gets the AST node inducing this scope. */
ScopeNode getNode() { this = result.getScope() }
/**
* Gets the function scope in which this scope is nested.
*
* For function scopes, this is the scope itself.
*/
FunctionScope getEnclosingFunctionScope() {
result = getOuterScope().(LocalScope).getEnclosingFunctionScope()
}
override string toString() { result = "local scope" }
}
/** A local scope induced by a file. */
class FileScope extends LocalScope {
FileScope() { getNode() instanceof File }
}
/** A local scope induced by a function definition. */
class FunctionScope extends LocalScope {
FuncDef f;
FunctionScope() { getNode() = f.getTypeExpr() }
/** Gets the function inducing this scope. */
FuncDef getFunction() { result = f }
override FunctionScope getEnclosingFunctionScope() { result = this }
override string toString() { result = "function scope" }
}
/**
* A declared or built-in entity (that is, package, type, constant, variable, function or label)
*/
class Entity extends @object {
/**
* Gets the name of this entity.
*
* Anonymous entities (such as the receiver variables of interface methods) have the empty string as their name.
*/
string getName() { objects(this, _, result) }
/** Gets the package in which this entity is declared, if any. */
Package getPackage() { result.getScope() = this.getScope() }
/** Holds if this entity is declared in a package with path `pkg` and has the given `name`. */
predicate hasQualifiedName(string pkg, string name) {
pkg = getPackage().getPath() and
name = getName()
}
/** Gets the qualified name of this entity, if any. */
string getQualifiedName() {
exists(string pkg, string name | hasQualifiedName(pkg, name) | result = pkg + "." + name)
}
/**
* Gets the scope in which this entity is declared, if any.
*
* Entities corresponding to fields and methods do not have a scope.
*/
Scope getScope() { objectscopes(this, result) }
/** Gets the declaring identifier for this entity. */
Ident getDeclaration() { result.declares(this) }
/** Gets a reference to this entity. */
Name getAReference() { result.getTarget() = this }
/** Gets the type of this entity. */
Type getType() { objecttypes(this, result) }
/** Gets a textual representation of this entity. */
string toString() { result = getName() }
/**
* 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://lgtm.com/help/ql/locations).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
// take the location of the declaration if there is one
getDeclaration().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
or
// otherwise fall back on dummy location
not exists(getDeclaration()) and
filepath = "" and
startline = 0 and
startcolumn = 0 and
endline = 0 and
endcolumn = 0
}
}
/** A declared entity (that is, type, constant, variable or function). */
class DeclaredEntity extends Entity, @declobject {
/** Gets the expression to which this entity is initialized, if any. */
Expr getInit() {
exists(ValueSpec spec, int i |
spec.getNameExpr(i) = getDeclaration() and
spec.getInit(i) = result
)
}
}
/** A built-in entity (that is, type, constant or function). */
class BuiltinEntity extends Entity, @builtinobject { }
/** An imported package. */
class PackageEntity extends Entity, @pkgobject { }
/** A built-in or declared named type. */
class TypeEntity extends Entity, @typeobject { }
/** A declared named type. */
class DeclaredType extends TypeEntity, DeclaredEntity, @decltypeobject {
/** Gets the declaration specifier declaring this type. */
TypeSpec getSpec() { result.getNameExpr() = this.getDeclaration() }
}
/** A built-in named type. */
class BuiltinType extends TypeEntity, BuiltinEntity, @builtintypeobject { }
/** A built-in or declared constant, variable, field, method or function. */
class ValueEntity extends Entity, @valueobject {
/** Gets a data-flow node that reads the value of this entity. */
Read getARead() { result.reads(this) }
/** Gets a control-flow node that updates the value of this entity. */
Write getAWrite() { result.writes(this, _) }
}
/** A built-in or declared constant. */
class Constant extends ValueEntity, @constobject { }
/** A declared constant. */
class DeclaredConstant extends Constant, DeclaredEntity, @declconstobject {
/** Gets the declaration specifier declaring this constant. */
ValueSpec getSpec() { result.getANameExpr() = this.getDeclaration() }
}
/** A built-in constant. */
class BuiltinConstant extends Constant, BuiltinEntity, @builtinconstobject { }
/**
* A built-in or declared variable.
*
* Note that Go currently does not have any built-in variables, so this class is effectively
* an alias for `DeclaredVariable`.
*/
class Variable extends ValueEntity, @varobject { }
/** A declared variable. */
class DeclaredVariable extends Variable, DeclaredEntity, @declvarobject {
/** Gets the declaration specifier declaring this variable. */
ValueSpec getSpec() { result.getANameExpr() = this.getDeclaration() }
}
/** A variable declared in a local scope (as opposed to a package scope or the universal scope). */
class LocalVariable extends DeclaredVariable {
LocalVariable() { getScope() instanceof LocalScope }
/** Gets the innermost function containing the scope of this variable, if any. */
FuncDef getDeclaringFunction() {
result = getScope().(LocalScope).getEnclosingFunctionScope().getFunction()
}
/** Holds if this variable is referenced inside a nested function. */
predicate isCaptured() { getDeclaringFunction() != getAReference().getEnclosingFunction() }
}
/**
* A (named) function parameter.
*
* Note that receiver variables are considered parameters.
*/
class Parameter extends DeclaredVariable {
FuncDef f;
int index;
Parameter() {
f.(MethodDecl).getReceiverDecl().getNameExpr() = this.getDeclaration() and
index = -1
or
exists(FuncTypeExpr tp | tp = f.getTypeExpr() |
this =
rank[index + 1](DeclaredVariable parm, int j, int k |
parm.getDeclaration() = tp.getParameterDecl(j).getNameExpr(k)
|
parm order by j, k
)
)
}
/** Gets the function to which this parameter belongs. */
FuncDef getFunction() { result = f }
/**
* Gets the index of this parameter among all parameters of the function.
*
* The receiver is considered to have index -1.
*/
int getIndex() { result = index }
/** Holds if this is the `i`th parameter of function `fd`. */
predicate isParameterOf(FuncDef fd, int i) { fd = f and i = index }
}
/** The receiver variable of a method. */
class ReceiverVariable extends Parameter {
override MethodDecl f;
ReceiverVariable() { index = -1 }
/** Holds if this is the receiver variable of method `m`. */
predicate isReceiverOf(MethodDecl m) { m = f }
}
/** A (named) function result variable. */
class ResultVariable extends DeclaredVariable {
FuncDef f;
int index;
ResultVariable() {
exists(FuncTypeExpr tp | tp = f.getTypeExpr() |
this =
rank[index + 1](DeclaredVariable parm, int j, int k |
parm.getDeclaration() = tp.getResultDecl(j).getNameExpr(k)
|
parm order by j, k
)
)
}
/** Gets the function to which this result variable belongs. */
FuncDef getFunction() { result = f }
/** Gets the index of this result among all results of the function. */
int getIndex() { result = index }
/** Holds if this is the `i`th result of function `fd`. */
predicate isResultOf(FuncDef fd, int i) { fd = f and i = index }
}
/**
* A struct field.
*
* Note that field identity is determined by type identity: if two struct types are identical in
* the sense of the Go language specification (https://golang.org/ref/spec#Type_identity), then
* any of their fields that have the same name are also identical. This, in turn, means that a
* field can have two or more declarations.
*
* For example, consider the following two type declarations:
*
* ```go
* type T1 struct { x int }
* type T2 struct { x int }
* ```
*
* Types `T1` and `T2` are different, but their underlying struct types are identical. Hence
* the two declarations of `x` refer to the same field.
*/
class Field extends Variable {
StructType declaringType;
Field() { fieldstructs(this, declaringType) }
/** Gets the struct type declaring this field. */
StructType getDeclaringType() { result = declaringType }
override Package getPackage() {
exists(Type tp | tp.getUnderlyingType() = declaringType | result = tp.getPackage())
}
/**
* Holds if this field has name `f` and it belongs to a type with qualified name `tp`.
*
* Note that due to field embedding the same field may have multiple qualified names.
*/
override predicate hasQualifiedName(string tp, string f) {
exists(Type base |
tp = base.getQualifiedName() and
this = base.getField(f)
)
}
/**
* Holds if this field has name `f` and it belongs to a type `tp` declared in package `pkg`.
*
* Note that due to field embedding the same field may belong to multiple types.
*/
predicate hasQualifiedName(string pkg, string tp, string f) {
exists(Type base |
base.hasQualifiedName(pkg, tp) and
this = base.getField(f)
)
}
}
/**
* A field that belongs to a struct that may be embedded within another struct.
*
* When a selector addresses such a field, it is possible it is implicitly addressing a nested struct.
*/
class PromotedField extends Field {
PromotedField() { this = any(StructType t).getFieldOfEmbedded(_, _, _, _) }
}
/** A built-in or declared function. */
class Function extends ValueEntity, @functionobject {
/** Gets a call to this function. */
pragma[nomagic]
DataFlow::CallNode getACall() {
this = result.getTarget()
or
this.(DeclaredFunction).getFuncDecl() = result.getACallee()
}
/** Gets the declaration of this function, if any. */
FuncDecl getFuncDecl() { none() }
/** Holds if this function has no observable side effects. */
predicate mayHaveSideEffects() { none() }
/**
* Holds if this function may return without panicking, exiting the process, or looping forever.
*
* This predicate is an over-approximation: it may hold for functions that can never
* return normally, but it never fails to hold for functions that can.
*
* Note this is declared here and not in `DeclaredFunction` so that library models can override this
* by extending `Function` rather than having to remember to extend `DeclaredFunction`.
*/
predicate mayReturnNormally() {
not mustPanic() and
(ControlFlow::mayReturnNormally(getFuncDecl()) or not exists(getBody()))
}
/**
* Holds if calling this function may cause a runtime panic.
*
* This predicate is an over-approximation: it may hold for functions that can never
* cause a runtime panic, but it never fails to hold for functions that can.
*/
predicate mayPanic() { any() }
/**
* Holds if calling this function always causes a runtime panic.
*
* This predicate is an over-approximation: it may not hold for functions that do
* cause a runtime panic, but it never holds for functions that do not.
*/
predicate mustPanic() { none() }
/** Gets the number of parameters of this function. */
int getNumParameter() { result = getType().(SignatureType).getNumParameter() }
/** Gets the type of the `i`th parameter of this function. */
Type getParameterType(int i) { result = getType().(SignatureType).getParameterType(i) }
/** Gets the number of results of this function. */
int getNumResult() { result = getType().(SignatureType).getNumResult() }
/** Gets the type of the `i`th result of this function. */
Type getResultType(int i) { result = getType().(SignatureType).getResultType(i) }
/** Gets the body of this function, if any. */
BlockStmt getBody() { result = getFuncDecl().getBody() }
/** Gets the `i`th parameter of this function. */
Parameter getParameter(int i) { result.isParameterOf(getFuncDecl(), i) }
/** Gets a parameter of this function. */
Parameter getAParameter() { result = getParameter(_) }
/** Gets the `i`th reslt variable of this function. */
ResultVariable getResult(int i) { result.isResultOf(getFuncDecl(), i) }
/** Gets a result variable of this function. */
ResultVariable getAResult() { result = getResult(_) }
}
/**
* A method, that is, a function with a receiver variable, or a function declared in an interface.
*
* Note that method identity is determined by receiver type identity: if two methods have the same
* name and their receiver types are identical in the sense of the Go language specification
* (https://golang.org/ref/spec#Type_identity), then the two methods are identical as well.
*/
class Method extends Function {
Variable receiver;
Method() { methodreceivers(this, receiver) }
override Package getPackage() {
// a method doesn't have a scope, so manually associate it with its receiver's
// package.
result = this.getReceiverType().getPackage()
}
/** Holds if this method is declared in an interface. */
predicate isInterfaceMethod() { getReceiverType().getUnderlyingType() instanceof InterfaceType }
/** Gets the receiver variable of this method. */
Variable getReceiver() { result = receiver }
/** Gets the type of the receiver variable of this method. */
Type getReceiverType() { result = receiver.getType() }
/**
* Gets the receiver base type of this method, that is, either the base type of the receiver type
* if it is a pointer type, or the receiver type itself if it is not a pointer type.
*/
Type getReceiverBaseType() {
exists(Type recv | recv = getReceiverType() |
if recv instanceof PointerType
then result = recv.(PointerType).getBaseType()
else result = recv
)
}
/** Holds if this method has name `m` and belongs to the method set of type `tp` or `*tp`. */
private predicate isIn(NamedType tp, string m) {
this = tp.getMethod(m) or
this = tp.getPointerType().getMethod(m)
}
/**
* Holds if this method has name `m` and belongs to the method set of a type `T` or `*T` where
* `T` has qualified name `tp`.
*
* Note that `meth.hasQualifiedName(tp, m)` is almost, but not quite, equivalent to
* `exists(Type t | tp = t.getQualifiedName() and meth = t.getMethod(m))`: the latter
* distinguishes between the method sets of `T` and `*T`, while the former does not.
*/
override predicate hasQualifiedName(string tp, string m) {
exists(NamedType t |
this.isIn(t, m) and
tp = t.getQualifiedName()
)
}
/**
* Holds if this method has name `m` and belongs to the method set of a type `T` or `*T` where
* `T` is declared in package `pkg` and has name `tp`.
*
* Note that `meth.hasQualifiedName(pkg, tp, m)` is almost, but not quite, equivalent to
* `exists(Type t | t.hasQualifiedName(pkg, tp) and meth = t.getMethod(m))`: the latter
* distinguishes between the method sets of `T` and `*T`, while the former does not.
*/
predicate hasQualifiedName(string pkg, string tp, string m) {
exists(NamedType t |
this.isIn(t, m) and
t.hasQualifiedName(pkg, tp)
)
}
/**
* Holds if this method implements the method `m`, that is, if `m` is a method
* on an interface, and this is a method with the same name on a type that
* implements that interface.
*
* Note that all methods implement themselves, and interface methods _only_
* implement themselves.
*/
predicate implements(Method m) {
this = m
or
not isInterfaceMethod() and
exists(Type t |
this = t.getMethod(m.getName()) and
t.implements(m.getReceiverType().getUnderlyingType())
)
}
/**
* Holds if this method implements the method that has qualified name `pkg.tp.name`, that is, if
* `pkg.tp.name` is a method on an interface, and this is a method with the same name on a type
* that implements that interface.
*/
predicate implements(string pkg, string tp, string name) {
exists(Method m | m.hasQualifiedName(pkg, tp, name) | this.implements(m))
}
}
/**
* A method whose receiver may be embedded within a struct.
*
* When a selector addresses such a method, it is possible it is implicitly addressing a nested struct.
*/
class PromotedMethod extends Method {
PromotedMethod() { this = any(StructType t).getMethodOfEmbedded(_, _, _) }
}
/** A declared function. */
class DeclaredFunction extends Function, DeclaredEntity, @declfunctionobject {
override FuncDecl getFuncDecl() { result.getNameExpr() = this.getDeclaration() }
override predicate mayHaveSideEffects() {
not exists(getBody())
or
exists(BlockStmt body | body = getBody() |
body.mayHaveSideEffects()
or
// functions declared in files with build constraints may be defined differently
// for different platforms, so allow them to avoid false positives
body.getFile().hasBuildConstraints()
)
}
}
/** A built-in function. */
class BuiltinFunction extends Function, BuiltinEntity, @builtinfunctionobject {
override predicate mayHaveSideEffects() { builtinFunction(getName(), false, _, _) }
override predicate mayPanic() { builtinFunction(getName(), _, true, _) }
override predicate mustPanic() { builtinFunction(getName(), _, _, true) }
/**
* Holds if this function is pure, that is, it has no observable side effects and
* no non-determinism.
*/
predicate isPure() { not mayHaveSideEffects() }
}
/** A statement label. */
class Label extends Entity, @labelobject { }
/**
* Holds if `name` is a built-in function, where
*
* - `isPure` is true if the function has no observable side effects, and false otherwise;
* - `mayPanic` is true if calling this function may cause a panic, and false otherwise;
* - `mustPanic` is ture if calling this function always causes a panic, and false otherwise.
*
* Allocating memory is not considered an observable side effect.
*/
private predicate builtinFunction(string name, boolean isPure, boolean mayPanic, boolean mustPanic) {
name = "append" and isPure = false and mayPanic = false and mustPanic = false
or
name = "cap" and isPure = true and mayPanic = false and mustPanic = false
or
name = "close" and isPure = false and mayPanic = true and mustPanic = false
or
name = "complex" and isPure = true and mayPanic = true and mustPanic = false
or
name = "copy" and isPure = false and mayPanic = true and mustPanic = false
or
name = "delete" and isPure = false and mayPanic = false and mustPanic = false
or
name = "imag" and isPure = true and mayPanic = false and mustPanic = false
or
name = "len" and isPure = true and mayPanic = false and mustPanic = false
or
name = "make" and isPure = true and mayPanic = true and mustPanic = false
or
name = "new" and isPure = true and mayPanic = false and mustPanic = false
or
name = "panic" and isPure = false and mayPanic = true and mustPanic = true
or
name = "print" and isPure = false and mayPanic = false and mustPanic = false
or
name = "println" and isPure = false and mayPanic = false and mustPanic = false
or
name = "real" and isPure = true and mayPanic = false and mustPanic = false
or
name = "recover" and isPure = false and mayPanic = false and mustPanic = false
}
/** Provides helper predicates for working with built-in objects from the universe scope. */
module Builtin {
// built-in types
/** Gets the built-in type `bool`. */
BuiltinType bool() { result.getName() = "bool" }
/** Gets the built-in type `byte`. */
BuiltinType byte() { result.getName() = "byte" }
/** Gets the built-in type `complex64`. */
BuiltinType complex64() { result.getName() = "complex64" }
/** Gets the built-in type `complex128`. */
BuiltinType complex128() { result.getName() = "complex128" }
/** Gets the built-in type `error`. */
BuiltinType error() { result.getName() = "error" }
/** Gets the built-in type `float32`. */
BuiltinType float32() { result.getName() = "float32" }
/** Gets the built-in type `float64`. */
BuiltinType float64() { result.getName() = "float64" }
/** Gets the built-in type `int`. */
BuiltinType int_() { result.getName() = "int" }
/** Gets the built-in type `int8`. */
BuiltinType int8() { result.getName() = "int8" }
/** Gets the built-in type `int16`. */
BuiltinType int16() { result.getName() = "int16" }
/** Gets the built-in type `int32`. */
BuiltinType int32() { result.getName() = "int32" }
/** Gets the built-in type `int64`. */
BuiltinType int64() { result.getName() = "int64" }
/** Gets the built-in type `rune`. */
BuiltinType rune() { result.getName() = "rune" }
/** Gets the built-in type `string`. */
BuiltinType string_() { result.getName() = "string" }
/** Gets the built-in type `uint`. */
BuiltinType uint() { result.getName() = "uint" }
/** Gets the built-in type `uint8`. */
BuiltinType uint8() { result.getName() = "uint8" }
/** Gets the built-in type `uint16`. */
BuiltinType uint16() { result.getName() = "uint16" }
/** Gets the built-in type `uint32`. */
BuiltinType uint32() { result.getName() = "uint32" }
/** Gets the built-in type `uint64`. */
BuiltinType uint64() { result.getName() = "uint64" }
/** Gets the built-in type `uintptr`. */
BuiltinType uintptr() { result.getName() = "uintptr" }
// built-in constants
/** Gets the built-in constant `true`. */
BuiltinConstant true_() { result.getName() = "true" }
/** Gets the built-in constant `false`. */
BuiltinConstant false_() { result.getName() = "false" }
/** Gets the built-in constant corresponding to `b`. */
BuiltinConstant bool(boolean b) {
b = true and result = true_()
or
b = false and result = false_()
}
/** Gets the built-in constant `iota`. */
BuiltinConstant iota() { result.getName() = "iota" }
// built-in zero value
/** Gets the built-in zero-value `nil`. */
BuiltinConstant nil() { result.getName() = "nil" }
/** Gets the built-in function `append`. */
BuiltinFunction append() { result.getName() = "append" }
/** Gets the built-in function `cap`. */
BuiltinFunction cap() { result.getName() = "cap" }
/** Gets the built-in function `close`. */
BuiltinFunction close() { result.getName() = "close" }
/** Gets the built-in function `complex`. */
BuiltinFunction complex() { result.getName() = "complex" }
/** Gets the built-in function `copy`. */
BuiltinFunction copy() { result.getName() = "copy" }
/** Gets the built-in function `delete`. */
BuiltinFunction delete() { result.getName() = "delete" }
/** Gets the built-in function `imag`. */
BuiltinFunction imag() { result.getName() = "imag" }
/** Gets the built-in function `len`. */
BuiltinFunction len() { result.getName() = "len" }
/** Gets the built-in function `make`. */
BuiltinFunction make() { result.getName() = "make" }
/** Gets the built-in function `new`. */
BuiltinFunction new() { result.getName() = "new" }
/** Gets the built-in function `panic`. */
BuiltinFunction panic() { result.getName() = "panic" }
/** Gets the built-in function `print`. */
BuiltinFunction print() { result.getName() = "print" }
/** Gets the built-in function `println`. */
BuiltinFunction println() { result.getName() = "println" }
/** Gets the built-in function `real`. */
BuiltinFunction real() { result.getName() = "real" }
/** Gets the built-in function `recover`. */
BuiltinFunction recover() { result.getName() = "recover" }
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,494 @@
/**
* Provides predicates and classes for working with string operations.
*/
import go
/** Provides predicates and classes for working with string operations. */
module StringOps {
/**
* An expression that is equivalent to `strings.HasPrefix(A, B)` or `!strings.HasPrefix(A, B)`.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `StringOps::HasPrefix::Range` instead.
*/
class HasPrefix extends DataFlow::Node {
HasPrefix::Range range;
HasPrefix() { range = this }
/**
* Gets the `A` in `strings.HasPrefix(A, B)`.
*/
DataFlow::Node getBaseString() { result = range.getBaseString() }
/**
* Gets the `B` in `strings.HasPrefix(A, B)`.
*/
DataFlow::Node getSubstring() { result = range.getSubstring() }
/**
* Gets the polarity of the check.
*
* If the polarity is `false` the check returns `true` if the string does not start
* with the given substring.
*/
boolean getPolarity() { result = range.getPolarity() }
}
class StartsWith = HasPrefix;
/** Provides predicates and classes for working with prefix checks. */
module HasPrefix {
/**
* An expression that is equivalent to `strings.HasPrefix(A, B)` or `!strings.HasPrefix(A, B)`.
*
* Extend this class to model new APIs. If you want to refine existing API models, extend
* `StringOps::HasPrefix` instead.
*/
abstract class Range extends DataFlow::Node {
/**
* Gets the `A` in `strings.HasPrefix(A, B)`.
*/
abstract DataFlow::Node getBaseString();
/**
* Gets the `B` in `strings.HasPrefix(A, B)`.
*/
abstract DataFlow::Node getSubstring();
/**
* Gets the polarity of the check.
*
* If the polarity is `false` the check returns `true` if the string does not start
* with the given substring.
*/
boolean getPolarity() { result = true }
}
/**
* An expression of the form `strings.HasPrefix(A, B)`.
*/
private class StringsHasPrefix extends Range, DataFlow::CallNode {
StringsHasPrefix() { getTarget().hasQualifiedName("strings", "HasPrefix") }
override DataFlow::Node getBaseString() { result = getArgument(0) }
override DataFlow::Node getSubstring() { result = getArgument(1) }
}
/**
* Holds if `eq` is of the form `nd == 0` or `nd != 0`.
*/
pragma[noinline]
private predicate comparesToZero(DataFlow::EqualityTestNode eq, DataFlow::Node nd) {
exists(DataFlow::Node zero |
eq.hasOperands(globalValueNumber(nd).getANode(), zero) and
zero.getIntValue() = 0
)
}
/**
* An expression of the form `strings.Index(A, B) == 0`.
*/
private class HasPrefix_IndexOfEquals extends Range, DataFlow::EqualityTestNode {
DataFlow::CallNode indexOf;
HasPrefix_IndexOfEquals() {
comparesToZero(this, indexOf) and
indexOf.getTarget().hasQualifiedName("strings", "Index")
}
override DataFlow::Node getBaseString() { result = indexOf.getArgument(0) }
override DataFlow::Node getSubstring() { result = indexOf.getArgument(1) }
override boolean getPolarity() { result = expr.getPolarity() }
}
/**
* Holds if `eq` is of the form `str[0] == rhs` or `str[0] != rhs`.
*/
pragma[noinline]
private predicate comparesFirstCharacter(
DataFlow::EqualityTestNode eq, DataFlow::Node str, DataFlow::Node rhs
) {
exists(DataFlow::ElementReadNode read |
eq.hasOperands(globalValueNumber(read).getANode(), rhs) and
str = read.getBase() and
str.getType().getUnderlyingType() instanceof StringType and
read.getIndex().getIntValue() = 0
)
}
/**
* A comparison of the form `x[0] == 'k'` for some rune literal `k`.
*/
private class HasPrefix_FirstCharacter extends Range, DataFlow::EqualityTestNode {
DataFlow::Node base;
DataFlow::Node runeLiteral;
HasPrefix_FirstCharacter() { comparesFirstCharacter(this, base, runeLiteral) }
override DataFlow::Node getBaseString() { result = base }
override DataFlow::Node getSubstring() { result = runeLiteral }
override boolean getPolarity() { result = expr.getPolarity() }
}
/**
* A comparison of the form `x[:len(y)] == y`.
*/
private class HasPrefix_Substring extends Range, DataFlow::EqualityTestNode {
DataFlow::SliceNode slice;
DataFlow::Node substring;
HasPrefix_Substring() {
eq(_, slice, substring) and
slice.getLow().getIntValue() = 0 and
(
exists(DataFlow::CallNode len |
len = Builtin::len().getACall() and
len.getArgument(0) = globalValueNumber(substring).getANode() and
slice.getHigh() = globalValueNumber(len).getANode()
)
or
substring.getStringValue().length() = slice.getHigh().getIntValue()
)
}
override DataFlow::Node getBaseString() { result = slice.getBase() }
override DataFlow::Node getSubstring() { result = substring }
override boolean getPolarity() { result = expr.getPolarity() }
}
}
/**
* A data-flow node that performs string concatenation.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `StringOps::Concatenation::Range` instead.
*/
class Concatenation extends DataFlow::Node {
Concatenation::Range self;
Concatenation() { this = self }
/**
* Gets the `n`th operand of this string concatenation, if there is a data-flow node for it.
*/
DataFlow::Node getOperand(int n) { result = self.getOperand(n) }
/**
* Gets the string value of the `n`th operand of this string concatenation, if it is a constant.
*/
string getOperandStringValue(int n) { result = self.getOperandStringValue(n) }
/**
* Gets the number of operands of this string concatenation.
*/
int getNumOperand() { result = self.getNumOperand() }
}
/** Provides predicates and classes for working with string concatenations. */
module Concatenation {
/**
* A data-flow node that performs string concatenation.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `StringOps::Concatenation` instead.
*/
abstract class Range extends DataFlow::Node {
/**
* Gets the `n`th operand of this string concatenation, if there is a data-flow node for it.
*/
abstract DataFlow::Node getOperand(int n);
/**
* Gets the string value of the `n`th operand of this string concatenation, if it is
* a constant.
*/
string getOperandStringValue(int n) { result = getOperand(n).getStringValue() }
/**
* Gets the number of operands of this string concatenation.
*/
int getNumOperand() { result = count(getOperand(_)) }
}
/** A string concatenation using the `+` or `+=` operator. */
private class PlusConcat extends Range, DataFlow::BinaryOperationNode {
PlusConcat() {
getType() instanceof StringType and
getOperator() = "+"
}
override DataFlow::Node getOperand(int n) {
n = 0 and result = getLeftOperand()
or
n = 1 and result = getRightOperand()
}
}
/**
* Gets a regular expression for matching simple format-string components, including flags,
* width and precision specifiers, but not including `*` specifiers or explicit argument
* indices.
*/
pragma[noinline]
private string getFormatComponentRegex() {
exists(
string literal, string opt_flag, string width, string prec, string opt_width_and_prec,
string operator, string verb
|
literal = "([^%]|%%)+" and
opt_flag = "[-+ #0]?" and
width = "\\d+|\\*" and
prec = "\\.(\\d+|\\*)" and
opt_width_and_prec = "(" + width + ")?(" + prec + ")?" and
operator = "[bcdeEfFgGoOpqstTxXUv]" and
verb = "(%" + opt_flag + opt_width_and_prec + operator + ")"
|
result = "(" + literal + "|" + verb + ")"
)
}
/**
* A call to `fmt.Sprintf`, considered as a string concatenation.
*
* Only calls with simple format strings (no `*` specifiers, no explicit argument indices)
* are supported. Such format strings can be viewed as sequences of alternating literal and
* non-literal components. A literal component contains no `%` characters except `%%` pairs,
* while a non-literal component consists of `%`, a verb, and possibly flags and specifiers.
* Each non-literal component consumes exactly one argument.
*
* Literal components give rise to concatenation operands that have a string value but no
* data-flow node; non-literal `%s` or `%v` components give rise to concatenation operands
* that do have an associated data-flow node but possibly no string value; any other non-literal
* components give rise to concatenation operands that have neither an associated data-flow
* node nor a string value. This is because verbs like `%q` perform additional string
* transformations that we cannot easily represent.
*/
private class SprintfConcat extends Range, DataFlow::CallNode {
string fmt;
SprintfConcat() {
exists(Function sprintf | sprintf.hasQualifiedName("fmt", "Sprintf") |
this = sprintf.getACall() and
fmt = getArgument(0).getStringValue() and
fmt.regexpMatch(getFormatComponentRegex() + "*")
)
}
/**
* Gets the `n`th component of this format string.
*/
private string getComponent(int n) {
result = fmt.regexpFind(getFormatComponentRegex(), n, _)
}
override DataFlow::Node getOperand(int n) {
exists(int i, string part | part = "%s" or part = "%v" |
part = getComponent(n) and
i = n / 2 and
result = getArgument(i + 1)
)
}
override string getOperandStringValue(int n) {
result = Range.super.getOperandStringValue(n)
or
exists(string cmp | cmp = getComponent(n) |
(cmp.charAt(0) != "%" or cmp.charAt(1) = "%") and
result = cmp.replaceAll("%%", "%")
)
}
override int getNumOperand() { result = max(int i | exists(getComponent(i))) + 1 }
}
/**
* Holds if `src` flows to `dst` through the `n`th operand of the given concatenation operator.
*/
predicate taintStep(DataFlow::Node src, DataFlow::Node dst, Concatenation cat, int n) {
src = cat.getOperand(n) and
dst = cat
}
/**
* Holds if there is a taint step from `src` to `dst` through string concatenation.
*/
predicate taintStep(DataFlow::Node src, DataFlow::Node dst) { taintStep(src, dst, _, _) }
}
private newtype TConcatenationElement =
/** A root concatenation element that is not itself an operand of a string concatenation. */
MkConcatenationRoot(Concatenation cat) { not cat = any(Concatenation parent).getOperand(_) } or
/** A concatenation element that is an operand of a string concatenation. */
MkConcatenationOperand(Concatenation parent, int i) { i in [0 .. parent.getNumOperand() - 1] }
/**
* An element of a string concatenation, which either itself performs a string concatenation or
* occurs as an operand in a string concatenation.
*
* For example, the expression `x + y + z` contains the following concatenation
* elements:
*
* - The leaf elements `x`, `y`, and `z`
* - The intermediate element `x + y`, which is both a concatenation and an operand
* - The root element `x + y + z`
*/
class ConcatenationElement extends TConcatenationElement {
/**
* Gets the data-flow node corresponding to this concatenation element, if any.
*/
DataFlow::Node asNode() {
this = MkConcatenationRoot(result)
or
exists(Concatenation parent, int i | this = MkConcatenationOperand(parent, i) |
result = parent.getOperand(i)
)
}
/**
* Gets the string value of this concatenation element if it is a constant.
*/
string getStringValue() {
result = asNode().getStringValue()
or
exists(Concatenation parent, int i | this = MkConcatenationOperand(parent, i) |
result = parent.getOperandStringValue(i)
)
}
/**
* Gets the `n`th operand of this string concatenation.
*/
ConcatenationOperand getOperand(int n) { result = MkConcatenationOperand(asNode(), n) }
/**
* Gets an operand of this string concatenation.
*/
ConcatenationOperand getAnOperand() { result = this.getOperand(_) }
/**
* Gets the number of operands of this string concatenation.
*/
int getNumOperand() { result = count(this.getAnOperand()) }
/**
* Gets the first operand of this string concatenation.
*
* For example, the first operand of `(x + y) + z` is `(x + y)`.
*/
ConcatenationOperand getFirstOperand() { result = getOperand(0) }
/**
* Gets the last operand of this string concatenation.
*
* For example, the last operand of `x + (y + z)` is `(y + z)`.
*/
ConcatenationOperand getLastOperand() { result = getOperand(getNumOperand() - 1) }
/**
* Gets the root of the concatenation tree to which this element belongs.
*/
ConcatenationRoot getConcatenationRoot() { this = result.getAnOperand*() }
/**
* Gets a leaf in the concatenation tree that this element is the root of.
*/
ConcatenationLeaf getALeaf() { result = this.getAnOperand*() }
/**
* Gets the first leaf in this concatenation tree.
*
* For example, the first leaf of `(x + y) + z` is `x`.
*/
ConcatenationLeaf getFirstLeaf() { result = getFirstOperand*() }
/**
* Gets the last leaf in this concatenation tree.
*
* For example, the last leaf of `x + (y + z)` is `z`.
*/
ConcatenationLeaf getLastLeaf() { result = getLastOperand*() }
/** Gets a textual representation of this concatenation element. */
string toString() {
if exists(asNode())
then result = asNode().toString()
else
if exists(getStringValue())
then result = getStringValue()
else result = "concatenation element"
}
/**
* 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
) {
asNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
or
// use dummy location for elements that don't have a corresponding node
not exists(asNode()) and
filepath = "" and
startline = 0 and
startcolumn = 0 and
endline = 0 and
endcolumn = 0
}
}
/**
* One of the operands in a string concatenation.
*
* See `ConcatenationElement` for more information.
*/
class ConcatenationOperand extends ConcatenationElement, MkConcatenationOperand { }
/**
* A data-flow node that performs a string concatenation, and is not an
* immediate operand in a larger string concatenation.
*
* See `ConcatenationElement` for more information.
*/
class ConcatenationRoot extends ConcatenationElement, MkConcatenationRoot { }
/**
* An operand to a concatenation that is not itself a concatenation.
*
* See `ConcatenationElement` for more information.
*/
class ConcatenationLeaf extends ConcatenationOperand {
ConcatenationLeaf() { not exists(getAnOperand()) }
/**
* Gets the operand immediately preceding this one in its parent concatenation.
*
* For example, in `(x + y) + z`, the previous leaf for `z` is `y`.
*/
ConcatenationLeaf getPreviousLeaf() {
exists(ConcatenationElement parent, int i |
result = parent.getOperand(i - 1).getLastLeaf() and
this = parent.getOperand(i).getFirstLeaf()
)
}
/**
* Gets the operand immediately succeeding this one in its parent concatenation.
*
* For example, in `(x + y) + z`, the previous leaf for `y` is `z`.
*/
ConcatenationLeaf getNextLeaf() { this = result.getPreviousLeaf() }
}
}

View File

@@ -0,0 +1,695 @@
/**
* Provides classes for working with Go types.
*/
import go
/** A Go type. */
class Type extends @type {
/** Gets the name of this type, if it has one. */
string getName() { typename(this, result) }
/**
* Gets the underlying type of this type after any type aliases have been replaced
* with their definition.
*/
Type getUnderlyingType() { result = this }
/**
* Gets the entity associated with this type.
*/
TypeEntity getEntity() { type_objects(this, result) }
/** Gets the package in which this type is declared, if any. */
Package getPackage() { result = this.getEntity().getPackage() }
/**
* Gets the qualified name of this type, if any.
*
* Only (defined) named types like `io.Writer` have a qualified name. Basic types like `int`,
* pointer types like `*io.Writer`, and other composite types do not have a qualified name.
*/
string getQualifiedName() { result = getEntity().getQualifiedName() }
/**
* Holds if this type is declared in a package with path `pkg` and has name `name`.
*
* Only (defined) named types like `io.Writer` have a qualified name. Basic types like `int`,
* pointer types like `*io.Writer`, and other composite types do not have a qualified name.
*/
predicate hasQualifiedName(string pkg, string name) { getEntity().hasQualifiedName(pkg, name) }
/**
* Holds if the method set of this type contains a method named `m` of type `t`.
*/
predicate hasMethod(string m, SignatureType t) { t = getMethod(m).getType() }
/**
* Gets the method `m` belonging to the method set of this type, if any.
*
* Note that this predicate never has a result for struct types. Methods are associated
* with the corresponding named type instead.
*/
Method getMethod(string m) {
result.getReceiverType() = this and
result.getName() = m
}
/**
* Gets the field `f` of this type.
*
* This includes fields promoted from an embedded field.
*/
Field getField(string f) { result = getUnderlyingType().getField(f) }
/**
* Holds if this type implements interface `i`, that is, the method set of `i`
* is contained in the method set of this type.
*/
predicate implements(InterfaceType i) {
isEmptyInterface(i)
or
this.hasMethod(getExampleMethodName(i), _) and
forall(string m, SignatureType t | i.hasMethod(m, t) | this.hasMethod(m, t))
}
/**
* Holds if this type implements an interface that has the qualified name `pkg.name`,
* that is, the method set of `pkg.name` is contained in the method set of this type.
*/
predicate implements(string pkg, string name) {
exists(Type t | t.hasQualifiedName(pkg, name) | this.implements(t.getUnderlyingType()))
}
/**
* Gets the pointer type that has this type as its base type.
*/
PointerType getPointerType() { result.getBaseType() = this }
/**
* Gets a pretty-printed representation of this type, including its structure where applicable.
*/
string pp() { result = toString() }
/**
* Gets a basic textual representation of this type.
*/
string toString() { result = getName() }
}
/** An invalid type. */
class InvalidType extends @invalidtype, Type {
override string toString() { result = "invalid type" }
}
/** A basic type. */
class BasicType extends @basictype, Type { }
/** Either the normal or literal boolean type */
class BoolType extends @booltype, BasicType { }
/** The `bool` type of a non-literal expression */
class BoolExprType extends @boolexprtype, BoolType {
override string getName() { result = "bool" }
}
/** A numeric type such as `int` or `float64`. */
class NumericType extends @numerictype, BasicType {
/**
* Gets the implementation-independent size (in bits) of this numeric type.
*
* This predicate is not defined for types with an implementation-specific size, that is,
* `uint`, `int` or `uintptr`.
*/
int getSize() { none() }
/**
* Gets a possible implementation-specific size (in bits) of this numeric type.
*
* This predicate is not defined for `uintptr` since the language specification says nothing
* about its size.
*/
int getASize() { result = getSize() }
}
/** An integer type such as `int` or `uint64`. */
class IntegerType extends @integertype, NumericType { }
/** A signed integer type such as `int`. */
class SignedIntegerType extends @signedintegertype, IntegerType { }
/** The type `int`. */
class IntType extends @inttype, SignedIntegerType {
override int getASize() { result = 32 or result = 64 }
override string getName() { result = "int" }
}
/** The type `int8`. */
class Int8Type extends @int8type, SignedIntegerType {
override int getSize() { result = 8 }
override string getName() { result = "int8" }
}
/** The type `int16`. */
class Int16Type extends @int16type, SignedIntegerType {
override int getSize() { result = 16 }
override string getName() { result = "int16" }
}
/** The type `int32`. */
class Int32Type extends @int32type, SignedIntegerType {
override int getSize() { result = 32 }
override string getName() { result = "int32" }
}
/** The type `int64`. */
class Int64Type extends @int64type, SignedIntegerType {
override int getSize() { result = 64 }
override string getName() { result = "int64" }
}
/** An unsigned integer type such as `uint`. */
class UnsignedIntegerType extends @unsignedintegertype, IntegerType { }
/** The type `uint`. */
class UintType extends @uinttype, UnsignedIntegerType {
override int getASize() { result = 32 or result = 64 }
override string getName() { result = "uint" }
}
/** The type `uint8`. */
class Uint8Type extends @uint8type, UnsignedIntegerType {
override int getSize() { result = 8 }
override string getName() { result = "uint8" }
}
/** The type `uint16`. */
class Uint16Type extends @uint16type, UnsignedIntegerType {
override int getSize() { result = 16 }
override string getName() { result = "uint16" }
}
/** The type `uint32`. */
class Uint32Type extends @uint32type, UnsignedIntegerType {
override int getSize() { result = 32 }
override string getName() { result = "uint32" }
}
/** The type `uint64`. */
class Uint64Type extends @uint64type, UnsignedIntegerType {
override int getSize() { result = 64 }
override string getName() { result = "uint64" }
}
/** The type `uintptr`. */
class UintptrType extends @uintptrtype, BasicType {
override string getName() { result = "uintptr" }
}
/** A floating-point type such as `float64`. */
class FloatType extends @floattype, NumericType { }
/** The type `float32`. */
class Float32Type extends @float32type, FloatType {
override int getSize() { result = 32 }
override string getName() { result = "float32" }
}
/** The type `float64`. */
class Float64Type extends @float64type, FloatType {
override int getSize() { result = 64 }
override string getName() { result = "float64" }
}
/** A complex-number type such as `complex64`. */
class ComplexType extends @complextype, NumericType { }
/** The type `complex64`. */
class Complex64Type extends @complex64type, ComplexType {
override int getSize() { result = 64 }
override string getName() { result = "complex64" }
}
/** The type `complex128`. */
class Complex128Type extends @complex128type, ComplexType {
override int getSize() { result = 128 }
override string getName() { result = "complex128" }
}
/** Either the normal or literal string type */
class StringType extends @stringtype, BasicType { }
/** The `string` type of a non-literal expression */
class StringExprType extends @stringexprtype, StringType {
override string getName() { result = "string" }
}
/** The type `unsafe.Pointer`. */
class UnsafePointerType extends @unsafepointertype, BasicType {
override string getName() { result = "unsafe.Pointer" }
}
/** The type of a literal. */
class LiteralType extends @literaltype, BasicType { }
/** The type of a bool literal. */
class BoolLiteralType extends @boolliteraltype, LiteralType, BoolType {
override string toString() { result = "bool literal" }
}
/** The type of an integer literal. */
class IntLiteralType extends @intliteraltype, LiteralType, SignedIntegerType {
override string toString() { result = "int literal" }
}
/** The type of a rune literal. */
class RuneLiteralType extends @runeliteraltype, LiteralType, SignedIntegerType {
override string toString() { result = "rune literal" }
}
/** The type of a float literal. */
class FloatLiteralType extends @floatliteraltype, LiteralType, FloatType {
override string toString() { result = "float literal" }
}
/** The type of a complex literal. */
class ComplexLiteralType extends @complexliteraltype, LiteralType, ComplexType {
override string toString() { result = "complex literal" }
}
/** The type of a string literal. */
class StringLiteralType extends @stringliteraltype, LiteralType, StringType {
override string toString() { result = "string literal" }
}
/** The type of `nil`. */
class NilLiteralType extends @nilliteraltype, LiteralType {
override string toString() { result = "nil literal" }
}
/** A composite type, that is, not a basic type. */
class CompositeType extends @compositetype, Type { }
/** An array type. */
class ArrayType extends @arraytype, CompositeType {
/** Gets the element type of this array type. */
Type getElementType() { element_type(this, result) }
/** Gets the length of this array type as a string. */
string getLengthString() { array_length(this, result) }
/** Gets the length of this array type if it can be represented as a QL integer. */
int getLength() { result = getLengthString().toInt() }
override Package getPackage() { result = this.getElementType().getPackage() }
override string pp() { result = "[" + getLength() + "]" + getElementType().pp() }
override string toString() { result = "array type" }
}
/** A slice type. */
class SliceType extends @slicetype, CompositeType {
/** Gets the element type of this slice type. */
Type getElementType() { element_type(this, result) }
override Package getPackage() { result = this.getElementType().getPackage() }
override string pp() { result = "[]" + getElementType().pp() }
override string toString() { result = "slice type" }
}
/** A byte slice type */
class ByteSliceType extends SliceType {
ByteSliceType() { this.getElementType() instanceof Uint8Type }
}
/** A struct type. */
class StructType extends @structtype, CompositeType {
/**
* Holds if this struct contains a field `name` with type `tp`;
* `isEmbedded` is true if the field is embedded.
*
* Note that this predicate does not take promoted fields into account.
*/
predicate hasOwnField(int i, string name, Type tp, boolean isEmbedded) {
exists(string n | component_types(this, i, n, tp) |
if n = ""
then (
isEmbedded = true and
(
name = tp.(NamedType).getName()
or
name = tp.(PointerType).getBaseType().(NamedType).getName()
)
) else (
isEmbedded = false and
name = n
)
)
}
/**
* Get a field with the name `name`; `isEmbedded` is true if the field is embedded.
*
* Note that this does not take promoted fields into account.
*/
Field getOwnField(string name, boolean isEmbedded) {
result.getDeclaringType() = this and
result.getName() = name and
this.hasOwnField(_, name, _, isEmbedded)
}
/**
* Holds if there is an embedded field at `depth`, with either type `tp` or a pointer to `tp`.
*/
private predicate hasEmbeddedField(Type tp, int depth) {
exists(Field f | this.hasFieldCand(_, f, depth, true) |
tp = f.getType() or
tp = f.getType().(PointerType).getBaseType()
)
}
/**
* Gets a field of `embeddedParent`, which is then embedded into this struct type.
*/
Field getFieldOfEmbedded(Field embeddedParent, string name, int depth, boolean isEmbedded) {
// embeddedParent is a field of 'this' at depth 'depth - 1'
this.hasFieldCand(_, embeddedParent, depth - 1, true) and
// embeddedParent's type has the result field
exists(StructType embeddedType, Type fieldType |
fieldType = embeddedParent.getType().getUnderlyingType() and
pragma[only_bind_into](embeddedType) =
[fieldType, fieldType.(PointerType).getBaseType().getUnderlyingType()]
|
result = embeddedType.getOwnField(name, isEmbedded)
)
}
/**
* Gets a method of `embeddedParent`, which is then embedded into this struct type.
*/
Method getMethodOfEmbedded(Field embeddedParent, string name, int depth) {
// embeddedParent is a field of 'this' at depth 'depth - 1'
this.hasFieldCand(_, embeddedParent, depth - 1, true) and
result.getName() = name and
(
result.getReceiverBaseType() = embeddedParent.getType()
or
result.getReceiverBaseType() = embeddedParent.getType().(PointerType).getBaseType()
or
methodhosts(result, embeddedParent.getType())
)
}
private predicate hasFieldCand(string name, Field f, int depth, boolean isEmbedded) {
f = this.getOwnField(name, isEmbedded) and depth = 0
or
not this.hasOwnField(_, name, _, _) and
f = this.getFieldOfEmbedded(_, name, depth, isEmbedded)
}
private predicate hasMethodCand(string name, Method m, int depth) {
name = m.getName() and
exists(Type embedded | this.hasEmbeddedField(embedded, depth - 1) |
m.getReceiverType() = embedded
)
}
/**
* Holds if this struct contains a field `name` with type `tp`, possibly inside a (nested)
* embedded field.
*/
predicate hasField(string name, Type tp) {
exists(int mindepth |
mindepth = min(int depth | this.hasFieldCand(name, _, depth, _)) and
tp = unique(Field f | f = this.getFieldCand(name, mindepth, _)).getType()
)
}
private Field getFieldCand(string name, int depth, boolean isEmbedded) {
result = this.getOwnField(name, isEmbedded) and depth = 0
or
exists(Type embedded | hasEmbeddedField(embedded, depth - 1) |
result = embedded.getUnderlyingType().(StructType).getOwnField(name, isEmbedded)
)
}
override Field getField(string name) { result = getFieldAtDepth(name, _) }
/**
* Gets the field `f` with depth `depth` of this type.
*
* This includes fields promoted from an embedded field. It is not possible
* to access a field that is shadowed by a promoted field with this function.
* The number of embedded fields traversed to reach `f` is called its depth.
* The depth of a field `f` declared in this type is zero.
*/
Field getFieldAtDepth(string name, int depth) {
depth = min(int depthCand | exists(getFieldCand(name, depthCand, _))) and
result = getFieldCand(name, depth, _) and
strictcount(getFieldCand(name, depth, _)) = 1
}
Method getMethodAtDepth(string name, int depth) {
depth = min(int depthCand | hasMethodCand(name, _, depthCand)) and
result = unique(Method m | hasMethodCand(name, m, depth))
}
override predicate hasMethod(string name, SignatureType tp) {
exists(int mindepth |
mindepth = min(int depth | this.hasMethodCand(name, _, depth)) and
tp = unique(Method m | this.hasMethodCand(name, m, mindepth)).getType()
)
}
language[monotonicAggregates]
override string pp() {
result =
"struct { " +
concat(int i, string name, Type tp |
component_types(this, i, name, tp)
|
name + " " + tp.pp(), "; " order by i
) + " }"
}
override string toString() { result = "struct type" }
}
/** A pointer type. */
class PointerType extends @pointertype, CompositeType {
/** Gets the base type of this pointer type. */
Type getBaseType() { base_type(this, result) }
override Package getPackage() { result = this.getBaseType().getPackage() }
override Method getMethod(string m) {
result = CompositeType.super.getMethod(m)
or
// https://golang.org/ref/spec#Method_sets: "the method set of a pointer type *T is
// the set of all methods declared with receiver *T or T"
result = getBaseType().getMethod(m)
or
// promoted methods from embedded types
exists(StructType s, Type embedded |
s = getBaseType().(NamedType).getUnderlyingType() and
s.hasOwnField(_, _, embedded, true) and
// ensure that `m` can be promoted
not s.hasOwnField(_, m, _, _) and
not exists(Method m2 | m2.getReceiverBaseType() = getBaseType() and m2.getName() = m)
|
result = embedded.getMethod(m)
or
// If S contains an embedded field T, the method set of *S includes promoted methods with receiver T or T*
not embedded instanceof PointerType and
result = embedded.getPointerType().getMethod(m)
or
// If S contains an embedded field *T, the method set of *S includes promoted methods with receiver T or *T
result = embedded.(PointerType).getBaseType().getMethod(m)
)
}
override string pp() { result = "* " + getBaseType().pp() }
override string toString() { result = "pointer type" }
}
/** An interface type. */
class InterfaceType extends @interfacetype, CompositeType {
/** Gets the type of method `name` of this interface type. */
Type getMethodType(string name) { component_types(this, _, name, result) }
override predicate hasMethod(string m, SignatureType t) { t = getMethodType(m) }
language[monotonicAggregates]
override string pp() {
result =
"interface { " +
concat(string name, Type tp |
tp = getMethodType(name)
|
name + " " + tp.pp(), "; " order by name
) + " }"
}
override string toString() { result = "interface type" }
}
/** A tuple type. */
class TupleType extends @tupletype, CompositeType {
/** Gets the `i`th component type of this tuple type. */
Type getComponentType(int i) { component_types(this, i, _, result) }
language[monotonicAggregates]
override string pp() {
result =
"(" + concat(int i, Type tp | tp = getComponentType(i) | tp.pp(), ", " order by i) + ")"
}
override string toString() { result = "tuple type" }
}
/** A signature type. */
class SignatureType extends @signaturetype, CompositeType {
/** Gets the `i`th parameter type of this signature type. */
Type getParameterType(int i) { i >= 0 and component_types(this, i + 1, _, result) }
/** Gets the `i`th result type of this signature type. */
Type getResultType(int i) { i >= 0 and component_types(this, -(i + 1), _, result) }
/** Gets the number of parameters specified by this signature. */
int getNumParameter() { result = count(int i | exists(getParameterType(i))) }
/** Gets the number of results specified by this signature. */
int getNumResult() { result = count(int i | exists(getResultType(i))) }
language[monotonicAggregates]
override string pp() {
result =
"func(" + concat(int i, Type tp | tp = getParameterType(i) | tp.pp(), ", " order by i) + ") " +
concat(int i, Type tp | tp = getResultType(i) | tp.pp(), ", " order by i)
}
override string toString() { result = "signature type" }
}
/** A map type. */
class MapType extends @maptype, CompositeType {
/** Gets the key type of this map type. */
Type getKeyType() { key_type(this, result) }
/** Gets the value type of this map type. */
Type getValueType() { element_type(this, result) }
override string pp() { result = "[" + getKeyType().pp() + "]" + getValueType().pp() }
override string toString() { result = "map type" }
}
/** A channel type. */
class ChanType extends @chantype, CompositeType {
/** Gets the element type of this channel type. */
Type getElementType() { element_type(this, result) }
/** Holds if this channel can send data. */
predicate canSend() { none() }
/** Holds if this channel can receive data. */
predicate canReceive() { none() }
}
/** A channel type that can only send. */
class SendChanType extends @sendchantype, ChanType {
override predicate canSend() { any() }
override string pp() { result = "chan<- " + getElementType().pp() }
override string toString() { result = "send-channel type" }
}
/** A channel type that can only receive. */
class RecvChanType extends @recvchantype, ChanType {
override predicate canReceive() { any() }
override string pp() { result = "<-chan " + getElementType().pp() }
override string toString() { result = "receive-channel type" }
}
/** A channel type that can both send and receive. */
class SendRecvChanType extends @sendrcvchantype, ChanType {
override predicate canSend() { any() }
override predicate canReceive() { any() }
override string pp() { result = "chan " + getElementType().pp() }
override string toString() { result = "send-receive-channel type" }
}
/** A named type. */
class NamedType extends @namedtype, CompositeType {
/** Gets the type which this type is defined to be. */
Type getBaseType() { underlying_type(this, result) }
override Method getMethod(string m) {
result = CompositeType.super.getMethod(m)
or
methodhosts(result, this) and
result.getName() = m
or
// handle promoted methods
exists(StructType s, Type embedded |
s = getBaseType() and
s.hasOwnField(_, _, embedded, true) and
// ensure `m` can be promoted
not s.hasOwnField(_, m, _, _) and
not exists(Method m2 | m2.getReceiverType() = this and m2.getName() = m)
|
// If S contains an embedded field T, the method set of S includes promoted methods with receiver T
result = embedded.getMethod(m)
or
// If S contains an embedded field *T, the method set of S includes promoted methods with receiver T or *T
result = embedded.(PointerType).getBaseType().getMethod(m)
)
}
override Type getUnderlyingType() { result = getBaseType().getUnderlyingType() }
}
/**
* A type that implements the builtin interface `error`.
*/
class ErrorType extends Type {
ErrorType() { this.implements(Builtin::error().getType().getUnderlyingType()) }
}
/**
* Holds if `i` is the empty interface type, which is implemented by every type with a method set.
*/
pragma[noinline]
private predicate isEmptyInterface(InterfaceType i) { not i.hasMethod(_, _) }
/**
* Gets the name of a method in the method set of `i`.
*
* This is used to restrict the set of interfaces to consider in the definition of `implements`,
* so it does not matter which method name is chosen (we use the lexicographically least).
*/
private string getExampleMethodName(InterfaceType i) { result = min(string m | i.hasMethod(m, _)) }

View File

@@ -0,0 +1,18 @@
/** This module provides general utility classes and predicates. */
/**
* A Boolean value.
*
* This is a self-binding convenience wrapper for `boolean`.
*/
class Boolean extends boolean {
Boolean() { this = true or this = false }
}
/**
* Gets a regexp pattern that matches common top-level domain names.
*/
string commonTLD() {
// according to ranking by http://google.com/search?q=site:.<<TLD>>
result = "(?:com|org|edu|gov|uk|net|io)(?![a-z0-9])"
}

View File

@@ -0,0 +1,198 @@
/** Provides the `VariableWithFields` class, for working with variables with a chain of field or element accesses chained to it. */
import go
private newtype TVariableWithFields =
TVariableRoot(Variable v) or
TVariableFieldStep(VariableWithFields base, Field f) {
exists(fieldAccessPathAux(base, f)) or exists(fieldWriteAccessPathAux(base, f))
} or
TVariableElementStep(VariableWithFields base, string e) {
exists(elementAccessPathAux(base, e)) or exists(elementWriteAccessPathAux(base, e))
}
/**
* Gets a representation of the write target `wt` as a variable with fields value if there is one.
*/
private TVariableWithFields writeAccessPath(IR::WriteTarget wt) {
exists(Variable v | wt = v.getAWrite().getLhs() | result = TVariableRoot(v))
or
exists(VariableWithFields base, Field f | wt = fieldWriteAccessPathAux(base, f) |
result = TVariableFieldStep(base, f)
)
or
exists(VariableWithFields base, string e | wt = elementWriteAccessPathAux(base, e) |
result = TVariableElementStep(base, e)
)
}
/**
* Gets a representation of `insn` as a variable with fields value if there is one.
*/
private TVariableWithFields accessPath(IR::Instruction insn) {
exists(Variable v | insn = v.getARead().asInstruction() | result = TVariableRoot(v))
or
exists(VariableWithFields base, Field f | insn = fieldAccessPathAux(base, f) |
result = TVariableFieldStep(base, f)
)
or
exists(VariableWithFields base, string e | insn = elementAccessPathAux(base, e) |
result = TVariableElementStep(base, e)
)
}
/**
* Gets an IR instruction that reads a field `f` from a node that is represented
* by variable with fields value `base`.
*/
private IR::Instruction fieldAccessPathAux(TVariableWithFields base, Field f) {
exists(IR::FieldReadInstruction fr, IR::Instruction frb |
fr.getBase() = frb or
fr.getBase() = IR::implicitDerefInstruction(frb.(IR::EvalInstruction).getExpr())
|
base = accessPath(frb) and
f = fr.getField() and
result = fr
)
}
/**
* Gets an IR write target that represents a field `f` from a node that is represented
* by variable with fields value `base`.
*/
private IR::WriteTarget fieldWriteAccessPathAux(TVariableWithFields base, Field f) {
exists(IR::FieldTarget ft, IR::Instruction ftb |
ft.getBase() = ftb or
ft.getBase() = IR::implicitDerefInstruction(ftb.(IR::EvalInstruction).getExpr())
|
base = accessPath(ftb) and
ft.getField() = f and
result = ft
)
}
/**
* Gets an IR instruction that reads an element `e` from a node that is represented
* by variable with fields value `base`.
*/
private IR::Instruction elementAccessPathAux(TVariableWithFields base, string e) {
exists(IR::ElementReadInstruction er, IR::EvalInstruction erb |
er.getBase() = erb or
er.getBase() = IR::implicitDerefInstruction(erb.getExpr())
|
base = accessPath(erb) and
e = er.getIndex().getExactValue() and
result = er
)
}
/**
* Gets an IR write target that represents an element `e` from a node that is represented
* by variable with fields value `base`.
*/
private IR::WriteTarget elementWriteAccessPathAux(TVariableWithFields base, string e) {
exists(IR::ElementTarget et, IR::EvalInstruction etb |
et.getBase() = etb or
et.getBase() = IR::implicitDerefInstruction(etb.getExpr())
|
base = accessPath(etb) and
e = et.getIndex().getExactValue() and
result = et
)
}
/** A variable with zero or more fields or elements read from it. */
class VariableWithFields extends TVariableWithFields {
/**
* Gets the variable corresponding to the base of this variable with fields.
*
* For example, the variable corresponding to `a` for the variable with fields
* corresponding to `a.b[c]`.
*/
Variable getBaseVariable() { this.getParent*() = TVariableRoot(result) }
/**
* Gets the variable with fields corresponding to the parent of this variable with fields.
*
* For example, the variable with fields corresponding to `a.b` for the variable with fields
* corresponding to `a.b[c]`.
*/
VariableWithFields getParent() {
exists(VariableWithFields base |
this = TVariableFieldStep(base, _) or this = TVariableElementStep(base, _)
|
result = base
)
}
/** Gets a use that refers to this variable with fields. */
DataFlow::Node getAUse() { this = accessPath(result.asInstruction()) }
/** Gets the type of this variable with fields. */
Type getType() {
exists(IR::Instruction acc | this = accessPath(acc) | result = acc.getResultType())
}
/** Gets a textual representation of this element. */
string toString() {
exists(Variable var | this = TVariableRoot(var) | result = "(" + var + ")")
or
exists(VariableWithFields base, Field f | this = TVariableFieldStep(base, f) |
result = base + "." + f.getName()
)
or
exists(VariableWithFields base, string e | this = TVariableElementStep(base, e) |
result = base + "[" + e + "]"
)
}
/**
* Gets the qualified name of the source variable or variable and fields that this represents.
*
* For example, for the variable with fields that represents the field `a.b[c]`, this would get the string
* `"a.b.c"`.
*/
string getQualifiedName() {
exists(Variable v | this = TVariableRoot(v) | result = v.getName())
or
exists(VariableWithFields base, Field f | this = TVariableFieldStep(base, f) |
result = base.getQualifiedName() + "." + f.getName()
)
or
exists(VariableWithFields base, string e | this = TVariableElementStep(base, e) |
result = base.getQualifiedName() + "." + e.replaceAll(".", "\\.")
)
}
/**
* Gets a write of this variable with fields.
*/
Write getAWrite() { this = writeAccessPath(result.getLhs()) }
/**
* Gets the field that is the last step of this variable with fields, if any.
*
* For example, the field `c` for the variable with fields `a.b.c`.
*/
Field getField() { this = TVariableFieldStep(_, result) }
/**
* Gets the element that this variable with fields reads, if any.
*
* For example, the string value of `c` for the variable with fields `a.b[c]`.
*/
string getElement() { this = TVariableElementStep(_, result) }
/**
* 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.getBaseVariable().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
}

View File

@@ -0,0 +1,50 @@
/** Provides a class for generated files. */
import go
/** Provides a class for generated files. */
module GeneratedFile {
/**
* A file that has been generated.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `GeneratedFile` instead.
*/
abstract class Range extends File { }
private string generatorCommentRegex() {
result = "Generated By\\b.*\\bDo not edit" or
result =
"This (file|class|interface|art[ei]fact) (was|is|(has been)) (?:auto[ -]?)?gener(e?)ated" or
result = "Any modifications to this file will be lost" or
result =
"This (file|class|interface|art[ei]fact) (was|is) (?:mechanically|automatically) generated" or
result = "The following code was (?:auto[ -]?)?generated (?:by|from)" or
result = "Autogenerated by Thrift" or
result = "(Code g|G)enerated from .* by ANTLR"
}
private class CommentHeuristicGeneratedFile extends Range {
CommentHeuristicGeneratedFile() {
exists(Comment c | c.getFile() = this |
c.getText().regexpMatch("(?i).*\\b(" + concat(generatorCommentRegex(), "|") + ")\\b.*")
or
// regular expression recommended for Go code generators
// (https://golang.org/pkg/cmd/go/internal/generate/)
c.getText().regexpMatch("^\\s*Code generated .* DO NOT EDIT\\.\\s*$")
)
}
}
}
/**
* A file that has been generated.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `GeneratedFile::Range` instead.
*/
class GeneratedFile extends File {
GeneratedFile::Range self;
GeneratedFile() { this = self }
}

View File

@@ -0,0 +1,380 @@
/**
* Provides classes for working with HTTP-related concepts such as requests and responses.
*/
import go
/** Provides classes for modeling HTTP-related APIs. */
module HTTP {
/** Provides a class for modeling new HTTP response-writer APIs. */
module ResponseWriter {
/**
* A variable that is an HTTP response writer.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `HTTP::ResponseWriter` instead.
*/
abstract class Range extends Variable {
/**
* Gets a data-flow node that is a use of this response writer.
*
* Note that `PostUpdateNode`s for nodes that this predicate gets do not need to be
* included, as they are handled by the concrete `ResponseWriter`'s `getANode`.
*/
abstract DataFlow::Node getANode();
}
}
/**
* A variable that is an HTTP response writer.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `HTTP::ResponseWriter::Range` instead.
*/
class ResponseWriter extends Variable {
ResponseWriter::Range self;
ResponseWriter() { this = self }
/** Gets the body that is written in this HTTP response. */
ResponseBody getBody() { result.getResponseWriter() = this }
/** Gets a header write that is written in this HTTP response. */
HeaderWrite getAHeaderWrite() { result.getResponseWriter() = this }
/** Gets a redirect that is sent in this HTTP response. */
Redirect getARedirect() { result.getResponseWriter() = this }
/** Gets a data-flow node that is a use of this response writer. */
DataFlow::Node getANode() {
result = self.getANode() or
result.(DataFlow::PostUpdateNode).getPreUpdateNode() = self.getANode()
}
}
/** Provides a class for modeling new HTTP header-write APIs. */
module HeaderWrite {
/**
* A data-flow node that represents a write to an HTTP header.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `HTTP::HeaderWrite` instead.
*/
abstract class Range extends DataFlow::ExprNode {
/** Gets the (lower-case) name of a header set by this definition. */
string getHeaderName() { result = this.getName().getStringValue().toLowerCase() }
/** Gets the value of the header set by this definition. */
string getHeaderValue() {
result = this.getValue().getStringValue()
or
result = this.getValue().getIntValue().toString()
}
/** Holds if this header write defines the header `header`. */
predicate definesHeader(string header, string value) {
header = this.getHeaderName() and
value = this.getHeaderValue()
}
/**
* Gets the node representing the name of the header defined by this write.
*
* Note that a `HeaderWrite` targeting a constant header (e.g. a routine that always
* sets the `Content-Type` header) may not have such a node, so callers should use
* `getHeaderName` in preference to this method).
*/
abstract DataFlow::Node getName();
/** Gets the node representing the value of the header defined by this write. */
abstract DataFlow::Node getValue();
/** Gets the response writer associated with this header write, if any. */
abstract ResponseWriter getResponseWriter();
}
}
/**
* A data-flow node that represents a write to an HTTP header.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `HTTP::HeaderWrite::Range` instead.
*/
class HeaderWrite extends DataFlow::ExprNode {
HeaderWrite::Range self;
HeaderWrite() { this = self }
/** Gets the (lower-case) name of a header set by this definition. */
string getHeaderName() { result = self.getHeaderName() }
/** Gets the value of the header set by this definition. */
string getHeaderValue() { result = self.getHeaderValue() }
/** Holds if this header write defines the header `header`. */
predicate definesHeader(string header, string value) { self.definesHeader(header, value) }
/**
* Gets the node representing the name of the header defined by this write.
*
* Note that a `HeaderWrite` targeting a constant header (e.g. a routine that always
* sets the `Content-Type` header) may not have such a node, so callers should use
* `getHeaderName` in preference to this method).
*/
DataFlow::Node getName() { result = self.getName() }
/** Gets the node representing the value of the header defined by this write. */
DataFlow::Node getValue() { result = self.getValue() }
/** Gets the response writer associated with this header write, if any. */
ResponseWriter getResponseWriter() { result = self.getResponseWriter() }
}
/** A data-flow node whose value is written to an HTTP header. */
class Header extends DataFlow::Node {
HeaderWrite hw;
Header() {
this = hw.getName()
or
this = hw.getValue()
}
/** Gets the response writer associated with this header write, if any. */
ResponseWriter getResponseWriter() { result = hw.getResponseWriter() }
}
/** A data-flow node whose value is written to the value of an HTTP header. */
class HeaderValue extends Header {
HeaderValue() { this = hw.getValue() }
}
/** A data-flow node whose value is written to the name of an HTTP header. */
class HeaderName extends Header {
HeaderName() { this = hw.getName() }
}
/** Provides a class for modeling new HTTP request-body APIs. */
module RequestBody {
/**
* An expression representing a reader whose content is written to an HTTP request body.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `HTTP::RequestBody` instead.
*/
abstract class Range extends DataFlow::Node { }
}
/**
* An expression representing a reader whose content is written to an HTTP request body.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `HTTP::RequestBody::Range` instead.
*/
class RequestBody extends DataFlow::Node {
RequestBody::Range self;
RequestBody() { this = self }
}
/** Provides a class for modeling new HTTP response-body APIs. */
module ResponseBody {
/**
* An expression which is written to an HTTP response body.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `HTTP::ResponseBody` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets the response writer associated with this header write, if any. */
abstract ResponseWriter getResponseWriter();
/** Gets a content-type associated with this body. */
string getAContentType() {
exists(HTTP::HeaderWrite hw | hw = getResponseWriter().getAHeaderWrite() |
hw.getHeaderName() = "content-type" and
result = hw.getHeaderValue()
)
or
result = getAContentTypeNode().getStringValue()
}
/** Gets a dataflow node for a content-type associated with this body. */
DataFlow::Node getAContentTypeNode() {
exists(HTTP::HeaderWrite hw | hw = getResponseWriter().getAHeaderWrite() |
hw.getHeaderName() = "content-type" and
result = hw.getValue()
)
}
}
}
/**
* An expression which is written to an HTTP response body.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `HTTP::ResponseBody::Range` instead.
*/
class ResponseBody extends DataFlow::Node {
ResponseBody::Range self;
ResponseBody() { this = self }
/** Gets the response writer associated with this header write, if any. */
ResponseWriter getResponseWriter() { result = self.getResponseWriter() }
/** Gets a content-type associated with this body. */
string getAContentType() { result = self.getAContentType() }
/** Gets a dataflow node for a content-type associated with this body. */
DataFlow::Node getAContentTypeNode() { result = self.getAContentTypeNode() }
}
/** Provides a class for modeling new HTTP template response-body APIs. */
module TemplateResponseBody {
/**
* An expression which is written to an HTTP response body via a template execution.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `HTTP::ResponseBody` instead.
*/
abstract class Range extends ResponseBody::Range {
/** Gets the read of the variable inside the template where this value is read. */
abstract HtmlTemplate::TemplateRead getRead();
}
}
/**
* An expression which is written to an HTTP response body via a template execution.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `HTTP::TemplateResponseBody::Range` instead.
*/
class TemplateResponseBody extends ResponseBody {
override TemplateResponseBody::Range self;
TemplateResponseBody() { this = self }
/** Gets the read of the variable inside the template where this value is read. */
HtmlTemplate::TemplateRead getRead() { result = self.getRead() }
}
/** Provides a class for modeling new HTTP client request APIs. */
module ClientRequest {
/**
* A call that performs a request to a URL.
*
* Example: An HTTP POST request is a client request that sends some
* `data` to a `url`, where both the headers and the body of the request
* contribute to the `data`.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `HTTP::ClientRequest` instead.
*/
abstract class Range extends DataFlow::Node {
/**
* Gets the URL of the request.
*/
abstract DataFlow::Node getUrl();
}
}
/**
* A call that performs a request to a URL.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `HTTP::ClientRequest::Range` instead.
*/
class ClientRequest extends DataFlow::Node {
ClientRequest::Range self;
ClientRequest() { this = self }
/**
* Gets the URL of the request.
*/
DataFlow::Node getUrl() { result = self.getUrl() }
}
/** Provides a class for modeling new HTTP redirect APIs. */
module Redirect {
/**
* An HTTP redirect.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `HTTP::Redirect` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets the data-flow node representing the URL being redirected to. */
abstract DataFlow::Node getUrl();
/** Gets the response writer that this redirect is sent on, if any. */
abstract ResponseWriter getResponseWriter();
}
/**
* An assignment of the HTTP Location header, which indicates the location for a
* redirect.
*/
private class LocationHeaderSet extends Range, HeaderWrite {
LocationHeaderSet() { this.getHeaderName() = "location" }
override DataFlow::Node getUrl() { result = this.getValue() }
override ResponseWriter getResponseWriter() { result = HeaderWrite.super.getResponseWriter() }
}
/**
* An HTTP request attribute that is generally not attacker-controllable for
* open redirect exploits; for example, a form field submitted in a POST request.
*/
abstract class UnexploitableSource extends DataFlow::Node { }
}
/**
* An HTTP redirect.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `HTTP::Redirect::Range` instead.
*/
class Redirect extends DataFlow::Node {
Redirect::Range self;
Redirect() { this = self }
/** Gets the data-flow node representing the URL being redirected to. */
DataFlow::Node getUrl() { result = self.getUrl() }
/** Gets the response writer that this redirect is sent on, if any. */
ResponseWriter getResponseWriter() { result = self.getResponseWriter() }
}
/** Provides a class for modeling new HTTP handler APIs. */
module RequestHandler {
/**
* An HTTP request handler.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `HTTP::RequestHandler` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets a node that is used in a check that is tested before this handler is run. */
abstract predicate guardedBy(DataFlow::Node check);
}
}
/**
* An HTTP request handler.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `HTTP::RequestHandler::Range` instead.
*/
class RequestHandler extends DataFlow::Node {
RequestHandler::Range self;
RequestHandler() { this = self }
/** Gets a node that is used in a check that is tested before this handler is run. */
predicate guardedBy(DataFlow::Node check) { self.guardedBy(check) }
}
}

View File

@@ -0,0 +1,200 @@
/**
* Provides classes for working with basic blocks.
*/
import go
private import ControlFlowGraphImpl
/**
* Holds if `nd` starts a new basic block.
*/
private predicate startsBB(ControlFlow::Node nd) {
count(nd.getAPredecessor()) != 1
or
nd.getAPredecessor().isBranch()
}
/**
* Holds if the first node of basic block `succ` is a control flow
* successor of the last node of basic block `bb`.
*/
private predicate succBB(BasicBlock bb, BasicBlock succ) { succ = bb.getLastNode().getASuccessor() }
/**
* Holds if the first node of basic block `bb` is a control flow
* successor of the last node of basic block `pre`.
*/
private predicate predBB(BasicBlock bb, BasicBlock pre) { succBB(pre, bb) }
/** Holds if `bb` is an entry basic block. */
private predicate entryBB(BasicBlock bb) { bb.getFirstNode().isEntryNode() }
/** Holds if `bb` is an exit basic block. */
private predicate exitBB(BasicBlock bb) { bb.getLastNode().isExitNode() }
cached
private module Internal {
/**
* Holds if `succ` is a control flow successor of `nd` within the same basic block.
*/
private predicate intraBBSucc(ControlFlow::Node nd, ControlFlow::Node succ) {
succ = nd.getASuccessor() and
not startsBB(succ)
}
/**
* Holds if `nd` 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 `nd` along the `intraBBSucc` relation.
*/
cached
predicate bbIndex(BasicBlock bb, ControlFlow::Node nd, int i) =
shortestDistances(startsBB/1, intraBBSucc/2)(bb, nd, i)
cached
int bbLength(BasicBlock bb) { result = strictcount(ControlFlow::Node nd | bbIndex(bb, nd, _)) }
cached
predicate reachableBB(BasicBlock bb) {
entryBB(bb)
or
exists(BasicBlock predBB | succBB(predBB, bb) | reachableBB(predBB))
}
}
private import Internal
/** Holds if `dom` is an immediate dominator of `bb`. */
cached
private predicate bbIDominates(BasicBlock dom, BasicBlock bb) =
idominance(entryBB/1, succBB/2)(_, dom, bb)
/** Holds if `dom` is an immediate post-dominator of `bb`. */
cached
private predicate bbIPostDominates(BasicBlock dom, BasicBlock bb) =
idominance(exitBB/1, predBB/2)(_, dom, bb)
/**
* A basic block, that is, a maximal straight-line sequence of control flow nodes
* without branches or joins.
*
* At the database level, a basic block is represented by its first control flow node.
*/
class BasicBlock extends TControlFlowNode {
BasicBlock() { startsBB(this) }
/** Gets a basic block succeeding this one. */
BasicBlock getASuccessor() { succBB(this, result) }
/** Gets a basic block preceding this one. */
BasicBlock getAPredecessor() { result.getASuccessor() = this }
/** Gets a node in this block. */
ControlFlow::Node getANode() { result = getNode(_) }
/** Gets the node at the given position in this block. */
ControlFlow::Node getNode(int pos) { bbIndex(this, result, pos) }
/** Gets the first node in this block. */
ControlFlow::Node getFirstNode() { result = this }
/** Gets the last node in this block. */
ControlFlow::Node getLastNode() { result = getNode(length() - 1) }
/** Gets the length of this block. */
int length() { result = bbLength(this) }
/** Gets the basic block that immediately dominates this basic block. */
ReachableBasicBlock getImmediateDominator() { bbIDominates(result, this) }
/** Gets the innermost function or file to which this basic block belongs. */
ControlFlow::Root getRoot() { result = getFirstNode().getRoot() }
/** Gets a textual representation of this basic block. */
string toString() { result = "basic block" }
/**
* Holds if this basic block 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
) {
getFirstNode().hasLocationInfo(filepath, startline, startcolumn, _, _) and
getLastNode().hasLocationInfo(_, _, _, endline, endcolumn)
}
}
/**
* An entry basic block, that is, a basic block whose first node is an entry node.
*/
class EntryBasicBlock extends BasicBlock {
EntryBasicBlock() { entryBB(this) }
}
/**
* A basic block that is reachable from an entry basic block.
*/
class ReachableBasicBlock extends BasicBlock {
ReachableBasicBlock() { reachableBB(this) }
/**
* Holds if this basic block strictly dominates `bb`.
*/
cached
predicate strictlyDominates(ReachableBasicBlock bb) { bbIDominates+(this, bb) }
/**
* Holds if this basic block dominates `bb`.
*
* This predicate is reflexive: each reachable basic block dominates itself.
*/
predicate dominates(ReachableBasicBlock bb) {
bb = this or
strictlyDominates(bb)
}
/**
* Holds if this basic block strictly post-dominates `bb`.
*/
cached
predicate strictlyPostDominates(ReachableBasicBlock bb) { bbIPostDominates+(this, bb) }
/**
* Holds if this basic block post-dominates `bb`.
*
* This predicate is reflexive: each reachable basic block post-dominates itself.
*/
predicate postDominates(ReachableBasicBlock bb) {
bb = this or
strictlyPostDominates(bb)
}
}
/**
* A reachable basic block with more than one predecessor.
*/
class ReachableJoinBlock extends ReachableBasicBlock {
ReachableJoinBlock() { getFirstNode().isJoin() }
/**
* Holds if this basic block belongs to the dominance frontier of `b`, that is
* `b` dominates a predecessor of this block, but not this block itself.
*
* Algorithm from Cooper et al., "A Simple, Fast Dominance Algorithm" (Figure 5),
* who in turn attribute it to Ferrante et al., "The program dependence graph and
* its use in optimization".
*/
predicate inDominanceFrontierOf(ReachableBasicBlock b) {
b = getAPredecessor() and not b = getImmediateDominator()
or
exists(ReachableBasicBlock prev | inDominanceFrontierOf(prev) |
b = prev.getImmediateDominator() and
not b = getImmediateDominator()
)
}
}

View File

@@ -0,0 +1,290 @@
/**
* Provides classes for working with a CFG-based program representation.
*/
import go
private import ControlFlowGraphImpl
/** Provides helper predicates for mapping btween CFG nodes and the AST. */
module ControlFlow {
/** A file or function with which a CFG is associated. */
class Root extends AstNode {
Root() { exists(this.(File).getADecl()) or exists(this.(FuncDef).getBody()) }
/** Holds if `nd` belongs to this file or function. */
predicate isRootOf(AstNode nd) {
this = nd.getEnclosingFunction()
or
not exists(nd.getEnclosingFunction()) and
this = nd.getFile()
}
/** Gets the synthetic entry node of the CFG for this file or function. */
EntryNode getEntryNode() { result = ControlFlow::entryNode(this) }
/** Gets the synthetic exit node of the CFG for this file or function. */
ExitNode getExitNode() { result = ControlFlow::exitNode(this) }
}
/**
* A node in the intra-procedural control-flow graph of a Go function or file.
*
* Nodes correspond to expressions and statements that compute a value or perform
* an operation (as opposed to providing syntactic structure or type information).
*
* There are also synthetic entry and exit nodes for each Go function and file
* that mark the beginning and the end, respectively, of the execution of the
* function and the loading of the file.
*/
class Node extends TControlFlowNode {
/** Gets a node that directly follows this one in the control-flow graph. */
Node getASuccessor() { result = CFG::succ(this) }
/** Gets a node that directly precedes this one in the control-flow graph. */
Node getAPredecessor() { this = result.getASuccessor() }
/** Holds if this is a node with more than one successor. */
predicate isBranch() { strictcount(getASuccessor()) > 1 }
/** Holds if this is a node with more than one predecessor. */
predicate isJoin() { strictcount(getAPredecessor()) > 1 }
/** Holds if this is the first control-flow node in `subtree`. */
predicate isFirstNodeOf(AstNode subtree) { CFG::firstNode(subtree, this) }
/** Holds if this node is the (unique) entry node of a function or file. */
predicate isEntryNode() { this instanceof MkEntryNode }
/** Holds if this node is the (unique) exit node of a function or file. */
predicate isExitNode() { this instanceof MkExitNode }
/** Gets the basic block to which this node belongs. */
BasicBlock getBasicBlock() { result.getANode() = this }
/** Holds if this node dominates `dominee` in the control-flow graph. */
pragma[inline]
predicate dominatesNode(ControlFlow::Node dominee) {
exists(ReachableBasicBlock thisbb, ReachableBasicBlock dbb, int i, int j |
this = thisbb.getNode(i) and dominee = dbb.getNode(j)
|
thisbb.strictlyDominates(dbb)
or
thisbb = dbb and i <= j
)
}
/** Gets the innermost function or file to which this node belongs. */
Root getRoot() { none() }
/** Gets the file to which this node belongs. */
File getFile() { hasLocationInfo(result.getAbsolutePath(), _, _, _, _) }
/**
* Gets a textual representation of this control flow node.
*/
string toString() { result = "control-flow node" }
/**
* 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
) {
filepath = "" and
startline = 0 and
startcolumn = 0 and
endline = 0 and
endcolumn = 0
}
}
/**
* A control-flow node that initializes or updates the value of a constant, a variable,
* a field, or an (array, slice, or map) element.
*/
class WriteNode extends Node {
IR::WriteInstruction self;
WriteNode() { this = self }
/** Gets the left-hand side of this write. */
IR::WriteTarget getLhs() { result = self.getLhs() }
/** Gets the right-hand side of this write. */
DataFlow::Node getRhs() { self.getRhs() = result.asInstruction() }
/** Holds if this node sets variable or constant `v` to `rhs`. */
predicate writes(ValueEntity v, DataFlow::Node rhs) { self.writes(v, rhs.asInstruction()) }
/** Holds if this node defines SSA variable `v` to be `rhs`. */
predicate definesSsaVariable(SsaVariable v, DataFlow::Node rhs) {
self.getLhs().asSsaVariable() = v and
self.getRhs() = rhs.asInstruction()
}
/**
* Holds if this node sets the value of field `f` on `base` (or its implicit dereference) to
* `rhs`.
*
* For example, for the assignment `x.width = newWidth`, `base` is either the data-flow node
* corresponding to `x` or (if `x` is a pointer) the data-flow node corresponding to the
* implicit dereference `*x`, `f` is the field referenced by `width`, and `rhs` is the data-flow
* node corresponding to `newWidth`.
*/
predicate writesField(DataFlow::Node base, Field f, DataFlow::Node rhs) {
exists(IR::FieldTarget trg | trg = self.getLhs() |
(
trg.getBase() = base.asInstruction() or
trg.getBase() = MkImplicitDeref(base.asExpr())
) and
trg.getField() = f and
self.getRhs() = rhs.asInstruction()
)
}
/**
* Holds if this node sets the value of element `idx` on `base` (or its implicit dereference)
* to `rhs`.
*
* For example, for the assignment `xs[i] = v`, `base` is either the data-flow node
* corresponding to `xs` or (if `xs` is a pointer) the data-flow node corresponding to the
* implicit dereference `*xs`, `index` is the data-flow node corresponding to `i`, and `rhs`
* is the data-flow node corresponding to `base`.
*/
predicate writesElement(DataFlow::Node base, DataFlow::Node index, DataFlow::Node rhs) {
exists(IR::ElementTarget trg | trg = self.getLhs() |
(
trg.getBase() = base.asInstruction() or
trg.getBase() = MkImplicitDeref(base.asExpr())
) and
trg.getIndex() = index.asInstruction() and
self.getRhs() = rhs.asInstruction()
)
}
/**
* Holds if this node sets any field or element of `base` to `rhs`.
*/
predicate writesComponent(DataFlow::Node base, DataFlow::Node rhs) {
writesElement(base, _, rhs) or writesField(base, _, rhs)
}
}
/**
* A control-flow node recording the fact that a certain expression has a known
* Boolean value at this point in the program.
*/
class ConditionGuardNode extends IR::Instruction, MkConditionGuardNode {
Expr cond;
boolean outcome;
ConditionGuardNode() { this = MkConditionGuardNode(cond, outcome) }
private predicate ensuresAux(Expr expr, boolean b) {
expr = cond and b = outcome
or
expr = any(ParenExpr par | ensuresAux(par, b)).getExpr()
or
expr = any(NotExpr ne | ensuresAux(ne, b.booleanNot())).getOperand()
or
expr = any(LandExpr land | ensuresAux(land, true)).getAnOperand() and
b = true
or
expr = any(LorExpr lor | ensuresAux(lor, false)).getAnOperand() and
b = false
}
/** Holds if this guard ensures that the result of `nd` is `b`. */
predicate ensures(DataFlow::Node nd, boolean b) {
ensuresAux(any(Expr e | nd = DataFlow::exprNode(e)), b)
}
/** Holds if this guard ensures that `lesser <= greater + bias` holds. */
predicate ensuresLeq(DataFlow::Node lesser, DataFlow::Node greater, int bias) {
exists(DataFlow::RelationalComparisonNode rel, boolean b |
ensures(rel, b) and
rel.leq(b, lesser, greater, bias)
)
or
ensuresEq(lesser, greater) and
bias = 0
}
/** Holds if this guard ensures that `i = j` holds. */
predicate ensuresEq(DataFlow::Node i, DataFlow::Node j) {
exists(DataFlow::EqualityTestNode eq, boolean b |
ensures(eq, b) and
eq.eq(b, i, j)
)
}
/** Holds if this guard ensures that `i != j` holds. */
predicate ensuresNeq(DataFlow::Node i, DataFlow::Node j) {
exists(DataFlow::EqualityTestNode eq, boolean b |
ensures(eq, b.booleanNot()) and
eq.eq(b, i, j)
)
}
/**
* Holds if this guard dominates basic block `bb`, that is, the guard
* is known to hold at `bb`.
*/
predicate dominates(ReachableBasicBlock bb) {
this = bb.getANode() or
dominates(bb.getImmediateDominator())
}
/**
* Gets the condition whose outcome the guard concerns.
*/
Expr getCondition() { result = cond }
override Root getRoot() { result.isRootOf(cond) }
override string toString() { result = cond + " is " + outcome }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
cond.hasLocationInfo(filepath, _, _, startline, startcolumn) and
endline = startline and
endcolumn = startcolumn
}
}
/**
* Gets the entry node of function or file `root`.
*/
Node entryNode(Root root) { result = MkEntryNode(root) }
/**
* Gets the exit node of function or file `root`.
*/
Node exitNode(Root root) { result = MkExitNode(root) }
/**
* Holds if the function `f` may return without panicking, exiting the process, or looping forever.
*
* This is defined conservatively, and so may also hold of a function that in fact
* cannot return normally, but never fails to hold of a function that can return normally.
*/
predicate mayReturnNormally(FuncDecl f) { CFG::mayReturnNormally(f.getBody()) }
/**
* Holds if `pred` is the node for the case `testExpr` in an expression
* switch statement which is switching on `switchExpr`, and `succ` is the
* node to be executed next if the case test succeeds.
*/
predicate isSwitchCaseTestPassingEdge(
ControlFlow::Node pred, ControlFlow::Node succ, Expr switchExpr, Expr testExpr
) {
CFG::isSwitchCaseTestPassingEdge(pred, succ, switchExpr, testExpr)
}
}
class Write = ControlFlow::WriteNode;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
/**
* Provides a library for local (intra-procedural) and global (inter-procedural)
* data flow analysis: deciding whether data can flow from a _source_ to a
* _sink_.
*
* Unless configured otherwise, _flow_ means that the exact value of
* the source may reach the sink. We do not track flow across pointer
* dereferences or array indexing. To track these types of flow, where the
* exact value may not be preserved, import
* `semmle.code.go.dataflow.TaintTracking`.
*
* To use global (interprocedural) data flow, extend the class
* `DataFlow::Configuration` as documented on that class. To use local
* (intraprocedural) data flow, invoke `DataFlow::localFlow` or
* `DataFlow::LocalFlowStep` with arguments of type `DataFlow::Node`.
*/
import go
/**
* Provides a library for local (intra-procedural) and global (inter-procedural)
* data flow analysis.
*/
module DataFlow {
import semmle.go.dataflow.internal.DataFlowImpl
import Properties
}
class Read = DataFlow::ReadNode;

View File

@@ -0,0 +1,27 @@
/**
* Provides a library for local (intra-procedural) and global (inter-procedural)
* data flow analysis: deciding whether data can flow from a _source_ to a
* _sink_.
*
* Unless configured otherwise, _flow_ means that the exact value of
* the source may reach the sink. We do not track flow across pointer
* dereferences or array indexing. To track these types of flow, where the
* exact value may not be preserved, import
* `semmle.code.go.dataflow.TaintTracking`.
*
* To use global (interprocedural) data flow, extend the class
* `DataFlow::Configuration` as documented on that class. To use local
* (intraprocedural) data flow, invoke `DataFlow::localFlow` or
* `DataFlow::LocalFlowStep` with arguments of type `DataFlow::Node`.
*/
import go
/**
* Provides a library for local (intra-procedural) and global (inter-procedural)
* data flow analysis.
*/
module DataFlow2 {
import semmle.go.dataflow.internal.DataFlowImpl2
import Properties
}

View File

@@ -0,0 +1,310 @@
/**
* Provides QL classes for indicating data flow through a function parameter, return value,
* or receiver.
*/
import go
private import semmle.go.dataflow.internal.DataFlowPrivate
/**
* An abstract representation of an input to a function, which is either a parameter
* or the receiver parameter.
*/
private newtype TFunctionInput =
TInParameter(int i) { exists(SignatureType s | exists(s.getParameterType(i))) } or
TInReceiver() or
TInResult(int index) {
// the one and only result
index = -1
or
// one among several results
exists(SignatureType s | exists(s.getResultType(index)))
}
/**
* An abstract representation of an input to a function, which is either a parameter
* or the receiver parameter.
*/
class FunctionInput extends TFunctionInput {
/** Holds if this represents the `i`th parameter of a function. */
predicate isParameter(int i) { none() }
/** Holds if this represents the receiver of a function. */
predicate isReceiver() { none() }
/** Holds if this represents the result of a function. */
predicate isResult() { none() }
/** Holds if this represents the `i`th result of a function. */
predicate isResult(int i) { none() }
/** Gets the data-flow node corresponding to this input for the call `c`. */
final DataFlow::Node getNode(DataFlow::CallNode c) { result = getEntryNode(c) }
/** Gets the data-flow node through which data is passed into this input for the call `c`. */
abstract DataFlow::Node getEntryNode(DataFlow::CallNode c);
/** Gets the data-flow node through which data from this input enters function `f`. */
abstract DataFlow::Node getExitNode(FuncDef f);
/** Gets a textual representation of this element. */
abstract string toString();
}
/** Defines convenience methods that get particular `FunctionInput` instances. */
module FunctionInput {
/** Gets a `FunctionInput` representing the `i`th parameter. */
FunctionInput parameter(int i) { result.isParameter(i) }
/** Gets a `FunctionInput` representing the receiver. */
FunctionInput receiver() { result.isReceiver() }
/** Gets a `FunctionInput` representing the result of a single-result function. */
FunctionInput functionResult() { result.isResult() }
/** Gets a `FunctionInput` representing the `i`th result. */
FunctionInput functionResult(int i) { result.isResult(i) }
}
/** A parameter position of a function, viewed as a source of input. */
private class ParameterInput extends FunctionInput, TInParameter {
int index;
ParameterInput() { this = TInParameter(index) }
override predicate isParameter(int i) { i = index }
override DataFlow::Node getEntryNode(DataFlow::CallNode c) { result = c.getArgument(index) }
override DataFlow::Node getExitNode(FuncDef f) {
result = DataFlow::parameterNode(f.getParameter(index))
}
override string toString() { result = "parameter " + index }
}
/** The receiver of a function, viewed as a source of input. */
private class ReceiverInput extends FunctionInput, TInReceiver {
override predicate isReceiver() { any() }
override DataFlow::Node getEntryNode(DataFlow::CallNode c) {
result = c.(DataFlow::MethodCallNode).getReceiver()
}
override DataFlow::Node getExitNode(FuncDef f) {
result = DataFlow::receiverNode(f.(MethodDecl).getReceiver())
}
override string toString() { result = "receiver" }
}
/**
* A result position of a function, viewed as an input.
*
* Results are usually outputs rather than inputs, but for taint tracking it can be useful to
* think of taint propagating backwards from a result of a function to its arguments. For instance,
* the function `bufio.NewWriter` returns a writer `bw` that buffers write operations to an
* underlying writer `w`. If tainted data is written to `bw`, then it makes sense to propagate
* that taint back to the underlying writer `w`, which can be modeled by saying that
* `bufio.NewWriter` propagates taint from its result to its first argument.
*/
private class ResultInput extends FunctionInput, TInResult {
int index;
ResultInput() { this = TInResult(index) }
override predicate isResult() { index = -1 }
override predicate isResult(int i) {
i = 0 and isResult()
or
i = index and i >= 0
}
override DataFlow::Node getEntryNode(DataFlow::CallNode c) {
exists(DataFlow::Node pred |
index = -1 and
pred = c.getResult()
or
index >= 0 and
pred = c.getResult(index)
|
// if the result is assigned to an SSA variable, we want to propagate mutations backwards
// through that variable
exists(DataFlow::SsaNode ssa | ssa.getInit() = pred | result = ssa)
or
// otherwise the entry node is simply the result
not exists(DataFlow::SsaNode ssa | ssa.getInit() = pred) and
result = pred
)
}
override DataFlow::Node getExitNode(FuncDef f) { none() }
override string toString() {
index = -1 and result = "result"
or
index >= 0 and result = "result " + index
}
}
/**
* An abstract representation of an output of a function, which is one of its results.
*/
private newtype TFunctionOutput =
TOutResult(int index) {
// the one and only result
index = -1
or
// one among several results
exists(SignatureType s | exists(s.getResultType(index)))
} or
TOutReceiver() or
TOutParameter(int index) { exists(SignatureType s | exists(s.getParameterType(index))) }
/**
* An abstract representation of an output of a function, which is one of its results
* or a parameter with mutable type.
*/
class FunctionOutput extends TFunctionOutput {
/** Holds if this represents the (single) result of a function. */
predicate isResult() { none() }
/** Holds if this represents the `i`th result of a function. */
predicate isResult(int i) { none() }
/** Holds if this represents the receiver of a function. */
predicate isReceiver() { none() }
/** Holds if this represents the `i`th parameter of a function. */
predicate isParameter(int i) { none() }
/** Gets the data-flow node corresponding to this output for the call `c`. */
final DataFlow::Node getNode(DataFlow::CallNode c) { result = getExitNode(c) }
/** Gets the data-flow node through which data is passed into this output for the function `f`. */
abstract DataFlow::Node getEntryNode(FuncDef f);
/** Gets the data-flow node through which data is returned from this output for the call `c`. */
abstract DataFlow::Node getExitNode(DataFlow::CallNode c);
/** Gets a textual representation of this element. */
abstract string toString();
}
/** Defines convenience methods that get particular `FunctionOutput` instances. */
module FunctionOutput {
/** Gets a `FunctionOutput` representing the result of a single-result function. */
FunctionOutput functionResult() { result.isResult() }
/** Gets a `FunctionOutput` representing the `i`th result. */
FunctionOutput functionResult(int i) { result.isResult(i) }
/** Gets a `FunctionOutput` representing the receiver after a function returns. */
FunctionOutput receiver() { result.isReceiver() }
/** Gets a `FunctionOutput` representing the `i`th parameter after a function returns. */
FunctionOutput parameter(int i) { result.isParameter(i) }
}
/** A result position of a function, viewed as an output. */
private class OutResult extends FunctionOutput, TOutResult {
int index;
OutResult() { this = TOutResult(index) }
override predicate isResult() { index = -1 }
override predicate isResult(int i) {
i = 0 and isResult()
or
i = index and i >= 0
}
override DataFlow::Node getEntryNode(FuncDef f) {
// return expressions
exists(IR::ReturnInstruction ret | f = ret.getRoot() |
index = -1 and
result = DataFlow::instructionNode(ret.getResult())
or
index >= 0 and
ret.returnsMultipleResults() and
result = DataFlow::instructionNode(ret.getResult(index))
)
or
// expressions assigned to result variables
exists(Write w, int nr | nr = f.getType().getNumResult() |
index = -1 and
nr = 1 and
w.writes(f.getResultVar(0), result)
or
index >= 0 and
nr > 1 and
w.writes(f.getResultVar(index), result)
)
}
override DataFlow::Node getExitNode(DataFlow::CallNode c) {
index = -1 and result = c.getResult()
or
result = c.getResult(index)
}
override string toString() {
index = -1 and result = "result"
or
index >= 0 and result = "result " + index
}
}
/** The receiver of a function, viewed as an output. */
private class OutReceiver extends FunctionOutput, TOutReceiver {
override predicate isReceiver() { any() }
override DataFlow::Node getEntryNode(FuncDef f) {
// there is no generic way of assigning to a receiver; operations that taint a receiver
// have to be handled on a case-by-case basis
none()
}
override DataFlow::Node getExitNode(DataFlow::CallNode c) {
exists(DataFlow::Node arg |
arg = getArgument(c, -1) and
result.(DataFlow::PostUpdateNode).getPreUpdateNode() = arg
)
}
override string toString() { result = "receiver" }
}
/**
* A parameter of a function, viewed as an output.
*
* Note that slices passed to varargs parameters using `...` are not included, since in this
* case it is ambiguous whether the output should be the slice itself or one of its elements.
*/
private class OutParameter extends FunctionOutput, TOutParameter {
int index;
OutParameter() { this = TOutParameter(index) }
override predicate isParameter(int i) { i = index }
override DataFlow::Node getEntryNode(FuncDef f) {
// there is no generic way of assigning to a parameter; operations that taint a parameter
// have to be handled on a case-by-case basis
none()
}
override DataFlow::Node getExitNode(DataFlow::CallNode c) {
exists(DataFlow::Node arg |
arg = getArgument(c, index) and
// exclude slices passed to varargs parameters using `...` calls
not (c.hasEllipsis() and index = c.getNumArgument() - 1)
|
result.(DataFlow::PostUpdateNode).getPreUpdateNode() = arg
)
}
override string toString() { result = "parameter " + index }
}

View File

@@ -0,0 +1,591 @@
/**
* Provides an implementation of Global Value Numbering.
* See https://en.wikipedia.org/wiki/Global_value_numbering
*
* The predicate `globalValueNumber` converts an expression into a `GVN`,
* which is an abstract type representing the value of the expression. If
* two expressions have the same `GVN` then they compute the same value.
* For example:
*
* ```
* func f(x int, y int) {
* g(x+y, x+y);
* }
* ```
*
* In this example, both arguments in the call to `g` compute the same value,
* so both arguments have the same `GVN`. In other words, we can find
* this call with the following query:
*
* ```
* from CallExpr call, GVN v
* where v = globalValueNumber(call.getArgument(0))
* and v = globalValueNumber(call.getArgument(1))
* select call
* ```
*
* The analysis is conservative, so two expressions might have different
* `GVN`s even though the actually always compute the same value. The most
* common reason for this is that the analysis cannot prove that there
* are no side-effects that might cause the computed value to change.
*/
/*
* Note to developers: the correctness of this module depends on the
* definitions of GVN, globalValueNumber, and analyzableExpr being kept in
* sync with each other. If you change this module then make sure that the
* change is symmetric across all three.
*/
import go
/**
* Holds if the result is a control flow node that might change the
* value of any package variable. This is used in the implementation
* of `MkOtherVariable`, because we need to be quite conservative when
* we assign a value number to a package variable. For example:
*
* ```
* x = g+1;
* dosomething();
* y = g+1;
* ```
*
* It is not safe to assign the same value number to both instances
* of `g+1` in this example, because the call to `dosomething` might
* change the value of `g`.
*/
private ControlFlow::Node nodeWithPossibleSideEffect() {
exists(DataFlow::CallNode call |
call.getCall().mayHaveOwnSideEffects() and
not isPureFn(call.getTarget()) and
result = call.asInstruction()
)
or
// If the lhs of an assignment is not analyzable by SSA, then
// we need to treat the assignment as having a possible side-effect.
result instanceof Write and
not exists(SsaExplicitDefinition ssa | result = ssa.getInstruction())
}
private predicate isPureFn(Function f) {
f.(BuiltinFunction).isPure()
or
isPureStmt(f.(DeclaredFunction).getBody())
}
private predicate isPureStmt(Stmt s) {
exists(BlockStmt blk | blk = s | forall(Stmt ch | ch = blk.getAStmt() | isPureStmt(ch)))
or
isPureExpr(s.(ReturnStmt).getExpr())
}
private predicate isPureExpr(Expr e) {
e instanceof BasicLit
or
exists(FuncDef f | f = e.getEnclosingFunction() |
e = f.getAParameter().getAReference()
or
e = f.(MethodDecl).getReceiver().getAReference()
)
or
isPureExpr(e.(SelectorExpr).getBase())
or
exists(CallExpr ce | e = ce |
isPureFn(ce.getTarget()) and
forall(Expr arg | arg = ce.getAnArgument() | isPureExpr(arg))
)
}
/**
* Gets the entry node of the control flow graph of which `node` is a
* member.
*/
private ControlFlow::Node getControlFlowEntry(ControlFlow::Node node) {
result = node.getRoot().getEntryNode()
}
private predicate entryNode(ControlFlow::Node node) { node.isEntryNode() }
/**
* Holds if there is a control flow edge from `src` to `dst` or
* if `dst` is an expression with a possible side-effect. The idea
* is to treat side effects as entry points in the control flow
* graph so that we can use the dominator tree to find the most recent
* side-effect.
*/
private predicate sideEffectCFG(ControlFlow::Node src, ControlFlow::Node dst) {
src.getASuccessor() = dst
or
// Add an edge from the entry point to any node that might have a side
// effect.
dst = nodeWithPossibleSideEffect() and
src = getControlFlowEntry(dst)
}
/**
* Holds if `dominator` is the immediate dominator of `node` in
* the side-effect CFG.
*/
private predicate iDomEffect(ControlFlow::Node dominator, ControlFlow::Node node) =
idominance(entryNode/1, sideEffectCFG/2)(_, dominator, node)
/**
* Gets the most recent side effect. To be more precise, `result` is a
* dominator of `node` and no side-effects can occur between `result` and
* `node`.
*
* `sideEffectCFG` has an edge from the function entry to every node with a
* side-effect. This means that every node with a side-effect has the
* function entry as its immediate dominator. So if node `x` dominates node
* `y` then there can be no side effects between `x` and `y` unless `x` is
* the function entry. So the optimal choice for `result` has the function
* entry as its immediate dominator.
*
* Example:
*
* ```
* 000: int f(int a, int b, int *p) {
* 001: int r = 0;
* 002: if (a) {
* 003: if (b) {
* 004: sideEffect1();
* 005: }
* 006: } else {
* 007: sideEffect2();
* 008: }
* 009: if (a) {
* 010: r++; // Not a side-effect, because r is an SSA variable.
* 011: }
* 012: if (b) {
* 013: r++; // Not a side-effect, because r is an SSA variable.
* 014: }
* 015: return *p;
* 016: }
* ```
*
* Suppose we want to find the most recent side-effect for the dereference
* of `p` on line 015. The `sideEffectCFG` has an edge from the function
* entry (line 000) to the side effects at lines 004 and 007. Therefore,
* the immediate dominator tree looks like this:
*
* 000 - 001 - 002 - 003
* - 004
* - 007
* - 009 - 010
* - 012 - 013
* - 015
*
* The immediate dominator path to line 015 is 000 - 009 - 012 - 015.
* Therefore, the most recent side effect for line 015 is line 009.
*/
cached
private ControlFlow::Node mostRecentSideEffect(ControlFlow::Node node) {
exists(ControlFlow::Node entry |
entryNode(entry) and
iDomEffect(entry, result) and
iDomEffect*(result, node)
)
}
/** Used to represent the "global value number" of an expression. */
cached
private newtype GVNBase =
MkNumericConst(string val) { mkNumericConst(_, val) } or
MkStringConst(string val) { mkStringConst(_, val) } or
MkBoolConst(boolean val) { mkBoolConst(_, val) } or
MkIndirectSsa(SsaDefinition def) { not ssaInit(def, _) } or
MkFunc(Function fn) { mkFunc(_, fn) } or
// Variables with no SSA information. As a crude (but safe)
// approximation, we use `mostRecentSideEffect` to compute a definition
// location for the variable. This ensures that two instances of the same
// global variable will only get the same value number if they are
// guaranteed to have the same value.
MkOtherVariable(ValueEntity x, ControlFlow::Node dominator) { mkOtherVariable(_, x, dominator) } or
MkMethodAccess(GVN base, Function m) { mkMethodAccess(_, base, m) } or
MkFieldRead(GVN base, Field f, ControlFlow::Node dominator) { mkFieldRead(_, base, f, dominator) } or
MkPureCall(Function f, GVN callee, GVNList args) { mkPureCall(_, f, callee, args) } or
MkIndex(GVN base, GVN index, ControlFlow::Node dominator) { mkIndex(_, base, index, dominator) } or
// Dereference a pointer. The value might have changed since the last
// time the pointer was dereferenced, so we need to include a definition
// location. As a crude (but safe) approximation, we use
// `mostRecentSideEffect` to compute a definition location.
MkDeref(GVN base, ControlFlow::Node dominator) { mkDeref(_, base, dominator) } or
MkBinaryOp(GVN lhs, GVN rhs, string op) { mkBinaryOp(_, lhs, rhs, op) } or
MkUnaryOp(GVN child, string op) { mkUnaryOp(_, child, op) } or
// Any expression that is not handled by the cases above is
// given a unique number based on the expression itself.
MkUnanalyzable(DataFlow::Node e) { not analyzableExpr(e) }
private newtype GVNList =
MkNil() or
MkCons(GVN head, GVNList tail) { globalValueNumbers(_, _, head, tail) }
private GVNList globalValueNumbers(DataFlow::CallNode ce, int start) {
analyzableCall(ce, _) and
start = ce.getNumArgument() and
result = MkNil()
or
exists(GVN head, GVNList tail |
globalValueNumbers(ce, start, head, tail) and
result = MkCons(head, tail)
)
}
private predicate globalValueNumbers(DataFlow::CallNode ce, int start, GVN head, GVNList tail) {
analyzableCall(ce, _) and
head = globalValueNumber(ce.getArgument(start)) and
tail = globalValueNumbers(ce, start + 1)
}
/**
* A Global Value Number. A GVN is an abstract representation of the value
* computed by an expression. The relationship between `Expr` and `GVN` is
* many-to-one: every `Expr` has exactly one `GVN`, but multiple
* expressions can have the same `GVN`. If two expressions have the same
* `GVN`, it means that they compute the same value at run time. The `GVN`
* is an opaque value, so you cannot deduce what the run-time value of an
* expression will be from its `GVN`. The only use for the `GVN` of an
* expression is to find other expressions that compute the same value.
* Use the predicate `globalValueNumber` to get the `GVN` for an `Expr`.
*
* Note: `GVN` has `toString` and `getLocation` methods, so that it can be
* displayed in a results list. These work by picking an arbitrary
* expression with this `GVN` and using its `toString` and `getLocation`
* methods.
*/
class GVN extends GVNBase {
GVN() { this instanceof GVNBase }
/** Gets a data-flow node that has this GVN. */
DataFlow::Node getANode() { this = globalValueNumber(result) }
/** Gets the kind of the GVN. This can be useful for debugging. */
string getKind() {
this instanceof MkNumericConst and result = "NumericConst"
or
this instanceof MkStringConst and result = "StringConst"
or
this instanceof MkBoolConst and result = "BoolConst"
or
this instanceof MkIndirectSsa and result = "IndirectSsa"
or
this instanceof MkFunc and result = "Func"
or
this instanceof MkOtherVariable and result = "OtherVariable"
or
this instanceof MkMethodAccess and result = "MethodAccess"
or
this instanceof MkFieldRead and result = "FieldRead"
or
this instanceof MkPureCall and result = "PureCall"
or
this instanceof MkIndex and result = "Index"
or
this instanceof MkDeref and result = "Deref"
or
this instanceof MkBinaryOp and result = "BinaryOp"
or
this instanceof MkUnaryOp and result = "UnaryOp"
or
this instanceof MkUnanalyzable and result = "Unanalyzable"
}
/**
* Gets an example of a data-flow node with this GVN.
* This is useful for things like implementing toString().
*/
private DataFlow::Node exampleNode() {
// Pick the expression with the minimum source location. This is
// just an arbitrary way to pick an expression with this `GVN`.
result =
min(DataFlow::Node e, string f, int l, int c, string k |
e = getANode() and e.hasLocationInfo(f, l, c, _, _) and k = e.getNodeKind()
|
e order by f, l, c, k
)
}
/** Gets a textual representation of this element. */
string toString() { result = exampleNode().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
) {
exampleNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
}
private predicate mkNumericConst(DataFlow::Node nd, string val) {
nd.getType().getUnderlyingType() instanceof NumericType and
val = nd.getExactValue() and
nd.isPlatformIndependentConstant()
}
private predicate mkStringConst(DataFlow::Node nd, string val) {
val = nd.getStringValue() and
nd.isPlatformIndependentConstant()
}
private predicate mkBoolConst(DataFlow::Node nd, boolean val) {
val = nd.getBoolValue() and
nd.isPlatformIndependentConstant()
}
private predicate mkFunc(DataFlow::Node nd, Function f) {
nd = f.getARead() and
not f instanceof Method
}
private predicate analyzableConst(DataFlow::Node e) {
mkNumericConst(e, _) or mkStringConst(e, _) or mkBoolConst(e, _) or mkFunc(e, _)
}
private predicate analyzableMethodAccess(Read access, DataFlow::Node receiver, Method m) {
exists(IR::ReadInstruction r | r = access.asInstruction() |
r.readsMethod(receiver.asInstruction(), m) and
not r.isConst()
)
}
private predicate mkMethodAccess(DataFlow::Node access, GVN qualifier, Method m) {
exists(DataFlow::Node base |
analyzableMethodAccess(access, base, m) and
qualifier = globalValueNumber(base)
)
}
private predicate analyzableFieldRead(Read fread, DataFlow::Node base, Field f) {
exists(IR::ReadInstruction r | r = fread.asInstruction() |
r.readsField(base.asInstruction(), f) and
strictcount(mostRecentSideEffect(r)) = 1 and
not r.isConst()
)
}
private predicate mkFieldRead(
DataFlow::Node fread, GVN qualifier, Field v, ControlFlow::Node dominator
) {
exists(DataFlow::Node base |
analyzableFieldRead(fread, base, v) and
qualifier = globalValueNumber(base) and
dominator = mostRecentSideEffect(fread.asInstruction())
)
}
private predicate analyzableCall(DataFlow::CallNode ce, Function f) {
f = ce.getTarget() and
isPureFn(f) and
not ce.isConst()
}
private predicate mkPureCall(DataFlow::CallNode ce, Function f, GVN callee, GVNList args) {
analyzableCall(ce, f) and
callee = globalValueNumber(ce.getCalleeNode()) and
args = globalValueNumbers(ce, 0)
}
/**
* Holds if `v` is a variable whose value changes are not, or at least not fully, captured by SSA.
*
* This is the case for package variables (for which no SSA information exists), but also for
* variables of non-primitive type (for which deep mutations are not captured by SSA).
*/
private predicate incompleteSsa(ValueEntity v) {
not v instanceof Field and
(
not v instanceof SsaSourceVariable
or
v.(SsaSourceVariable).mayHaveIndirectReferences()
or
exists(Type tp | tp = v.(DeclaredVariable).getType().getUnderlyingType() |
not tp instanceof BasicType
)
)
}
/**
* Holds if `access` is an access to a variable `target` for which SSA information is incomplete.
*/
private predicate analyzableOtherVariable(DataFlow::Node access, ValueEntity target) {
access.asInstruction().reads(target) and
incompleteSsa(target) and
strictcount(mostRecentSideEffect(access.asInstruction())) = 1 and
not access.isConst() and
not target instanceof Function
}
private predicate mkOtherVariable(DataFlow::Node access, ValueEntity x, ControlFlow::Node dominator) {
analyzableOtherVariable(access, x) and
dominator = mostRecentSideEffect(access.asInstruction())
}
private predicate analyzableBinaryOp(
DataFlow::BinaryOperationNode op, string opname, DataFlow::Node lhs, DataFlow::Node rhs
) {
opname = op.getOperator() and
not op.mayHaveSideEffects() and
lhs = op.getLeftOperand() and
rhs = op.getRightOperand() and
not op.isConst()
}
private predicate mkBinaryOp(DataFlow::Node op, GVN lhs, GVN rhs, string opname) {
exists(DataFlow::Node l, DataFlow::Node r |
analyzableBinaryOp(op, opname, l, r) and
lhs = globalValueNumber(l) and
rhs = globalValueNumber(r)
)
}
private predicate analyzableUnaryOp(DataFlow::UnaryOperationNode op) {
not op.mayHaveSideEffects() and
not op.isConst()
}
private predicate mkUnaryOp(DataFlow::UnaryOperationNode op, GVN child, string opname) {
analyzableUnaryOp(op) and
child = globalValueNumber(op.getOperand()) and
opname = op.getOperator()
}
private predicate analyzableIndexExpr(DataFlow::ElementReadNode ae) {
strictcount(mostRecentSideEffect(ae.asInstruction())) = 1 and
not ae.isConst()
}
private predicate mkIndex(
DataFlow::ElementReadNode ae, GVN base, GVN offset, ControlFlow::Node dominator
) {
analyzableIndexExpr(ae) and
base = globalValueNumber(ae.getBase()) and
offset = globalValueNumber(ae.getIndex()) and
dominator = mostRecentSideEffect(ae.asInstruction())
}
private predicate analyzablePointerDereferenceExpr(DataFlow::PointerDereferenceNode deref) {
strictcount(mostRecentSideEffect(deref.asInstruction())) = 1 and
not deref.isConst()
}
private predicate mkDeref(DataFlow::PointerDereferenceNode deref, GVN p, ControlFlow::Node dominator) {
analyzablePointerDereferenceExpr(deref) and
p = globalValueNumber(deref.getOperand()) and
dominator = mostRecentSideEffect(deref.asInstruction())
}
private predicate ssaInit(SsaExplicitDefinition ssa, DataFlow::Node rhs) {
ssa.getRhs() = rhs.asInstruction()
}
/** Gets the global value number of data-flow node `nd`. */
cached
GVN globalValueNumber(DataFlow::Node nd) {
exists(string val |
mkNumericConst(nd, val) and
result = MkNumericConst(val)
)
or
exists(string val |
mkStringConst(nd, val) and
result = MkStringConst(val)
)
or
exists(boolean val |
mkBoolConst(nd, val) and
result = MkBoolConst(val)
)
or
exists(Function f |
mkFunc(nd, f) and
result = MkFunc(f)
)
or
exists(ValueEntity x, ControlFlow::Node dominator |
mkOtherVariable(nd, x, dominator) and
result = MkOtherVariable(x, dominator)
)
or
exists(GVN qualifier, Function target |
mkMethodAccess(nd, qualifier, target) and
result = MkMethodAccess(qualifier, target)
)
or
exists(GVN qualifier, Entity target, ControlFlow::Node dominator |
mkFieldRead(nd, qualifier, target, dominator) and
result = MkFieldRead(qualifier, target, dominator)
)
or
exists(Function f, GVN callee, GVNList args |
mkPureCall(nd, f, callee, args) and
result = MkPureCall(f, callee, args)
)
or
exists(GVN lhs, GVN rhs, string opname |
mkBinaryOp(nd, lhs, rhs, opname) and
result = MkBinaryOp(lhs, rhs, opname)
)
or
exists(GVN child, string opname |
mkUnaryOp(nd, child, opname) and
result = MkUnaryOp(child, opname)
)
or
exists(GVN x, GVN i, ControlFlow::Node dominator |
mkIndex(nd, x, i, dominator) and
result = MkIndex(x, i, dominator)
)
or
exists(GVN p, ControlFlow::Node dominator |
mkDeref(nd, p, dominator) and
result = MkDeref(p, dominator)
)
or
not analyzableExpr(nd) and
result = MkUnanalyzable(nd)
or
exists(DataFlow::SsaNode ssa |
nd = ssa.getAUse() and
not incompleteSsa(ssa.getSourceVariable()) and
result = globalValueNumber(ssa)
)
or
exists(SsaDefinition ssa | ssa = nd.(DataFlow::SsaNode).getDefinition() |
// Local variable with a defining value.
exists(DataFlow::Node init |
ssaInit(ssa, init) and
result = globalValueNumber(init)
)
or
// Local variable without a defining value.
not ssaInit(ssa, _) and
result = MkIndirectSsa(ssa)
)
}
/**
* Holds if the expression is explicitly handled by `globalValueNumber`.
* Unanalyzable expressions still need to be given a global value number,
* but it will be a unique number that is not shared with any other
* expression.
*/
private predicate analyzableExpr(DataFlow::Node e) {
analyzableConst(e) or
any(DataFlow::SsaNode ssa).getAUse() = e or
e instanceof DataFlow::SsaNode or
analyzableOtherVariable(e, _) or
analyzableMethodAccess(e, _, _) or
analyzableFieldRead(e, _, _) or
analyzableCall(e, _) or
analyzableBinaryOp(e, _, _, _) or
analyzableUnaryOp(e) or
analyzableIndexExpr(e) or
analyzablePointerDereferenceExpr(e)
}

View File

@@ -0,0 +1,101 @@
/**
* Provides a class for representing and reasoning about properties of data-flow nodes.
*/
import go
private newtype TProperty =
IsBoolean(Boolean b) or
IsNil(Boolean b)
/**
* A property which may or may not hold of a data-flow node.
*
* Supported properties currently are Boolean truth and `nil`-ness.
*/
class Property extends TProperty {
private predicate checkOnExpr(Expr test, Boolean outcome, DataFlow::Node nd) {
exists(EqualityTestExpr eq, Expr e, boolean isTrue |
eq = test and eq.hasOperands(nd.asExpr(), e)
|
this = IsBoolean(isTrue) and
isTrue = eq.getPolarity().booleanXor(e.getBoolValue().booleanXor(outcome))
or
this = IsNil(isTrue) and
e = Builtin::nil().getAReference() and
isTrue = eq.getPolarity().booleanXor(outcome).booleanNot()
)
or
// if test = outcome ==> nd matches this
// then !test = !outcome ==> nd matches this
this.checkOnExpr(test.(NotExpr).getOperand(), outcome.booleanNot(), nd)
or
// if test = outcome ==> nd matches this
// then (test) = outcome ==> nd matches this
this.checkOnExpr(test.(ParenExpr).getExpr(), outcome, nd)
or
// if test = true ==> nd matches this
// then (test && e) = true ==> nd matches this
outcome = true and
this.checkOnExpr(test.(LandExpr).getAnOperand(), outcome, nd)
or
// if test = false ==> nd matches this
// then (test || e) = false ==> nd matches this
outcome = false and
this.checkOnExpr(test.(LorExpr).getAnOperand(), outcome, nd)
or
test = nd.asExpr() and
test instanceof ValueExpr and
test.getType().getUnderlyingType() instanceof BoolType and
this = IsBoolean(outcome)
}
/**
* Holds if `test` evaluating to `outcome` means that this property holds of `nd`, where `nd` is a
* subexpression of `test`.
*/
predicate checkOn(DataFlow::Node test, Boolean outcome, DataFlow::Node nd) {
checkOnExpr(test.asExpr(), outcome, nd)
}
/** Holds if this is the property of having the Boolean value `b`. */
predicate isBoolean(boolean b) { this = IsBoolean(b) }
/** Returns the boolean represented by this property if it is a boolean. */
boolean asBoolean() { this = IsBoolean(result) }
/** Holds if this is the property of being `nil`. */
predicate isNil() { this = IsNil(true) }
/** Holds if this is the property of being non-`nil`. */
predicate isNonNil() { this = IsNil(false) }
/** Gets a textual representation of this property. */
string toString() {
exists(boolean b |
this = IsBoolean(b) and
result = "is " + b
)
or
this = IsNil(true) and
result = "is nil"
or
this = IsNil(false) and
result = "is not nil"
}
}
/**
* Gets a `Property` representing truth outcome `b`.
*/
Property booleanProperty(boolean b) { result = IsBoolean(b) }
/**
* Gets a `Property` representing `nil`-ness.
*/
Property nilProperty() { result = IsNil(true) }
/**
* Gets a `Property` representing non-`nil`-ness.
*/
Property notNilProperty() { result = IsNil(false) }

View File

@@ -0,0 +1,407 @@
/**
* Provides classes for working with static single assignment form (SSA).
*/
import go
private import SsaImpl
/**
* A variable that can be SSA converted, that is, a local variable, but not a variable
* declared in file scope.
*/
class SsaSourceVariable extends LocalVariable {
SsaSourceVariable() { not getScope() instanceof FileScope }
/**
* Holds if there may be indirect references of this variable that are not covered by `getAReference()`.
*
* This is the case for variables that have their address taken, and for variables whose
* name resolution information may be incomplete (for instance due to an extractor error).
*/
predicate mayHaveIndirectReferences() {
// variables that have their address taken
exists(AddressExpr addr | addr.getOperand().stripParens() = getAReference())
or
exists(DataFlow::MethodReadNode mrn |
mrn.getReceiver() = getARead() and
mrn.getMethod().getReceiverType() instanceof PointerType
)
or
// variables where there is an unresolved reference with the same name in the same
// scope or a nested scope, suggesting that name resolution information may be incomplete
exists(FunctionScope scope, FuncDef inner |
scope = this.getScope().(LocalScope).getEnclosingFunctionScope() and
unresolvedReference(getName(), inner) and
inner.getScope().getOuterScope*() = scope
)
}
}
/**
* Holds if there is an unresolved reference to `name` in `fn`.
*/
private predicate unresolvedReference(string name, FuncDef fn) {
exists(Ident unresolved |
unresolvedIdentifier(unresolved, name) and
not unresolved = any(SelectorExpr sel).getSelector() and
fn = unresolved.getEnclosingFunction()
)
}
/**
* Holds if `id` is an unresolved identifier with the given `name`.
*/
pragma[noinline]
private predicate unresolvedIdentifier(Ident id, string name) {
id.getName() = name and
id instanceof ReferenceExpr and
not id.refersTo(_)
}
/**
* An SSA variable.
*/
class SsaVariable extends TSsaDefinition {
/** Gets the source variable corresponding to this SSA variable. */
SsaSourceVariable getSourceVariable() { result = this.(SsaDefinition).getSourceVariable() }
/** Gets the (unique) definition of this SSA variable. */
SsaDefinition getDefinition() { result = this }
/** Gets the type of this SSA variable. */
Type getType() { result = getSourceVariable().getType() }
/** Gets a use in basic block `bb` that refers to this SSA variable. */
IR::Instruction getAUseIn(ReachableBasicBlock bb) {
exists(int i, SsaSourceVariable v | v = getSourceVariable() |
result = bb.getNode(i) and
this = getDefinition(bb, i, v)
)
}
/** Gets a use that refers to this SSA variable. */
IR::Instruction getAUse() { result = getAUseIn(_) }
/** Gets a textual representation of this element. */
string toString() { result = getDefinition().prettyPrintRef() }
/**
* 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
) {
getDefinition().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
}
/**
* An SSA definition.
*/
class SsaDefinition extends TSsaDefinition {
/** Gets the SSA variable defined by this definition. */
SsaVariable getVariable() { result = this }
/** Gets the source variable defined by this definition. */
abstract SsaSourceVariable getSourceVariable();
/**
* Gets the basic block to which this definition belongs.
*/
abstract ReachableBasicBlock getBasicBlock();
/**
* INTERNAL: Use `getBasicBlock()` and `getSourceVariable()` instead.
*
* Holds if this is a definition of source variable `v` at index `idx` in basic block `bb`.
*
* Phi nodes are considered to be at index `-1`, all other definitions at the index of
* the control flow node they correspond to.
*/
abstract predicate definesAt(ReachableBasicBlock bb, int idx, SsaSourceVariable v);
/**
* INTERNAL: Use `toString()` instead.
*
* Gets a pretty-printed representation of this SSA definition.
*/
abstract string prettyPrintDef();
/**
* INTERNAL: Do not use.
*
* Gets a pretty-printed representation of a reference to this SSA definition.
*/
abstract string prettyPrintRef();
/** Gets the innermost function or file to which this SSA definition belongs. */
ControlFlow::Root getRoot() { result = getBasicBlock().getRoot() }
/** Gets a textual representation of this element. */
string toString() { result = prettyPrintDef() }
/**
* 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/).
*/
abstract predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
);
}
/**
* An SSA definition that corresponds to an explicit assignment or other variable definition.
*/
class SsaExplicitDefinition extends SsaDefinition, TExplicitDef {
/** Gets the instruction where the definition happens. */
IR::Instruction getInstruction() {
exists(BasicBlock bb, int i | this = TExplicitDef(bb, i, _) | result = bb.getNode(i))
}
/** Gets the right-hand side of the definition. */
IR::Instruction getRhs() { getInstruction().writes(_, result) }
override predicate definesAt(ReachableBasicBlock bb, int i, SsaSourceVariable v) {
this = TExplicitDef(bb, i, v)
}
override ReachableBasicBlock getBasicBlock() { definesAt(result, _, _) }
override SsaSourceVariable getSourceVariable() { this = TExplicitDef(_, _, result) }
override string prettyPrintRef() {
exists(int l, int c | hasLocationInfo(_, l, c, _, _) | result = "def@" + l + ":" + c)
}
override string prettyPrintDef() { result = "definition of " + getSourceVariable() }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
getInstruction().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
}
/** Provides a helper predicate for working with explicit SSA definitions. */
module SsaExplicitDefinition {
/**
* Gets the SSA definition corresponding to definition `def`.
*/
SsaExplicitDefinition of(IR::Instruction def) { result.getInstruction() = def }
}
/**
* An SSA definition that does not correspond to an explicit variable definition.
*/
abstract class SsaImplicitDefinition extends SsaDefinition {
/**
* INTERNAL: Do not use.
*
* Gets the definition kind to include in `prettyPrintRef`.
*/
abstract string getKind();
override string prettyPrintRef() {
exists(int l, int c | hasLocationInfo(_, l, c, _, _) | result = getKind() + "@" + l + ":" + c)
}
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
endline = startline and
endcolumn = startcolumn and
getBasicBlock().hasLocationInfo(filepath, startline, startcolumn, _, _)
}
}
/**
* An SSA definition representing the capturing of an SSA-convertible variable
* in the closure of a nested function.
*
* Capturing definitions appear at the beginning of such functions, as well as
* at any function call that may affect the value of the variable.
*/
class SsaVariableCapture extends SsaImplicitDefinition, TCapture {
override predicate definesAt(ReachableBasicBlock bb, int i, SsaSourceVariable v) {
this = TCapture(bb, i, v)
}
override ReachableBasicBlock getBasicBlock() { definesAt(result, _, _) }
override SsaSourceVariable getSourceVariable() { definesAt(_, _, result) }
override string getKind() { result = "capture" }
override string prettyPrintDef() { result = "capture variable " + getSourceVariable() }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
exists(ReachableBasicBlock bb, int i | definesAt(bb, i, _) |
bb.getNode(i).hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
)
}
}
/**
* An SSA definition such as a phi node that has no actual semantics, but simply serves to
* merge or filter data flow.
*/
abstract class SsaPseudoDefinition extends SsaImplicitDefinition {
/**
* Gets an input of this pseudo-definition.
*/
abstract SsaVariable getAnInput();
/**
* Gets a textual representation of the inputs of this pseudo-definition
* in lexicographical order.
*/
string ppInputs() { result = concat(getAnInput().getDefinition().prettyPrintRef(), ", ") }
}
/**
* An SSA phi node, that is, a pseudo-definition for a variable at a point
* in the flow graph where otherwise two or more definitions for the variable
* would be visible.
*/
class SsaPhiNode extends SsaPseudoDefinition, TPhi {
override SsaVariable getAnInput() {
result = getDefReachingEndOf(getBasicBlock().getAPredecessor(), getSourceVariable())
}
override predicate definesAt(ReachableBasicBlock bb, int i, SsaSourceVariable v) {
bb = getBasicBlock() and v = getSourceVariable() and i = -1
}
override ReachableBasicBlock getBasicBlock() { this = TPhi(result, _) }
override SsaSourceVariable getSourceVariable() { this = TPhi(_, result) }
override string getKind() { result = "phi" }
override string prettyPrintDef() { result = getSourceVariable() + " = phi(" + ppInputs() + ")" }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
endline = startline and
endcolumn = startcolumn and
getBasicBlock().hasLocationInfo(filepath, startline, startcolumn, _, _)
}
}
/**
* An SSA variable, possibly with a chain of field reads on it.
*/
private newtype TSsaWithFields =
TRoot(SsaVariable v) or
TStep(SsaWithFields base, Field f) { exists(accessPathAux(base, f)) }
/**
* Gets a representation of `nd` as an ssa-with-fields value if there is one.
*/
private TSsaWithFields accessPath(IR::Instruction insn) {
exists(SsaVariable v | insn = v.getAUse() | result = TRoot(v))
or
exists(SsaWithFields base, Field f | insn = accessPathAux(base, f) | result = TStep(base, f))
}
/**
* Gets a data-flow node that reads a field `f` from a node that is represented
* by ssa-with-fields value `base`.
*/
private IR::Instruction accessPathAux(TSsaWithFields base, Field f) {
exists(IR::FieldReadInstruction fr, IR::Instruction frb |
fr.getBase() = frb or
fr.getBase() = IR::implicitDerefInstruction(frb.(IR::EvalInstruction).getExpr())
|
base = accessPath(frb) and
f = fr.getField() and
result = fr
)
}
/** An SSA variable with zero or more fields read from it. */
class SsaWithFields extends TSsaWithFields {
/**
* Gets the SSA variable corresponding to the base of this SSA variable with fields.
*
* For example, the SSA variable corresponding to `a` for the SSA variable with fields
* corresponding to `a.b`.
*/
SsaVariable getBaseVariable() {
this = TRoot(result)
or
exists(SsaWithFields base, Field f | this = TStep(base, f) | result = base.getBaseVariable())
}
/** Gets a use that refers to this SSA variable with fields. */
DataFlow::Node getAUse() { this = accessPath(result.asInstruction()) }
/** Gets the type of this SSA variable with fields. */
Type getType() {
exists(SsaVariable var | this = TRoot(var) | result = var.getType())
or
exists(Field f | this = TStep(_, f) | result = f.getType())
}
/** Gets a textual representation of this element. */
string toString() {
exists(SsaVariable var | this = TRoot(var) | result = "(" + var + ")")
or
exists(SsaWithFields base, Field f | this = TStep(base, f) | result = base + "." + f.getName())
}
/**
* Gets an SSA-with-fields variable that is similar to this SSA-with-fields variable in the
* sense that it has the same root variable and the same sequence of field accesses.
*/
SsaWithFields similar() {
result.getBaseVariable().getSourceVariable() = this.getBaseVariable().getSourceVariable() and
result.getQualifiedName() = this.getQualifiedName()
}
/**
* Gets the qualified name of the source variable or variable and fields that this represents.
*
* For example, for an SSA variable that represents the field `a.b`, this would get the string
* `"a.b"`.
*/
string getQualifiedName() {
exists(SsaVariable v | this = TRoot(v) and result = v.getSourceVariable().getName())
or
exists(SsaWithFields base, Field f | this = TStep(base, f) |
result = base.getQualifiedName() + "." + f.getName()
)
}
/**
* 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.getBaseVariable().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
}
/**
* Gets a read similar to `node`, according to the same rules as `SsaWithFields.similar()`.
*/
DataFlow::Node getASimilarReadNode(DataFlow::Node node) {
exists(SsaWithFields readFields | node = readFields.getAUse() |
result = readFields.similar().getAUse()
)
}

View File

@@ -0,0 +1,295 @@
/**
* INTERNAL: Analyses should use module `SSA` instead.
*
* Provides predicates for constructing an SSA representation for functions.
*/
import go
cached
private module Internal {
/** Holds if the `i`th node of `bb` defines `v`. */
cached
predicate defAt(ReachableBasicBlock bb, int i, SsaSourceVariable v) {
bb.getNode(i).(IR::Instruction).writes(v, _)
}
/** Holds if the `i`th node of `bb` reads `v`. */
cached
predicate useAt(ReachableBasicBlock bb, int i, SsaSourceVariable v) {
bb.getNode(i).(IR::Instruction).reads(v)
}
/**
* A data type representing SSA definitions.
*
* We distinguish three kinds of SSA definitions:
*
* 1. Variable definitions, including declarations, assignments and increments/decrements.
* 2. Pseudo-definitions for captured variables at the beginning of the capturing function
* as well as after calls.
* 3. Phi nodes.
*
* SSA definitions are only introduced where necessary. In particular,
* unreachable code has no SSA definitions associated with it, and neither
* have dead assignments (that is, assignments whose value is never read).
*/
cached
newtype TSsaDefinition =
/**
* An SSA definition that corresponds to an explicit assignment or other variable definition.
*/
TExplicitDef(ReachableBasicBlock bb, int i, SsaSourceVariable v) {
defAt(bb, i, v) and
(liveAfterDef(bb, i, v) or v.isCaptured())
} or
/**
* An SSA definition representing the capturing of an SSA-convertible variable
* in the closure of a nested function.
*
* Capturing definitions appear at the beginning of such functions, as well as
* at any function call that may affect the value of the variable.
*/
TCapture(ReachableBasicBlock bb, int i, SsaSourceVariable v) {
mayCapture(bb, i, v) and
liveAfterDef(bb, i, v)
} or
/**
* An SSA phi node, that is, a pseudo-definition for a variable at a point
* in the flow graph where otherwise two or more definitions for the variable
* would be visible.
*/
TPhi(ReachableJoinBlock bb, SsaSourceVariable v) {
liveAtEntry(bb, v) and
inDefDominanceFrontier(bb, v)
}
/**
* Holds if `bb` is in the dominance frontier of a block containing a definition of `v`.
*/
pragma[noinline]
private predicate inDefDominanceFrontier(ReachableJoinBlock bb, SsaSourceVariable v) {
exists(ReachableBasicBlock defbb, SsaDefinition def |
def.definesAt(defbb, _, v) and
bb.inDominanceFrontierOf(defbb)
)
}
/**
* Holds if `v` is a captured variable which is declared in `declFun` and read in `useFun`.
*/
private predicate readsCapturedVar(FuncDef useFun, SsaSourceVariable v, FuncDef declFun) {
declFun = v.getDeclaringFunction() and
useFun = any(IR::Instruction u | u.reads(v)).getRoot() and
v.isCaptured()
}
/** Holds if the `i`th node of `bb` in function `f` is an entry node. */
private predicate entryNode(FuncDef f, ReachableBasicBlock bb, int i) {
f = bb.getRoot() and
bb.getNode(i).isEntryNode()
}
/**
* Holds if the `i`th node of `bb` in function `f` is a function call.
*/
private predicate callNode(FuncDef f, ReachableBasicBlock bb, int i) {
f = bb.getRoot() and
bb.getNode(i).(IR::EvalInstruction).getExpr() instanceof CallExpr
}
/**
* Holds if the `i`th node of basic block `bb` may induce a pseudo-definition for
* modelling updates to captured variable `v`. Whether the definition is actually
* introduced depends on whether `v` is live at this point in the program.
*/
private predicate mayCapture(ReachableBasicBlock bb, int i, SsaSourceVariable v) {
exists(FuncDef capturingContainer, FuncDef declContainer |
// capture initial value of variable declared in enclosing scope
readsCapturedVar(capturingContainer, v, declContainer) and
capturingContainer != declContainer and
entryNode(capturingContainer, bb, i)
or
// re-capture value of variable after a call if it is assigned non-locally
readsCapturedVar(capturingContainer, v, declContainer) and
assignedThroughClosure(v) and
callNode(capturingContainer, bb, i)
)
}
/** A classification of variable references into reads and writes. */
private newtype RefKind =
ReadRef() or
WriteRef()
/**
* Holds if the `i`th node of basic block `bb` is a reference to `v`, either a read
* (when `tp` is `ReadRef()`) or a direct or indirect write (when `tp` is `WriteRef()`).
*/
private predicate ref(ReachableBasicBlock bb, int i, SsaSourceVariable v, RefKind tp) {
useAt(bb, i, v) and tp = ReadRef()
or
(mayCapture(bb, i, v) or defAt(bb, i, v)) and
tp = WriteRef()
}
/**
* 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 `tp`.
*/
private int refRank(ReachableBasicBlock bb, int i, SsaSourceVariable v, RefKind tp) {
i = rank[result](int j | ref(bb, j, v, _)) and
ref(bb, i, v, tp)
}
/**
* Gets the maximum rank among all references to `v` in basic block `bb`.
*/
private int maxRefRank(ReachableBasicBlock bb, SsaSourceVariable v) {
result = max(refRank(bb, _, v, _))
}
/**
* Holds if variable `v` is live after the `i`th node of basic block `bb`, where
* `i` is the index of a node that may assign or capture `v`.
*
* For the purposes of this predicate, function calls are considered as writes of captured variables.
*/
private predicate liveAfterDef(ReachableBasicBlock bb, int i, SsaSourceVariable v) {
exists(int r | r = refRank(bb, i, v, WriteRef()) |
// the next reference to `v` inside `bb` is a read
r + 1 = refRank(bb, _, v, ReadRef())
or
// this is the last reference to `v` inside `bb`, but `v` is live at entry
// to a successor basic block of `bb`
r = maxRefRank(bb, v) and
liveAtSuccEntry(bb, v)
)
}
/**
* Holds if variable `v` is live at the beginning of basic block `bb`.
*
* For the purposes of this predicate, function calls are considered as writes of captured variables.
*/
private predicate liveAtEntry(ReachableBasicBlock bb, SsaSourceVariable v) {
// the first reference to `v` inside `bb` is a read
refRank(bb, _, v, ReadRef()) = 1
or
// there is no reference to `v` inside `bb`, but `v` is live at entry
// to a successor basic block of `bb`
not exists(refRank(bb, _, v, _)) and
liveAtSuccEntry(bb, v)
}
/**
* Holds if `v` is live at the beginning of any successor of basic block `bb`.
*/
private predicate liveAtSuccEntry(ReachableBasicBlock bb, SsaSourceVariable v) {
liveAtEntry(bb.getASuccessor(), v)
}
/**
* Holds if `v` is assigned outside its declaring function.
*/
private predicate assignedThroughClosure(SsaSourceVariable v) {
any(IR::Instruction def | def.writes(v, _)).getRoot() != v.getDeclaringFunction()
}
/**
* Holds if the `i`th node of `bb` is a use or an SSA definition of variable `v`, with
* `k` indicating whether it is the former or the latter.
*/
private predicate ssaRef(ReachableBasicBlock bb, int i, SsaSourceVariable v, RefKind k) {
useAt(bb, i, v) and k = ReadRef()
or
any(SsaDefinition def).definesAt(bb, i, v) and k = WriteRef()
}
/**
* Gets the (1-based) rank of the `i`th node of `bb` among all SSA definitions
* and uses of `v` in `bb`, with `k` indicating whether it is a definition or a use.
*
* For example, if `bb` is a basic block with a phi node for `v` (considered
* to be at index -1), uses `v` at node 2 and defines it at node 5, we have:
*
* ```
* ssaRefRank(bb, -1, v, WriteRef()) = 1 // phi node
* ssaRefRank(bb, 2, v, ReadRef()) = 2 // use at node 2
* ssaRefRank(bb, 5, v, WriteRef()) = 3 // definition at node 5
* ```
*/
private int ssaRefRank(ReachableBasicBlock bb, int i, SsaSourceVariable v, RefKind k) {
i = rank[result](int j | ssaRef(bb, j, v, _)) and
ssaRef(bb, i, v, k)
}
/**
* Gets the minimum rank of a read in `bb` such that all references to `v` between that
* read and the read at index `i` are reads (and not writes).
*/
private int rewindReads(ReachableBasicBlock bb, int i, SsaSourceVariable v) {
exists(int r | r = ssaRefRank(bb, i, v, ReadRef()) |
exists(int j, RefKind k | r - 1 = ssaRefRank(bb, j, v, k) |
k = ReadRef() and result = rewindReads(bb, j, v)
or
k = WriteRef() and result = r
)
or
r = 1 and result = r
)
}
/**
* Gets the SSA definition of `v` in `bb` that reaches the read of `v` at node `i`, if any.
*/
private SsaDefinition getLocalDefinition(ReachableBasicBlock bb, int i, SsaSourceVariable v) {
exists(int r | r = rewindReads(bb, i, v) |
exists(int j | result.definesAt(bb, j, v) and ssaRefRank(bb, j, v, _) = r - 1)
)
}
/**
* Gets an SSA definition of `v` that reaches the end of the immediate dominator of `bb`.
*/
pragma[noinline]
private SsaDefinition getDefReachingEndOfImmediateDominator(
ReachableBasicBlock bb, SsaSourceVariable v
) {
result = getDefReachingEndOf(bb.getImmediateDominator(), v)
}
/**
* Gets an SSA definition of `v` that reaches the end of basic block `bb`.
*/
cached
SsaDefinition getDefReachingEndOf(ReachableBasicBlock bb, SsaSourceVariable v) {
exists(int lastRef | lastRef = max(int i | ssaRef(bb, i, v, _)) |
result = getLocalDefinition(bb, lastRef, v)
or
result.definesAt(bb, lastRef, v) and
liveAtSuccEntry(bb, v)
)
or
// In SSA form, the (unique) reaching definition of a use is the closest
// definition that dominates the use. If two definitions dominate a node
// then one must dominate the other, so we can find the reaching definition
// by following the idominance relation backwards.
result = getDefReachingEndOfImmediateDominator(bb, v) and
not exists(SsaDefinition ssa | ssa.definesAt(bb, _, v)) and
liveAtSuccEntry(bb, v)
}
/**
* Gets the unique SSA definition of `v` whose value reaches the `i`th node of `bb`,
* which is a use of `v`.
*/
cached
SsaDefinition getDefinition(ReachableBasicBlock bb, int i, SsaSourceVariable v) {
result = getLocalDefinition(bb, i, v)
or
rewindReads(bb, i, v) = 1 and result = getDefReachingEndOf(bb.getImmediateDominator(), v)
}
}
import Internal

View File

@@ -0,0 +1,14 @@
/**
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) taint-tracking analyses.
*/
import semmle.go.dataflow.DataFlow
/**
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) taint-tracking analyses.
*/
module TaintTracking {
import semmle.go.dataflow.internal.tainttracking1.TaintTrackingImpl
}

View File

@@ -0,0 +1,12 @@
/**
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) taint-tracking analyses.
*/
/**
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) taint-tracking analyses.
*/
module TaintTracking2 {
import semmle.go.dataflow.internal.tainttracking2.TaintTrackingImpl
}

View File

@@ -0,0 +1,21 @@
/**
* Provides an implementation of a commonly used barrier guard for sanitizing untrusted URLs.
*/
import go
/**
* A call to a function called `isLocalUrl`, `isValidRedirect`, or similar, which is
* considered a barrier guard for sanitizing untrusted URLs.
*/
class RedirectCheckBarrierGuard extends DataFlow::BarrierGuard, DataFlow::CallNode {
RedirectCheckBarrierGuard() {
this.getCalleeName().regexpMatch("(?i)(is_?)?(local_?url|valid_?redir(ect)?)(ur[li])?")
}
override predicate checks(Expr e, boolean outcome) {
// `isLocalUrl(e)` is a barrier for `e` if it evaluates to `true`
getAnArgument().asExpr() = e and
outcome = true
}
}

View File

@@ -0,0 +1,25 @@
/**
* Provides an implementation of a commonly used barrier guard for sanitizing untrusted URLs.
*/
import go
/**
* A call to a regexp match function, considered as a barrier guard for sanitizing untrusted URLs.
*
* This is overapproximate: we do not attempt to reason about the correctness of the regexp.
*/
class RegexpCheck extends DataFlow::BarrierGuard {
RegexpMatchFunction matchfn;
DataFlow::CallNode call;
RegexpCheck() {
matchfn.getACall() = call and
this = matchfn.getResult().getNode(call).getASuccessor*()
}
override predicate checks(Expr e, boolean branch) {
e = matchfn.getValue().getNode(call).asExpr() and
(branch = false or branch = true)
}
}

View File

@@ -0,0 +1,32 @@
/**
* Provides an implementation of a commonly used barrier guard for sanitizing untrusted URLs.
*/
import go
/**
* An equality check comparing a data-flow node against a constant string, considered as
* a barrier guard for sanitizing untrusted URLs.
*
* Additionally, a check comparing `url.Hostname()` against a constant string is also
* considered a barrier guard for `url`.
*/
class UrlCheck extends DataFlow::BarrierGuard, DataFlow::EqualityTestNode {
DataFlow::Node url;
UrlCheck() {
exists(this.getAnOperand().getStringValue()) and
(
url = this.getAnOperand()
or
exists(DataFlow::MethodCallNode mc | mc = this.getAnOperand() |
mc.getTarget().getName() = "Hostname" and
url = mc.getReceiver()
)
)
}
override predicate checks(Expr e, boolean outcome) {
e = url.asExpr() and outcome = this.getPolarity()
}
}

Some files were not shown because too many files have changed in this diff Show More