QL: Merge pull request #82 from github/esbena/codeql-action-on-other-repos

This commit is contained in:
Mathias Vorreiter Pedersen
2021-10-14 07:44:15 +01:00
committed by GitHub
4666 changed files with 715469 additions and 1 deletions

View File

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

View File

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

View File

@@ -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
View File

@@ -0,0 +1 @@
abe3f2148b92b1a94a0a3676cb4dab7d9211076f

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,18 @@
/**
* @name If statements with empty then branch
* @description Finds 'if' statements where the 'then' branch is
* an empty block statement
* @id go/examples/emptythen
* @tags if
* then
* empty
* conditional
* branch
* statement
*/
import go
from IfStmt i
where i.getThen().getNumStmt() = 0
select i

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,15 @@
/**
* @name Jump-to-definition links
* @description Generates use-definition pairs that provide the data
* for jump-to-definition in the code viewer.
* @kind definitions
* @id go/jump-to-definition
*/
import go
from Ident def, Ident use, Entity e
where
use.uses(e) and
def.declares(e)
select use, def, "V"

View File

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

View File

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

View File

@@ -0,0 +1,23 @@
/**
* Provides classes and predicates related to contextual queries
* in the code viewer.
*/
import go
/**
* Returns the `File` matching the given source file name as encoded by the VS
* Code extension.
*/
cached
File getFileBySourceArchiveName(string name) {
// The name provided for a file in the source archive by the VS Code extension
// has some differences from the absolute path in the database:
// 1. colons are replaced by underscores
// 2. there's a leading slash, even for Windows paths: "C:/foo/bar" ->
// "/C_/foo/bar"
// 3. double slashes in UNC prefixes are replaced with a single slash
// We can handle 2 and 3 together by unconditionally adding a leading slash
// before replacing double slashes.
name = ("/" + result.getAbsolutePath().replaceAll(":", "_")).replaceAll("//", "/")
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,53 @@
/** Provides classes for working with Go frontend errors recorded during extraction. */
import go
/**
* An error reported by the Go frontend during extraction.
*/
class Error extends @error {
/** Gets the message associated with this error. */
string getMessage() { errors(this, _, result, _, _, _, _, _, _) }
/** Gets the raw position reported by the frontend for this error. */
string getRawPosition() { errors(this, _, _, result, _, _, _, _, _) }
/** Gets the package in which this error was reported. */
Package getPackage() { errors(this, _, _, _, _, _, _, result, _) }
/** Gets the index of this error among all errors reported for the same package. */
int getIndex() { errors(this, _, _, _, _, _, _, _, result) }
/** Gets the file in which this error was reported, if it can be determined. */
ExtractedOrExternalFile getFile() { hasLocationInfo(result.getAbsolutePath(), _, _, _, _) }
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [LGTM locations](https://lgtm.com/help/ql/locations).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
errors(this, _, _, _, filepath, startline, startcolumn, _, _) and
endline = startline and
endcolumn = startcolumn
}
/** Gets a textual representation of this error. */
string toString() { result = getMessage() }
}
/** An error reported by an unknown part of the Go frontend. */
class UnknownError extends Error, @unknownerror { }
/** An error reported by the Go frontend driver. */
class ListError extends Error, @listerror { }
/** An error reported by the Go parser. */
class ParseError extends Error, @parseerror { }
/** An error reported by the Go type checker. */
class TypeError extends Error, @typeerror { }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,278 @@
/** Provides classes for working with files and folders. */
import go
/** A file or folder. */
abstract class Container extends @container {
/**
* Gets the absolute, canonical path of this container, using forward slashes
* as path separator.
*
* The path starts with a _root prefix_ followed by zero or more _path
* segments_ separated by forward slashes.
*
* The root prefix is of one of the following forms:
*
* 1. A single forward slash `/` (Unix-style)
* 2. An upper-case drive letter followed by a colon and a forward slash,
* such as `C:/` (Windows-style)
* 3. Two forward slashes, a computer name, and then another forward slash,
* such as `//FileServer/` (UNC-style)
*
* Path segments are never empty (that is, absolute paths never contain two
* contiguous slashes, except as part of a UNC-style root prefix). Also, path
* segments never contain forward slashes, and no path segment is of the
* form `.` (one dot) or `..` (two dots).
*
* Note that an absolute path never ends with a forward slash, except if it is
* a bare root prefix, that is, the path has no path segments. A container
* whose absolute path has no segments is always a `Folder`, not a `File`.
*/
abstract string getAbsolutePath();
/**
* Gets a URL representing the location of this container.
*
* For more information see https://lgtm.com/help/ql/locations#providing-urls.
*/
abstract string getURL();
/**
* Gets the relative path of this file or folder from the root folder of the
* analyzed source location. The relative path of the root folder itself is
* the empty string.
*
* This has no result if the container is outside the source root, that is,
* if the root folder is not a reflexive, transitive parent of this container.
*/
string getRelativePath() {
exists(string absPath, string pref |
absPath = getAbsolutePath() and sourceLocationPrefix(pref)
|
absPath = pref and result = ""
or
absPath = pref.regexpReplaceAll("/$", "") + "/" + result and
not result.matches("/%")
)
}
/**
* Gets the base name of this container including extension, that is, the last
* segment of its absolute path, or the empty string if it has no segments.
*
* Here are some examples of absolute paths and the corresponding base names
* (surrounded with quotes to avoid ambiguity):
*
* <table border="1">
* <tr><th>Absolute path</th><th>Base name</th></tr>
* <tr><td>"/tmp/tst.go"</td><td>"tst.go"</td></tr>
* <tr><td>"C:/Program Files (x86)"</td><td>"Program Files (x86)"</td></tr>
* <tr><td>"/"</td><td>""</td></tr>
* <tr><td>"C:/"</td><td>""</td></tr>
* <tr><td>"D:/"</td><td>""</td></tr>
* <tr><td>"//FileServer/"</td><td>""</td></tr>
* </table>
*/
string getBaseName() {
result = getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1)
}
/**
* Gets the extension of this container, that is, the suffix of its base name
* after the last dot character, if any.
*
* In particular,
*
* - if the name does not include a dot, there is no extension, so this
* predicate has no result;
* - if the name ends in a dot, the extension is the empty string;
* - if the name contains multiple dots, the extension follows the last dot.
*
* Here are some examples of absolute paths and the corresponding extensions
* (surrounded with quotes to avoid ambiguity):
*
* <table border="1">
* <tr><th>Absolute path</th><th>Extension</th></tr>
* <tr><td>"/tmp/tst.go"</td><td>"go"</td></tr>
* <tr><td>"/tmp/.classpath"</td><td>"classpath"</td></tr>
* <tr><td>"/bin/bash"</td><td>not defined</td></tr>
* <tr><td>"/tmp/tst2."</td><td>""</td></tr>
* <tr><td>"/tmp/x.tar.gz"</td><td>"gz"</td></tr>
* </table>
*/
string getExtension() { result = getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3) }
/**
* Gets the stem of this container, that is, the prefix of its base name up to
* (but not including) the last dot character if there is one, or the entire
* base name if there is not.
*
* Here are some examples of absolute paths and the corresponding stems
* (surrounded with quotes to avoid ambiguity):
*
* <table border="1">
* <tr><th>Absolute path</th><th>Stem</th></tr>
* <tr><td>"/tmp/tst.go"</td><td>"tst"</td></tr>
* <tr><td>"/tmp/.classpath"</td><td>""</td></tr>
* <tr><td>"/bin/bash"</td><td>"bash"</td></tr>
* <tr><td>"/tmp/tst2."</td><td>"tst2"</td></tr>
* <tr><td>"/tmp/x.tar.gz"</td><td>"x.tar"</td></tr>
* </table>
*/
string getStem() { result = getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1) }
/** Gets the parent container of this file or folder, if any. */
Container getParentContainer() { containerparent(result, this) }
/** Gets a file or sub-folder in this container. */
Container getAChildContainer() { this = result.getParentContainer() }
/** Gets a file in this container. */
File getAFile() { result = getAChildContainer() }
/** Gets the file in this container that has the given `baseName`, if any. */
File getFile(string baseName) {
result = getAFile() and
result.getBaseName() = baseName
}
/** Gets a sub-folder in this container. */
Folder getAFolder() { result = getAChildContainer() }
/** Gets the sub-folder in this container that has the given `baseName`, if any. */
Folder getFolder(string baseName) {
result = getAFolder() and
result.getBaseName() = baseName
}
/**
* Gets a textual representation of the path of this container.
*
* This is the absolute path of the container.
*/
string toString() { result = getAbsolutePath() }
}
/** A folder. */
class Folder extends Container, @folder {
override string getAbsolutePath() { folders(this, result) }
/** Gets the file or subfolder in this folder that has the given `name`, if any. */
Container getChildContainer(string name) {
result = getAChildContainer() and
result.getBaseName() = name
}
/** Gets the file in this folder that has the given `stem` and `extension`, if any. */
File getFile(string stem, string extension) {
result = getAChildContainer() and
result.getStem() = stem and
result.getExtension() = extension
}
/** Gets a subfolder contained in this folder. */
Folder getASubFolder() { result = getAChildContainer() }
/** Gets the URL of this folder. */
override string getURL() { result = "folder://" + getAbsolutePath() }
}
/** Any file, including files that have not been extracted but are referred to as locations for errors. */
class ExtractedOrExternalFile extends Container, @file, Documentable, ExprParent, GoModExprParent,
DeclParent, ScopeNode {
override Location getLocation() { has_location(this, result) }
override string getAbsolutePath() { files(this, result) }
/** Gets the number of lines in this file. */
int getNumberOfLines() { numlines(this, result, _, _) }
/** Gets the number of lines containing code in this file. */
int getNumberOfLinesOfCode() { numlines(this, _, result, _) }
/** Gets the number of lines containing comments in this file. */
int getNumberOfLinesOfComments() { numlines(this, _, _, result) }
/** Gets the package name as specified in the package clause of this file. */
Ident getPackageNameExpr() { result = getChildExpr(0) }
/** Gets the name of the package to which this file belongs. */
string getPackageName() { result = getPackageNameExpr().getName() }
/** Holds if this file contains at least one build constraint. */
pragma[noinline]
predicate hasBuildConstraints() { exists(BuildConstraintComment bc | this = bc.getFile()) }
/**
* Holds if this file contains build constraints that ensure that it
* is only built on architectures of bit size `bitSize`, which can be
* 32 or 64.
*/
predicate constrainsIntBitSize(int bitSize) {
explicitlyConstrainsIntBitSize(bitSize) or
implicitlyConstrainsIntBitSize(bitSize)
}
/**
* Holds if this file contains explicit build constraints that ensure
* that it is only built on an architecture of bit size `bitSize`,
* which can be 32 or 64.
*/
predicate explicitlyConstrainsIntBitSize(int bitSize) {
exists(BuildConstraintComment bcc | this = bcc.getFile() |
forex(string disjunct | disjunct = bcc.getADisjunct() |
disjunct.splitAt(",").(Architecture).getBitSize() = bitSize
or
disjunct.splitAt("/").(Architecture).getBitSize() = bitSize
)
)
}
/**
* Holds if this file has a name which acts as an implicit build
* constraint that ensures that it is only built on an
* architecture of bit size `bitSize`, which can be 32 or 64.
*/
predicate implicitlyConstrainsIntBitSize(int bitSize) {
exists(Architecture arch | arch.getBitSize() = bitSize |
this.getStem().regexpMatch("(?i).*_\\Q" + arch + "\\E(_test)?")
)
}
override string toString() { result = Container.super.toString() }
/** Gets the URL of this file. */
override string getURL() { result = "file://" + this.getAbsolutePath() + ":0:0:0:0" }
/** Gets the `i`th child comment group. */
CommentGroup getCommentGroup(int i) { comment_groups(result, this, i) }
/** Gets a child comment group. */
CommentGroup getACommentGroup() { result = getCommentGroup(_) }
/** Gets the number of child comment groups of this file. */
int getNumCommentGroups() { result = count(getACommentGroup()) }
override string getAPrimaryQlClass() { result = "File" }
}
/** A file that has been extracted. */
class File extends ExtractedOrExternalFile {
File() {
// getAChild is specifically for the Go AST and so does not apply to non-go files
// we care about all non-go extracted files, as only go files can have `@file` entries due to requiring a file entry for diagnostic errors
not this.getExtension() = "go"
or
exists(this.getAChild())
}
}
/** A Go file. */
class GoFile extends File {
GoFile() { this.getExtension() = "go" }
}
/** An HTML file. */
class HtmlFile extends File {
HtmlFile() { this.getExtension().regexpMatch("x?html?") }
}

View File

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

View File

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

View File

@@ -0,0 +1,81 @@
/** Provides classes for working with locations and program elements that have locations. */
import go
/**
* A location as given by a file, a start line, a start column,
* an end line, and an end column.
*
* For more information about locations see [LGTM locations](https://lgtm.com/help/ql/locations).
*/
class Location extends @location {
/** Gets the file for this location. */
File getFile() { locations_default(this, result, _, _, _, _) }
/** Gets the 1-based line number (inclusive) where this location starts. */
int getStartLine() { locations_default(this, _, result, _, _, _) }
/** Gets the 1-based column number (inclusive) where this location starts. */
int getStartColumn() { locations_default(this, _, _, result, _, _) }
/** Gets the 1-based line number (inclusive) where this location ends. */
int getEndLine() { locations_default(this, _, _, _, result, _) }
/** Gets the 1-based column number (inclusive) where this location ends. */
int getEndColumn() { locations_default(this, _, _, _, _, result) }
/** Gets the number of lines covered by this location. */
int getNumLines() { result = getEndLine() - getStartLine() + 1 }
/** Gets a textual representation of this element. */
string toString() {
exists(string filepath, int startline, int startcolumn, int endline, int endcolumn |
hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) and
result = filepath + "@" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
)
}
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [LGTM locations](https://lgtm.com/help/ql/locations).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
exists(File f |
locations_default(this, f, startline, startcolumn, endline, endcolumn) and
filepath = f.getAbsolutePath()
)
}
}
/** A program element with a location. */
class Locatable extends @locatable {
/** Gets the file this program element comes from. */
File getFile() { result = getLocation().getFile() }
/** Gets this element's location. */
Location getLocation() { has_location(this, result) }
/** Gets the number of lines covered by this element. */
int getNumLines() { result = getLocation().getNumLines() }
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [LGTM locations](https://lgtm.com/help/ql/locations).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
/** Gets a textual representation of this element. */
string toString() { result = "locatable element" }
}

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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)
}
}

View File

@@ -0,0 +1,5 @@
import semmle.go.dataflow.internal.TaintTrackingUtil as Public
module Private {
import semmle.go.dataflow.DataFlow::DataFlow as DataFlow
}

View File

@@ -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)
}
}

View File

@@ -0,0 +1,5 @@
import semmle.go.dataflow.internal.TaintTrackingUtil as Public
module Private {
import semmle.go.dataflow.DataFlow2::DataFlow2 as DataFlow
}

View File

@@ -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() }
}

View 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)
}
}

View 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)
}
}
}

View 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())
)
}
}
}

View 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")
}
}
}

View File

@@ -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)
)
}
}
}

View 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() }
}
}

View File

@@ -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 }
}
}

View 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)
}
}

View File

@@ -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()
}
}

View File

@@ -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)
}
}
}

View 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)
)
}
}
}

View 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() }
}
}

View 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)) }
}
}
}

View File

@@ -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