mirror of
https://github.com/github/codeql.git
synced 2026-02-09 11:41:06 +01:00
QL: Merge pull request #82 from github/esbena/codeql-action-on-other-repos
This commit is contained in:
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\/.*/
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @name Using implicit `this`
|
||||
* @description Writing member predicate calls with an implicit `this` can be confusing
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @problem.severity recommendation
|
||||
* @precision very-high
|
||||
* @id ql/implicit-this
|
||||
* @tags maintainability
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
private import go
|
||||
private import DataFlowPrivate
|
||||
|
||||
/**
|
||||
* Holds if `call` is an interface call to method `m`, meaning that its receiver `recv` has
|
||||
* interface type `tp`.
|
||||
*/
|
||||
private predicate isInterfaceCallReceiver(
|
||||
DataFlow::CallNode call, DataFlow::Node recv, InterfaceType tp, string m
|
||||
) {
|
||||
call.getReceiver() = recv and
|
||||
recv.getType().getUnderlyingType() = tp and
|
||||
m = call.getCalleeName()
|
||||
}
|
||||
|
||||
/** Gets a data-flow node that may flow into the receiver value of `call`, which is an interface value. */
|
||||
private DataFlow::Node getInterfaceCallReceiverSource(DataFlow::CallNode call) {
|
||||
isInterfaceCallReceiver(call, result.getASuccessor*(), _, _)
|
||||
}
|
||||
|
||||
/** Gets the type of `nd`, which must be a valid type and not an interface type. */
|
||||
private Type getConcreteType(DataFlow::Node nd) {
|
||||
result = nd.getType() and
|
||||
not result.getUnderlyingType() instanceof InterfaceType and
|
||||
not result instanceof InvalidType
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if all concrete (that is, non-interface) types of `nd` concrete types can be determined by
|
||||
* local reasoning.
|
||||
*
|
||||
* `nd` is restricted to nodes that flow into the receiver value of an interface call, since that is
|
||||
* all we are ultimately interested in.
|
||||
*/
|
||||
private predicate isConcreteValue(DataFlow::Node nd) {
|
||||
nd = getInterfaceCallReceiverSource(_) and
|
||||
(
|
||||
exists(getConcreteType(nd))
|
||||
or
|
||||
forex(DataFlow::Node pred | pred = nd.getAPredecessor() | isConcreteValue(pred))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `call` is an interface call to method `m` with receiver `recv`, where the concrete
|
||||
* types of `recv` can be established by local reasoning.
|
||||
*/
|
||||
private predicate isConcreteInterfaceCall(DataFlow::Node call, DataFlow::Node recv, string m) {
|
||||
isInterfaceCallReceiver(call, recv, _, m) and isConcreteValue(recv)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a function that might be called by `call`, where the receiver of `call` has interface type,
|
||||
* but its concrete types can be determined by local reasoning.
|
||||
*/
|
||||
private FuncDecl getConcreteTarget(DataFlow::CallNode call) {
|
||||
exists(DataFlow::Node recv, string m | isConcreteInterfaceCall(call, recv, m) |
|
||||
exists(Type concreteReceiverType, DeclaredFunction concreteTarget |
|
||||
concreteReceiverType = getConcreteType(getInterfaceCallReceiverSource(call)) and
|
||||
concreteTarget = concreteReceiverType.getMethod(m) and
|
||||
result = concreteTarget.getFuncDecl()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `call` is a method call whose receiver has an interface type.
|
||||
*/
|
||||
private predicate isInterfaceMethodCall(DataFlow::CallNode call) {
|
||||
isInterfaceCallReceiver(call, _, _, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a method that might be called by `call`, where we restrict the result to
|
||||
* implement the interface type of the receiver of `call`.
|
||||
*/
|
||||
private MethodDecl getRestrictedInterfaceTarget(DataFlow::CallNode call) {
|
||||
exists(InterfaceType tp, Type recvtp, string m |
|
||||
isInterfaceCallReceiver(call, _, tp, m) and
|
||||
result = recvtp.getMethod(m).(DeclaredFunction).getFuncDecl() and
|
||||
recvtp.implements(tp)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a function that might be called by `call`.
|
||||
*/
|
||||
DataFlowCallable viableCallable(CallExpr ma) {
|
||||
exists(DataFlow::CallNode call | call.asExpr() = ma |
|
||||
if isConcreteInterfaceCall(call, _, _)
|
||||
then result = getConcreteTarget(call)
|
||||
else
|
||||
if isInterfaceMethodCall(call)
|
||||
then result = getRestrictedInterfaceTarget(call)
|
||||
else result = call.getACallee()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the set of viable implementations that can be called by `call`
|
||||
* might be improved by knowing the call context.
|
||||
*/
|
||||
predicate mayBenefitFromCallContext(DataFlowCall call, DataFlowCallable f) { none() }
|
||||
|
||||
/**
|
||||
* Gets a viable dispatch target of `call` in the context `ctx`. This is
|
||||
* restricted to those `call`s for which a context might make a difference.
|
||||
*/
|
||||
DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) { none() }
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Provides Go-specific definitions for use in the data flow library.
|
||||
*/
|
||||
module Private {
|
||||
import DataFlowPrivate
|
||||
import DataFlowDispatch
|
||||
}
|
||||
|
||||
module Public {
|
||||
import DataFlowUtil
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
private import go
|
||||
private import DataFlowUtil
|
||||
private import DataFlowImplCommon
|
||||
|
||||
private newtype TReturnKind =
|
||||
MkReturnKind(int i) { exists(SignatureType st | exists(st.getResultType(i))) }
|
||||
|
||||
/**
|
||||
* A return kind. A return kind describes how a value can be returned
|
||||
* from a callable. For Go, this is either a return of a single value
|
||||
* or of one of multiple values.
|
||||
*/
|
||||
class ReturnKind extends TReturnKind {
|
||||
/** Gets a textual representation of this return kind. */
|
||||
string toString() { exists(int i | this = MkReturnKind(i) | result = "return[" + i + "]") }
|
||||
}
|
||||
|
||||
/** A data flow node that represents returning a value from a function. */
|
||||
class ReturnNode extends ResultNode {
|
||||
ReturnKind kind;
|
||||
|
||||
ReturnNode() { kind = MkReturnKind(i) }
|
||||
|
||||
/** Gets the kind of this returned value. */
|
||||
ReturnKind getKind() { result = kind }
|
||||
}
|
||||
|
||||
/** A data flow node that represents the output of a call. */
|
||||
class OutNode extends DataFlow::Node {
|
||||
DataFlow::CallNode call;
|
||||
int i;
|
||||
|
||||
OutNode() { this = call.getResult(i) }
|
||||
|
||||
/** Gets the underlying call. */
|
||||
DataFlowCall getCall() { result = call.asExpr() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that can read the value returned from `call` with return kind
|
||||
* `kind`.
|
||||
*/
|
||||
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) {
|
||||
exists(DataFlow::CallNode c, int i | c.asExpr() = call and kind = MkReturnKind(i) |
|
||||
result = c.getResult(i)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data can flow from `node1` to `node2` in a way that loses the
|
||||
* calling context. For example, this would happen with flow through a
|
||||
* global or static variable.
|
||||
*/
|
||||
predicate jumpStep(Node n1, Node n2) {
|
||||
exists(ValueEntity v, Write w |
|
||||
not v instanceof SsaSourceVariable and
|
||||
not v instanceof Field and
|
||||
w.writes(v, n1) and
|
||||
n2 = v.getARead()
|
||||
)
|
||||
}
|
||||
|
||||
private newtype TContent =
|
||||
TFieldContent(Field f) or
|
||||
TCollectionContent() or
|
||||
TArrayContent() or
|
||||
TPointerContent(PointerType p)
|
||||
|
||||
/**
|
||||
* A reference contained in an object. Examples include instance fields, the
|
||||
* contents of a collection object, the contents of an array or pointer.
|
||||
*/
|
||||
class Content extends TContent {
|
||||
/** Gets a textual representation of this element. */
|
||||
abstract string toString();
|
||||
|
||||
predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
|
||||
path = "" and sl = 0 and sc = 0 and el = 0 and ec = 0
|
||||
}
|
||||
}
|
||||
|
||||
private class FieldContent extends Content, TFieldContent {
|
||||
Field f;
|
||||
|
||||
FieldContent() { this = TFieldContent(f) }
|
||||
|
||||
override string toString() { result = f.toString() }
|
||||
|
||||
override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
|
||||
f.getDeclaration().hasLocationInfo(path, sl, sc, el, ec)
|
||||
}
|
||||
}
|
||||
|
||||
private class CollectionContent extends Content, TCollectionContent {
|
||||
override string toString() { result = "collection" }
|
||||
}
|
||||
|
||||
private class ArrayContent extends Content, TArrayContent {
|
||||
override string toString() { result = "array" }
|
||||
}
|
||||
|
||||
private class PointerContent extends Content, TPointerContent {
|
||||
override string toString() { result = "pointer" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data can flow from `node1` to `node2` via an assignment to `c`.
|
||||
* Thus, `node2` references an object with a field `f` that contains the
|
||||
* value of `node1`.
|
||||
*/
|
||||
predicate storeStep(Node node1, Content c, PostUpdateNode node2) {
|
||||
// a write `(*p).f = rhs` is modelled as two store steps: `rhs` is flows into field `f` of `(*p)`,
|
||||
// which in turn flows into the pointer content of `p`
|
||||
exists(Write w, Field f, DataFlow::Node base, DataFlow::Node rhs | w.writesField(base, f, rhs) |
|
||||
node1 = rhs and
|
||||
node2.getPreUpdateNode() = base and
|
||||
c = TFieldContent(f)
|
||||
or
|
||||
node1 = base and
|
||||
node2.getPreUpdateNode() = node1.(PointerDereferenceNode).getOperand() and
|
||||
c = TPointerContent(node2.getType())
|
||||
)
|
||||
or
|
||||
node1 = node2.(AddressOperationNode).getOperand() and
|
||||
c = TPointerContent(node2.getType())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data can flow from `node1` to `node2` via a read of `f`.
|
||||
* Thus, `node1` references an object with a field `f` whose value ends up in
|
||||
* `node2`.
|
||||
*/
|
||||
predicate readStep(Node node1, Content f, Node node2) {
|
||||
node1 = node2.(PointerDereferenceNode).getOperand() and
|
||||
f = TPointerContent(node1.getType())
|
||||
or
|
||||
exists(FieldReadNode read |
|
||||
node2 = read and
|
||||
node1 = read.getBase() and
|
||||
f = TFieldContent(read.getField())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if values stored inside content `c` are cleared at node `n`.
|
||||
*/
|
||||
predicate clearsContent(Node n, Content c) {
|
||||
none() // stub implementation
|
||||
}
|
||||
|
||||
/** Gets the type of `n` used for type pruning. */
|
||||
DataFlowType getNodeType(Node n) { result = n.getType() }
|
||||
|
||||
/** Gets a string representation of a type returned by `getNodeType()`. */
|
||||
string ppReprType(Type t) { result = t.toString() }
|
||||
|
||||
/**
|
||||
* Holds if `t1` and `t2` are compatible, that is, whether data can flow from
|
||||
* a node of type `t1` to a node of type `t2`.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate compatibleTypes(Type t1, Type t2) {
|
||||
any() // stub implementation
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Java QL library compatibility wrappers
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/** A node that performs a type cast. */
|
||||
class CastNode extends ExprNode {
|
||||
override ConversionExpr expr;
|
||||
}
|
||||
|
||||
class DataFlowCallable = FuncDef;
|
||||
|
||||
class DataFlowExpr = Expr;
|
||||
|
||||
class DataFlowType = Type;
|
||||
|
||||
class DataFlowLocation = Location;
|
||||
|
||||
/** A function call relevant for data flow. */
|
||||
class DataFlowCall extends Expr {
|
||||
DataFlow::CallNode call;
|
||||
|
||||
DataFlowCall() { this = call.asExpr() }
|
||||
|
||||
/**
|
||||
* Gets the nth argument for this call.
|
||||
*/
|
||||
Node getArgument(int n) { result = call.getArgument(n) }
|
||||
|
||||
/** Gets the data flow node corresponding to this call. */
|
||||
ExprNode getNode() { result = call }
|
||||
|
||||
/** Gets the enclosing callable of this call. */
|
||||
DataFlowCallable getEnclosingCallable() { result = this.getEnclosingFunction() }
|
||||
}
|
||||
|
||||
/** Holds if `e` is an expression that always has the same Boolean value `val`. */
|
||||
private predicate constantBooleanExpr(Expr e, boolean val) {
|
||||
e.getBoolValue() = val
|
||||
or
|
||||
exists(SsaExplicitDefinition v, Expr src |
|
||||
IR::evalExprInstruction(e) = v.getVariable().getAUse() and
|
||||
IR::evalExprInstruction(src) = v.getRhs() and
|
||||
constantBooleanExpr(src, val)
|
||||
)
|
||||
}
|
||||
|
||||
/** An argument that always has the same Boolean value. */
|
||||
private class ConstantBooleanArgumentNode extends ArgumentNode, ExprNode {
|
||||
ConstantBooleanArgumentNode() { constantBooleanExpr(this.getExpr(), _) }
|
||||
|
||||
/** Gets the Boolean value of this expression. */
|
||||
boolean getBooleanValue() { constantBooleanExpr(this.getExpr(), result) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a guard that will certainly not hold in calling context `call`.
|
||||
*
|
||||
* In particular it does not hold because it checks that `param` has value `b`, but
|
||||
* in context `call` it is known to have value `!b`. Note this is `noinline`d in order
|
||||
* to avoid a bad join order in `isUnreachableInCall`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private ControlFlow::ConditionGuardNode getAFalsifiedGuard(DataFlowCall call) {
|
||||
exists(ParameterNode param, ConstantBooleanArgumentNode arg |
|
||||
// get constant bool argument and parameter for this call
|
||||
viableParamArg(call, param, arg) and
|
||||
// which is used in a guard controlling `n` with the opposite value of `arg`
|
||||
result.ensures(param.getAUse(), arg.getBooleanValue().booleanNot())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the node `n` is unreachable when the call context is `call`.
|
||||
*/
|
||||
predicate isUnreachableInCall(Node n, DataFlowCall call) {
|
||||
getAFalsifiedGuard(call).dominates(n.getBasicBlock())
|
||||
}
|
||||
|
||||
int accessPathLimit() { result = 5 }
|
||||
|
||||
/** The unit type. */
|
||||
private newtype TUnit = TMkUnit()
|
||||
|
||||
/** The trivial type with a single element. */
|
||||
class Unit extends TUnit {
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() { result = "unit" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `i`th argument of call `c`, where the receiver of a method call
|
||||
* counts as argument -1.
|
||||
*/
|
||||
Node getArgument(CallNode c, int i) {
|
||||
result = c.getArgument(i)
|
||||
or
|
||||
result = c.(MethodCallNode).getReceiver() and
|
||||
i = -1
|
||||
}
|
||||
|
||||
/** Holds if `n` should be hidden from path explanations. */
|
||||
predicate nodeIsHidden(Node n) { none() }
|
||||
|
||||
class LambdaCallKind = Unit;
|
||||
|
||||
/** Holds if `creation` is an expression that creates a lambda of kind `kind` for `c`. */
|
||||
predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) { none() }
|
||||
|
||||
/** Holds if `call` is a lambda call of kind `kind` where `receiver` is the lambda expression. */
|
||||
predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { none() }
|
||||
|
||||
/** Extra data-flow steps needed for lambda flow analysis. */
|
||||
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() }
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,384 @@
|
||||
/**
|
||||
* Provides Go-specific definitions for use in the taint-tracking library.
|
||||
*/
|
||||
|
||||
private import go
|
||||
|
||||
/**
|
||||
* Holds if taint can flow from `src` to `sink` in zero or more
|
||||
* local (intra-procedural) steps.
|
||||
*/
|
||||
predicate localTaint(DataFlow::Node src, DataFlow::Node sink) { localTaintStep*(src, sink) }
|
||||
|
||||
/**
|
||||
* Holds if taint can flow from `src` to `sink` in zero or more
|
||||
* local (intra-procedural) steps.
|
||||
*/
|
||||
predicate localExprTaint(Expr src, Expr sink) {
|
||||
localTaint(DataFlow::exprNode(src), DataFlow::exprNode(sink))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if taint can flow in one local step from `src` to `sink`.
|
||||
*/
|
||||
predicate localTaintStep(DataFlow::Node src, DataFlow::Node sink) {
|
||||
DataFlow::localFlowStep(src, sink) or
|
||||
localAdditionalTaintStep(src, sink)
|
||||
}
|
||||
|
||||
private newtype TUnit = TMkUnit()
|
||||
|
||||
/** A singleton class containing a single dummy "unit" value. */
|
||||
private class Unit extends TUnit {
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() { result = "unit" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A unit class for adding additional taint steps.
|
||||
*
|
||||
* Extend this class to add additional taint steps that should apply to all
|
||||
* taint configurations.
|
||||
*/
|
||||
class AdditionalTaintStep extends Unit {
|
||||
/**
|
||||
* Holds if the step from `node1` to `node2` should be considered a taint
|
||||
* step for all configurations.
|
||||
*/
|
||||
abstract predicate step(DataFlow::Node node1, DataFlow::Node node2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the additional step from `pred` to `succ` should be included in all
|
||||
* global taint flow configurations.
|
||||
*/
|
||||
predicate localAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
referenceStep(pred, succ) or
|
||||
elementWriteStep(pred, succ) or
|
||||
fieldReadStep(pred, succ) or
|
||||
elementStep(pred, succ) or
|
||||
tupleStep(pred, succ) or
|
||||
stringConcatStep(pred, succ) or
|
||||
sliceStep(pred, succ) or
|
||||
any(FunctionModel fm).taintStep(pred, succ) or
|
||||
any(AdditionalTaintStep a).step(pred, succ)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if taint flows from `pred` to `succ` via a reference or dereference.
|
||||
*
|
||||
* The taint-tracking library does not distinguish between a reference and its referent,
|
||||
* treating one as tainted if the other is.
|
||||
*/
|
||||
predicate referenceStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::AddressOperationNode addr |
|
||||
// from `x` to `&x`
|
||||
pred = addr.getOperand() and
|
||||
succ = addr
|
||||
or
|
||||
// from `&x` to `x`
|
||||
pred = addr and
|
||||
succ.(DataFlow::PostUpdateNode).getPreUpdateNode() = addr.getOperand()
|
||||
)
|
||||
or
|
||||
exists(DataFlow::PointerDereferenceNode deref |
|
||||
// from `x` to `*x`
|
||||
pred = deref.getOperand() and
|
||||
succ = deref
|
||||
or
|
||||
// from `*x` to `x`
|
||||
pred = deref and
|
||||
succ.(DataFlow::PostUpdateNode).getPreUpdateNode() = deref.getOperand()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is an assignment of the form `succ[idx] = pred`, meaning that `pred` may taint
|
||||
* `succ`.
|
||||
*/
|
||||
predicate elementWriteStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
any(DataFlow::Write w).writesElement(succ.(DataFlow::PostUpdateNode).getPreUpdateNode(), _, pred)
|
||||
}
|
||||
|
||||
/** Holds if taint flows from `pred` to `succ` via a field read. */
|
||||
predicate fieldReadStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
succ.(DataFlow::FieldReadNode).getBase() = pred
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if taint flows from `pred` to `succ` via an array, map, slice, or string
|
||||
* index operation.
|
||||
*/
|
||||
predicate elementStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
succ.(DataFlow::ElementReadNode).getBase() = pred
|
||||
or
|
||||
exists(IR::GetNextEntryInstruction nextEntry |
|
||||
pred.asInstruction() = nextEntry.getDomain() and
|
||||
// only step into the value, not the index
|
||||
succ.asInstruction() = IR::extractTupleElement(nextEntry, 1)
|
||||
)
|
||||
}
|
||||
|
||||
deprecated predicate arrayStep = elementStep/2;
|
||||
|
||||
/** Holds if taint flows from `pred` to `succ` via an extract tuple operation. */
|
||||
predicate tupleStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
succ = DataFlow::extractTupleElement(pred, _)
|
||||
}
|
||||
|
||||
/** Holds if taint flows from `pred` to `succ` via string concatenation. */
|
||||
predicate stringConcatStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::BinaryOperationNode conc |
|
||||
conc.getOperator() = "+" and conc.getType() instanceof StringType
|
||||
|
|
||||
succ = conc and conc.getAnOperand() = pred
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if taint flows from `pred` to `succ` via a slice operation. */
|
||||
predicate sliceStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
succ.(DataFlow::SliceNode).getBase() = pred
|
||||
}
|
||||
|
||||
/**
|
||||
* A model of a function specifying that the function propagates taint from
|
||||
* a parameter or qualifier to a result.
|
||||
*/
|
||||
abstract class FunctionModel extends Function {
|
||||
/** Holds if taint propagates through this function from `input` to `output`. */
|
||||
abstract predicate hasTaintFlow(FunctionInput input, FunctionOutput output);
|
||||
|
||||
/** Gets an input node for this model for the call `c`. */
|
||||
DataFlow::Node getAnInputNode(DataFlow::CallNode c) { this.taintStepForCall(result, _, c) }
|
||||
|
||||
/** Gets an output node for this model for the call `c`. */
|
||||
DataFlow::Node getAnOutputNode(DataFlow::CallNode c) { this.taintStepForCall(_, result, c) }
|
||||
|
||||
/** Holds if this function model causes taint to flow from `pred` to `succ` for the call `c`. */
|
||||
predicate taintStepForCall(DataFlow::Node pred, DataFlow::Node succ, DataFlow::CallNode c) {
|
||||
c = this.getACall() and
|
||||
exists(FunctionInput inp, FunctionOutput outp | this.hasTaintFlow(inp, outp) |
|
||||
pred = inp.getNode(c) and
|
||||
succ = outp.getNode(c)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if this function model causes taint to flow from `pred` to `succ`. */
|
||||
predicate taintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
this.taintStepForCall(pred, succ, _)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the additional step from `src` to `sink` should be included in all
|
||||
* global taint flow configurations.
|
||||
*/
|
||||
predicate defaultAdditionalTaintStep(DataFlow::Node src, DataFlow::Node sink) {
|
||||
localAdditionalTaintStep(src, sink)
|
||||
}
|
||||
|
||||
/**
|
||||
* A sanitizer in all global taint flow configurations but not in local taint.
|
||||
*/
|
||||
abstract class DefaultTaintSanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* Holds if `node` should be a sanitizer in all global taint flow configurations
|
||||
* but not in local taint.
|
||||
*/
|
||||
predicate defaultTaintSanitizer(DataFlow::Node node) { node instanceof DefaultTaintSanitizer }
|
||||
|
||||
/**
|
||||
* A sanitizer guard in all global taint flow configurations but not in local taint.
|
||||
*/
|
||||
abstract class DefaultTaintSanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* Holds if `guard` should be a sanitizer guard in all global taint flow configurations
|
||||
* but not in local taint.
|
||||
*/
|
||||
predicate defaultTaintSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof DefaultTaintSanitizerGuard
|
||||
}
|
||||
|
||||
/**
|
||||
* An equality test acting as a sanitizer guard for `nonConstNode` by
|
||||
* restricting it to a known value.
|
||||
*
|
||||
* Note that comparisons to `nil` are excluded. This is needed for performance
|
||||
* reasons.
|
||||
*/
|
||||
class EqualityTestGuard extends DefaultTaintSanitizerGuard, DataFlow::EqualityTestNode {
|
||||
DataFlow::Node nonConstNode;
|
||||
|
||||
EqualityTestGuard() {
|
||||
this.getAnOperand().isConst() and
|
||||
nonConstNode = this.getAnOperand() and
|
||||
not nonConstNode.isConst() and
|
||||
not this.getAnOperand() = Builtin::nil().getARead()
|
||||
}
|
||||
|
||||
override predicate checks(Expr e, boolean outcome) {
|
||||
e = nonConstNode.asExpr() and
|
||||
outcome = this.getPolarity()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data flows from `node` to `switchExprNode`, which is the expression
|
||||
* of a switch statement.
|
||||
*/
|
||||
private predicate flowsToSwitchExpression(DataFlow::Node node, DataFlow::Node switchExprNode) {
|
||||
switchExprNode.asExpr() = any(ExpressionSwitchStmt ess).getExpr() and
|
||||
DataFlow::localFlow(node, switchExprNode)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `inputNode` is the exit node of a parameter to `fd` and data flows
|
||||
* from `inputNode` to the expression of a switch statement.
|
||||
*/
|
||||
private predicate isPossibleInputNode(DataFlow::Node inputNode, FuncDef fd) {
|
||||
inputNode = any(FunctionInput inp | inp.isParameter(_)).getExitNode(fd) and
|
||||
flowsToSwitchExpression(inputNode, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a predecessor of `succ` without following edges corresponding to
|
||||
* passing a constant case test in a switch statement which is switching on
|
||||
* an expression which data flows to from `inputNode`.
|
||||
*/
|
||||
private ControlFlow::Node getANonTestPassingPredecessor(
|
||||
ControlFlow::Node succ, DataFlow::Node inputNode
|
||||
) {
|
||||
isPossibleInputNode(inputNode, succ.getRoot().(FuncDef)) and
|
||||
result = succ.getAPredecessor() and
|
||||
not exists(Expr testExpr, DataFlow::Node switchExprNode |
|
||||
flowsToSwitchExpression(inputNode, switchExprNode) and
|
||||
ControlFlow::isSwitchCaseTestPassingEdge(result, succ, switchExprNode.asExpr(), testExpr) and
|
||||
testExpr.isConst()
|
||||
)
|
||||
}
|
||||
|
||||
private ControlFlow::Node getANonTestPassingReachingNodeRecursive(
|
||||
ControlFlow::Node n, DataFlow::Node inputNode
|
||||
) {
|
||||
isPossibleInputNode(inputNode, n.getRoot().(FuncDef)) and
|
||||
(
|
||||
result = n or
|
||||
result =
|
||||
getANonTestPassingReachingNodeRecursive(getANonTestPassingPredecessor(n, inputNode), inputNode)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node by following predecessors from `ret` without following edges
|
||||
* corresponding to passing a constant case test in a switch statement which is
|
||||
* switching on an expression which data flows to from `inputNode`.
|
||||
*/
|
||||
private ControlFlow::Node getANonTestPassingReachingNodeBase(
|
||||
IR::ReturnInstruction ret, DataFlow::Node inputNode
|
||||
) {
|
||||
result = getANonTestPassingReachingNodeRecursive(ret, inputNode)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if every way to get from the entry node of the function to `ret`
|
||||
* involves passing a constant test case in a switch statement which is
|
||||
* switching on an expression which data flows to from `inputNode`.
|
||||
*/
|
||||
private predicate mustPassConstantCaseTestToReach(
|
||||
IR::ReturnInstruction ret, DataFlow::Node inputNode
|
||||
) {
|
||||
isPossibleInputNode(inputNode, ret.getRoot().(FuncDef)) and
|
||||
not exists(ControlFlow::Node entry | entry = ret.getRoot().getEntryNode() |
|
||||
entry = getANonTestPassingReachingNodeBase(ret, inputNode)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if whenever `outp` of function `f` satisfies `p`, the input `inp` of
|
||||
* `f` matched a constant in a case clause of a switch statement.
|
||||
*
|
||||
* We check this by looking for guards on `inp` that collectively dominate all
|
||||
* the `return` statements in `f` that can return `true`. This means that if
|
||||
* `f` returns `true`, one of the guards must have been satisfied. (Similar
|
||||
* reasoning is applied for statements returning `false`, `nil` or a non-`nil`
|
||||
* value.)
|
||||
*/
|
||||
predicate functionEnsuresInputIsConstant(
|
||||
Function f, FunctionInput inp, FunctionOutput outp, DataFlow::Property p
|
||||
) {
|
||||
exists(FuncDecl fd | fd.getFunction() = f |
|
||||
exists(boolean b |
|
||||
p.isBoolean(b) and
|
||||
forex(DataFlow::Node ret, IR::ReturnInstruction ri |
|
||||
ret = outp.getEntryNode(fd) and
|
||||
ri.getReturnStmt().getAnExpr() = ret.asExpr() and
|
||||
DataFlow::possiblyReturnsBool(fd, outp, ret, b)
|
||||
|
|
||||
mustPassConstantCaseTestToReach(ri, inp.getExitNode(fd))
|
||||
)
|
||||
)
|
||||
or
|
||||
p.isNonNil() and
|
||||
forex(DataFlow::Node ret, IR::ReturnInstruction ri |
|
||||
ret = outp.getEntryNode(fd) and
|
||||
ri.getReturnStmt().getAnExpr() = ret.asExpr() and
|
||||
DataFlow::possiblyReturnsNonNil(fd, outp, ret)
|
||||
|
|
||||
mustPassConstantCaseTestToReach(ri, inp.getExitNode(fd))
|
||||
)
|
||||
or
|
||||
p.isNil() and
|
||||
forex(DataFlow::Node ret, IR::ReturnInstruction ri |
|
||||
ret = outp.getEntryNode(fd) and
|
||||
ri.getReturnStmt().getAnExpr() = ret.asExpr() and
|
||||
ret.asExpr() = Builtin::nil().getAReference()
|
||||
|
|
||||
exists(DataFlow::Node exprNode |
|
||||
DataFlow::localFlow(inp.getExitNode(fd), exprNode) and
|
||||
mustPassConstantCaseTestToReach(ri, inp.getExitNode(fd))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if whenever `outputNode` satisfies `p`, `inputNode` matched a constant
|
||||
* in a case clause of a switch statement.
|
||||
*/
|
||||
pragma[noinline]
|
||||
predicate inputIsConstantIfOutputHasProperty(
|
||||
DataFlow::Node inputNode, DataFlow::Node outputNode, DataFlow::Property p
|
||||
) {
|
||||
exists(Function f, FunctionInput inp, FunctionOutput outp, DataFlow::CallNode call |
|
||||
functionEnsuresInputIsConstant(f, inp, outp, p) and
|
||||
call = f.getACall() and
|
||||
inputNode = inp.getNode(call) and
|
||||
DataFlow::localFlow(outp.getNode(call), outputNode)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A comparison against a list of constants, acting as a sanitizer guard for
|
||||
* `guardedExpr` by restricting it to a known value.
|
||||
*
|
||||
* Currently this only looks for functions containing a switch statement, but
|
||||
* it could equally look for a check for membership of a constant map or
|
||||
* constant array, which does not need to be in its own function.
|
||||
*/
|
||||
class ListOfConstantsComparisonSanitizerGuard extends TaintTracking::DefaultTaintSanitizerGuard {
|
||||
DataFlow::Node guardedExpr;
|
||||
boolean outcome;
|
||||
|
||||
ListOfConstantsComparisonSanitizerGuard() {
|
||||
exists(DataFlow::Node outputNode, DataFlow::Property p |
|
||||
inputIsConstantIfOutputHasProperty(guardedExpr, outputNode, p) and
|
||||
p.checkOn(this, outcome, outputNode)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate checks(Expr e, boolean branch) {
|
||||
e = guardedExpr.asExpr() and branch = outcome
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Provides an implementation of global (interprocedural) taint tracking.
|
||||
* This file re-exports the local (intraprocedural) taint-tracking analysis
|
||||
* from `TaintTrackingParameter::Public` and adds a global analysis, mainly
|
||||
* exposed through the `Configuration` class. For some languages, this file
|
||||
* exists in several identical copies, allowing queries to use multiple
|
||||
* `Configuration` classes that depend on each other without introducing
|
||||
* mutual recursion among those configurations.
|
||||
*/
|
||||
|
||||
import TaintTrackingParameter::Public
|
||||
private import TaintTrackingParameter::Private
|
||||
|
||||
/**
|
||||
* A configuration of interprocedural taint tracking analysis. This defines
|
||||
* sources, sinks, and any other configurable aspect of the analysis. Each
|
||||
* use of the taint tracking library must define its own unique extension of
|
||||
* this abstract class.
|
||||
*
|
||||
* A taint-tracking configuration is a special data flow configuration
|
||||
* (`DataFlow::Configuration`) that allows for flow through nodes that do not
|
||||
* necessarily preserve values but are still relevant from a taint tracking
|
||||
* perspective. (For example, string concatenation, where one of the operands
|
||||
* is tainted.)
|
||||
*
|
||||
* To create a configuration, extend this class with a subclass whose
|
||||
* characteristic predicate is a unique singleton string. For example, write
|
||||
*
|
||||
* ```ql
|
||||
* class MyAnalysisConfiguration extends TaintTracking::Configuration {
|
||||
* MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" }
|
||||
* // Override `isSource` and `isSink`.
|
||||
* // Optionally override `isSanitizer`.
|
||||
* // Optionally override `isSanitizerIn`.
|
||||
* // Optionally override `isSanitizerOut`.
|
||||
* // Optionally override `isSanitizerGuard`.
|
||||
* // Optionally override `isAdditionalTaintStep`.
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Then, to query whether there is flow between some `source` and `sink`,
|
||||
* write
|
||||
*
|
||||
* ```ql
|
||||
* exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink))
|
||||
* ```
|
||||
*
|
||||
* Multiple configurations can coexist, but it is unsupported to depend on
|
||||
* another `TaintTracking::Configuration` or a `DataFlow::Configuration` in the
|
||||
* overridden predicates that define sources, sinks, or additional steps.
|
||||
* Instead, the dependency should go to a `TaintTracking2::Configuration` or a
|
||||
* `DataFlow2::Configuration`, `DataFlow3::Configuration`, etc.
|
||||
*/
|
||||
abstract class Configuration extends DataFlow::Configuration {
|
||||
bindingset[this]
|
||||
Configuration() { any() }
|
||||
|
||||
/**
|
||||
* Holds if `source` is a relevant taint source.
|
||||
*
|
||||
* The smaller this predicate is, the faster `hasFlow()` will converge.
|
||||
*/
|
||||
// overridden to provide taint-tracking specific qldoc
|
||||
abstract override predicate isSource(DataFlow::Node source);
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant taint sink.
|
||||
*
|
||||
* The smaller this predicate is, the faster `hasFlow()` will converge.
|
||||
*/
|
||||
// overridden to provide taint-tracking specific qldoc
|
||||
abstract override predicate isSink(DataFlow::Node sink);
|
||||
|
||||
/** Holds if the node `node` is a taint sanitizer. */
|
||||
predicate isSanitizer(DataFlow::Node node) { none() }
|
||||
|
||||
final override predicate isBarrier(DataFlow::Node node) {
|
||||
isSanitizer(node) or
|
||||
defaultTaintSanitizer(node)
|
||||
}
|
||||
|
||||
/** Holds if taint propagation into `node` is prohibited. */
|
||||
predicate isSanitizerIn(DataFlow::Node node) { none() }
|
||||
|
||||
final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) }
|
||||
|
||||
/** Holds if taint propagation out of `node` is prohibited. */
|
||||
predicate isSanitizerOut(DataFlow::Node node) { none() }
|
||||
|
||||
final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) }
|
||||
|
||||
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
|
||||
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
|
||||
|
||||
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
|
||||
isSanitizerGuard(guard) or defaultTaintSanitizerGuard(guard)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the additional taint propagation step from `node1` to `node2`
|
||||
* must be taken into account in the analysis.
|
||||
*/
|
||||
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
|
||||
|
||||
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
isAdditionalTaintStep(node1, node2) or
|
||||
defaultAdditionalTaintStep(node1, node2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if taint may flow from `source` to `sink` for this configuration.
|
||||
*/
|
||||
// overridden to provide taint-tracking specific qldoc
|
||||
override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) {
|
||||
super.hasFlow(source, sink)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import semmle.go.dataflow.internal.TaintTrackingUtil as Public
|
||||
|
||||
module Private {
|
||||
import semmle.go.dataflow.DataFlow::DataFlow as DataFlow
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Provides an implementation of global (interprocedural) taint tracking.
|
||||
* This file re-exports the local (intraprocedural) taint-tracking analysis
|
||||
* from `TaintTrackingParameter::Public` and adds a global analysis, mainly
|
||||
* exposed through the `Configuration` class. For some languages, this file
|
||||
* exists in several identical copies, allowing queries to use multiple
|
||||
* `Configuration` classes that depend on each other without introducing
|
||||
* mutual recursion among those configurations.
|
||||
*/
|
||||
|
||||
import TaintTrackingParameter::Public
|
||||
private import TaintTrackingParameter::Private
|
||||
|
||||
/**
|
||||
* A configuration of interprocedural taint tracking analysis. This defines
|
||||
* sources, sinks, and any other configurable aspect of the analysis. Each
|
||||
* use of the taint tracking library must define its own unique extension of
|
||||
* this abstract class.
|
||||
*
|
||||
* A taint-tracking configuration is a special data flow configuration
|
||||
* (`DataFlow::Configuration`) that allows for flow through nodes that do not
|
||||
* necessarily preserve values but are still relevant from a taint tracking
|
||||
* perspective. (For example, string concatenation, where one of the operands
|
||||
* is tainted.)
|
||||
*
|
||||
* To create a configuration, extend this class with a subclass whose
|
||||
* characteristic predicate is a unique singleton string. For example, write
|
||||
*
|
||||
* ```ql
|
||||
* class MyAnalysisConfiguration extends TaintTracking::Configuration {
|
||||
* MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" }
|
||||
* // Override `isSource` and `isSink`.
|
||||
* // Optionally override `isSanitizer`.
|
||||
* // Optionally override `isSanitizerIn`.
|
||||
* // Optionally override `isSanitizerOut`.
|
||||
* // Optionally override `isSanitizerGuard`.
|
||||
* // Optionally override `isAdditionalTaintStep`.
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Then, to query whether there is flow between some `source` and `sink`,
|
||||
* write
|
||||
*
|
||||
* ```ql
|
||||
* exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink))
|
||||
* ```
|
||||
*
|
||||
* Multiple configurations can coexist, but it is unsupported to depend on
|
||||
* another `TaintTracking::Configuration` or a `DataFlow::Configuration` in the
|
||||
* overridden predicates that define sources, sinks, or additional steps.
|
||||
* Instead, the dependency should go to a `TaintTracking2::Configuration` or a
|
||||
* `DataFlow2::Configuration`, `DataFlow3::Configuration`, etc.
|
||||
*/
|
||||
abstract class Configuration extends DataFlow::Configuration {
|
||||
bindingset[this]
|
||||
Configuration() { any() }
|
||||
|
||||
/**
|
||||
* Holds if `source` is a relevant taint source.
|
||||
*
|
||||
* The smaller this predicate is, the faster `hasFlow()` will converge.
|
||||
*/
|
||||
// overridden to provide taint-tracking specific qldoc
|
||||
abstract override predicate isSource(DataFlow::Node source);
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant taint sink.
|
||||
*
|
||||
* The smaller this predicate is, the faster `hasFlow()` will converge.
|
||||
*/
|
||||
// overridden to provide taint-tracking specific qldoc
|
||||
abstract override predicate isSink(DataFlow::Node sink);
|
||||
|
||||
/** Holds if the node `node` is a taint sanitizer. */
|
||||
predicate isSanitizer(DataFlow::Node node) { none() }
|
||||
|
||||
final override predicate isBarrier(DataFlow::Node node) {
|
||||
isSanitizer(node) or
|
||||
defaultTaintSanitizer(node)
|
||||
}
|
||||
|
||||
/** Holds if taint propagation into `node` is prohibited. */
|
||||
predicate isSanitizerIn(DataFlow::Node node) { none() }
|
||||
|
||||
final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) }
|
||||
|
||||
/** Holds if taint propagation out of `node` is prohibited. */
|
||||
predicate isSanitizerOut(DataFlow::Node node) { none() }
|
||||
|
||||
final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) }
|
||||
|
||||
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
|
||||
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
|
||||
|
||||
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
|
||||
isSanitizerGuard(guard) or defaultTaintSanitizerGuard(guard)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the additional taint propagation step from `node1` to `node2`
|
||||
* must be taken into account in the analysis.
|
||||
*/
|
||||
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
|
||||
|
||||
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
isAdditionalTaintStep(node1, node2) or
|
||||
defaultAdditionalTaintStep(node1, node2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if taint may flow from `source` to `sink` for this configuration.
|
||||
*/
|
||||
// overridden to provide taint-tracking specific qldoc
|
||||
override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) {
|
||||
super.hasFlow(source, sink)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import semmle.go.dataflow.internal.TaintTrackingUtil as Public
|
||||
|
||||
module Private {
|
||||
import semmle.go.dataflow.DataFlow2::DataFlow2 as DataFlow
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Provides classes for modeling go.mod dependencies.
|
||||
*/
|
||||
|
||||
import go
|
||||
|
||||
/**
|
||||
* An abstract representation of a dependency.
|
||||
*/
|
||||
abstract class Dependency extends Locatable {
|
||||
/**
|
||||
* Holds if this dependency has package path `path` and version `v`.
|
||||
*
|
||||
* If the version cannot be determined, `v` is bound to the string
|
||||
* `"unknown"`.
|
||||
*/
|
||||
abstract predicate info(string path, string v);
|
||||
|
||||
/** Gets the package path of this dependency. */
|
||||
string getDepPath() { this.info(result, _) }
|
||||
|
||||
/** Gets the version of this dependency. */
|
||||
string getDepVersion() { this.info(_, result) }
|
||||
|
||||
/**
|
||||
* Holds if this dependency is relevant for imports in file `file`. That is, an import of this
|
||||
* dependency's path that is in `file` will use this dependency.
|
||||
*/
|
||||
abstract predicate relevantForFile(File file);
|
||||
|
||||
/**
|
||||
* An import of this dependency.
|
||||
*/
|
||||
ImportSpec getAnImport() {
|
||||
result.getPath().regexpMatch("\\Q" + this.getDepPath() + "\\E(/.*)?") and
|
||||
this.relevantForFile(result.getFile())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A dependency from a go.mod file.
|
||||
*/
|
||||
class GoModDependency extends Dependency, GoModRequireLine {
|
||||
override predicate info(string path, string v) {
|
||||
this.replacementInfo(path, v)
|
||||
or
|
||||
not this.replacementInfo(_, _) and
|
||||
this.originalInfo(path, v)
|
||||
}
|
||||
|
||||
override predicate relevantForFile(File file) {
|
||||
exists(Folder parent | parent.getAFile() = this.getFile() |
|
||||
parent.getAFolder*().getAFile() = file
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a replace line that replaces this dependency with a dependency on `path`,
|
||||
* version `v`.
|
||||
*/
|
||||
predicate replacementInfo(string path, string v) {
|
||||
exists(GoModReplaceLine replace |
|
||||
replace.getFile() = this.getFile() and
|
||||
replace.getOriginalPath() = this.getPath()
|
||||
|
|
||||
path = replace.getReplacementPath() and
|
||||
(
|
||||
v = replace.getReplacementVersion()
|
||||
or
|
||||
not exists(replace.getReplacementVersion()) and
|
||||
v = "unknown"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a version that was excluded for this dependency.
|
||||
*/
|
||||
string getAnExcludedVersion() {
|
||||
exists(GoModExcludeLine exclude |
|
||||
exclude.getFile() = this.getFile() and
|
||||
exclude.getPath() = this.getPath()
|
||||
|
|
||||
result = exclude.getVersion()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this require line originally states dependency `path` had version `ver`.
|
||||
*
|
||||
* The actual info of this dependency can change based on `replace` directives in the same go.mod
|
||||
* file, which replace a dependency with another one.
|
||||
*/
|
||||
predicate originalInfo(string path, string v) { path = this.getPath() and v = this.getVersion() }
|
||||
}
|
||||
100
repo-tests/codeql-go/ql/lib/semmle/go/dependencies/SemVer.qll
Normal file
100
repo-tests/codeql-go/ql/lib/semmle/go/dependencies/SemVer.qll
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Provides classes for dealing with semantic versions, for dependency versions.
|
||||
*/
|
||||
|
||||
import semmle.go.dependencies.Dependencies
|
||||
|
||||
/**
|
||||
* A SemVer-formatted version string in a dependency.
|
||||
*
|
||||
* Pre-release information and build metadata is not yet supported.
|
||||
*/
|
||||
class DependencySemVer extends string {
|
||||
Dependency dep;
|
||||
string normalized;
|
||||
|
||||
DependencySemVer() {
|
||||
this = dep.getDepVersion() and
|
||||
normalized = normalizeSemver(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this version may be before `last`.
|
||||
*/
|
||||
bindingset[last]
|
||||
predicate maybeBefore(string last) { normalized < normalizeSemver(last) }
|
||||
|
||||
/**
|
||||
* Holds if this version may be after `first`.
|
||||
*/
|
||||
bindingset[first]
|
||||
predicate maybeAfter(string first) { normalizeSemver(first) < normalized }
|
||||
|
||||
/**
|
||||
* Holds if this version may be between `first` (inclusive) and `last` (exclusive).
|
||||
*/
|
||||
bindingset[first, last]
|
||||
predicate maybeBetween(string first, string last) {
|
||||
normalizeSemver(first) <= normalized and
|
||||
normalized < normalizeSemver(last)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this version is equivalent to `other`.
|
||||
*/
|
||||
bindingset[other]
|
||||
predicate is(string other) { normalized = normalizeSemver(other) }
|
||||
|
||||
/**
|
||||
* Gets the dependency that uses this string.
|
||||
*/
|
||||
Dependency getDependency() { result = dep }
|
||||
}
|
||||
|
||||
bindingset[str]
|
||||
private string leftPad(string str) { result = ("000" + str).suffix(str.length()) }
|
||||
|
||||
/**
|
||||
* Normalizes a SemVer string such that the lexicographical ordering
|
||||
* of two normalized strings is consistent with the SemVer ordering.
|
||||
*
|
||||
* Pre-release information and build metadata is not yet supported.
|
||||
*/
|
||||
bindingset[orig]
|
||||
private string normalizeSemver(string orig) {
|
||||
exists(string pattern, string major, string minor, string patch |
|
||||
pattern = "v?(\\d+)\\.(\\d+)\\.(\\d+)(\\D.*)?" and
|
||||
major = orig.regexpCapture(pattern, 1) and
|
||||
minor = orig.regexpCapture(pattern, 2) and
|
||||
patch = orig.regexpCapture(pattern, 3)
|
||||
|
|
||||
result = leftPad(major) + "." + leftPad(minor) + "." + leftPad(patch)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A version string in a dependency that has a SemVer, but also contains a git commit SHA.
|
||||
*
|
||||
* This class is useful for interacting with go.mod versions, which use SemVer, but can also contain
|
||||
* SHAs if no useful tags are found, or when a user wishes to specify a commit SHA.
|
||||
*
|
||||
* Pre-release information and build metadata is not yet supported.
|
||||
*/
|
||||
class DependencySemShaVersion extends DependencySemVer {
|
||||
string sha;
|
||||
|
||||
DependencySemShaVersion() { sha = this.regexpCapture(".*-([0-9a-f]+)", 1) }
|
||||
|
||||
/**
|
||||
* Gets the commit SHA associated with this version.
|
||||
*/
|
||||
string getSha() { result = sha }
|
||||
|
||||
bindingset[other]
|
||||
override predicate is(string other) {
|
||||
this.getSha() = other.(DependencySemShaVersion).getSha()
|
||||
or
|
||||
not other instanceof DependencySemShaVersion and
|
||||
super.is(other)
|
||||
}
|
||||
}
|
||||
359
repo-tests/codeql-go/ql/lib/semmle/go/frameworks/Beego.qll
Normal file
359
repo-tests/codeql-go/ql/lib/semmle/go/frameworks/Beego.qll
Normal file
@@ -0,0 +1,359 @@
|
||||
/**
|
||||
* Provides classes for working with untrusted flow sources, sinks and taint propagators
|
||||
* from the `github.com/beego/beego` package.
|
||||
*/
|
||||
|
||||
import go
|
||||
import semmle.go.security.Xss
|
||||
private import semmle.go.security.SafeUrlFlowCustomizations
|
||||
|
||||
/**
|
||||
* Provides classes for working with untrusted flow sources, sinks and taint propagators
|
||||
* from the [Beego](`github.com/beego/beego`) package.
|
||||
*/
|
||||
module Beego {
|
||||
/** Gets the module path `github.com/astaxie/beego` or `github.com/beego/beego`. */
|
||||
string modulePath() { result = ["github.com/astaxie/beego", "github.com/beego/beego"] }
|
||||
|
||||
/** Gets the path for the root package of beego. */
|
||||
string packagePath() { result = package(modulePath(), "") }
|
||||
|
||||
/** Gets the path for the context package of beego. */
|
||||
string contextPackagePath() { result = package(modulePath(), "context") }
|
||||
|
||||
/** Gets the path for the logs package of beego. */
|
||||
string logsPackagePath() { result = package(modulePath(), "logs") }
|
||||
|
||||
/** Gets the path for the utils package of beego. */
|
||||
string utilsPackagePath() { result = package(modulePath(), "utils") }
|
||||
|
||||
/**
|
||||
* `BeegoInput` sources of untrusted data.
|
||||
*/
|
||||
private class BeegoInputSource extends UntrustedFlowSource::Range {
|
||||
string methodName;
|
||||
FunctionOutput output;
|
||||
|
||||
BeegoInputSource() {
|
||||
exists(DataFlow::MethodCallNode c | this = output.getExitNode(c) |
|
||||
c.getTarget().hasQualifiedName(contextPackagePath(), "BeegoInput", methodName)
|
||||
) and
|
||||
(
|
||||
methodName = "Bind" and
|
||||
output.isParameter(0)
|
||||
or
|
||||
methodName in [
|
||||
"Cookie", "Data", "GetData", "Header", "Param", "Params", "Query", "Refer", "Referer",
|
||||
"URI", "URL", "UserAgent"
|
||||
] and
|
||||
output.isResult(0)
|
||||
)
|
||||
}
|
||||
|
||||
predicate isSafeUrlSource() { methodName in ["URI", "URL"] }
|
||||
}
|
||||
|
||||
/** `BeegoInput` sources that are safe to use for redirection. */
|
||||
private class BeegoInputSafeUrlSource extends SafeUrlFlow::Source {
|
||||
BeegoInputSafeUrlSource() { this.(BeegoInputSource).isSafeUrlSource() }
|
||||
}
|
||||
|
||||
/**
|
||||
* `beego.Controller` sources of untrusted data.
|
||||
*/
|
||||
private class BeegoControllerSource extends UntrustedFlowSource::Range {
|
||||
string methodName;
|
||||
FunctionOutput output;
|
||||
|
||||
BeegoControllerSource() {
|
||||
exists(DataFlow::MethodCallNode c |
|
||||
c.getTarget().hasQualifiedName(packagePath(), "Controller", methodName)
|
||||
|
|
||||
this = output.getExitNode(c)
|
||||
) and
|
||||
(
|
||||
methodName = "ParseForm" and
|
||||
output.isParameter(0)
|
||||
or
|
||||
methodName in ["GetFile", "GetFiles", "GetString", "GetStrings", "Input"] and
|
||||
output.isResult(0)
|
||||
or
|
||||
methodName = "GetFile" and
|
||||
output.isResult(1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `beego/context.Context` sources of untrusted data.
|
||||
*/
|
||||
private class BeegoContextSource extends UntrustedFlowSource::Range {
|
||||
BeegoContextSource() {
|
||||
exists(Method m | m.hasQualifiedName(contextPackagePath(), "Context", "GetCookie") |
|
||||
this = m.getACall().getResult()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class BeegoOutputInstance extends HTTP::ResponseWriter::Range {
|
||||
SsaWithFields v;
|
||||
|
||||
BeegoOutputInstance() {
|
||||
this = v.getBaseVariable().getSourceVariable() and
|
||||
v.getType().(PointerType).getBaseType().hasQualifiedName(contextPackagePath(), "BeegoOutput")
|
||||
}
|
||||
|
||||
override DataFlow::Node getANode() { result = v.similar().getAUse().getASuccessor*() }
|
||||
|
||||
/** Gets a header object that corresponds to this HTTP response. */
|
||||
DataFlow::MethodCallNode getAHeaderObject() {
|
||||
result.getTarget().getName() = ["ContentType", "Header"] and
|
||||
this.getANode() = result.getReceiver()
|
||||
}
|
||||
}
|
||||
|
||||
private class BeegoHeaderWrite extends HTTP::HeaderWrite::Range, DataFlow::MethodCallNode {
|
||||
string methodName;
|
||||
|
||||
BeegoHeaderWrite() {
|
||||
this.getTarget().hasQualifiedName(contextPackagePath(), "BeegoOutput", methodName) and
|
||||
methodName in ["ContentType", "Header"]
|
||||
}
|
||||
|
||||
override DataFlow::Node getName() { methodName = "Header" and result = this.getArgument(0) }
|
||||
|
||||
override string getHeaderName() {
|
||||
result = HTTP::HeaderWrite::Range.super.getHeaderName()
|
||||
or
|
||||
methodName = "ContentType" and result = "content-type"
|
||||
}
|
||||
|
||||
override DataFlow::Node getValue() {
|
||||
if methodName = "ContentType"
|
||||
then result = this.getArgument(0)
|
||||
else result = this.getArgument(1)
|
||||
}
|
||||
|
||||
override HTTP::ResponseWriter getResponseWriter() {
|
||||
result.(BeegoOutputInstance).getAHeaderObject() = this
|
||||
}
|
||||
}
|
||||
|
||||
private class BeegoResponseBody extends HTTP::ResponseBody::Range {
|
||||
DataFlow::MethodCallNode call;
|
||||
string methodName;
|
||||
|
||||
BeegoResponseBody() {
|
||||
exists(Method m | m.hasQualifiedName(contextPackagePath(), "BeegoOutput", methodName) |
|
||||
call = m.getACall() and
|
||||
this = call.getArgument(0)
|
||||
) and
|
||||
methodName in ["Body", "JSON", "JSONP", "ServeFormatted", "XML", "YAML"]
|
||||
}
|
||||
|
||||
override HTTP::ResponseWriter getResponseWriter() { result.getANode() = call.getReceiver() }
|
||||
|
||||
override string getAContentType() {
|
||||
// Super-method provides content-types for `Body`, which requires us to search
|
||||
// for `ContentType` and `Header` calls against the same `BeegoOutput` instance
|
||||
result = super.getAContentType()
|
||||
or
|
||||
// Specifically describe methods that set the content-type and body in one operation:
|
||||
result = "application/json" and methodName = "JSON"
|
||||
or
|
||||
result = "application/javascript" and methodName = "JSONP"
|
||||
or
|
||||
// Actually ServeFormatted can serve JSON, XML or YAML depending on the incoming
|
||||
// `Accept` header, but the important bit is this method cannot serve text/html.
|
||||
result = "application/json" and methodName = "ServeFormatted"
|
||||
or
|
||||
result = "text/xml" and methodName = "XML"
|
||||
or
|
||||
result = "application/x-yaml" and methodName = "YAML"
|
||||
}
|
||||
}
|
||||
|
||||
private class ControllerResponseBody extends HTTP::ResponseBody::Range {
|
||||
string name;
|
||||
|
||||
ControllerResponseBody() {
|
||||
exists(Method m | m.hasQualifiedName(packagePath(), "Controller", name) |
|
||||
name = "CustomAbort" and this = m.getACall().getArgument(1)
|
||||
or
|
||||
name = "SetData" and this = m.getACall().getArgument(0)
|
||||
)
|
||||
}
|
||||
|
||||
override HTTP::ResponseWriter getResponseWriter() { none() }
|
||||
|
||||
override string getAContentType() {
|
||||
// Actually SetData can serve JSON, XML or YAML depending on the incoming
|
||||
// `Accept` header, but the important bit is this method cannot serve text/html.
|
||||
result = "application/json" and name = "SetData"
|
||||
// CustomAbort doesn't specify a content type, so we assume anything could happen.
|
||||
}
|
||||
}
|
||||
|
||||
private class ContextResponseBody extends HTTP::ResponseBody::Range {
|
||||
string name;
|
||||
|
||||
ContextResponseBody() {
|
||||
exists(Method m | m.hasQualifiedName(contextPackagePath(), "Context", name) |
|
||||
name = "Abort" and this = m.getACall().getArgument(1)
|
||||
or
|
||||
name = "WriteString" and this = m.getACall().getArgument(0)
|
||||
)
|
||||
}
|
||||
|
||||
override HTTP::ResponseWriter getResponseWriter() { none() }
|
||||
|
||||
// Neither method is likely to be used with well-typed data such as JSON output,
|
||||
// because there are better methods to do this. Assume the Content-Type could
|
||||
// be anything.
|
||||
override string getAContentType() { none() }
|
||||
}
|
||||
|
||||
private string getALogFunctionName() {
|
||||
result =
|
||||
[
|
||||
"Alert", "Critical", "Debug", "Emergency", "Error", "Info", "Informational", "Notice",
|
||||
"Trace", "Warn", "Warning"
|
||||
]
|
||||
}
|
||||
|
||||
private class ToplevelBeegoLoggers extends LoggerCall::Range, DataFlow::CallNode {
|
||||
ToplevelBeegoLoggers() {
|
||||
this.getTarget().hasQualifiedName([packagePath(), logsPackagePath()], getALogFunctionName())
|
||||
}
|
||||
|
||||
override DataFlow::Node getAMessageComponent() { result = this.getAnArgument() }
|
||||
}
|
||||
|
||||
private class BeegoLoggerMethods extends LoggerCall::Range, DataFlow::MethodCallNode {
|
||||
BeegoLoggerMethods() {
|
||||
this.getTarget().hasQualifiedName(logsPackagePath(), "BeeLogger", getALogFunctionName())
|
||||
}
|
||||
|
||||
override DataFlow::Node getAMessageComponent() { result = this.getAnArgument() }
|
||||
}
|
||||
|
||||
private class UtilLoggers extends LoggerCall::Range, DataFlow::CallNode {
|
||||
UtilLoggers() { this.getTarget().hasQualifiedName(utilsPackagePath(), "Display") }
|
||||
|
||||
override DataFlow::Node getAMessageComponent() { result = this.getAnArgument() }
|
||||
}
|
||||
|
||||
private class TopLevelTaintPropagators extends TaintTracking::FunctionModel {
|
||||
string name;
|
||||
|
||||
TopLevelTaintPropagators() {
|
||||
this.hasQualifiedName(packagePath(), name) and
|
||||
name in ["HTML2str", "Htmlquote", "Htmlunquote", "MapGet", "ParseForm", "Str2html", "Substr"]
|
||||
}
|
||||
|
||||
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
|
||||
name in ["HTML2str", "Htmlquote", "Htmlunquote", "MapGet", "Str2html", "Substr"] and
|
||||
input.isParameter(0) and
|
||||
output.isResult(0)
|
||||
or
|
||||
name = "ParseForm" and
|
||||
input.isParameter(0) and
|
||||
output.isParameter(1)
|
||||
}
|
||||
}
|
||||
|
||||
private class ContextTaintPropagators extends TaintTracking::FunctionModel {
|
||||
ContextTaintPropagators() { this.hasQualifiedName(contextPackagePath(), "WriteBody") }
|
||||
|
||||
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
|
||||
input.isParameter(2) and output.isParameter(1)
|
||||
}
|
||||
}
|
||||
|
||||
private class HtmlQuoteSanitizer extends SharedXss::Sanitizer {
|
||||
HtmlQuoteSanitizer() {
|
||||
exists(DataFlow::CallNode c | c.getTarget().hasQualifiedName(packagePath(), "Htmlquote") |
|
||||
this = c.getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class FsOperations extends FileSystemAccess::Range, DataFlow::CallNode {
|
||||
FsOperations() {
|
||||
this.getTarget().hasQualifiedName(packagePath(), "Walk")
|
||||
or
|
||||
exists(Method m | this = m.getACall() |
|
||||
m.hasQualifiedName(packagePath(), "FileSystem", "Open") or
|
||||
m.hasQualifiedName(packagePath(), "Controller", "SaveToFile")
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAPathArgument() {
|
||||
this.getTarget().getName() = ["Walk", "SaveToFile"] and result = this.getArgument(1)
|
||||
or
|
||||
this.getTarget().getName() = "Open" and result = this.getArgument(0)
|
||||
}
|
||||
}
|
||||
|
||||
private class RedirectMethods extends HTTP::Redirect::Range, DataFlow::CallNode {
|
||||
string package;
|
||||
string className;
|
||||
|
||||
RedirectMethods() {
|
||||
(
|
||||
package = packagePath() and className = "Controller"
|
||||
or
|
||||
package = contextPackagePath() and className = "Context"
|
||||
) and
|
||||
this = any(Method m | m.hasQualifiedName(package, className, "Redirect")).getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getUrl() {
|
||||
className = "Controller" and result = this.getArgument(0)
|
||||
or
|
||||
className = "Context" and result = this.getArgument(1)
|
||||
}
|
||||
|
||||
override HTTP::ResponseWriter getResponseWriter() { none() }
|
||||
}
|
||||
|
||||
private class UtilsTaintPropagators extends TaintTracking::FunctionModel {
|
||||
string name;
|
||||
|
||||
UtilsTaintPropagators() {
|
||||
this.hasQualifiedName(utilsPackagePath(), name) and
|
||||
name in [
|
||||
"GetDisplayString", "SliceChunk", "SliceDiff", "SliceFilter", "SliceIntersect",
|
||||
"SliceMerge", "SlicePad", "SliceRand", "SliceReduce", "SliceShuffle", "SliceUnique"
|
||||
]
|
||||
}
|
||||
|
||||
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
|
||||
name in [
|
||||
"GetDisplayString", "SliceIntersect", "SliceMerge", "SlicePad", "SliceRand",
|
||||
"SliceShuffle", "SliceUnique"
|
||||
] and
|
||||
input.isParameter(_) and
|
||||
output.isResult(0)
|
||||
or
|
||||
name in ["SliceChunk", "SliceDiff", "SliceFilter", "SliceReduce"] and
|
||||
input.isParameter(0) and
|
||||
output.isResult(0)
|
||||
}
|
||||
}
|
||||
|
||||
private class BeeMapModels extends TaintTracking::FunctionModel, Method {
|
||||
string name;
|
||||
|
||||
BeeMapModels() {
|
||||
this.hasQualifiedName(utilsPackagePath(), "BeeMap", name) and
|
||||
name in ["Get", "Set", "Items"]
|
||||
}
|
||||
|
||||
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
|
||||
name = "Set" and input.isParameter(1) and output.isReceiver()
|
||||
or
|
||||
name in ["Get", "Items"] and input.isReceiver() and output.isResult(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
101
repo-tests/codeql-go/ql/lib/semmle/go/frameworks/BeegoOrm.qll
Normal file
101
repo-tests/codeql-go/ql/lib/semmle/go/frameworks/BeegoOrm.qll
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Provides classes for working with untrusted flow sources, sinks and taint propagators
|
||||
* from the `github.com/astaxie/beego/orm` subpackage.
|
||||
*/
|
||||
|
||||
import go
|
||||
private import semmle.go.security.StoredXssCustomizations
|
||||
|
||||
/**
|
||||
* Provides classes for working with untrusted flow sources, sinks and taint propagators
|
||||
* from the [Beego ORM](`github.com/astaxie/beego/orm`) subpackage.
|
||||
*/
|
||||
module BeegoOrm {
|
||||
/** Gets the package name `github.com/astaxie/beego/orm`. */
|
||||
string packagePath() { result = package("github.com/astaxie/beego", "orm") }
|
||||
|
||||
private class DbSink extends SQL::QueryString::Range {
|
||||
DbSink() {
|
||||
exists(Method m, string methodName, int argNum |
|
||||
m.hasQualifiedName(packagePath(), "DB", methodName) and
|
||||
methodName in [
|
||||
"Exec", "ExecContext", "Prepare", "PrepareContext", "Query", "QueryContext", "QueryRow",
|
||||
"QueryRowContext"
|
||||
] and
|
||||
if methodName.matches("%Context") then argNum = 1 else argNum = 0
|
||||
|
|
||||
this = m.getACall().getArgument(argNum)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class QueryBuilderSink extends SQL::QueryString::Range {
|
||||
// Note this class doesn't do any escaping, unlike the true ORM part of the package
|
||||
QueryBuilderSink() {
|
||||
exists(Method impl | impl.implements(packagePath(), "QueryBuilder", _) |
|
||||
this = impl.getACall().getAnArgument()
|
||||
) and
|
||||
this.getType().getUnderlyingType() instanceof StringType
|
||||
}
|
||||
}
|
||||
|
||||
private class OrmerRawSink extends SQL::QueryString::Range {
|
||||
OrmerRawSink() {
|
||||
exists(Method impl | impl.implements(packagePath(), "Ormer", "Raw") |
|
||||
this = impl.getACall().getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class QuerySeterFilterRawSink extends SQL::QueryString::Range {
|
||||
QuerySeterFilterRawSink() {
|
||||
exists(Method impl | impl.implements(packagePath(), "QuerySeter", "FilterRaw") |
|
||||
this = impl.getACall().getArgument(1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class ConditionRawSink extends SQL::QueryString::Range {
|
||||
ConditionRawSink() {
|
||||
exists(Method impl | impl.implements(packagePath(), "Condition", "Raw") |
|
||||
this = impl.getACall().getArgument(1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class OrmerSource extends StoredXss::Source {
|
||||
OrmerSource() {
|
||||
exists(Method impl |
|
||||
impl.implements(packagePath(), "Ormer", ["Read", "ReadForUpdate", "ReadOrCreate"])
|
||||
|
|
||||
this = FunctionOutput::parameter(0).getExitNode(impl.getACall())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class StringFieldSource extends StoredXss::Source {
|
||||
StringFieldSource() {
|
||||
exists(Method m |
|
||||
m.hasQualifiedName(packagePath(), ["JSONField", "JsonbField", "TextField"],
|
||||
["RawValue", "String", "Value"])
|
||||
|
|
||||
this = m.getACall().getResult()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class SeterSource extends StoredXss::Source {
|
||||
SeterSource() {
|
||||
exists(Method impl |
|
||||
// All and One are exclusive to QuerySeter, QueryRow[s] are exclusive to RawSeter, the rest are common.
|
||||
impl.implements(packagePath(), ["QuerySeter", "RawSeter"],
|
||||
[
|
||||
"All", "One", "Values", "ValuesList", "ValuesFlat", "RowsToMap", "RowsToStruct",
|
||||
"QueryRow", "QueryRows"
|
||||
])
|
||||
|
|
||||
this = FunctionOutput::parameter(0).getExitNode(impl.getACall())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
29
repo-tests/codeql-go/ql/lib/semmle/go/frameworks/Chi.qll
Normal file
29
repo-tests/codeql-go/ql/lib/semmle/go/frameworks/Chi.qll
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Provides classes for working with untrusted flow sources from the `github.com/go-chi/chi` package.
|
||||
*/
|
||||
|
||||
import go
|
||||
|
||||
private module Chi {
|
||||
/** Gets the package name `github.com/go-chi/chi`. */
|
||||
string packagePath() { result = package("github.com/go-chi/chi", "") }
|
||||
|
||||
/**
|
||||
* Functions that extract URL parameters, considered as a source of untrusted flow.
|
||||
*/
|
||||
private class UserControlledFunction extends UntrustedFlowSource::Range, DataFlow::CallNode {
|
||||
UserControlledFunction() {
|
||||
this.getTarget().hasQualifiedName(packagePath(), ["URLParam", "URLParamFromCtx"])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods that extract URL parameters, considered as a source of untrusted flow.
|
||||
*/
|
||||
private class UserControlledRequestMethod extends UntrustedFlowSource::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
UserControlledRequestMethod() {
|
||||
this.getTarget().hasQualifiedName(packagePath(), "Context", "URLParam")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Provides models of commonly used functions in the official Couchbase Go SDK library.
|
||||
*/
|
||||
|
||||
import go
|
||||
|
||||
/**
|
||||
* Provides models of commonly used functions in the official Couchbase Go SDK library.
|
||||
*/
|
||||
module Couchbase {
|
||||
/**
|
||||
* Gets a package path for the official Couchbase Go SDK library.
|
||||
*
|
||||
* Note that v1 and v2 have different APIs, but the names are disjoint so there is no need to
|
||||
* distinguish between them.
|
||||
*/
|
||||
string packagePath() {
|
||||
result =
|
||||
package([
|
||||
"gopkg.in/couchbase/gocb", "github.com/couchbase/gocb", "github.com/couchbaselabs/gocb"
|
||||
], "")
|
||||
}
|
||||
|
||||
/**
|
||||
* Models of methods on `gocb/AnalyticsQuery` and `gocb/N1qlQuery` which which support a fluent
|
||||
* interface by returning the receiver. They are not inherently relevant to taint.
|
||||
*/
|
||||
private class QueryMethodV1 extends TaintTracking::FunctionModel, Method {
|
||||
QueryMethodV1() {
|
||||
exists(string queryTypeName, string methodName |
|
||||
queryTypeName = "AnalyticsQuery" and
|
||||
methodName in [
|
||||
"ContextId", "Deferred", "Pretty", "Priority", "RawParam", "ServerSideTimeout"
|
||||
]
|
||||
or
|
||||
queryTypeName = "N1qlQuery" and
|
||||
methodName in [
|
||||
"AdHoc", "Consistency", "ConsistentWith", "Custom", "PipelineBatch", "PipelineCap",
|
||||
"Profile", "ReadOnly", "ScanCap", "Timeout"
|
||||
]
|
||||
|
|
||||
this.hasQualifiedName(packagePath(), queryTypeName, methodName)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate hasTaintFlow(FunctionInput inp, FunctionOutput outp) {
|
||||
inp.isReceiver() and outp.isResult()
|
||||
}
|
||||
}
|
||||
|
||||
private class QueryFromN1qlStatementV1 extends TaintTracking::FunctionModel {
|
||||
QueryFromN1qlStatementV1() {
|
||||
this.hasQualifiedName(packagePath(), ["NewAnalyticsQuery", "NewN1qlQuery"])
|
||||
}
|
||||
|
||||
override predicate hasTaintFlow(FunctionInput inp, FunctionOutput outp) {
|
||||
inp.isParameter(0) and outp.isResult()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A query used in an API function acting on a `Bucket` or `Cluster` struct of v1 of
|
||||
* the official Couchbase Go library, gocb.
|
||||
*/
|
||||
private class CouchbaseV1Query extends NoSQL::Query::Range {
|
||||
CouchbaseV1Query() {
|
||||
// func (b *Bucket) ExecuteAnalyticsQuery(q *AnalyticsQuery, params interface{}) (AnalyticsResults, error)
|
||||
// func (b *Bucket) ExecuteN1qlQuery(q *N1qlQuery, params interface{}) (QueryResults, error)
|
||||
// func (c *Cluster) ExecuteAnalyticsQuery(q *AnalyticsQuery, params interface{}) (AnalyticsResults, error)
|
||||
// func (c *Cluster) ExecuteN1qlQuery(q *N1qlQuery, params interface{}) (QueryResults, error)
|
||||
exists(Method meth, string structName, string methodName |
|
||||
structName in ["Bucket", "Cluster"] and
|
||||
methodName in ["ExecuteN1qlQuery", "ExecuteAnalyticsQuery"] and
|
||||
meth.hasQualifiedName(packagePath(), structName, methodName) and
|
||||
this = meth.getACall().getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A query used in an API function acting on a `Bucket` or `Cluster` struct of v1 of
|
||||
* the official Couchbase Go library, gocb.
|
||||
*/
|
||||
private class CouchbaseV2Query extends NoSQL::Query::Range {
|
||||
CouchbaseV2Query() {
|
||||
// func (c *Cluster) AnalyticsQuery(statement string, opts *AnalyticsOptions) (*AnalyticsResult, error)
|
||||
// func (c *Cluster) Query(statement string, opts *QueryOptions) (*QueryResult, error)
|
||||
// func (s *Scope) AnalyticsQuery(statement string, opts *AnalyticsOptions) (*AnalyticsResult, error)
|
||||
// func (s *Scope) Query(statement string, opts *QueryOptions) (*QueryResult, error)
|
||||
exists(Method meth, string structName, string methodName |
|
||||
structName in ["Cluster", "Scope"] and
|
||||
methodName in ["AnalyticsQuery", "Query"] and
|
||||
meth.hasQualifiedName(packagePath(), structName, methodName) and
|
||||
this = meth.getACall().getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
123
repo-tests/codeql-go/ql/lib/semmle/go/frameworks/Echo.qll
Normal file
123
repo-tests/codeql-go/ql/lib/semmle/go/frameworks/Echo.qll
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Provides classes for working with untrusted flow sources, taint propagators, and HTTP sinks
|
||||
* from the `github.com/labstack/echo` package.
|
||||
*/
|
||||
|
||||
import go
|
||||
|
||||
private module Echo {
|
||||
/** Gets the package name `github.com/labstack/echo`. */
|
||||
private string packagePath() { result = package("github.com/labstack/echo", "") }
|
||||
|
||||
/**
|
||||
* Data from a `Context` interface method, considered as a source of untrusted flow.
|
||||
*/
|
||||
private class EchoContextSource extends UntrustedFlowSource::Range {
|
||||
EchoContextSource() {
|
||||
exists(DataFlow::MethodCallNode call, string methodName |
|
||||
methodName =
|
||||
[
|
||||
"Param", "ParamValues", "QueryParam", "QueryParams", "QueryString", "FormValue",
|
||||
"FormParams", "FormFile", "MultipartForm", "Cookie", "Cookies"
|
||||
] and
|
||||
call.getTarget().hasQualifiedName(packagePath(), "Context", methodName) and
|
||||
this = call.getResult(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data from a `Context` interface method that is not generally exploitable for open-redirect attacks.
|
||||
*/
|
||||
private class EchoContextRedirectUnexploitableSource extends HTTP::Redirect::UnexploitableSource {
|
||||
EchoContextRedirectUnexploitableSource() {
|
||||
exists(DataFlow::MethodCallNode call, string methodName |
|
||||
methodName = ["FormValue", "FormParams", "FormFile", "MultipartForm", "Cookie", "Cookies"] and
|
||||
call.getTarget().hasQualifiedName(packagePath(), "Context", methodName) and
|
||||
this = call.getResult(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Models of `Context.Get/Set`. `Context` behaves like a map, with corresponding taint propagation.
|
||||
*/
|
||||
private class ContextMapModels extends TaintTracking::FunctionModel, Method {
|
||||
string methodName;
|
||||
FunctionInput input;
|
||||
FunctionOutput output;
|
||||
|
||||
ContextMapModels() {
|
||||
(
|
||||
methodName = "Get" and input.isReceiver() and output.isResult()
|
||||
or
|
||||
methodName = "Set" and input.isParameter(1) and output.isReceiver()
|
||||
) and
|
||||
this.hasQualifiedName(packagePath(), "Context", methodName)
|
||||
}
|
||||
|
||||
override predicate hasTaintFlow(FunctionInput inp, FunctionOutput outp) {
|
||||
inp = input and outp = output
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a method on `Context` struct that unmarshals data into a target.
|
||||
*/
|
||||
private class EchoContextBinder extends UntrustedFlowSource::Range {
|
||||
EchoContextBinder() {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getTarget().hasQualifiedName(packagePath(), "Context", "Bind")
|
||||
|
|
||||
this = FunctionOutput::parameter(0).getExitNode(call)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `echo.Context` methods which set the content-type to `text/html` and write a result in one operation.
|
||||
*/
|
||||
private class EchoHtmlOutputs extends HTTP::ResponseBody::Range {
|
||||
EchoHtmlOutputs() {
|
||||
exists(Method m | m.hasQualifiedName(packagePath(), "Context", ["HTML", "HTMLBlob"]) |
|
||||
this = m.getACall().getArgument(1)
|
||||
)
|
||||
}
|
||||
|
||||
override HTTP::ResponseWriter getResponseWriter() { none() }
|
||||
|
||||
override string getAContentType() { result = "text/html" }
|
||||
}
|
||||
|
||||
/**
|
||||
* `echo.Context` methods which take a content-type as a parameter.
|
||||
*/
|
||||
private class EchoParameterizedOutputs extends HTTP::ResponseBody::Range {
|
||||
DataFlow::CallNode callNode;
|
||||
|
||||
EchoParameterizedOutputs() {
|
||||
exists(Method m | m.hasQualifiedName(packagePath(), "Context", ["Blob", "Stream"]) |
|
||||
callNode = m.getACall() and this = callNode.getArgument(2)
|
||||
)
|
||||
}
|
||||
|
||||
override HTTP::ResponseWriter getResponseWriter() { none() }
|
||||
|
||||
override DataFlow::Node getAContentTypeNode() { result = callNode.getArgument(1) }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `echo.Context.Redirect` method.
|
||||
*/
|
||||
private class EchoRedirectMethod extends HTTP::Redirect::Range, DataFlow::CallNode {
|
||||
EchoRedirectMethod() {
|
||||
exists(Method m | m.hasQualifiedName(packagePath(), "Context", "Redirect") |
|
||||
this = m.getACall()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getUrl() { result = this.getArgument(1) }
|
||||
|
||||
override HTTP::ResponseWriter getResponseWriter() { none() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Provides classes for working with concepts relating to the [github.com/elazarl/goproxy](https://pkg.go.dev/github.com/elazarl/goproxy) package.
|
||||
*/
|
||||
|
||||
import go
|
||||
|
||||
/**
|
||||
* Provides classes for working with concepts relating to the [github.com/elazarl/goproxy](https://pkg.go.dev/github.com/elazarl/goproxy) package.
|
||||
*/
|
||||
module ElazarlGoproxy {
|
||||
/** Gets the package name. */
|
||||
string packagePath() { result = package("github.com/elazarl/goproxy", "") }
|
||||
|
||||
private class NewResponse extends HTTP::HeaderWrite::Range, DataFlow::CallNode {
|
||||
NewResponse() { this.getTarget().hasQualifiedName(packagePath(), "NewResponse") }
|
||||
|
||||
override string getHeaderName() { this.definesHeader(result, _) }
|
||||
|
||||
override string getHeaderValue() { this.definesHeader(_, result) }
|
||||
|
||||
override DataFlow::Node getName() { none() }
|
||||
|
||||
override DataFlow::Node getValue() { result = this.getArgument([1, 2]) }
|
||||
|
||||
override predicate definesHeader(string header, string value) {
|
||||
header = "status" and value = this.getArgument(2).getIntValue().toString()
|
||||
or
|
||||
header = "content-type" and value = this.getArgument(1).getStringValue()
|
||||
}
|
||||
|
||||
override HTTP::ResponseWriter getResponseWriter() { none() }
|
||||
}
|
||||
|
||||
/** A body argument to a `NewResponse` call. */
|
||||
private class NewResponseBody extends HTTP::ResponseBody::Range {
|
||||
NewResponse call;
|
||||
|
||||
NewResponseBody() { this = call.getArgument(3) }
|
||||
|
||||
override DataFlow::Node getAContentTypeNode() { result = call.getArgument(1) }
|
||||
|
||||
override HTTP::ResponseWriter getResponseWriter() { none() }
|
||||
}
|
||||
|
||||
private class TextResponse extends HTTP::HeaderWrite::Range, DataFlow::CallNode {
|
||||
TextResponse() { this.getTarget().hasQualifiedName(packagePath(), "TextResponse") }
|
||||
|
||||
override string getHeaderName() { this.definesHeader(result, _) }
|
||||
|
||||
override string getHeaderValue() { this.definesHeader(_, result) }
|
||||
|
||||
override DataFlow::Node getName() { none() }
|
||||
|
||||
override DataFlow::Node getValue() { none() }
|
||||
|
||||
override predicate definesHeader(string header, string value) {
|
||||
header = "status" and value = "200"
|
||||
or
|
||||
header = "content-type" and value = "text/plain"
|
||||
}
|
||||
|
||||
override HTTP::ResponseWriter getResponseWriter() { none() }
|
||||
}
|
||||
|
||||
/** A body argument to a `TextResponse` call. */
|
||||
private class TextResponseBody extends HTTP::ResponseBody::Range, TextResponse {
|
||||
TextResponse call;
|
||||
|
||||
TextResponseBody() { this = call.getArgument(2) }
|
||||
|
||||
override DataFlow::Node getAContentTypeNode() { result = call.getArgument(1) }
|
||||
|
||||
override HTTP::ResponseWriter getResponseWriter() { none() }
|
||||
}
|
||||
|
||||
/** A handler attached to a goproxy proxy type. */
|
||||
private class ProxyHandler extends HTTP::RequestHandler::Range {
|
||||
DataFlow::MethodCallNode handlerReg;
|
||||
|
||||
ProxyHandler() {
|
||||
handlerReg
|
||||
.getTarget()
|
||||
.hasQualifiedName(packagePath(), "ReqProxyConds", ["Do", "DoFunc", "HandleConnect"]) and
|
||||
this = handlerReg.getArgument(0)
|
||||
}
|
||||
|
||||
override predicate guardedBy(DataFlow::Node check) {
|
||||
// note OnResponse is not modeled, as that server responses are not currently considered untrusted input
|
||||
exists(DataFlow::MethodCallNode onreqcall |
|
||||
onreqcall.getTarget().hasQualifiedName(packagePath(), "ProxyHttpServer", "OnRequest")
|
||||
|
|
||||
handlerReg.getReceiver() = onreqcall.getASuccessor*() and
|
||||
check = onreqcall.getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class UserControlledRequestData extends UntrustedFlowSource::Range {
|
||||
UserControlledRequestData() {
|
||||
exists(DataFlow::FieldReadNode frn | this = frn |
|
||||
// liberally consider ProxyCtx.UserData to be untrusted; it's a data field set by a request handler
|
||||
frn.getField().hasQualifiedName(packagePath(), "ProxyCtx", "UserData")
|
||||
)
|
||||
or
|
||||
exists(DataFlow::MethodCallNode call | this = call |
|
||||
call.getTarget().hasQualifiedName(packagePath(), "ProxyCtx", "Charset")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class ProxyLog extends LoggerCall::Range, DataFlow::MethodCallNode {
|
||||
ProxyLog() { this.getTarget().hasQualifiedName(packagePath(), "ProxyCtx", ["Logf", "Warnf"]) }
|
||||
|
||||
override DataFlow::Node getAMessageComponent() { result = this.getAnArgument() }
|
||||
}
|
||||
|
||||
private class MethodModels extends TaintTracking::FunctionModel, Method {
|
||||
FunctionInput inp;
|
||||
FunctionOutput outp;
|
||||
|
||||
MethodModels() {
|
||||
// Methods:
|
||||
// signature: func CertStorage.Fetch(hostname string, gen func() (*tls.Certificate, error)) (*tls.Certificate, error)
|
||||
//
|
||||
// `hostname` excluded because if the cert storage or generator function themselves have not
|
||||
// been tainted, `hostname` would be unlikely to fetch user-controlled data
|
||||
this.hasQualifiedName(packagePath(), "CertStorage", "Fetch") and
|
||||
(inp.isReceiver() or inp.isParameter(1)) and
|
||||
outp.isResult(0)
|
||||
}
|
||||
|
||||
override predicate hasTaintFlow(FunctionInput i, FunctionOutput o) { i = inp and o = outp }
|
||||
}
|
||||
}
|
||||
114
repo-tests/codeql-go/ql/lib/semmle/go/frameworks/Email.qll
Normal file
114
repo-tests/codeql-go/ql/lib/semmle/go/frameworks/Email.qll
Normal file
@@ -0,0 +1,114 @@
|
||||
/** Provides classes for working with email-related APIs. */
|
||||
|
||||
import go
|
||||
|
||||
/**
|
||||
* A data-flow node that represents data written to an email, either as part
|
||||
* of the headers or as part of the body.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `EmailData::Range` instead.
|
||||
*/
|
||||
class EmailData extends DataFlow::Node {
|
||||
EmailData::Range self;
|
||||
|
||||
EmailData() { this = self }
|
||||
}
|
||||
|
||||
/** Provides classes for working with data that is incorporated into an email. */
|
||||
module EmailData {
|
||||
/**
|
||||
* A data-flow node that represents data which is written to an email, either as part
|
||||
* of the headers or as part of the body.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `EmailData` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node { }
|
||||
|
||||
/** A data-flow node that is written to an email using the net/smtp package. */
|
||||
private class SmtpData extends Range {
|
||||
SmtpData() {
|
||||
// func (c *Client) Data() (io.WriteCloser, error)
|
||||
exists(Method data |
|
||||
data.hasQualifiedName("net/smtp", "Client", "Data") and
|
||||
this.(DataFlow::SsaNode).getInit() = data.getACall().getResult(0)
|
||||
)
|
||||
or
|
||||
// func SendMail(addr string, a Auth, from string, to []string, msg []byte) error
|
||||
exists(Function sendMail |
|
||||
sendMail.hasQualifiedName("net/smtp", "SendMail") and
|
||||
this = sendMail.getACall().getArgument(4)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets the package name `github.com/sendgrid/sendgrid-go/helpers/mail`. */
|
||||
private string sendgridMail() {
|
||||
result = package("github.com/sendgrid/sendgrid-go", "helpers/mail")
|
||||
}
|
||||
|
||||
private class NewContent extends TaintTracking::FunctionModel {
|
||||
NewContent() {
|
||||
// func NewContent(contentType string, value string) *Content
|
||||
this.hasQualifiedName(sendgridMail(), "NewContent")
|
||||
}
|
||||
|
||||
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
|
||||
input.isParameter(1) and output.isResult()
|
||||
}
|
||||
}
|
||||
|
||||
/** A data-flow node that is written to an email using the sendgrid/sendgrid-go package. */
|
||||
private class SendGridEmail extends Range {
|
||||
SendGridEmail() {
|
||||
// func NewSingleEmail(from *Email, subject string, to *Email, plainTextContent string, htmlContent string) *SGMailV3
|
||||
exists(Function newSingleEmail |
|
||||
newSingleEmail.hasQualifiedName(sendgridMail(), "NewSingleEmail") and
|
||||
this = newSingleEmail.getACall().getArgument([1, 3, 4])
|
||||
)
|
||||
or
|
||||
// func NewV3MailInit(from *Email, subject string, to *Email, content ...*Content) *SGMailV3
|
||||
exists(Function newv3MailInit |
|
||||
newv3MailInit.hasQualifiedName(sendgridMail(), "NewV3MailInit") and
|
||||
this = newv3MailInit.getACall().getArgument(any(int i | i = 1 or i >= 3))
|
||||
)
|
||||
or
|
||||
// func (s *SGMailV3) AddContent(c ...*Content) *SGMailV3
|
||||
exists(Method addContent |
|
||||
addContent.hasQualifiedName(sendgridMail(), "SGMailV3", "AddContent") and
|
||||
this = addContent.getACall().getAnArgument()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint model of the `Writer.CreatePart` method from `mime/multipart`.
|
||||
*
|
||||
* If tainted data is written to the multipart section created by this method, the underlying writer
|
||||
* should be considered tainted as well.
|
||||
*/
|
||||
private class MultipartWriterCreatePartModel extends TaintTracking::FunctionModel, Method {
|
||||
MultipartWriterCreatePartModel() {
|
||||
this.hasQualifiedName("mime/multipart", "Writer", "CreatePart")
|
||||
}
|
||||
|
||||
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
|
||||
input.isResult(0) and output.isReceiver()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint model of the `NewWriter` function from `mime/multipart`.
|
||||
*
|
||||
* If tainted data is written to the writer created by this function, the underlying writer
|
||||
* should be considered tainted as well.
|
||||
*/
|
||||
private class MultipartNewWriterModel extends TaintTracking::FunctionModel {
|
||||
MultipartNewWriterModel() { this.hasQualifiedName("mime/multipart", "NewWriter") }
|
||||
|
||||
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
|
||||
input.isResult() and output.isParameter(0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Provides classes modelling taint propagation through marshalling and encoding functions.
|
||||
*/
|
||||
|
||||
import go
|
||||
|
||||
/** Gets the package name `github.com/json-iterator/go`. */
|
||||
private string packagePath() { result = package("github.com/json-iterator/go", "") }
|
||||
|
||||
/** A model of json-iterator's `Unmarshal` function, propagating taint from the JSON input to the decoded object. */
|
||||
private class JsonIteratorUnmarshalFunction extends TaintTracking::FunctionModel,
|
||||
UnmarshalingFunction::Range {
|
||||
JsonIteratorUnmarshalFunction() {
|
||||
this.hasQualifiedName(packagePath(), ["Unmarshal", "UnmarshalFromString"])
|
||||
or
|
||||
this.(Method).implements(packagePath(), "API", ["Unmarshal", "UnmarshalFromString"])
|
||||
}
|
||||
|
||||
override DataFlow::FunctionInput getAnInput() { result.isParameter(0) }
|
||||
|
||||
override DataFlow::FunctionOutput getOutput() { result.isParameter(1) }
|
||||
|
||||
override string getFormat() { result = "JSON" }
|
||||
|
||||
override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) {
|
||||
inp = getAnInput() and outp = getOutput()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Provides classes modeling `github.com/evanphx/json-patch`.
|
||||
*/
|
||||
|
||||
import go
|
||||
|
||||
private module EvanphxJsonPatch {
|
||||
/** Gets the package name `github.com/evanphx/json-patch`. */
|
||||
private string packagePath() { result = package("github.com/evanphx/json-patch", "") }
|
||||
|
||||
private class MergeMergePatches extends TaintTracking::FunctionModel {
|
||||
MergeMergePatches() { this.hasQualifiedName(packagePath(), "MergeMergePatches") }
|
||||
|
||||
override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) {
|
||||
(inp.isParameter(0) or inp.isParameter(1)) and
|
||||
outp.isResult(0)
|
||||
}
|
||||
}
|
||||
|
||||
private class MergePatch extends TaintTracking::FunctionModel {
|
||||
MergePatch() { this.hasQualifiedName(packagePath(), "MergePatch") }
|
||||
|
||||
override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) {
|
||||
(inp.isParameter(0) or inp.isParameter(1)) and
|
||||
outp.isResult(0)
|
||||
}
|
||||
}
|
||||
|
||||
private class CreateMergePatch extends TaintTracking::FunctionModel {
|
||||
CreateMergePatch() { this.hasQualifiedName(packagePath(), "CreateMergePatch") }
|
||||
|
||||
override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) {
|
||||
(inp.isParameter(0) or inp.isParameter(1)) and
|
||||
outp.isResult(0)
|
||||
}
|
||||
}
|
||||
|
||||
private class DecodePatch extends TaintTracking::FunctionModel {
|
||||
DecodePatch() { this.hasQualifiedName(packagePath(), "DecodePatch") }
|
||||
|
||||
override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) {
|
||||
inp.isParameter(0) and
|
||||
outp.isResult(0)
|
||||
}
|
||||
}
|
||||
|
||||
private class Apply extends TaintTracking::FunctionModel, Method {
|
||||
Apply() {
|
||||
exists(string fn |
|
||||
fn in ["Apply", "ApplyWithOptions", "ApplyIndent", "ApplyIndentWithOptions"]
|
||||
|
|
||||
this.hasQualifiedName(packagePath(), "Patch", fn)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) {
|
||||
(inp.isParameter(0) or inp.isReceiver()) and
|
||||
outp.isResult(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
72
repo-tests/codeql-go/ql/lib/semmle/go/frameworks/Gin.qll
Normal file
72
repo-tests/codeql-go/ql/lib/semmle/go/frameworks/Gin.qll
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Provides classes for working with untrusted flow sources from the `github.com/gin-gonic/gin` package.
|
||||
*/
|
||||
|
||||
import go
|
||||
|
||||
private module Gin {
|
||||
/** Gets the package name `github.com/gin-gonic/gin`. */
|
||||
string packagePath() { result = package("github.com/gin-gonic/gin", "") }
|
||||
|
||||
/**
|
||||
* Data from a `Context` struct, considered as a source of untrusted flow.
|
||||
*/
|
||||
private class GithubComGinGonicGinContextSource extends UntrustedFlowSource::Range {
|
||||
GithubComGinGonicGinContextSource() {
|
||||
// Method calls:
|
||||
exists(DataFlow::MethodCallNode call, string methodName |
|
||||
call.getTarget().hasQualifiedName(packagePath(), "Context", methodName) and
|
||||
methodName in [
|
||||
"FullPath", "GetHeader", "QueryArray", "Query", "PostFormArray", "PostForm", "Param",
|
||||
"GetStringSlice", "GetString", "GetRawData", "ClientIP", "ContentType", "Cookie",
|
||||
"GetQueryArray", "GetQuery", "GetPostFormArray", "GetPostForm", "DefaultPostForm",
|
||||
"DefaultQuery", "GetPostFormMap", "GetQueryMap", "GetStringMap", "GetStringMapString",
|
||||
"GetStringMapStringSlice", "PostFormMap", "QueryMap"
|
||||
]
|
||||
|
|
||||
this = call.getResult(0)
|
||||
)
|
||||
or
|
||||
// Field reads:
|
||||
exists(DataFlow::Field fld |
|
||||
fld.hasQualifiedName(packagePath(), "Context", ["Accepted", "Params"]) and
|
||||
this = fld.getARead()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class ParamsGet extends TaintTracking::FunctionModel, Method {
|
||||
ParamsGet() { this.hasQualifiedName(packagePath(), "Params", "Get") }
|
||||
|
||||
override predicate hasTaintFlow(FunctionInput inp, FunctionOutput outp) {
|
||||
inp.isReceiver() and outp.isResult(0)
|
||||
}
|
||||
}
|
||||
|
||||
private class ParamsByName extends TaintTracking::FunctionModel, Method {
|
||||
ParamsByName() { this.hasQualifiedName(packagePath(), "Params", "ByName") }
|
||||
|
||||
override predicate hasTaintFlow(FunctionInput inp, FunctionOutput outp) {
|
||||
inp.isReceiver() and outp.isResult()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a method on `Context` struct that unmarshals data into a target.
|
||||
*/
|
||||
private class GithubComGinGonicGinContextBindSource extends UntrustedFlowSource::Range {
|
||||
GithubComGinGonicGinContextBindSource() {
|
||||
exists(DataFlow::MethodCallNode call, string methodName |
|
||||
call.getTarget().hasQualifiedName(packagePath(), "Context", methodName) and
|
||||
methodName in [
|
||||
"BindJSON", "BindYAML", "BindXML", "BindUri", "BindQuery", "BindWith", "BindHeader",
|
||||
"MustBindWith", "Bind", "ShouldBind", "ShouldBindBodyWith", "ShouldBindJSON",
|
||||
"ShouldBindQuery", "ShouldBindUri", "ShouldBindHeader", "ShouldBindWith",
|
||||
"ShouldBindXML", "ShouldBindYAML"
|
||||
]
|
||||
|
|
||||
this = FunctionOutput::parameter(0).getExitNode(call)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
28
repo-tests/codeql-go/ql/lib/semmle/go/frameworks/Glog.qll
Normal file
28
repo-tests/codeql-go/ql/lib/semmle/go/frameworks/Glog.qll
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Provides models of commonly used functions in the `github.com/golang/glog` and `k8s.io/klog`
|
||||
* packages.
|
||||
*/
|
||||
|
||||
import go
|
||||
|
||||
/**
|
||||
* Provides models of commonly used functions in the `github.com/golang/glog` packages and its
|
||||
* forks.
|
||||
*/
|
||||
module Glog {
|
||||
private class GlogCall extends LoggerCall::Range, DataFlow::CallNode {
|
||||
GlogCall() {
|
||||
exists(string pkg, Function f, string fn |
|
||||
pkg = package(["github.com/golang/glog", "gopkg.in/glog", "k8s.io/klog"], "") and
|
||||
fn.regexpMatch("(Error|Exit|Fatal|Info|Warning)(|f|ln)") and
|
||||
this = f.getACall()
|
||||
|
|
||||
f.hasQualifiedName(pkg, fn)
|
||||
or
|
||||
f.(Method).hasQualifiedName(pkg, "Verbose", fn)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAMessageComponent() { result = this.getAnArgument() }
|
||||
}
|
||||
}
|
||||
45
repo-tests/codeql-go/ql/lib/semmle/go/frameworks/GoKit.qll
Normal file
45
repo-tests/codeql-go/ql/lib/semmle/go/frameworks/GoKit.qll
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Provides classes for working with concepts relating to the [github.com/go-kit/kit](https://pkg.go.dev/github.com/go-kit/kit) package.
|
||||
*
|
||||
* Note that these models are not included by default; to include them, add `import semmle.go.frameworks.GoKit` to your query or to
|
||||
* `Customizations.qll`.
|
||||
*/
|
||||
|
||||
import go
|
||||
|
||||
/**
|
||||
* Provides classes for working with concepts relating to the [github.com/go-kit/kit](https://pkg.go.dev/github.com/go-kit/kit) package.
|
||||
*/
|
||||
module GoKit {
|
||||
/** Gets the package name. */
|
||||
string packagePath() { result = package("github.com/go-kit/kit", "") }
|
||||
|
||||
/**
|
||||
* Provides classes for working with concepts relating to the `endpoint` package of the
|
||||
* [github.com/go-kit/kit](https://pkg.go.dev/github.com/go-kit/kit) package.
|
||||
*/
|
||||
module Endpoint {
|
||||
/** Gets the package name. */
|
||||
string endpointPackagePath() { result = package("github.com/go-kit/kit", "endpoint") }
|
||||
|
||||
// gets a function that returns an endpoint
|
||||
private DataFlow::Node getAnEndpointFactoryResult() {
|
||||
exists(Function mkFn, FunctionOutput res |
|
||||
mkFn.getResultType(0).hasQualifiedName(endpointPackagePath(), "Endpoint") and
|
||||
result = res.getEntryNode(mkFn.getFuncDecl()).getAPredecessor*()
|
||||
)
|
||||
}
|
||||
|
||||
private FuncDef getAnEndpointFunction() {
|
||||
exists(Function endpointFn | endpointFn.getFuncDecl() = result |
|
||||
endpointFn.getARead() = getAnEndpointFactoryResult()
|
||||
)
|
||||
or
|
||||
DataFlow::exprNode(result.(FuncLit)) = getAnEndpointFactoryResult()
|
||||
}
|
||||
|
||||
private class EndpointRequest extends UntrustedFlowSource::Range {
|
||||
EndpointRequest() { this = DataFlow::parameterNode(getAnEndpointFunction().getParameter(1)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Provides models of the [go-restful library](https://github.com/emicklei/go-restful).
|
||||
*/
|
||||
|
||||
import go
|
||||
|
||||
/**
|
||||
* Provides models of the [go-restful library](https://github.com/emicklei/go-restful).
|
||||
*/
|
||||
private module GoRestfulHttp {
|
||||
/** Gets the package name `github.com/emicklei/go-restful`. */
|
||||
string packagePath() { result = package("github.com/emicklei/go-restful", "") }
|
||||
|
||||
/**
|
||||
* A model for methods defined on go-restful's `Request` object that may return user-controlled data.
|
||||
*/
|
||||
private class GoRestfulSourceMethod extends Method {
|
||||
GoRestfulSourceMethod() {
|
||||
this.hasQualifiedName(packagePath(), "Request",
|
||||
[
|
||||
"QueryParameters", "QueryParameter", "BodyParameter", "HeaderParameter", "PathParameter",
|
||||
"PathParameters"
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A model of go-restful's `Request` object as a source of user-controlled data.
|
||||
*/
|
||||
private class GoRestfulSource extends UntrustedFlowSource::Range {
|
||||
GoRestfulSource() { this = any(GoRestfulSourceMethod g).getACall() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A model of go-restful's `Request.ReadEntity` method as a source of user-controlled data.
|
||||
*/
|
||||
private class GoRestfulReadEntitySource extends UntrustedFlowSource::Range {
|
||||
GoRestfulReadEntitySource() {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getTarget().hasQualifiedName(packagePath(), "Request", "ReadEntity")
|
||||
|
|
||||
this = FunctionOutput::parameter(0).getExitNode(call)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user