mirror of
https://github.com/github/codeql.git
synced 2026-02-09 03:31:06 +01:00
QL: Merge branch 'main' into use-set-literal
This commit is contained in:
96
.github/workflows/codeql-analysis.yml
vendored
96
.github/workflows/codeql-analysis.yml
vendored
@@ -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
2
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"editor.formatOnSave": true,
|
||||
"files.eol": "\n",
|
||||
"files.exclude": {
|
||||
"codeql": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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" }
|
||||
|
||||
8
ql/src/codeql-suites/ql-all.qls
Normal file
8
ql/src/codeql-suites/ql-all.qls
Normal file
@@ -0,0 +1,8 @@
|
||||
- description: All Code Scanning queries for QL
|
||||
- queries: .
|
||||
- include:
|
||||
kind:
|
||||
- problem
|
||||
- path-problem
|
||||
- alert
|
||||
- path-alert
|
||||
@@ -6,3 +6,13 @@
|
||||
- path-problem
|
||||
- alert
|
||||
- path-alert
|
||||
precision:
|
||||
- high
|
||||
- very-high
|
||||
problem.severity:
|
||||
- error
|
||||
- warning
|
||||
- exclude:
|
||||
deprecated: //
|
||||
- exclude:
|
||||
query path: /^experimental\/.*/
|
||||
|
||||
244
ql/src/codeql/GlobalValueNumbering.qll
Normal file
244
ql/src/codeql/GlobalValueNumbering.qll
Normal 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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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" }
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`."
|
||||
22
ql/src/queries/performance/DontUseGetAQlClass.ql
Normal file
22
ql/src/queries/performance/DontUseGetAQlClass.ql
Normal 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"
|
||||
23
ql/src/queries/performance/MissingNoinline.ql
Normal file
23
ql/src/queries/performance/MissingNoinline.ql
Normal 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."
|
||||
34
ql/src/queries/style/ImplicitThis.ql
Normal file
34
ql/src/queries/style/ImplicitThis.ql
Normal 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`."
|
||||
96
ql/src/queries/style/SuperfluousExists.ql
Normal file
96
ql/src/queries/style/SuperfluousExists.ql
Normal 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"
|
||||
26
ql/src/queries/style/docs/ClassDocs.ql
Normal file
26
ql/src/queries/style/docs/ClassDocs.ql
Normal 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'."
|
||||
38
ql/src/queries/style/docs/NonUSSpelling.ql
Normal file
38
ql/src/queries/style/docs/NonUSSpelling.ql
Normal 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 + "'."
|
||||
@@ -1,3 +1,3 @@
|
||||
import ql
|
||||
|
||||
query AstNode getTarget(Call call) { result = call.getTarget().getDeclaration() }
|
||||
query AstNode getTarget(Call call) { result = call.getTarget() }
|
||||
|
||||
@@ -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 |
|
||||
|
||||
11
ql/test/queries/style/ImplicitThis/Bad.qll
Normal file
11
ql/test/queries/style/ImplicitThis/Bad.qll
Normal 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() }
|
||||
}
|
||||
21
ql/test/queries/style/ImplicitThis/Good.qll
Normal file
21
ql/test/queries/style/ImplicitThis/Good.qll
Normal 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" }
|
||||
}
|
||||
1
ql/test/queries/style/ImplicitThis/ImplicitThis.expected
Normal file
1
ql/test/queries/style/ImplicitThis/ImplicitThis.expected
Normal file
@@ -0,0 +1 @@
|
||||
| Bad.qll:10:41:10:48 | PredicateCall | Use of implicit `this`. |
|
||||
1
ql/test/queries/style/ImplicitThis/ImplicitThis.qlref
Normal file
1
ql/test/queries/style/ImplicitThis/ImplicitThis.qlref
Normal file
@@ -0,0 +1 @@
|
||||
queries/style/ImplicitThis.ql
|
||||
13
ql/test/queries/style/ImplicitThis/Okay.qll
Normal file
13
ql/test/queries/style/ImplicitThis/Okay.qll
Normal 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
1
repo-tests/codeql-go.txt
Normal file
@@ -0,0 +1 @@
|
||||
abe3f2148b92b1a94a0a3676cb4dab7d9211076f
|
||||
3
repo-tests/codeql-go/ql/config/legacy-support/qlpack.yml
Normal file
3
repo-tests/codeql-go/ql/config/legacy-support/qlpack.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
name: legacy-libraries-go
|
||||
version: 0.0.0
|
||||
libraryPathDependencies: codeql-go
|
||||
4
repo-tests/codeql-go/ql/examples/qlpack.yml
Normal file
4
repo-tests/codeql-go/ql/examples/qlpack.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
name: codeql/go-examples
|
||||
version: 0.0.2
|
||||
dependencies:
|
||||
codeql/go-all: ^0.0.2
|
||||
15
repo-tests/codeql-go/ql/examples/snippets/calltobuiltin.ql
Normal file
15
repo-tests/codeql-go/ql/examples/snippets/calltobuiltin.ql
Normal 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
|
||||
16
repo-tests/codeql-go/ql/examples/snippets/calltofunction.ql
Normal file
16
repo-tests/codeql-go/ql/examples/snippets/calltofunction.ql
Normal 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
|
||||
18
repo-tests/codeql-go/ql/examples/snippets/calltomethod.ql
Normal file
18
repo-tests/codeql-go/ql/examples/snippets/calltomethod.ql
Normal 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
|
||||
14
repo-tests/codeql-go/ql/examples/snippets/constant.ql
Normal file
14
repo-tests/codeql-go/ql/examples/snippets/constant.ql
Normal 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
|
||||
18
repo-tests/codeql-go/ql/examples/snippets/emptythen.ql
Normal file
18
repo-tests/codeql-go/ql/examples/snippets/emptythen.ql
Normal 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
|
||||
15
repo-tests/codeql-go/ql/examples/snippets/fieldread.ql
Normal file
15
repo-tests/codeql-go/ql/examples/snippets/fieldread.ql
Normal 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
|
||||
15
repo-tests/codeql-go/ql/examples/snippets/fieldwrite.ql
Normal file
15
repo-tests/codeql-go/ql/examples/snippets/fieldwrite.ql
Normal 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()
|
||||
13
repo-tests/codeql-go/ql/examples/snippets/function.ql
Normal file
13
repo-tests/codeql-go/ql/examples/snippets/function.ql
Normal 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
|
||||
15
repo-tests/codeql-go/ql/examples/snippets/nilcheck.ql
Normal file
15
repo-tests/codeql-go/ql/examples/snippets/nilcheck.ql
Normal 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
|
||||
12
repo-tests/codeql-go/ql/examples/snippets/param.ql
Normal file
12
repo-tests/codeql-go/ql/examples/snippets/param.ql
Normal 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
|
||||
15
repo-tests/codeql-go/ql/examples/snippets/pointertype.ql
Normal file
15
repo-tests/codeql-go/ql/examples/snippets/pointertype.ql
Normal 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
|
||||
12
repo-tests/codeql-go/ql/examples/snippets/receiver.ql
Normal file
12
repo-tests/codeql-go/ql/examples/snippets/receiver.ql
Normal 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
|
||||
12
repo-tests/codeql-go/ql/examples/snippets/result.ql
Normal file
12
repo-tests/codeql-go/ql/examples/snippets/result.ql
Normal 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
|
||||
13
repo-tests/codeql-go/ql/examples/snippets/type.ql
Normal file
13
repo-tests/codeql-go/ql/examples/snippets/type.ql
Normal 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
|
||||
16
repo-tests/codeql-go/ql/examples/snippets/typeinfo.ql
Normal file
16
repo-tests/codeql-go/ql/examples/snippets/typeinfo.ql
Normal 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
|
||||
13
repo-tests/codeql-go/ql/examples/snippets/updateinloop.ql
Normal file
13
repo-tests/codeql-go/ql/examples/snippets/updateinloop.ql
Normal 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
|
||||
13
repo-tests/codeql-go/ql/examples/snippets/variable.ql
Normal file
13
repo-tests/codeql-go/ql/examples/snippets/variable.ql
Normal 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()
|
||||
14
repo-tests/codeql-go/ql/examples/snippets/varread.ql
Normal file
14
repo-tests/codeql-go/ql/examples/snippets/varread.ql
Normal 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
|
||||
14
repo-tests/codeql-go/ql/examples/snippets/varwrite.ql
Normal file
14
repo-tests/codeql-go/ql/examples/snippets/varwrite.ql
Normal 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()
|
||||
16
repo-tests/codeql-go/ql/examples/snippets/zerocheck.ql
Normal file
16
repo-tests/codeql-go/ql/examples/snippets/zerocheck.ql
Normal 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
|
||||
12
repo-tests/codeql-go/ql/lib/Customizations.qll
Normal file
12
repo-tests/codeql-go/ql/lib/Customizations.qll
Normal 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
|
||||
15
repo-tests/codeql-go/ql/lib/definitions.ql
Normal file
15
repo-tests/codeql-go/ql/lib/definitions.ql
Normal 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"
|
||||
528
repo-tests/codeql-go/ql/lib/go.dbscheme
Normal file
528
repo-tests/codeql-go/ql/lib/go.dbscheme
Normal 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;
|
||||
|
||||
64
repo-tests/codeql-go/ql/lib/go.qll
Normal file
64
repo-tests/codeql-go/ql/lib/go.qll
Normal 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
|
||||
23
repo-tests/codeql-go/ql/lib/ideContextual.qll
Normal file
23
repo-tests/codeql-go/ql/lib/ideContextual.qll
Normal 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("//", "/")
|
||||
}
|
||||
20
repo-tests/codeql-go/ql/lib/localDefinitions.ql
Normal file
20
repo-tests/codeql-go/ql/lib/localDefinitions.ql
Normal 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"
|
||||
20
repo-tests/codeql-go/ql/lib/localReferences.ql
Normal file
20
repo-tests/codeql-go/ql/lib/localReferences.ql
Normal 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"
|
||||
30
repo-tests/codeql-go/ql/lib/printAst.ql
Normal file
30
repo-tests/codeql-go/ql/lib/printAst.ql
Normal 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() }
|
||||
}
|
||||
7
repo-tests/codeql-go/ql/lib/qlpack.yml
Normal file
7
repo-tests/codeql-go/ql/lib/qlpack.yml
Normal 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
|
||||
234
repo-tests/codeql-go/ql/lib/semmle/go/AST.qll
Normal file
234
repo-tests/codeql-go/ql/lib/semmle/go/AST.qll
Normal 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) }
|
||||
}
|
||||
37
repo-tests/codeql-go/ql/lib/semmle/go/Architectures.qll
Normal file
37
repo-tests/codeql-go/ql/lib/semmle/go/Architectures.qll
Normal 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 }
|
||||
}
|
||||
226
repo-tests/codeql-go/ql/lib/semmle/go/Comments.qll
Normal file
226
repo-tests/codeql-go/ql/lib/semmle/go/Comments.qll
Normal 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 */
|
||||
* </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 */
|
||||
*
|
||||
* /* a block
|
||||
* comment */
|
||||
* /* another block comment */
|
||||
* </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>*/</code>.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* <pre>
|
||||
* /* a block
|
||||
* comment */
|
||||
* </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>*/</code>.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* <pre>
|
||||
* /* a block
|
||||
* comment */
|
||||
* </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(" ") }
|
||||
}
|
||||
475
repo-tests/codeql-go/ql/lib/semmle/go/Concepts.qll
Normal file
475
repo-tests/codeql-go/ql/lib/semmle/go/Concepts.qll
Normal 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();
|
||||
}
|
||||
}
|
||||
610
repo-tests/codeql-go/ql/lib/semmle/go/Decls.qll
Normal file
610
repo-tests/codeql-go/ql/lib/semmle/go/Decls.qll
Normal 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" }
|
||||
}
|
||||
53
repo-tests/codeql-go/ql/lib/semmle/go/Errors.qll
Normal file
53
repo-tests/codeql-go/ql/lib/semmle/go/Errors.qll
Normal 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 { }
|
||||
2109
repo-tests/codeql-go/ql/lib/semmle/go/Expr.qll
Normal file
2109
repo-tests/codeql-go/ql/lib/semmle/go/Expr.qll
Normal file
File diff suppressed because it is too large
Load Diff
278
repo-tests/codeql-go/ql/lib/semmle/go/Files.qll
Normal file
278
repo-tests/codeql-go/ql/lib/semmle/go/Files.qll
Normal 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?") }
|
||||
}
|
||||
231
repo-tests/codeql-go/ql/lib/semmle/go/GoMod.qll
Normal file
231
repo-tests/codeql-go/ql/lib/semmle/go/GoMod.qll
Normal 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" }
|
||||
}
|
||||
207
repo-tests/codeql-go/ql/lib/semmle/go/HTML.qll
Normal file
207
repo-tests/codeql-go/ql/lib/semmle/go/HTML.qll
Normal 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) }
|
||||
}
|
||||
}
|
||||
81
repo-tests/codeql-go/ql/lib/semmle/go/Locations.qll
Normal file
81
repo-tests/codeql-go/ql/lib/semmle/go/Locations.qll
Normal 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" }
|
||||
}
|
||||
41
repo-tests/codeql-go/ql/lib/semmle/go/Packages.qll
Normal file
41
repo-tests/codeql-go/ql/lib/semmle/go/Packages.qll
Normal 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()
|
||||
}
|
||||
20
repo-tests/codeql-go/ql/lib/semmle/go/PrintAst.ql
Normal file
20
repo-tests/codeql-go/ql/lib/semmle/go/PrintAst.ql
Normal 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() }
|
||||
}
|
||||
271
repo-tests/codeql-go/ql/lib/semmle/go/PrintAst.qll
Normal file
271
repo-tests/codeql-go/ql/lib/semmle/go/PrintAst.qll
Normal 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"
|
||||
}
|
||||
755
repo-tests/codeql-go/ql/lib/semmle/go/Scopes.qll
Normal file
755
repo-tests/codeql-go/ql/lib/semmle/go/Scopes.qll
Normal 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" }
|
||||
}
|
||||
1135
repo-tests/codeql-go/ql/lib/semmle/go/Stmt.qll
Normal file
1135
repo-tests/codeql-go/ql/lib/semmle/go/Stmt.qll
Normal file
File diff suppressed because it is too large
Load Diff
494
repo-tests/codeql-go/ql/lib/semmle/go/StringOps.qll
Normal file
494
repo-tests/codeql-go/ql/lib/semmle/go/StringOps.qll
Normal 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() }
|
||||
}
|
||||
}
|
||||
695
repo-tests/codeql-go/ql/lib/semmle/go/Types.qll
Normal file
695
repo-tests/codeql-go/ql/lib/semmle/go/Types.qll
Normal 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, _)) }
|
||||
18
repo-tests/codeql-go/ql/lib/semmle/go/Util.qll
Normal file
18
repo-tests/codeql-go/ql/lib/semmle/go/Util.qll
Normal 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])"
|
||||
}
|
||||
198
repo-tests/codeql-go/ql/lib/semmle/go/VariableWithFields.qll
Normal file
198
repo-tests/codeql-go/ql/lib/semmle/go/VariableWithFields.qll
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
380
repo-tests/codeql-go/ql/lib/semmle/go/concepts/HTTP.qll
Normal file
380
repo-tests/codeql-go/ql/lib/semmle/go/concepts/HTTP.qll
Normal 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) }
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
1669
repo-tests/codeql-go/ql/lib/semmle/go/controlflow/IR.qll
Normal file
1669
repo-tests/codeql-go/ql/lib/semmle/go/controlflow/IR.qll
Normal file
File diff suppressed because it is too large
Load Diff
29
repo-tests/codeql-go/ql/lib/semmle/go/dataflow/DataFlow.qll
Normal file
29
repo-tests/codeql-go/ql/lib/semmle/go/dataflow/DataFlow.qll
Normal 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;
|
||||
27
repo-tests/codeql-go/ql/lib/semmle/go/dataflow/DataFlow2.qll
Normal file
27
repo-tests/codeql-go/ql/lib/semmle/go/dataflow/DataFlow2.qll
Normal 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
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
101
repo-tests/codeql-go/ql/lib/semmle/go/dataflow/Properties.qll
Normal file
101
repo-tests/codeql-go/ql/lib/semmle/go/dataflow/Properties.qll
Normal 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) }
|
||||
407
repo-tests/codeql-go/ql/lib/semmle/go/dataflow/SSA.qll
Normal file
407
repo-tests/codeql-go/ql/lib/semmle/go/dataflow/SSA.qll
Normal 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()
|
||||
)
|
||||
}
|
||||
295
repo-tests/codeql-go/ql/lib/semmle/go/dataflow/SsaImpl.qll
Normal file
295
repo-tests/codeql-go/ql/lib/semmle/go/dataflow/SsaImpl.qll
Normal 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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user