Packaging: Java refactoring

Split java pack into `codeql/java-all` and `codeql/java-queries`.
This commit is contained in:
Andrew Eisenberg
2021-08-18 13:43:21 -07:00
parent 39533317ff
commit 8e750f18ad
326 changed files with 41 additions and 12 deletions

View File

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

View File

@@ -0,0 +1,987 @@
/**
* An invocation of the compiler. Note that more than one file may be
* compiled per invocation. For example, this command compiles three
* source files:
*
* javac A.java B.java C.java
*
* The `id` simply identifies the invocation, while `cwd` is the working
* directory from which the compiler was invoked.
*/
compilations(
/**
* An invocation of the compiler. Note that more than one file may
* be compiled per invocation. For example, this command compiles
* three source files:
*
* javac A.java B.java C.java
*/
unique int id : @compilation,
string cwd : string ref
);
/**
* The arguments that were passed to the extractor for a compiler
* invocation. If `id` is for the compiler invocation
*
* javac A.java B.java C.java
*
* then typically there will be rows for
*
* num | arg
* --- | ---
* 0 | *path to extractor*
* 1 | `--javac-args`
* 2 | A.java
* 3 | B.java
* 4 | C.java
*/
#keyset[id, num]
compilation_args(
int id : @compilation ref,
int num : int ref,
string arg : string ref
);
/**
* The source files that are compiled by a compiler invocation.
* If `id` is for the compiler invocation
*
* javac A.java B.java C.java
*
* then there will be rows for
*
* num | arg
* --- | ---
* 0 | A.java
* 1 | B.java
* 2 | C.java
*/
#keyset[id, num]
compilation_compiling_files(
int id : @compilation ref,
int num : int ref,
int file : @file ref
);
/**
* The time taken by the extractor for a compiler invocation.
*
* For each file `num`, there will be rows for
*
* kind | seconds
* ---- | ---
* 1 | CPU seconds used by the extractor frontend
* 2 | Elapsed seconds during the extractor frontend
* 3 | CPU seconds used by the extractor backend
* 4 | Elapsed seconds during the extractor backend
*/
#keyset[id, num, kind]
compilation_time(
int id : @compilation ref,
int num : int ref,
/* kind:
1 = frontend_cpu_seconds
2 = frontend_elapsed_seconds
3 = extractor_cpu_seconds
4 = extractor_elapsed_seconds
*/
int kind : int ref,
float seconds : float ref
);
/**
* An error or warning generated by the extractor.
* The diagnostic message `diagnostic` was generated during compiler
* invocation `compilation`, and is the `file_number_diagnostic_number`th
* message generated while extracting the `file_number`th file of that
* invocation.
*/
#keyset[compilation, file_number, file_number_diagnostic_number]
diagnostic_for(
unique int diagnostic : @diagnostic ref,
int compilation : @compilation ref,
int file_number : int ref,
int file_number_diagnostic_number : int ref
);
/**
* If extraction was successful, then `cpu_seconds` and
* `elapsed_seconds` are the CPU time and elapsed time (respectively)
* that extraction took for compiler invocation `id`.
*/
compilation_finished(
unique int id : @compilation ref,
float cpu_seconds : float ref,
float elapsed_seconds : float ref
);
diagnostics(
unique int id: @diagnostic,
int severity: int ref,
string error_tag: string ref,
string error_message: string ref,
string full_error_message: string ref,
int location: @location_default ref
);
/*
* External artifacts
*/
externalData(
int id : @externalDataElement,
string path : string ref,
int column: int ref,
string value : string ref
);
snapshotDate(
unique date snapshotDate : date ref
);
sourceLocationPrefix(
string prefix : string ref
);
/*
* Duplicate code
*/
duplicateCode(
unique int id : @duplication,
string relativePath : string ref,
int equivClass : int ref
);
similarCode(
unique int id : @similarity,
string 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
);
/*
* SMAP
*/
smap_header(
int outputFileId: @file ref,
string outputFilename: string ref,
string defaultStratum: string ref
);
smap_files(
int outputFileId: @file ref,
string stratum: string ref,
int inputFileNum: int ref,
string inputFileName: string ref,
int inputFileId: @file ref
);
smap_lines(
int outputFileId: @file ref,
string stratum: string ref,
int inputFileNum: int ref,
int inputStartLine: int ref,
int inputLineCount: int ref,
int outputStartLine: int ref,
int outputLineIncrement: int ref
);
/*
* Locations and files
*/
@location = @location_default ;
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
);
hasLocation(
int locatableid: @locatable ref,
int id: @location ref
);
@sourceline = @locatable ;
#keyset[element_id]
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,
string simple: string ref,
string ext: string ref,
int fromSource: int ref // deprecated
);
folders(
unique int id: @folder,
string name: string ref,
string simple: string ref
);
@container = @folder | @file
containerparent(
int parent: @container ref,
unique int child: @container ref
);
/*
* Java
*/
cupackage(
unique int id: @file ref,
int packageid: @package ref
);
#keyset[fileid,keyName]
jarManifestMain(
int fileid: @file ref,
string keyName: string ref,
string value: string ref
);
#keyset[fileid,entryName,keyName]
jarManifestEntries(
int fileid: @file ref,
string entryName: string ref,
string keyName: string ref,
string value: string ref
);
packages(
unique int id: @package,
string nodeName: string ref
);
primitives(
unique int id: @primitive,
string nodeName: string ref
);
modifiers(
unique int id: @modifier,
string nodeName: string ref
);
classes(
unique int id: @class,
string nodeName: string ref,
int parentid: @package ref,
int sourceid: @class ref
);
isRecord(
unique int id: @class ref
);
interfaces(
unique int id: @interface,
string nodeName: string ref,
int parentid: @package ref,
int sourceid: @interface ref
);
fielddecls(
unique int id: @fielddecl,
int parentid: @reftype ref
);
#keyset[fieldId] #keyset[fieldDeclId,pos]
fieldDeclaredIn(
int fieldId: @field ref,
int fieldDeclId: @fielddecl ref,
int pos: int ref
);
fields(
unique int id: @field,
string nodeName: string ref,
int typeid: @type ref,
int parentid: @reftype ref,
int sourceid: @field ref
);
constrs(
unique int id: @constructor,
string nodeName: string ref,
string signature: string ref,
int typeid: @type ref,
int parentid: @reftype ref,
int sourceid: @constructor ref
);
methods(
unique int id: @method,
string nodeName: string ref,
string signature: string ref,
int typeid: @type ref,
int parentid: @reftype ref,
int sourceid: @method ref
);
#keyset[parentid,pos]
params(
unique int id: @param,
int typeid: @type ref,
int pos: int ref,
int parentid: @callable ref,
int sourceid: @param ref
);
paramName(
unique int id: @param ref,
string nodeName: string ref
);
isVarargsParam(
int param: @param ref
);
exceptions(
unique int id: @exception,
int typeid: @type ref,
int parentid: @callable ref
);
isAnnotType(
int interfaceid: @interface ref
);
isAnnotElem(
int methodid: @method ref
);
annotValue(
int parentid: @annotation ref,
int id2: @method ref,
unique int value: @expr ref
);
isEnumType(
int classid: @class ref
);
isEnumConst(
int fieldid: @field ref
);
#keyset[parentid,pos]
typeVars(
unique int id: @typevariable,
string nodeName: string ref,
int pos: int ref,
int kind: int ref, // deprecated
int parentid: @typeorcallable ref
);
wildcards(
unique int id: @wildcard,
string nodeName: string ref,
int kind: int ref
);
#keyset[parentid,pos]
typeBounds(
unique int id: @typebound,
int typeid: @reftype ref,
int pos: int ref,
int parentid: @boundedtype ref
);
#keyset[parentid,pos]
typeArgs(
int argumentid: @reftype ref,
int pos: int ref,
int parentid: @typeorcallable ref
);
isParameterized(
int memberid: @member ref
);
isRaw(
int memberid: @member ref
);
erasure(
unique int memberid: @member ref,
int erasureid: @member ref
);
#keyset[classid] #keyset[parent]
isAnonymClass(
int classid: @class ref,
int parent: @classinstancexpr ref
);
#keyset[classid] #keyset[parent]
isLocalClass(
int classid: @class ref,
int parent: @localclassdeclstmt ref
);
isDefConstr(
int constructorid: @constructor ref
);
#keyset[exprId]
lambdaKind(
int exprId: @lambdaexpr ref,
int bodyKind: int ref
);
arrays(
unique int id: @array,
string nodeName: string ref,
int elementtypeid: @type ref,
int dimension: int ref,
int componenttypeid: @type ref
);
enclInReftype(
unique int child: @reftype ref,
int parent: @reftype ref
);
extendsReftype(
int id1: @reftype ref,
int id2: @classorinterface ref
);
implInterface(
int id1: @classorarray ref,
int id2: @interface ref
);
permits(
int id1: @classorinterface ref,
int id2: @classorinterface ref
);
hasModifier(
int id1: @modifiable ref,
int id2: @modifier ref
);
imports(
unique int id: @import,
int holder: @typeorpackage ref,
string name: string ref,
int kind: int ref
);
#keyset[parent,idx]
stmts(
unique int id: @stmt,
int kind: int ref,
int parent: @stmtparent ref,
int idx: int ref,
int bodydecl: @callable ref
);
@stmtparent = @callable | @stmt | @switchexpr;
case @stmt.kind of
0 = @block
| 1 = @ifstmt
| 2 = @forstmt
| 3 = @enhancedforstmt
| 4 = @whilestmt
| 5 = @dostmt
| 6 = @trystmt
| 7 = @switchstmt
| 8 = @synchronizedstmt
| 9 = @returnstmt
| 10 = @throwstmt
| 11 = @breakstmt
| 12 = @continuestmt
| 13 = @emptystmt
| 14 = @exprstmt
| 15 = @labeledstmt
| 16 = @assertstmt
| 17 = @localvariabledeclstmt
| 18 = @localclassdeclstmt
| 19 = @constructorinvocationstmt
| 20 = @superconstructorinvocationstmt
| 21 = @case
| 22 = @catchclause
| 23 = @yieldstmt
;
#keyset[parent,idx]
exprs(
unique int id: @expr,
int kind: int ref,
int typeid: @type ref,
int parent: @exprparent ref,
int idx: int ref
);
callableEnclosingExpr(
unique int id: @expr ref,
int callable_id: @callable ref
);
statementEnclosingExpr(
unique int id: @expr ref,
int statement_id: @stmt ref
);
isParenthesized(
unique int id: @expr ref,
int parentheses: int ref
);
case @expr.kind of
1 = @arrayaccess
| 2 = @arraycreationexpr
| 3 = @arrayinit
| 4 = @assignexpr
| 5 = @assignaddexpr
| 6 = @assignsubexpr
| 7 = @assignmulexpr
| 8 = @assigndivexpr
| 9 = @assignremexpr
| 10 = @assignandexpr
| 11 = @assignorexpr
| 12 = @assignxorexpr
| 13 = @assignlshiftexpr
| 14 = @assignrshiftexpr
| 15 = @assignurshiftexpr
| 16 = @booleanliteral
| 17 = @integerliteral
| 18 = @longliteral
| 19 = @floatingpointliteral
| 20 = @doubleliteral
| 21 = @characterliteral
| 22 = @stringliteral
| 23 = @nullliteral
| 24 = @mulexpr
| 25 = @divexpr
| 26 = @remexpr
| 27 = @addexpr
| 28 = @subexpr
| 29 = @lshiftexpr
| 30 = @rshiftexpr
| 31 = @urshiftexpr
| 32 = @andbitexpr
| 33 = @orbitexpr
| 34 = @xorbitexpr
| 35 = @andlogicalexpr
| 36 = @orlogicalexpr
| 37 = @ltexpr
| 38 = @gtexpr
| 39 = @leexpr
| 40 = @geexpr
| 41 = @eqexpr
| 42 = @neexpr
| 43 = @postincexpr
| 44 = @postdecexpr
| 45 = @preincexpr
| 46 = @predecexpr
| 47 = @minusexpr
| 48 = @plusexpr
| 49 = @bitnotexpr
| 50 = @lognotexpr
| 51 = @castexpr
| 52 = @newexpr
| 53 = @conditionalexpr
| 54 = @parexpr // deprecated
| 55 = @instanceofexpr
| 56 = @localvariabledeclexpr
| 57 = @typeliteral
| 58 = @thisaccess
| 59 = @superaccess
| 60 = @varaccess
| 61 = @methodaccess
| 62 = @unannotatedtypeaccess
| 63 = @arraytypeaccess
| 64 = @packageaccess
| 65 = @wildcardtypeaccess
| 66 = @declannotation
| 67 = @uniontypeaccess
| 68 = @lambdaexpr
| 69 = @memberref
| 70 = @annotatedtypeaccess
| 71 = @typeannotation
| 72 = @intersectiontypeaccess
| 73 = @switchexpr
;
@classinstancexpr = @newexpr | @lambdaexpr | @memberref
@annotation = @declannotation | @typeannotation
@typeaccess = @unannotatedtypeaccess | @annotatedtypeaccess
@assignment = @assignexpr
| @assignop;
@unaryassignment = @postincexpr
| @postdecexpr
| @preincexpr
| @predecexpr;
@assignop = @assignaddexpr
| @assignsubexpr
| @assignmulexpr
| @assigndivexpr
| @assignremexpr
| @assignandexpr
| @assignorexpr
| @assignxorexpr
| @assignlshiftexpr
| @assignrshiftexpr
| @assignurshiftexpr;
@literal = @booleanliteral
| @integerliteral
| @longliteral
| @floatingpointliteral
| @doubleliteral
| @characterliteral
| @stringliteral
| @nullliteral;
@binaryexpr = @mulexpr
| @divexpr
| @remexpr
| @addexpr
| @subexpr
| @lshiftexpr
| @rshiftexpr
| @urshiftexpr
| @andbitexpr
| @orbitexpr
| @xorbitexpr
| @andlogicalexpr
| @orlogicalexpr
| @ltexpr
| @gtexpr
| @leexpr
| @geexpr
| @eqexpr
| @neexpr;
@unaryexpr = @postincexpr
| @postdecexpr
| @preincexpr
| @predecexpr
| @minusexpr
| @plusexpr
| @bitnotexpr
| @lognotexpr;
@caller = @classinstancexpr
| @methodaccess
| @constructorinvocationstmt
| @superconstructorinvocationstmt;
callableBinding(
unique int callerid: @caller ref,
int callee: @callable ref
);
memberRefBinding(
unique int id: @expr ref,
int callable: @callable ref
);
@exprparent = @stmt | @expr | @callable | @field | @fielddecl | @class | @interface | @param | @localvar | @typevariable;
variableBinding(
unique int expr: @varaccess ref,
int variable: @variable ref
);
@variable = @localscopevariable | @field;
@localscopevariable = @localvar | @param;
localvars(
unique int id: @localvar,
string nodeName: string ref,
int typeid: @type ref,
int parentid: @localvariabledeclexpr ref
);
@namedexprorstmt = @breakstmt
| @continuestmt
| @labeledstmt
| @literal;
namestrings(
string name: string ref,
string value: string ref,
unique int parent: @namedexprorstmt ref
);
/*
* Modules
*/
#keyset[name]
modules(
unique int id: @module,
string name: string ref
);
isOpen(
int id: @module ref
);
#keyset[fileId]
cumodule(
int fileId: @file ref,
int moduleId: @module ref
);
@directive = @requires
| @exports
| @opens
| @uses
| @provides
#keyset[directive]
directives(
int id: @module ref,
int directive: @directive ref
);
requires(
unique int id: @requires,
int target: @module ref
);
isTransitive(
int id: @requires ref
);
isStatic(
int id: @requires ref
);
exports(
unique int id: @exports,
int target: @package ref
);
exportsTo(
int id: @exports ref,
int target: @module ref
);
opens(
unique int id: @opens,
int target: @package ref
);
opensTo(
int id: @opens ref,
int target: @module ref
);
uses(
unique int id: @uses,
string serviceInterface: string ref
);
provides(
unique int id: @provides,
string serviceInterface: string ref
);
providesWith(
int id: @provides ref,
string serviceImpl: string ref
);
/*
* Javadoc
*/
javadoc(
unique int id: @javadoc
);
isNormalComment(
int commentid : @javadoc ref
);
isEolComment(
int commentid : @javadoc ref
);
hasJavadoc(
int documentableid: @member ref,
int javadocid: @javadoc ref
);
#keyset[parentid,idx]
javadocTag(
unique int id: @javadocTag,
string name: string ref,
int parentid: @javadocParent ref,
int idx: int ref
);
#keyset[parentid,idx]
javadocText(
unique int id: @javadocText,
string text: string ref,
int parentid: @javadocParent ref,
int idx: int ref
);
@javadocParent = @javadoc | @javadocTag;
@javadocElement = @javadocTag | @javadocText;
@typeorpackage = @type | @package;
@typeorcallable = @type | @callable;
@classorinterface = @interface | @class;
@boundedtype = @typevariable | @wildcard;
@reftype = @classorinterface | @array | @boundedtype;
@classorarray = @class | @array;
@type = @primitive | @reftype;
@callable = @method | @constructor;
@element = @file | @package | @primitive | @class | @interface | @method | @constructor | @modifier | @param | @exception | @field |
@annotation | @boundedtype | @array | @localvar | @expr | @stmt | @import | @fielddecl;
@modifiable = @member_modifiable| @param | @localvar ;
@member_modifiable = @class | @interface | @method | @constructor | @field ;
@member = @method | @constructor | @field | @reftype ;
@locatable = @file | @class | @interface | @fielddecl | @field | @constructor | @method | @param | @exception
| @boundedtype | @typebound | @array | @primitive
| @import | @stmt | @expr | @localvar | @javadoc | @javadocTag | @javadocText
| @xmllocatable;
@top = @element | @locatable | @folder;
/*
* 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;
/*
* configuration files with key value pairs
*/
configs(
unique int id: @config
);
configNames(
unique int id: @configName,
int config: @config ref,
string name: string ref
);
configValues(
unique int id: @configValue,
int config: @config ref,
string value: string ref
);
configLocations(
int locatable: @configLocatable ref,
int location: @location_default ref
);
@configLocatable = @config | @configName | @configValue;

File diff suppressed because it is too large Load Diff

38
java/ql/lib/java.qll Normal file
View File

@@ -0,0 +1,38 @@
/** Provides all default Java QL imports. */
import Customizations
import semmle.code.FileSystem
import semmle.code.Location
import semmle.code.Unit
import semmle.code.java.Annotation
import semmle.code.java.CompilationUnit
import semmle.code.java.ControlFlowGraph
import semmle.code.java.Dependency
import semmle.code.java.Element
import semmle.code.java.Exception
import semmle.code.java.Expr
import semmle.code.java.GeneratedFiles
import semmle.code.java.Generics
import semmle.code.java.Import
import semmle.code.java.J2EE
import semmle.code.java.Javadoc
import semmle.code.java.JDK
import semmle.code.java.JDKAnnotations
import semmle.code.java.JMX
import semmle.code.java.Member
import semmle.code.java.Modifier
import semmle.code.java.Modules
import semmle.code.java.Package
import semmle.code.java.Statement
import semmle.code.java.Type
import semmle.code.java.UnitTests
import semmle.code.java.Variable
import semmle.code.java.controlflow.BasicBlocks
import semmle.code.java.metrics.MetricCallable
import semmle.code.java.metrics.MetricElement
import semmle.code.java.metrics.MetricField
import semmle.code.java.metrics.MetricPackage
import semmle.code.java.metrics.MetricRefType
import semmle.code.java.metrics.MetricStmt
import semmle.code.xml.Ant
import semmle.code.xml.XML

View File

@@ -0,0 +1,4 @@
---
dependencies: {}
compiled: false
lockVersion: 1.0.0

7
java/ql/lib/qlpack.yml Normal file
View File

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

View File

@@ -0,0 +1,215 @@
/** Provides classes for working with files and folders. */
import Location
/** A file or folder. */
class Container extends @container, Top {
/**
* 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 [Providing URLs](https://help.semmle.com/QL/learn-ql/ql/locations.html#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.java"</td><td>"tst.java"</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.java"</td><td>"java"</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.java"</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 this container.
*
* The default implementation gets the absolute path to the container, but subclasses may override
* to provide a different result. To get the absolute path of any `Container`, call
* `Container.getAbsolutePath()` directly.
*/
override string toString() { result = getAbsolutePath() }
}
/** A folder. */
class Folder extends Container, @folder {
override string getAbsolutePath() { folders(this, result, _) }
/** Gets the URL of this folder. */
override string getURL() { result = "folder://" + getAbsolutePath() }
override string getAPrimaryQlClass() { result = "Folder" }
}
/**
* A file.
*
* Note that `File` extends `Container` as it may be a `jar` file.
*/
class File extends Container, @file {
override string getAbsolutePath() { files(this, result, _, _, _) }
/** Gets the URL of this file. */
override string getURL() { result = "file://" + this.getAbsolutePath() + ":0:0:0:0" }
override string getAPrimaryQlClass() { result = "File" }
}
/**
* A Java archive file with a ".jar" extension.
*/
class JarFile extends File {
JarFile() { getExtension() = "jar" }
/**
* Gets the main attribute with the specified `key`
* from this JAR file's manifest.
*/
string getManifestMainAttribute(string key) { jarManifestMain(this, key, result) }
/**
* Gets the "Specification-Version" main attribute
* from this JAR file's manifest.
*/
string getSpecificationVersion() { result = getManifestMainAttribute("Specification-Version") }
/**
* Gets the "Implementation-Version" main attribute
* from this JAR file's manifest.
*/
string getImplementationVersion() { result = getManifestMainAttribute("Implementation-Version") }
/**
* Gets the per-entry attribute for the specified `entry` and `key`
* from this JAR file's manifest.
*/
string getManifestEntryAttribute(string entry, string key) {
jarManifestEntries(this, entry, key, result)
}
override string getAPrimaryQlClass() { result = "JarFile" }
}

View File

@@ -0,0 +1,206 @@
/**
* Provides classes and predicates for working with locations.
*
* Locations represent parts of files and are used to map elements to their source location.
*/
import FileSystem
import semmle.code.java.Element
private import semmle.code.SMAP
/** Holds if element `e` has name `name`. */
predicate hasName(Element e, string name) {
classes(e, name, _, _)
or
interfaces(e, name, _, _)
or
primitives(e, name)
or
constrs(e, name, _, _, _, _)
or
methods(e, name, _, _, _, _)
or
fields(e, name, _, _, _)
or
packages(e, name)
or
files(e, _, name, _, _)
or
paramName(e, name)
or
exists(int pos |
params(e, _, pos, _, _) and
not paramName(e, _) and
name = "p" + pos
)
or
localvars(e, name, _, _)
or
typeVars(e, name, _, _, _)
or
wildcards(e, name, _)
or
arrays(e, name, _, _, _)
or
modifiers(e, name)
}
/**
* Top is the root of the QL type hierarchy; it defines some default
* methods for obtaining locations and a standard `toString()` method.
*/
class Top extends @top {
/** Gets the source location for this element. */
Location getLocation() { fixedHasLocation(this, 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://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
hasLocationInfoAux(filepath, startline, startcolumn, endline, endcolumn)
or
exists(string outFilepath, int outStartline, int outEndline |
hasLocationInfoAux(outFilepath, outStartline, _, outEndline, _) and
hasSmapLocationInfo(filepath, startline, startcolumn, endline, endcolumn, outFilepath,
outStartline, outEndline)
)
}
private predicate hasLocationInfoAux(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
exists(File f, Location l | fixedHasLocation(this, l, f) |
locations_default(l, f, startline, startcolumn, endline, endcolumn) and
filepath = f.getAbsolutePath()
)
}
/** Gets the file associated with this element. */
File getFile() { fixedHasLocation(this, _, result) }
/**
* Gets the total number of lines that this element ranges over,
* including lines of code, comment and whitespace-only lines.
*/
int getTotalNumberOfLines() { numlines(this, result, _, _) }
/** Gets the number of lines of code that this element ranges over. */
int getNumberOfLinesOfCode() { numlines(this, _, result, _) }
/** Gets the number of comment lines that this element ranges over. */
int getNumberOfCommentLines() { numlines(this, _, _, result) }
/** Gets a textual representation of this element. */
cached
string toString() { hasName(this, result) }
/**
* Gets the name of a primary CodeQL class to which this element belongs.
*
* For most elements, this is simply the most precise syntactic category to
* which they belong; for example, `AddExpr` is a primary class, but
* `BinaryExpr` is not.
*
* This predicate always has a result. If no primary class can be
* determined, the result is `"???"`. If multiple primary classes match,
* this predicate can have multiple results.
*/
string getAPrimaryQlClass() { result = "???" }
}
/** A location maps language elements to positions in source files. */
class Location extends @location {
/** 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 total number of lines that this location ranges over,
* including lines of code, comment and whitespace-only lines.
*/
int getNumberOfLines() {
exists(@sourceline s | hasLocation(s, this) |
numlines(s, result, _, _)
or
not numlines(s, _, _, _) and result = 0
)
}
/** Gets the number of lines of code that this location ranges over. */
int getNumberOfLinesOfCode() {
exists(@sourceline s | hasLocation(s, this) |
numlines(s, _, result, _)
or
not numlines(s, _, _, _) and result = 0
)
}
/** Gets the number of comment lines that this location ranges over. */
int getNumberOfCommentLines() {
exists(@sourceline s | hasLocation(s, this) |
numlines(s, _, _, result)
or
not numlines(s, _, _, _) and result = 0
)
}
/** Gets the file containing this location. */
File getFile() { locations_default(this, result, _, _, _, _) }
/** Gets a string representation containing the file and range for this location. */
string toString() {
exists(File f, int startLine, int startCol, int endLine, int endCol |
locations_default(this, f, startLine, startCol, endLine, endCol)
|
if endLine = startLine
then
result =
f.toString() + ":" + startLine.toString() + "[" + startCol.toString() + "-" +
endCol.toString() + "]"
else
result =
f.toString() + ":" + startLine.toString() + "[" + startCol.toString() + "]-" +
endLine.toString() + "[" + endCol.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://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
exists(File f | locations_default(this, f, startline, startcolumn, endline, endcolumn) |
filepath = f.getAbsolutePath()
)
}
}
private predicate hasSourceLocation(Top l, Location loc, File f) {
hasLocation(l, loc) and f = loc.getFile() and f.getExtension() = "java"
}
cached
private predicate fixedHasLocation(Top l, Location loc, File f) {
hasSourceLocation(l, loc, f)
or
hasLocation(l, loc) and not hasSourceLocation(l, _, _) and locations_default(loc, f, _, _, _, _)
}

View File

@@ -0,0 +1,57 @@
/**
* Provides classes and predicates for working with SMAP files (see JSR-045).
*/
import java
/**
* Holds if there exists a mapping between an SMAP input file and line
* and a corresponding SMAP output file and line range.
*/
private predicate smap(File inputFile, int inLine, File outputFile, int outLineStart, int outLineEnd) {
exists(
string defaultStratum, int inputFileNum, int inStart, int inCount, int outStart, int outIncr,
int n
|
smap_header(outputFile, _, defaultStratum) and
smap_files(outputFile, defaultStratum, inputFileNum, _, inputFile) and
smap_lines(outputFile, defaultStratum, inputFileNum, inStart, inCount, outStart, outIncr) and
inLine in [inStart .. inStart + inCount - 1] and
outLineStart = outStart + n * outIncr and
outLineEnd = (n + 1) * outIncr - 1 + outStart and
n = inLine - inStart
)
}
/**
* Holds if there exists a mapping between an SMAP input file and line
* and a corresponding SMAP output file and line.
*/
pragma[nomagic]
private predicate smap(File inputFile, int inLine, File outputFile, int outLine) {
exists(int outLineStart, int outLineEnd |
smap(inputFile, inLine, outputFile, outLineStart, outLineEnd) and
outLine in [outLineStart .. outLineEnd]
)
}
/**
* Holds if an SMAP input location (with path, line and column information)
* has a corresponding SMAP output location (with path and line information).
*
* For example, an SMAP input location may be a location within a JSP file,
* which may have a corresponding SMAP output location in generated Java code.
*/
predicate hasSmapLocationInfo(
string inputPath, int isl, int isc, int iel, int iec, string outputPath, int osl, int oel
) {
exists(File inputFile, File outputFile |
inputPath = inputFile.getAbsolutePath() and
outputPath = outputFile.getAbsolutePath() and
locations_default(_, outputFile, osl, _, oel, _) and
smap(inputFile, isl, outputFile, osl) and
smap(inputFile, iel - 1, outputFile, oel) and
isc = 1 and
iec = 0
)
}

View File

@@ -0,0 +1,10 @@
/** Provides the `Unit` class. */
/** 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" }
}

View File

@@ -0,0 +1,72 @@
/**
* Provides classes and predicates for working with configuration files, such
* as Java `.properties` or `.ini` files.
*/
import semmle.code.Location
/** An element in a configuration file has a location. */
abstract class ConfigLocatable extends @configLocatable {
/** Gets the source location for this element. */
Location getLocation() { configLocations(this, result) }
/** Gets the file associated with this element. */
File getFile() { result = getLocation().getFile() }
/** Gets a textual representation of this element. */
abstract string toString();
}
/**
* A name-value pair often used to store configuration properties
* for applications, such as the port, name or address of a database.
*/
class ConfigPair extends @config, ConfigLocatable {
/** Gets the name of this `ConfigPair`, if any. */
ConfigName getNameElement() { configNames(result, this, _) }
/** Gets the value of this `ConfigPair`, if any. */
ConfigValue getValueElement() { configValues(result, this, _) }
/**
* Gets the string value of the name of this `ConfigPair` if
* it exists and the empty string if it doesn't.
*/
string getEffectiveName() {
if exists(getNameElement()) then result = getNameElement().getName() else result = ""
}
/**
* Gets the string value of the value of this `ConfigPair` if
* it exists and the empty string if it doesn't.
*/
string getEffectiveValue() {
if exists(getValueElement()) then result = getValueElement().getValue() else result = ""
}
/** Gets a printable representation of this `ConfigPair`. */
override string toString() { result = getEffectiveName() + "=" + getEffectiveValue() }
}
/** The name element of a `ConfigPair`. */
class ConfigName extends @configName, ConfigLocatable {
/** Gets the name as a string. */
string getName() { configNames(this, _, result) }
/** Gets a printable representation of this `ConfigName`. */
override string toString() { result = getName() }
}
/** The value element of a `ConfigPair`. */
class ConfigValue extends @configValue, ConfigLocatable {
/** Gets the value as a string. */
string getValue() { configValues(this, _, result) }
/** Gets a printable representation of this `ConfigValue`. */
override string toString() { result = getValue() }
}
/** A Java property is a name-value pair in a `.properties` file. */
class JavaProperty extends ConfigPair {
JavaProperty() { getFile().getExtension() = "properties" }
}

View File

@@ -0,0 +1,165 @@
/**
* Provides classes and predicates for working with Java annotations.
*
* Annotations are used to add meta-information to language elements in a
* uniform fashion. They can be seen as typed modifiers that can take
* parameters.
*
* Each annotation type has zero or more annotation elements that contain a
* name and possibly a value.
*/
import Element
import Expr
import Type
import Member
import JDKAnnotations
/** Any annotation used to annotate language elements with meta-information. */
class Annotation extends @annotation, Expr {
/** Holds if this annotation applies to a declaration. */
predicate isDeclAnnotation() { this instanceof DeclAnnotation }
/** Holds if this annotation applies to a type. */
predicate isTypeAnnotation() { this instanceof TypeAnnotation }
/** Gets the element being annotated. */
Element getAnnotatedElement() {
exists(Element e | e = this.getParent() |
if e.(Field).getCompilationUnit().fromSource()
then
exists(FieldDeclaration decl |
decl.getField(0) = e and
result = decl.getAField()
)
else result = e
)
}
/** Gets the annotation type declaration for this annotation. */
override AnnotationType getType() { result = Expr.super.getType() }
/** Gets the annotation element with the specified `name`. */
AnnotationElement getAnnotationElement(string name) {
result = this.getType().getAnnotationElement(name)
}
/** Gets a value of an annotation element. */
Expr getAValue() { filteredAnnotValue(this, _, result) }
/** Gets the value of the annotation element with the specified `name`. */
Expr getValue(string name) { filteredAnnotValue(this, this.getAnnotationElement(name), result) }
/** Gets the element being annotated. */
Element getTarget() { result = getAnnotatedElement() }
override string toString() { result = this.getType().getName() }
/** This expression's Halstead ID (used to compute Halstead metrics). */
override string getHalsteadID() { result = "Annotation" }
/**
* Gets a value of the annotation element with the specified `name`, which must be declared as an array
* type.
*
* If the annotation element is defined with an array initializer, then the returned value will
* be one of the elements of that array. Otherwise, the returned value will be the single
* expression defined for the value.
*/
Expr getAValue(string name) {
getType().getAnnotationElement(name).getType() instanceof Array and
exists(Expr value | value = getValue(name) |
if value instanceof ArrayInit then result = value.(ArrayInit).getAnInit() else result = value
)
}
override string getAPrimaryQlClass() { result = "Annotation" }
}
/** An `Annotation` that applies to a declaration. */
class DeclAnnotation extends @declannotation, Annotation { }
/** An `Annotation` that applies to a type. */
class TypeAnnotation extends @typeannotation, Annotation { }
/**
* There may be duplicate entries in annotValue(...) - one entry for
* information populated from bytecode, and one for information populated
* from source. This removes the duplication.
*/
private predicate filteredAnnotValue(Annotation a, Method m, Expr val) {
annotValue(a, m, val) and
(sourceAnnotValue(a, m, val) or not sourceAnnotValue(a, m, _))
}
private predicate sourceAnnotValue(Annotation a, Method m, Expr val) {
annotValue(a, m, val) and
val.getFile().getExtension() = "java"
}
/** An abstract representation of language elements that can be annotated. */
class Annotatable extends Element {
/** Holds if this element has an annotation. */
predicate hasAnnotation() { exists(Annotation a | a.getAnnotatedElement() = this) }
/** Holds if this element has the specified annotation. */
predicate hasAnnotation(string package, string name) {
exists(AnnotationType at | at = getAnAnnotation().getType() |
at.nestedName() = name and at.getPackage().getName() = package
)
}
/** Gets an annotation that applies to this element. */
Annotation getAnAnnotation() { result.getAnnotatedElement() = this }
/**
* Holds if this or any enclosing `Annotatable` has a `@SuppressWarnings("<category>")`
* annotation attached to it for the specified `category`.
*/
predicate suppressesWarningsAbout(string category) {
exists(string withQuotes |
withQuotes = getAnAnnotation().(SuppressWarningsAnnotation).getASuppressedWarning()
|
category = withQuotes.substring(1, withQuotes.length() - 1)
)
or
this.(Member).getDeclaringType().suppressesWarningsAbout(category)
or
this.(Expr).getEnclosingCallable().suppressesWarningsAbout(category)
or
this.(Stmt).getEnclosingCallable().suppressesWarningsAbout(category)
or
this.(NestedClass).getEnclosingType().suppressesWarningsAbout(category)
or
this.(LocalVariableDecl).getCallable().suppressesWarningsAbout(category)
}
}
/** An annotation type is a special kind of interface type declaration. */
class AnnotationType extends Interface {
AnnotationType() { isAnnotType(this) }
/** Gets the annotation element with the specified `name`. */
AnnotationElement getAnnotationElement(string name) {
methods(result, _, _, _, this, _) and result.hasName(name)
}
/** Gets an annotation element that is a member of this annotation type. */
AnnotationElement getAnAnnotationElement() { methods(result, _, _, _, this, _) }
/** Holds if this annotation type is annotated with the meta-annotation `@Inherited`. */
predicate isInherited() {
exists(Annotation ann |
ann.getAnnotatedElement() = this and
ann.getType().hasQualifiedName("java.lang.annotation", "Inherited")
)
}
}
/** An annotation element is a member declared in an annotation type. */
class AnnotationElement extends Member {
AnnotationElement() { isAnnotElem(this) }
/** Gets the type of this annotation element. */
Type getType() { methods(this, _, _, result, _, _) }
}

View File

@@ -0,0 +1,102 @@
/**
* Provides classes and predicates for reasoning about instances of
* `java.util.Collection` and their methods.
*/
import java
/**
* The type `t` is a parameterization of `g`, where the `i`-th type parameter of
* `g` is instantiated to `a`?
*
* For example, `List<Integer>` parameterizes `List<T>`, instantiating its `0`-th
* type parameter to `Integer`, while the raw type `List` also parameterizes
* `List<T>`, instantiating the type parameter to `Object`.
*/
predicate instantiates(RefType t, GenericType g, int i, RefType arg) {
t = g.getAParameterizedType() and
arg = t.(ParameterizedType).getTypeArgument(i)
or
t = g.getRawType() and
exists(g.getTypeParameter(i)) and
arg instanceof TypeObject
}
/**
* Generalisation of `instantiates` that takes subtyping into account:
*
* - `HashSet<Integer>` indirectly instantiates `Collection` (but also `HashSet` and `Set`),
* with the `0`-th type parameter being `Integer`;
* - a class `MyList extends ArrayList<Runnable>` also instantiates `Collection`
* (as well as `AbstractList`, `AbstractCollection` and `List`), with the `0`-th type
* parameter being `Runnable`;
* - the same is true of `class MyOtherList<T> extends ArrayList<Runnable>` (note that
* it does _not_ instantiate the type parameter to `T`);
* - a class `MyIntMap<V> extends HashMap<Integer, V>` instantiates `Map` (among others)
* with the `0`-th type parameter being `Integer` and the `1`-th type parameter being `V`.
*/
pragma[nomagic]
predicate indirectlyInstantiates(RefType t, GenericType g, int i, RefType arg) {
instantiates(t, g, i, arg)
or
exists(RefType sup |
t.extendsOrImplements(sup) and
indirectlyInstantiates(sup, g, i, arg)
)
}
/** A reference type that extends a parameterization of `java.util.Collection`. */
class CollectionType extends RefType {
CollectionType() {
this.getSourceDeclaration().getASourceSupertype*().hasQualifiedName("java.util", "Collection")
}
/** Gets the type of elements stored in this collection. */
RefType getElementType() {
exists(GenericInterface coll | coll.hasQualifiedName("java.util", "Collection") |
indirectlyInstantiates(this, coll, 0, result)
)
}
}
/** A method declared in a collection type. */
class CollectionMethod extends Method {
CollectionMethod() { this.getDeclaringType() instanceof CollectionType }
/** Gets the type of elements of the collection to which this method belongs. */
RefType getReceiverElementType() {
result = this.getDeclaringType().(CollectionType).getElementType()
}
}
/** The `size` method on `java.util.Collection`. */
class CollectionSizeMethod extends CollectionMethod {
CollectionSizeMethod() { this.hasName("size") and this.hasNoParameters() }
}
/** A method that mutates the collection it belongs to. */
class CollectionMutator extends CollectionMethod {
CollectionMutator() { this.getName().regexpMatch("add.*|remove.*|push|pop|clear") }
}
/** A method call that mutates a collection. */
class CollectionMutation extends MethodAccess {
CollectionMutation() { this.getMethod() instanceof CollectionMutator }
/** Holds if the result of this call is not immediately discarded. */
predicate resultIsChecked() { not this.getParent() instanceof ExprStmt }
}
/** A method that queries the contents of a collection without mutating it. */
class CollectionQueryMethod extends CollectionMethod {
CollectionQueryMethod() { this.getName().regexpMatch("contains|containsAll|get|size|peek") }
}
/** A `new` expression that allocates a fresh, empty collection. */
class FreshCollection extends ClassInstanceExpr {
FreshCollection() {
this.getConstructedType() instanceof CollectionType and
this.getNumArgument() = 0 and
not exists(this.getAnonymousClass())
}
}

View File

@@ -0,0 +1,35 @@
/**
* Provides classes and predicates for working with Java compilation units.
*/
import Element
import Package
import semmle.code.FileSystem
/**
* A compilation unit is a `.java` or `.class` file.
*/
class CompilationUnit extends Element, File {
CompilationUnit() { cupackage(this, _) }
/** Gets the name of the compilation unit (not including its extension). */
override string getName() { result = Element.super.getName() }
/**
* Holds if this compilation unit has the specified `name`,
* which must not include the file extension.
*/
override predicate hasName(string name) { Element.super.hasName(name) }
override string toString() { result = Element.super.toString() }
/** Gets the declared package of this compilation unit. */
Package getPackage() { cupackage(this, result) }
/**
* Gets the module associated with this compilation unit, if any.
*/
Module getModule() { cumodule(this, result) }
override string getAPrimaryQlClass() { result = "CompilationUnit" }
}

View File

@@ -0,0 +1,94 @@
/**
* Provides classes and predicates for representing completions.
*/
/*
* A completion represents how a statement or expression terminates.
*
* There are five kinds of completions: normal completion,
* `return` completion, `break` completion,
* `continue` completion, and `throw` completion.
*
* Normal completions are further subdivided into boolean completions and all
* other normal completions. A boolean completion adds the information that the
* cfg node terminated with the given boolean value due to a subexpression
* terminating with the other given boolean value. This is only
* relevant for conditional contexts in which the value controls the
* control-flow successor.
*/
import java
/**
* A label of a `LabeledStmt`.
*/
newtype Label = MkLabel(string l) { exists(LabeledStmt lbl | l = lbl.getLabel()) }
/**
* Either a `Label` or nothing.
*/
newtype MaybeLabel =
JustLabel(Label l) or
NoLabel()
/**
* A completion of a statement or an expression.
*/
newtype Completion =
/**
* The statement or expression completes normally and continues to the next statement.
*/
NormalCompletion() or
/**
* The statement or expression completes by returning from the function.
*/
ReturnCompletion() or
/**
* The expression completes with value `outerValue` overall and with the last control
* flow node having value `innerValue`.
*/
BooleanCompletion(boolean outerValue, boolean innerValue) {
(outerValue = true or outerValue = false) and
(innerValue = true or innerValue = false)
} or
/**
* The expression or statement completes via a `break` statement.
*/
BreakCompletion(MaybeLabel l) or
/**
* The expression or statement completes via a `yield` statement.
*/
YieldCompletion(NormalOrBooleanCompletion c) or
/**
* The expression or statement completes via a `continue` statement.
*/
ContinueCompletion(MaybeLabel l) or
/**
* The expression or statement completes by throwing a `ThrowableType`.
*/
ThrowCompletion(ThrowableType tt)
/** A completion that is either a `NormalCompletion` or a `BooleanCompletion`. */
class NormalOrBooleanCompletion extends Completion {
NormalOrBooleanCompletion() {
this instanceof NormalCompletion or this instanceof BooleanCompletion
}
/** Gets a textual representation of this completion. */
string toString() { result = "completion" }
}
/** Gets the completion `ContinueCompletion(NoLabel())`. */
ContinueCompletion anonymousContinueCompletion() { result = ContinueCompletion(NoLabel()) }
/** Gets the completion `ContinueCompletion(JustLabel(l))`. */
ContinueCompletion labelledContinueCompletion(Label l) { result = ContinueCompletion(JustLabel(l)) }
/** Gets the completion `BreakCompletion(NoLabel())`. */
BreakCompletion anonymousBreakCompletion() { result = BreakCompletion(NoLabel()) }
/** Gets the completion `BreakCompletion(JustLabel(l))`. */
BreakCompletion labelledBreakCompletion(Label l) { result = BreakCompletion(JustLabel(l)) }
/** Gets the completion `BooleanCompletion(value, value)`. */
Completion basicBooleanCompletion(boolean value) { result = BooleanCompletion(value, value) }

View File

@@ -0,0 +1,47 @@
import java
/**
* Holds if `e` is synchronized by a local synchronized statement `sync` on the variable `v`.
*/
predicate locallySynchronizedOn(Expr e, SynchronizedStmt sync, Variable v) {
e.getEnclosingStmt().getEnclosingStmt+() = sync and
sync.getExpr().(VarAccess).getVariable() = v
}
/**
* Holds if `e` is synchronized by a local synchronized statement on a `this` of type `thisType`, or by a synchronized
* modifier on the enclosing (non-static) method.
*/
predicate locallySynchronizedOnThis(Expr e, RefType thisType) {
exists(SynchronizedStmt sync | e.getEnclosingStmt().getEnclosingStmt+() = sync |
sync.getExpr().(ThisAccess).getType().(RefType).getSourceDeclaration() = thisType
)
or
exists(SynchronizedCallable c | c = e.getEnclosingCallable() |
not c.isStatic() and thisType = c.getDeclaringType()
)
}
/**
* Holds if `e` is synchronized by a `synchronized` modifier on the enclosing (static) method.
*/
predicate locallySynchronizedOnClass(Expr e, RefType classType) {
exists(SynchronizedCallable c | c = e.getEnclosingCallable() |
c.isStatic() and classType = c.getDeclaringType()
)
}
/**
* A callable that is synchronized on its enclosing instance, either by a `synchronized` modifier, or
* by having a body which is precisely `synchronized(this) { ... }`.
*/
class SynchronizedCallable extends Callable {
SynchronizedCallable() {
this.isSynchronized()
or
// The body is just `synchronized(this) { ... }`.
exists(SynchronizedStmt s | this.getBody().(SingletonBlock).getStmt() = s |
s.getExpr().(ThisAccess).getType() = this.getDeclaringType()
)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,140 @@
/**
* Provides support for conversion contexts, in which an expression is converted
* (implicitly or explicitly) to a different type.
*
* See the Java Language Specification, Section 5, for details.
*/
import java
import semmle.code.java.arithmetic.Overflow
/**
* A expression where an implicit conversion can occur.
*
* See the Java Language Specification, Section 5.
*/
abstract class ConversionSite extends Expr {
/**
* Gets the type that is converted to.
*/
abstract Type getConversionTarget();
/**
* Gets the type that is converted from.
*/
Type getConversionSource() { result = this.getType() }
/**
* Whether this conversion site actually induces a conversion.
*/
predicate isTrivial() { getConversionTarget() = getConversionSource() }
/**
* Whether this conversion is implicit.
*/
predicate isImplicit() { any() }
abstract string kind();
}
/**
* An assignment conversion. For example, `x += b` converts `b`
* to be of the type of `x`.
*
* See the Java Language Specification, Section 5.2.
*/
class AssignmentConversionContext extends ConversionSite {
Variable v;
AssignmentConversionContext() {
this = v.getAnAssignedValue()
or
exists(Assignment a | a.getDest() = v.getAnAccess() and this = a.getSource())
}
override Type getConversionTarget() { result = v.getType() }
override string kind() { result = "assignment context" }
}
/**
* An return conversion. For example, `return b` converts `b`
* to be of the return type of the enclosing callable.
*
* Note that the Java Language Specification handles these as
* assignment conversions (section 5.2), but for clarity we split them out here.
*/
class ReturnConversionSite extends ConversionSite {
ReturnStmt r;
ReturnConversionSite() { this = r.getResult() }
override Type getConversionTarget() { result = r.getEnclosingCallable().getReturnType() }
override string kind() { result = "return context" }
}
/**
* An invocation conversion. For example `f(b)` converts `b` to
* have the type of the corresponding parameter of `f`.
*
* See the Java Language Specification, Section 5.3.
*/
class InvocationConversionContext extends ConversionSite {
Call c;
int index;
InvocationConversionContext() { this = c.getArgument(index) }
override Type getConversionTarget() { result = c.getCallee().getParameter(index).getType() }
override string kind() { result = "invocation context" }
}
/**
* A string conversion. For example `a + b`, where `a` is a
* `String`, converts `b` to have type `String`.
*
* See the Java Language Specification, Section 5.4.
*/
class StringConversionContext extends ConversionSite {
AddExpr a;
StringConversionContext() {
a.getAnOperand() = this and
not this.getType() instanceof TypeString and
a.getAnOperand().getType() instanceof TypeString
}
override Type getConversionTarget() { result instanceof TypeString }
override string kind() { result = "string context" }
}
class CastConversionContext extends ConversionSite {
CastExpr c;
CastConversionContext() { this = c.getExpr() }
override Type getConversionTarget() { result = c.getType() }
override predicate isImplicit() { none() }
override string kind() { result = "cast context" }
}
/**
* A numeric conversion. For example, `a * b` converts `a` and
* `b` to have an appropriate numeric type.
*
* See the Java Language Specification, Section 5.4.
*/
class NumericConversionContext extends ConversionSite {
ArithExpr e;
NumericConversionContext() { this = e.getAnOperand() }
override Type getConversionTarget() { result = e.getType() }
override string kind() { result = "numeric context" }
}

View File

@@ -0,0 +1,107 @@
/**
* Provides utility predicates for representing dependencies between types.
*/
import Type
import Generics
import Expr
/**
* Holds if type `t` depends on type `dep`.
*
* Dependencies are restricted to generic and non-generic reference types.
*
* Dependencies on parameterized or raw types are decomposed into
* a dependency on the corresponding generic type and separate
* dependencies on (source declarations of) any type arguments.
*
* For example, a dependency on type `List<Set<String>>` is represented by
* dependencies on the generic types `List` and `Set` as well as a dependency
* on the type `String` but not on the parameterized types `List<Set<String>>`
* or `Set<String>`.
*/
predicate depends(RefType t, RefType dep) {
// Type `t` is neither a parameterized nor a raw type and is distinct from `dep`.
not isParameterized(t) and
not isRaw(t) and
not t = dep and
// Type `t` depends on:
(
// its supertypes,
usesType(t.getASupertype(), dep)
or
// its enclosing type,
usesType(t.(NestedType).getEnclosingType(), dep)
or
// the type of any field declared in `t`,
exists(Field f | f.getDeclaringType() = t | usesType(f.getType(), dep))
or
// the return type of any method declared in `t`,
exists(Method m | m.getDeclaringType() = t | usesType(m.getReturnType(), dep))
or
// the type of any parameter of a callable in `t`,
exists(Callable c | c.getDeclaringType() = t | usesType(c.getAParamType(), dep))
or
// the type of any exception in the `throws` clause of a callable declared in `t`,
exists(Exception e | e.getCallable().getDeclaringType() = t | usesType(e.getType(), dep))
or
// the declaring type of a callable accessed in `t`,
exists(Callable c | c.getAReference().getEnclosingCallable().getDeclaringType() = t |
usesType(c.getSourceDeclaration().getDeclaringType(), dep)
)
or
// the declaring type of a field accessed in `t`,
exists(Field f | f.getAnAccess().getEnclosingCallable().getDeclaringType() = t |
usesType(f.getSourceDeclaration().getDeclaringType(), dep)
)
or
// the type of a local variable declared in `t`,
exists(LocalVariableDeclExpr decl | decl.getEnclosingCallable().getDeclaringType() = t |
usesType(decl.getType(), dep)
)
or
// the type of a type literal accessed in `t`,
exists(TypeLiteral l | l.getEnclosingCallable().getDeclaringType() = t |
usesType(l.getReferencedType(), dep)
)
or
// the type of an annotation (or one of its element values) that annotates `t` or one of its members,
exists(Annotation a |
a.getAnnotatedElement() = t or
a.getAnnotatedElement().(Member).getDeclaringType() = t
|
usesType(a.getType(), dep) or
usesType(a.getAValue().getType(), dep)
)
or
// the type accessed in an `instanceof` expression in `t`.
exists(InstanceOfExpr ioe | t = ioe.getEnclosingCallable().getDeclaringType() |
usesType(ioe.getCheckedType(), dep)
)
)
}
/**
* Bind the reference type `dep` to the source declaration of any types used to construct `t`,
* including (possibly nested) type parameters of parameterized types, element types of array types,
* and bounds of type variables or wildcards.
*/
cached
predicate usesType(Type t, RefType dep) {
dep = inside*(t).getSourceDeclaration() and
not dep instanceof TypeVariable and
not dep instanceof Wildcard and
not dep instanceof Array
}
/**
* Gets a type argument of a parameterized type,
* the element type of an array type, or
* a bound of a type variable or wildcard.
*/
private RefType inside(Type t) {
result = t.(TypeVariable).getATypeBound().getType() or
result = t.(Wildcard).getATypeBound().getType() or
result = t.(ParameterizedType).getATypeArgument() or
result = t.(Array).getElementType()
}

View File

@@ -0,0 +1,165 @@
/**
* This library provides utility predicates for representing the number of dependencies between types.
*/
import Type
import Generics
import Expr
/**
* The number of dependencies from type `t` on type `dep`.
*
* Dependencies are restricted to generic and non-generic reference types.
*
* Dependencies on parameterized or raw types are decomposed into
* a dependency on the corresponding generic type and separate
* dependencies on (source declarations of) any type arguments.
*
* For example, a dependency on type `List<Set<String>>` is represented by
* dependencies on the generic types `List` and `Set` as well as a dependency
* on the type `String` but not on the parameterized types `List<Set<String>>`
* or `Set<String>`.
*/
pragma[nomagic]
predicate numDepends(RefType t, RefType dep, int value) {
// Type `t` is neither a parameterized nor a raw type and is distinct from `dep`.
not isParameterized(t) and
not isRaw(t) and
not t = dep and
// Type `t` depends on:
value =
strictcount(Element elem |
// its supertypes,
exists(RefType s | elem = s and t.hasSupertype(s) | usesType(s, dep))
or
// its enclosing types,
exists(RefType s | elem = s and t.getEnclosingType() = s | usesType(s, dep))
or
// the type of any field declared in `t`,
exists(Field f | elem = f and f.getDeclaringType() = t | usesType(f.getType(), dep))
or
// the return type of any method declared in `t`,
exists(Method m | elem = m and m.getDeclaringType() = t | usesType(m.getReturnType(), dep))
or
// the type of any parameter of a callable in `t`,
exists(Parameter p | elem = p and p.getCallable().getDeclaringType() = t |
usesType(p.getType(), dep)
)
or
// the type of any exception in the `throws` clause of a callable declared in `t`,
exists(Exception e | elem = e and e.getCallable().getDeclaringType() = t |
usesType(e.getType(), dep)
)
or
// the declaring type of a callable accessed in `t`,
exists(Call c |
elem = c and
c.getEnclosingCallable().getDeclaringType() = t
|
usesType(c.getCallee().getSourceDeclaration().getDeclaringType(), dep)
)
or
// the declaring type of a field accessed in `t`,
exists(FieldAccess fa |
elem = fa and
fa.getEnclosingCallable().getDeclaringType() = t
|
usesType(fa.getField().getSourceDeclaration().getDeclaringType(), dep)
)
or
// the type of a local variable declared in `t`,
exists(LocalVariableDeclExpr decl |
elem = decl and
decl.getEnclosingCallable().getDeclaringType() = t
|
usesType(decl.getType(), dep)
)
or
// the type of a type literal accessed in `t`,
exists(TypeLiteral l |
elem = l and
l.getEnclosingCallable().getDeclaringType() = t
|
usesType(l.getReferencedType(), dep)
)
or
// the type of an annotation (or one of its element values) that annotates `t` or one of its members,
exists(Annotation a |
a.getAnnotatedElement() = t or
a.getAnnotatedElement().(Member).getDeclaringType() = t
|
elem = a and usesType(a.getType(), dep)
or
elem = a.getAValue() and
elem.getFile().getExtension() = "java" and
usesType(elem.(Expr).getType(), dep)
)
or
// the type accessed in an `instanceof` expression in `t`.
exists(InstanceOfExpr ioe |
elem = ioe and
t = ioe.getEnclosingCallable().getDeclaringType()
|
usesType(ioe.getCheckedType(), dep)
)
)
}
predicate filePackageDependencyCount(File sourceFile, int total, string entity) {
exists(Package targetPackage |
total =
strictsum(RefType sourceType, RefType targetType, int num |
sourceType.getFile() = sourceFile and
sourceType.fromSource() and
sourceType.getPackage() != targetPackage and
targetType.getPackage() = targetPackage and
numDepends(sourceType, targetType, num)
|
num
) and
entity = "/" + sourceFile.getRelativePath() + "<|>" + targetPackage + "<|>N/A"
)
}
private string nameVersionRegex() { result = "([_.A-Za-z0-9-]*)-([0-9][A-Za-z0-9.+_-]*)" }
/**
* Given a JAR filename, try to split it into a name and version.
* This is a heuristic approach assuming that the a dash is used to
* separate the library name from a largely numeric version such as
* `commons-io-2.4`.
*/
bindingset[target]
predicate hasDashedVersion(string target, string name, string version) {
exists(string regex | regex = nameVersionRegex() |
name = target.regexpCapture(regex, 1) and
version = target.regexpCapture(regex, 2)
)
}
predicate fileJarDependencyCount(File sourceFile, int total, string entity) {
exists(Container targetJar, string jarStem |
jarStem = targetJar.getStem() and
targetJar.(File).getExtension() = "jar" and
jarStem != "rt"
|
total =
strictsum(RefType r, RefType dep, int num |
r.getFile() = sourceFile and
r.fromSource() and
dep.getFile().getParentContainer*() = targetJar and
numDepends(r, dep, num)
|
num
) and
exists(string name, string version |
if hasDashedVersion(jarStem, _, _)
then hasDashedVersion(jarStem, name, version)
else (
name = jarStem and version = "unknown"
)
|
entity = "/" + sourceFile.getRelativePath() + "<|>" + name + "<|>" + version
)
)
}

View File

@@ -0,0 +1,69 @@
/**
* Provides a class that represents named elements in Java programs.
*/
import CompilationUnit
import semmle.code.Location
import Javadoc
/** A program element that has a name. */
class Element extends @element, Top {
/** Holds if this element has the specified `name`. */
predicate hasName(string name) { hasName(this, name) }
/** Gets the name of this element. */
string getName() { this.hasName(result) }
/**
* Holds if this element transitively contains the specified element `e`.
*/
predicate contains(Element e) { this.hasChildElement+(e) }
/**
* Holds if this element is the immediate parent of the specified element `e`.
*
* It is usually preferable to use more specific predicates such as
* `getEnclosingCallable()`, `getDeclaringType()` and/or `getEnclosingType()`
* instead of this general predicate.
*/
predicate hasChildElement(Element e) { hasChildElement(this, e) }
/**
* Holds if this element pertains to a source file.
*
* Elements pertaining to source files may include generated elements
* not visible in source code, such as implicit default constructors.
*/
predicate fromSource() { getCompilationUnit().getExtension() = "java" }
/** Gets the compilation unit that this element belongs to. */
CompilationUnit getCompilationUnit() { result = getFile() }
/** Cast this element to a `Documentable`. */
Documentable getDoc() { result = this }
}
/**
* Holds if element `parent` is immediately above element `e` in the syntax tree.
*/
private predicate hasChildElement(Element parent, Element e) {
cupackage(e, parent)
or
enclInReftype(e, parent)
or
not enclInReftype(e, _) and
e.(Class).getCompilationUnit() = parent
or
not enclInReftype(e, _) and
e.(Interface).getCompilationUnit() = parent
or
methods(e, _, _, _, parent, _)
or
constrs(e, _, _, _, parent, _)
or
params(e, _, _, parent, _)
or
fields(e, _, _, parent, _)
or
typeVars(e, _, _, _, parent)
}

View File

@@ -0,0 +1,32 @@
/**
* Provides classes and predicates for working with Java exceptions.
*/
import Element
import Type
/**
* An Exception represents an element listed in the `throws` clause
* of a method of constructor.
*
* For example, `E` is an exception thrown by method `m` in
* `void m() throws E;`, whereas `T` is an exception _type_ in
* `class T extends Exception { }`.
*/
class Exception extends Element, @exception {
/** Gets the type of this exception. */
RefType getType() { exceptions(this, result, _) }
/** Gets the callable whose `throws` clause contains this exception. */
Callable getCallable() { exceptions(this, _, result) }
/** Gets the name of this exception, that is, the name of its type. */
override string getName() { result = this.getType().getName() }
/** Holds if this exception has the specified `name`. */
override predicate hasName(string name) { this.getType().hasName(name) }
override string toString() { result = this.getType().toString() }
override string getAPrimaryQlClass() { result = "Exception" }
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,62 @@
/**
* Provides classes and predicates for working with the most common types of generated files.
*/
import Type
private import semmle.code.java.frameworks.JavaxAnnotations
/** A Java class that is detected as having been generated. */
abstract class GeneratedClass extends Class { }
/**
* A Java class annotated with a `@Generated` annotation.
*/
class AnnotatedGeneratedClass extends GeneratedClass {
AnnotatedGeneratedClass() { this.getAnAnnotation() instanceof GeneratedAnnotation }
}
/** A Java class generated by an ANTLR scanner or parser class. */
class AntlrGenerated extends GeneratedClass {
AntlrGenerated() {
exists(RefType t | this.getASupertype+() = t |
// ANTLR v3
t.hasQualifiedName("org.antlr.runtime", "Lexer") or
t.hasQualifiedName("org.antlr.runtime", "Parser") or
t.hasQualifiedName("org.antlr.runtime.tree", "TreeParser") or
// ANTLR v2
t.hasQualifiedName("antlr", "TreeParser") or
t.hasQualifiedName("antlr", "CharScanner") or
t.hasQualifiedName("antlr", "LLkParser")
)
}
}
/** A generated callable is a callable declared in a generated class. */
class GeneratedCallable extends Callable {
GeneratedCallable() { this.getDeclaringType() instanceof GeneratedClass }
}
/**
* A file that is detected as having been generated.
*/
abstract class GeneratedFile extends File { }
/**
* A file detected as generated based on commonly-used marker comments.
*/
library class MarkerCommentGeneratedFile extends GeneratedFile {
MarkerCommentGeneratedFile() {
exists(JavadocElement t | t.getFile() = this |
exists(string msg | msg = t.getText() |
msg.regexpMatch("(?i).*\\bGenerated By\\b.*\\bDo not edit\\b.*") or
msg.regexpMatch("(?i).*\\bThis (file|class|interface|art[ei]fact) (was|is|(has been)) (?:auto[ -]?)?gener(e?)ated.*") or
msg.regexpMatch("(?i).*\\bAny modifications to this file will be lost\\b.*") or
msg.regexpMatch("(?i).*\\bThis (file|class|interface|art[ei]fact) (was|is) (?:mechanically|automatically) generated\\b.*") or
msg.regexpMatch("(?i).*\\bThe following code was (?:auto[ -]?)?generated (?:by|from)\\b.*") or
msg.regexpMatch("(?i).*\\bAutogenerated by Thrift.*") or
msg.regexpMatch("(?i).*\\bGenerated By.*JavaCC.*") or
msg.regexpMatch("(?i).*\\bGenerated from .* by ANTLR.*")
)
)
}
}

View File

@@ -0,0 +1,604 @@
/**
* Provides classes and predicates for working with generic types.
*
* A generic type as declared in the program, for example
*
* ```
* class X<T> { }
* ```
* is represented by a `GenericType`.
*
* A parameterized instance of such a type, for example
*
* ```
* X<String>
* ```
* is represented by a `ParameterizedType`.
*
* For dealing with legacy code that is unaware of generics, every generic type has a
* "raw" version, represented by a `RawType`. In the example, `X` is the raw version of
* `X<T>`.
*
* The erasure of a parameterized or raw type is its generic counterpart.
*
* Type parameters may have bounds as in
*
* ```
* class X<T extends Number> { }
* ```
* which are represented by a `TypeBound`.
*
* The terminology for generic methods is analogous.
*/
import Type
/**
* A generic type is a type that has a type parameter.
*
* For example, `X` in `class X<T> { }`.
*/
class GenericType extends RefType {
GenericType() { typeVars(_, _, _, _, this) }
/**
* Gets a parameterization of this generic type, where each use of
* a formal type parameter has been replaced by its argument.
*
* For example, `List<Number>` is a parameterization of
* the generic type `List<E>`, where `E` is a type parameter.
*/
ParameterizedType getAParameterizedType() { result.getErasure() = this }
/**
* Gets the raw type corresponding to this generic type.
*
* The raw version of this generic type is the type that is formed by
* using the name of this generic type without specifying its type arguments.
*
* For example, `List` is the raw version of the generic type
* `List<E>`, where `E` is a type parameter.
*/
RawType getRawType() { result.getErasure() = this }
/**
* Gets the `i`-th type parameter of this generic type.
*/
TypeVariable getTypeParameter(int i) { typeVars(result, _, i, _, this) }
/**
* Gets a type parameter of this generic type.
*/
TypeVariable getATypeParameter() { result = getTypeParameter(_) }
/**
* Gets the number of type parameters of this generic type.
*/
int getNumberOfTypeParameters() { result = strictcount(getATypeParameter()) }
override string getAPrimaryQlClass() { result = "GenericType" }
}
/** A generic type that is a class. */
class GenericClass extends GenericType, Class {
override string getAPrimaryQlClass() {
result = Class.super.getAPrimaryQlClass() or
result = GenericType.super.getAPrimaryQlClass()
}
}
/** A generic type that is an interface. */
class GenericInterface extends GenericType, Interface {
override string getAPrimaryQlClass() {
result = Interface.super.getAPrimaryQlClass() or
result = GenericType.super.getAPrimaryQlClass()
}
}
/**
* A common super-class for Java types that may have a type bound.
* This includes type parameters and wildcards.
*/
abstract class BoundedType extends RefType, @boundedtype {
/** Holds if this type is bounded. */
predicate hasTypeBound() { exists(TypeBound tb | tb = this.getATypeBound()) }
/** Gets a type bound for this type, if any. */
TypeBound getATypeBound() { result.getBoundedType() = this }
/** Gets the first type bound for this type, if any. */
TypeBound getFirstTypeBound() { result = getATypeBound() and result.getPosition() = 0 }
/**
* Gets an upper type bound of this type, or `Object`
* if no explicit type bound is present.
*/
abstract RefType getUpperBoundType();
/**
* Gets the first upper type bound of this type, or `Object`
* if no explicit type bound is present.
*/
abstract RefType getFirstUpperBoundType();
/** Gets a transitive upper bound for this type that is not itself a bounded type. */
RefType getAnUltimateUpperBoundType() {
result = getUpperBoundType() and not result instanceof BoundedType
or
result = getUpperBoundType().(BoundedType).getAnUltimateUpperBoundType()
}
override string getAPrimaryQlClass() { result = "BoundedType" }
}
/**
* A type parameter used in the declaration of a generic type or method.
*
* For example, `T` is a type parameter in
* `class X<T> { }` and in `<T> void m() { }`.
*/
class TypeVariable extends BoundedType, @typevariable {
/** Gets the generic type that is parameterized by this type parameter, if any. */
RefType getGenericType() { typeVars(this, _, _, _, result) }
/** Gets the generic callable that is parameterized by this type parameter, if any. */
GenericCallable getGenericCallable() { typeVars(this, _, _, _, result) }
/**
* Gets an upper bound of this type parameter, or `Object`
* if no explicit type bound is present.
*/
pragma[nomagic]
override RefType getUpperBoundType() {
if this.hasTypeBound()
then result = this.getATypeBound().getType()
else result instanceof TypeObject
}
/**
* Gets the first upper bound of this type parameter, or `Object`
* if no explicit type bound is present.
*/
pragma[nomagic]
override RefType getFirstUpperBoundType() {
if this.hasTypeBound()
then result = this.getFirstTypeBound().getType()
else result instanceof TypeObject
}
/** Gets the lexically enclosing package of this type parameter, if any. */
override Package getPackage() {
result = getGenericType().getPackage() or
result = getGenericCallable().getDeclaringType().getPackage()
}
/** Finds a type that was supplied for this parameter. */
RefType getASuppliedType() {
exists(RefType typearg |
exists(GenericType gen, int pos |
this = gen.getTypeParameter(pos) and
typearg = gen.getAParameterizedType().getTypeArgument(pos)
)
or
typearg = any(GenericCall call).getATypeArgument(this)
|
if typearg.(Wildcard).isUnconstrained() and this.hasTypeBound()
then result.(Wildcard).getUpperBound().getType() = this.getUpperBoundType()
else result = typearg
)
}
/** Finds a non-typevariable type that was transitively supplied for this parameter. */
RefType getAnUltimatelySuppliedType() {
result = getASuppliedType() and not result instanceof TypeVariable
or
result = getASuppliedType().(TypeVariable).getAnUltimatelySuppliedType()
}
override string getAPrimaryQlClass() { result = "TypeVariable" }
}
/**
* A wildcard used as a type argument.
*
* For example, in
*
* ```
* Map<? extends Number, ? super Float>
* ```
* the first wildcard has an upper bound of `Number`
* and the second wildcard has a lower bound of `Float`.
*/
class Wildcard extends BoundedType, @wildcard {
/**
* Holds if this wildcard is either unconstrained (i.e. `?`) or
* has a type bound.
*/
override predicate hasTypeBound() { BoundedType.super.hasTypeBound() }
/**
* Holds if this wildcard is either unconstrained (i.e. `?`) or
* has an upper bound.
*/
predicate hasUpperBound() { wildcards(this, _, 1) }
/** Holds if this wildcard has a lower bound. */
predicate hasLowerBound() { wildcards(this, _, 2) }
/** Gets the upper bound for this wildcard, if any. */
TypeBound getUpperBound() { this.hasUpperBound() and result = this.getATypeBound() }
/**
* Gets an upper bound type of this wildcard, or `Object`
* if no explicit type bound is present.
*/
override RefType getUpperBoundType() {
if this.hasUpperBound()
then result = this.getUpperBound().getType()
else result instanceof TypeObject
}
/**
* Gets the first upper bound type of this wildcard, or `Object`
* if no explicit type bound is present.
*/
override RefType getFirstUpperBoundType() {
if this.hasUpperBound()
then result = this.getFirstTypeBound().getType()
else result instanceof TypeObject
}
/** Gets the lower bound of this wildcard, if any. */
TypeBound getLowerBound() { this.hasLowerBound() and result = this.getATypeBound() }
/**
* Gets the lower bound type for this wildcard,
* if an explicit lower bound is present.
*/
Type getLowerBoundType() { result = this.getLowerBound().getType() }
/**
* Holds if this is the unconstrained wildcard `?`.
*/
predicate isUnconstrained() {
not hasLowerBound() and
wildcards(this, "?", _)
}
override string getAPrimaryQlClass() { result = "Wildcard" }
}
/**
* A type bound on a type variable.
*
* For example, `Number` is a type bound on the type variable
* `T` in `class X<T extends Number> { }`.
*
* Type variables can have multiple type bounds, specified by
* an intersection type `T0 & T1 & ... & Tn`.
* A bound with position 0 is an interface type or class type (possibly `Object`) and
* a bound with a non-zero position is an interface type.
*/
class TypeBound extends @typebound {
/**
* Gets the type variable that is bounded by this type bound.
*
* For example, `T` is the type variable bounded by the
* type `Number` in `T extends Number`.
*/
BoundedType getBoundedType() { typeBounds(this, _, _, result) }
/**
* Gets the type of this bound.
*
* For example, `Number` is the type of the bound (of
* the type variable `T`) in `T extends Number`.
*/
RefType getType() { typeBounds(this, result, _, _) }
/**
* Gets the (zero-indexed) position of this bound.
*
* For example, in
*
* ```
* class X<T extends Runnable & Cloneable> { }
* ```
* the position of the bound `Runnable` is 0 and
* the position of the bound `Cloneable` is 1.
*/
int getPosition() { typeBounds(this, _, result, _) }
/** Gets a textual representation of this type bound. */
string toString() { result = this.getType().getName() }
}
// -------- Parameterizations of generic types --------
/**
* A parameterized type is an instantiation of a generic type, where
* each formal type variable has been replaced with a type argument.
*
* For example, `List<Number>` is a parameterization of
* the generic type `List<E>`, where `E` is a type parameter.
*/
class ParameterizedType extends RefType {
ParameterizedType() {
typeArgs(_, _, this) or
typeVars(_, _, _, _, this)
}
/**
* The erasure of a parameterized type is its generic counterpart.
*
* For example, the erasure of both `X<Number>` and `X<Integer>` is `X<T>`.
*/
override RefType getErasure() { erasure(this, result) or this.(GenericType) = result }
/**
* Gets the generic type corresponding to this parameterized type.
*
* For example, the generic type for both `X<Number>` and `X<Integer>` is `X<T>`.
*/
GenericType getGenericType() { result.getAParameterizedType() = this }
/**
* Gets a type argument for this parameterized type.
*
* For example, `Number` in `List<Number>`.
*/
RefType getATypeArgument() {
typeArgs(result, _, this) or
typeVars(result, _, _, _, this)
}
/** Gets the type argument of this parameterized type at the specified position. */
RefType getTypeArgument(int pos) {
typeArgs(result, pos, this) or
typeVars(result, _, pos, _, this)
}
/** Gets the number of type arguments of this parameterized type. */
int getNumberOfTypeArguments() {
result =
count(int pos |
typeArgs(_, pos, this) or
typeVars(_, _, pos, _, this)
)
}
/** Holds if this type originates from source code. */
override predicate fromSource() { typeVars(_, _, _, _, this) and RefType.super.fromSource() }
override string getAPrimaryQlClass() { result = "ParameterizedType" }
}
/** A parameterized type that is a class. */
class ParameterizedClass extends Class, ParameterizedType {
override string getAPrimaryQlClass() {
result = Class.super.getAPrimaryQlClass() or
result = ParameterizedType.super.getAPrimaryQlClass()
}
}
/** A parameterized type that is an interface. */
class ParameterizedInterface extends Interface, ParameterizedType {
override string getAPrimaryQlClass() {
result = Interface.super.getAPrimaryQlClass() or
result = ParameterizedType.super.getAPrimaryQlClass()
}
}
/**
* The raw version of a generic type is the type that is formed by
* using the name of a generic type without specifying its type arguments.
*
* For example, `List` is the raw version of the generic type
* `List<E>`, where `E` is a type parameter.
*
* Raw types typically occur in legacy code that was written
* prior to the introduction of generic types in Java 5.
*/
class RawType extends RefType {
RawType() { isRaw(this) }
/**
* The erasure of a raw type is its generic counterpart.
*
* For example, the erasure of `List` is `List<E>`.
*/
override RefType getErasure() { erasure(this, result) }
/** Holds if this type originates from source code. */
override predicate fromSource() { not any() }
override string getAPrimaryQlClass() { result = "RawType" }
}
/** A raw type that is a class. */
class RawClass extends Class, RawType {
override string getAPrimaryQlClass() {
result = Class.super.getAPrimaryQlClass() or
result = RawType.super.getAPrimaryQlClass()
}
}
/** A raw type that is an interface. */
class RawInterface extends Interface, RawType {
override string getAPrimaryQlClass() {
result = Interface.super.getAPrimaryQlClass() or
result = RawType.super.getAPrimaryQlClass()
}
}
// -------- Generic callables --------
/**
* A generic callable is a callable with a type parameter.
*/
class GenericCallable extends Callable {
GenericCallable() {
exists(Callable srcDecl |
methods(this, _, _, _, _, srcDecl) or constrs(this, _, _, _, _, srcDecl)
|
typeVars(_, _, _, _, srcDecl)
)
}
/**
* Gets the `i`-th type parameter of this generic callable.
*/
TypeVariable getTypeParameter(int i) { typeVars(result, _, i, _, this.getSourceDeclaration()) }
/**
* Gets a type parameter of this generic callable.
*/
TypeVariable getATypeParameter() { result = getTypeParameter(_) }
/**
* Gets the number of type parameters of this generic callable.
*/
int getNumberOfTypeParameters() { result = strictcount(getATypeParameter()) }
}
/**
* A call where the callee is a generic callable.
*/
class GenericCall extends Call {
GenericCall() { this.getCallee() instanceof GenericCallable }
private RefType getAnInferredTypeArgument(TypeVariable v) {
typevarArg(this, v, result)
or
not typevarArg(this, v, _) and
v = this.getCallee().(GenericCallable).getATypeParameter() and
result.(Wildcard).getUpperBound().getType() = v.getUpperBoundType()
}
private RefType getAnExplicitTypeArgument(TypeVariable v) {
exists(GenericCallable gen, MethodAccess call, int i |
this = call and
gen = call.getCallee() and
v = gen.getTypeParameter(i) and
result = call.getTypeArgument(i).getType()
)
}
/** Gets a type argument of the call for the given `TypeVariable`. */
RefType getATypeArgument(TypeVariable v) {
result = getAnExplicitTypeArgument(v)
or
not exists(getAnExplicitTypeArgument(v)) and
result = getAnInferredTypeArgument(v)
}
}
/** Infers a type argument of `call` for `v` using a simple unification. */
private predicate typevarArg(Call call, TypeVariable v, RefType typearg) {
exists(GenericCallable gen |
gen = call.getCallee() and
v = gen.getATypeParameter()
|
hasSubstitution(gen.getReturnType(), call.(Expr).getType(), v, typearg) or
exists(int n |
hasSubtypedSubstitution(gen.getParameterType(n), call.getArgument(n).getType(), v, typearg)
)
)
}
/**
* The reflexive transitive closure of `RefType.extendsOrImplements` including reflexivity on `Type`s.
*/
private Type getShallowSupertype(Type t) { result = t or t.(RefType).extendsOrImplements+(result) }
/**
* Manual magic sets optimization for the "inputs" of `hasSubstitution` and
* `hasParameterSubstitution`.
*/
private predicate unificationTargets(RefType t1, Type t2) {
exists(GenericCallable gen, Call call | gen = call.getCallee() |
t1 = gen.getReturnType() and t2 = call.(Expr).getType()
or
exists(int n |
t1 = gen.getParameterType(n) and t2 = getShallowSupertype(call.getArgument(n).getType())
)
)
or
exists(Array a1, Array a2 |
unificationTargets(a1, a2) and
t1 = a1.getComponentType() and
t2 = a2.getComponentType()
)
or
exists(ParameterizedType pt1, ParameterizedType pt2, int pos |
unificationTargets(pt1, pt2) and
t1 = pt1.getTypeArgument(pos) and
t2 = pt2.getTypeArgument(pos)
)
}
/**
* Unifies `t1` and `t2` with respect to `v` allowing subtyping.
*
* `t1` contains `v` and equals a supertype of `t2` if `subst` is substituted for `v`.
* Only shallow supertypes of `t2` are considered in order to get the most precise result for `subst`.
* For example:
* If `t1` is `List<V>` and `t2` is `ArrayList<T>` we only want `subst` to be `T` and not, say, `?`.
*/
pragma[nomagic]
private predicate hasSubtypedSubstitution(RefType t1, Type t2, TypeVariable v, RefType subst) {
hasSubstitution(t1, t2, v, subst) or
exists(GenericType g | hasParameterSubstitution(g, t1, g, getShallowSupertype(t2), v, subst))
}
/**
* Unifies `t1` and `t2` with respect to `v`.
*
* `t1` contains `v` and equals `t2` if `subst` is substituted for `v`.
* As a special case `t2` can be a primitive type and the equality hold when
* `t2` is auto-boxed.
*/
private predicate hasSubstitution(RefType t1, Type t2, TypeVariable v, RefType subst) {
unificationTargets(t1, t2) and
(
t1 = v and
(t2 = subst or t2.(PrimitiveType).getBoxedType() = subst)
or
hasSubstitution(t1.(Array).getComponentType(), t2.(Array).getComponentType(), v, subst)
or
exists(GenericType g | hasParameterSubstitution(g, t1, g, t2, v, subst))
)
}
private predicate hasParameterSubstitution(
GenericType g1, ParameterizedType pt1, GenericType g2, ParameterizedType pt2, TypeVariable v,
RefType subst
) {
unificationTargets(pt1, pt2) and
exists(int pos | hasSubstitution(pt1.getTypeArgument(pos), pt2.getTypeArgument(pos), v, subst)) and
g1 = pt1.getGenericType() and
g2 = pt2.getGenericType()
}
/**
* A generic constructor is a constructor with a type parameter.
*
* For example, `<T> C(T t) { }` is a generic constructor for type `C`.
*/
class GenericConstructor extends Constructor, GenericCallable {
override GenericConstructor getSourceDeclaration() {
result = Constructor.super.getSourceDeclaration()
}
override ConstructorCall getAReference() { result = Constructor.super.getAReference() }
}
/**
* A generic method is a method with a type parameter.
*
* For example, `<T> void m(T t) { }` is a generic method.
*/
class GenericMethod extends Method, GenericCallable {
override GenericSrcMethod getSourceDeclaration() { result = Method.super.getSourceDeclaration() }
}
/** A generic method that is the same as its source declaration. */
class GenericSrcMethod extends SrcMethod, GenericMethod { }

View File

@@ -0,0 +1,148 @@
/**
* Provides classes and predicates for working with Java imports.
*/
import semmle.code.Location
import CompilationUnit
/** A common super-class for all kinds of Java import declarations. */
class Import extends Element, @import {
/** Gets the compilation unit in which this import declaration occurs. */
override CompilationUnit getCompilationUnit() { result = this.getFile() }
/** Holds if this import declaration occurs in source code. */
override predicate fromSource() { any() }
/*abstract*/ override string toString() { result = "import" }
}
/**
* A single-type-import declaration.
*
* For example, `import java.util.Set;`.
*/
class ImportType extends Import {
ImportType() { imports(this, _, _, 1) }
/** Gets the imported type. */
RefType getImportedType() { imports(this, result, _, _) }
override string toString() { result = "import " + this.getImportedType().toString() }
override string getAPrimaryQlClass() { result = "ImportType" }
}
/**
* A type-import-on-demand declaration that allows all accessible
* nested types of a named type to be imported as needed.
*
* For example, `import java.util.Map.*;` imports
* the nested type `java.util.Map.Entry` from the type
* `java.util.Map`.
*/
class ImportOnDemandFromType extends Import {
ImportOnDemandFromType() { imports(this, _, _, 2) }
/** Gets the type from which accessible nested types are imported. */
RefType getTypeHoldingImport() { imports(this, result, _, _) }
/** Gets an imported type. */
NestedType getAnImport() { result.getEnclosingType() = this.getTypeHoldingImport() }
override string toString() { result = "import " + this.getTypeHoldingImport().toString() + ".*" }
override string getAPrimaryQlClass() { result = "ImportOnDemandFromType" }
}
/**
* A type-import-on-demand declaration that allows all accessible
* types of a named package to be imported as needed.
*
* For example, `import java.util.*;`.
*/
class ImportOnDemandFromPackage extends Import {
ImportOnDemandFromPackage() { imports(this, _, _, 3) }
/** Gets the package from which accessible types are imported. */
Package getPackageHoldingImport() { imports(this, result, _, _) }
/** Gets an imported type. */
RefType getAnImport() { result.getPackage() = this.getPackageHoldingImport() }
/** Gets a printable representation of this import declaration. */
override string toString() {
result = "import " + this.getPackageHoldingImport().toString() + ".*"
}
override string getAPrimaryQlClass() { result = "ImportOnDemandFromPackage" }
}
/**
* A static-import-on-demand declaration, which allows all accessible
* static members of a named type to be imported as needed.
*
* For example, `import static java.lang.System.*;`.
*/
class ImportStaticOnDemand extends Import {
ImportStaticOnDemand() { imports(this, _, _, 4) }
/** Gets the type from which accessible static members are imported. */
RefType getTypeHoldingImport() { imports(this, result, _, _) }
/** Gets an imported type. */
NestedType getATypeImport() { result.getEnclosingType() = this.getTypeHoldingImport() }
/** Gets an imported method. */
Method getAMethodImport() { result.getDeclaringType() = this.getTypeHoldingImport() }
/** Gets an imported field. */
Field getAFieldImport() { result.getDeclaringType() = this.getTypeHoldingImport() }
/** Gets a printable representation of this import declaration. */
override string toString() {
result = "import static " + this.getTypeHoldingImport().toString() + ".*"
}
override string getAPrimaryQlClass() { result = "ImportStaticOnDemand" }
}
/**
* A single-static-import declaration, which imports all accessible
* static members with a given simple name from a type.
*
* For example, `import static java.util.Collections.sort;`
* imports all the static methods named `sort` from the
* class `java.util.Collections`.
*/
class ImportStaticTypeMember extends Import {
ImportStaticTypeMember() { imports(this, _, _, 5) }
/** Gets the type from which static members with a given name are imported. */
RefType getTypeHoldingImport() { imports(this, result, _, _) }
/** Gets the name of the imported member(s). */
override string getName() { imports(this, _, result, _) }
/** Gets an imported member. */
Member getAMemberImport() {
this.getTypeHoldingImport().getAMember() = result and
result.getName() = this.getName() and
result.isStatic()
}
/** Gets an imported type. */
NestedType getATypeImport() { result = this.getAMemberImport() }
/** Gets an imported method. */
Method getAMethodImport() { result = this.getAMemberImport() }
/** Gets an imported field. */
Field getAFieldImport() { result = this.getAMemberImport() }
/** Gets a printable representation of this import declaration. */
override string toString() {
result = "import static " + this.getTypeHoldingImport().toString() + "." + this.getName()
}
override string getAPrimaryQlClass() { result = "ImportStaticTypeMember" }
}

View File

@@ -0,0 +1,63 @@
/**
* Provides classes and predicates for working with J2EE bean types.
*/
import Type
/** An entity bean. */
class EntityBean extends Class {
EntityBean() {
exists(Interface i | i.hasQualifiedName("javax.ejb", "EntityBean") | this.hasSupertype+(i))
}
}
/** An enterprise bean. */
class EnterpriseBean extends RefType {
EnterpriseBean() {
exists(Interface i | i.hasQualifiedName("javax.ejb", "EnterpriseBean") | this.hasSupertype+(i))
}
}
/** A local EJB home interface. */
class LocalEJBHomeInterface extends Interface {
LocalEJBHomeInterface() {
exists(Interface i | i.hasQualifiedName("javax.ejb", "EJBLocalHome") | this.hasSupertype+(i))
}
}
/** A remote EJB home interface. */
class RemoteEJBHomeInterface extends Interface {
RemoteEJBHomeInterface() {
exists(Interface i | i.hasQualifiedName("javax.ejb", "EJBHome") | this.hasSupertype+(i))
}
}
/** A local EJB interface. */
class LocalEJBInterface extends Interface {
LocalEJBInterface() {
exists(Interface i | i.hasQualifiedName("javax.ejb", "EJBLocalObject") | this.hasSupertype+(i))
}
}
/** A remote EJB interface. */
class RemoteEJBInterface extends Interface {
RemoteEJBInterface() {
exists(Interface i | i.hasQualifiedName("javax.ejb", "EJBObject") | this.hasSupertype+(i))
}
}
/** A message bean. */
class MessageBean extends Class {
MessageBean() {
exists(Interface i | i.hasQualifiedName("javax.ejb", "MessageDrivenBean") |
this.hasSupertype+(i)
)
}
}
/** A session bean. */
class SessionBean extends Class {
SessionBean() {
exists(Interface i | i.hasQualifiedName("javax.ejb", "SessionBean") | this.hasSupertype+(i))
}
}

View File

@@ -0,0 +1,440 @@
/**
* Provides classes and predicates for working with standard classes and methods from the JDK.
*/
import Member
import semmle.code.java.security.ExternalProcess
// --- Standard types ---
/** The class `java.lang.Object`. */
class TypeObject extends Class {
pragma[noinline]
TypeObject() { this.hasQualifiedName("java.lang", "Object") }
}
/** The interface `java.lang.Cloneable`. */
class TypeCloneable extends Interface {
TypeCloneable() { this.hasQualifiedName("java.lang", "Cloneable") }
}
/** The class `java.lang.ProcessBuilder`. */
class TypeProcessBuilder extends Class {
TypeProcessBuilder() { hasQualifiedName("java.lang", "ProcessBuilder") }
}
/** The class `java.lang.Runtime`. */
class TypeRuntime extends Class {
TypeRuntime() { hasQualifiedName("java.lang", "Runtime") }
}
/** The class `java.lang.String`. */
class TypeString extends Class {
TypeString() { this.hasQualifiedName("java.lang", "String") }
}
/** The `length()` method of the class `java.lang.String`. */
class StringLengthMethod extends Method {
StringLengthMethod() { this.hasName("length") and this.getDeclaringType() instanceof TypeString }
}
/** The class `java.lang.StringBuffer`. */
class TypeStringBuffer extends Class {
TypeStringBuffer() { this.hasQualifiedName("java.lang", "StringBuffer") }
}
/** The class `java.lang.StringBuilder`. */
class TypeStringBuilder extends Class {
TypeStringBuilder() { this.hasQualifiedName("java.lang", "StringBuilder") }
}
/** Class `java.lang.StringBuffer` or `java.lang.StringBuilder`. */
class StringBuildingType extends Class {
StringBuildingType() { this instanceof TypeStringBuffer or this instanceof TypeStringBuilder }
}
/** The class `java.lang.System`. */
class TypeSystem extends Class {
TypeSystem() { this.hasQualifiedName("java.lang", "System") }
}
/** The class `java.lang.Throwable`. */
class TypeThrowable extends Class {
TypeThrowable() { this.hasQualifiedName("java.lang", "Throwable") }
}
/** The class `java.lang.Exception`. */
class TypeException extends Class {
TypeException() { this.hasQualifiedName("java.lang", "Exception") }
}
/** The class `java.lang.Error`. */
class TypeError extends Class {
TypeError() { this.hasQualifiedName("java.lang", "Error") }
}
/** The class `java.lang.RuntimeException`. */
class TypeRuntimeException extends Class {
TypeRuntimeException() { this.hasQualifiedName("java.lang", "RuntimeException") }
}
/** The class `java.lang.ClassCastException`. */
class TypeClassCastException extends Class {
TypeClassCastException() { this.hasQualifiedName("java.lang", "ClassCastException") }
}
/**
* The class `java.lang.Class`.
*
* This includes the generic source declaration, any parameterized instances and the raw type.
*/
class TypeClass extends Class {
TypeClass() { this.getSourceDeclaration().hasQualifiedName("java.lang", "Class") }
}
/**
* The class `java.lang.Constructor`.
*
* This includes the generic source declaration, any parameterized instances and the raw type.
*/
class TypeConstructor extends Class {
TypeConstructor() {
this.getSourceDeclaration().hasQualifiedName("java.lang.reflect", "Constructor")
}
}
/** The class `java.lang.Math`. */
class TypeMath extends Class {
TypeMath() { this.hasQualifiedName("java.lang", "Math") }
}
/** The class `java.lang.Number`. */
class TypeNumber extends RefType {
TypeNumber() { this.hasQualifiedName("java.lang", "Number") }
}
/** A (reflexive, transitive) subtype of `java.lang.Number`. */
class NumberType extends RefType {
NumberType() { exists(TypeNumber number | hasSubtype*(number, this)) }
}
/** A numeric type, including both primitive and boxed types. */
class NumericType extends Type {
NumericType() {
exists(string name |
name = this.(PrimitiveType).getName() or
name = this.(BoxedType).getPrimitiveType().getName()
|
name.regexpMatch("byte|short|int|long|double|float")
)
}
}
/** An immutable type. */
class ImmutableType extends Type {
ImmutableType() {
this instanceof PrimitiveType or
this instanceof NullType or
this instanceof VoidType or
this instanceof BoxedType or
this instanceof TypeString
}
}
// --- Java IO ---
/** The interface `java.io.Serializable`. */
class TypeSerializable extends Interface {
TypeSerializable() { hasQualifiedName("java.io", "Serializable") }
}
/** The interface `java.io.ObjectOutput`. */
class TypeObjectOutput extends Interface {
TypeObjectOutput() { hasQualifiedName("java.io", "ObjectOutput") }
}
/** The type `java.io.ObjectOutputStream`. */
class TypeObjectOutputStream extends RefType {
TypeObjectOutputStream() { hasQualifiedName("java.io", "ObjectOutputStream") }
}
/** The class `java.nio.file.Paths`. */
class TypePaths extends Class {
TypePaths() { this.hasQualifiedName("java.nio.file", "Paths") }
}
/** The type `java.nio.file.Path`. */
class TypePath extends RefType {
TypePath() { this.hasQualifiedName("java.nio.file", "Path") }
}
/** The class `java.nio.file.FileSystem`. */
class TypeFileSystem extends Class {
TypeFileSystem() { this.hasQualifiedName("java.nio.file", "FileSystem") }
}
/** The class `java.io.File`. */
class TypeFile extends Class {
TypeFile() { this.hasQualifiedName("java.io", "File") }
}
// --- Standard methods ---
/**
* Any constructor of class `java.lang.ProcessBuilder`.
*/
class ProcessBuilderConstructor extends Constructor, ExecCallable {
ProcessBuilderConstructor() { this.getDeclaringType() instanceof TypeProcessBuilder }
override int getAnExecutedArgument() { result = 0 }
}
/**
* Any of the methods named `command` on class `java.lang.ProcessBuilder`.
*/
class MethodProcessBuilderCommand extends Method, ExecCallable {
MethodProcessBuilderCommand() {
hasName("command") and
getDeclaringType() instanceof TypeProcessBuilder
}
override int getAnExecutedArgument() { result = 0 }
}
/**
* Any method named `exec` on class `java.lang.Runtime`.
*/
class MethodRuntimeExec extends Method, ExecCallable {
MethodRuntimeExec() {
hasName("exec") and
getDeclaringType() instanceof TypeRuntime
}
override int getAnExecutedArgument() { result = 0 }
}
/**
* Any method named `getenv` on class `java.lang.System`.
*/
class MethodSystemGetenv extends Method {
MethodSystemGetenv() {
hasName("getenv") and
getDeclaringType() instanceof TypeSystem
}
}
/**
* Any method named `getProperty` on class `java.lang.System`.
*/
class MethodSystemGetProperty extends Method {
MethodSystemGetProperty() {
hasName("getProperty") and
getDeclaringType() instanceof TypeSystem
}
}
/**
* An access to a method named `getProperty` on class `java.lang.System`.
*/
class MethodAccessSystemGetProperty extends MethodAccess {
MethodAccessSystemGetProperty() { getMethod() instanceof MethodSystemGetProperty }
/**
* Holds if this call has a compile-time constant first argument with the value `propertyName`.
* For example: `System.getProperty("user.dir")`.
*/
predicate hasCompileTimeConstantGetPropertyName(string propertyName) {
this.getArgument(0).(CompileTimeConstantExpr).getStringValue() = propertyName
}
}
/**
* Any method named `exit` on class `java.lang.Runtime` or `java.lang.System`.
*/
class MethodExit extends Method {
MethodExit() {
hasName("exit") and
(getDeclaringType() instanceof TypeRuntime or getDeclaringType() instanceof TypeSystem)
}
}
/**
* A method named `writeObject` on type `java.io.ObjectOutput`
* or `java.io.ObjectOutputStream`.
*/
class WriteObjectMethod extends Method {
WriteObjectMethod() {
hasName("writeObject") and
(
getDeclaringType() instanceof TypeObjectOutputStream or
getDeclaringType() instanceof TypeObjectOutput
)
}
}
/**
* A method that reads an object on type `java.io.ObjectInputStream`,
* including `readObject`, `readObjectOverride`, `readUnshared` and `resolveObject`.
*/
class ReadObjectMethod extends Method {
ReadObjectMethod() {
this.getDeclaringType().hasQualifiedName("java.io", "ObjectInputStream") and
(
this.hasName("readObject") or
this.hasName("readObjectOverride") or
this.hasName("readUnshared") or
this.hasName("resolveObject")
)
}
}
/** The method `Class.getName()`. */
class ClassNameMethod extends Method {
ClassNameMethod() {
hasName("getName") and
getDeclaringType() instanceof TypeClass
}
}
/** The method `Class.getSimpleName()`. */
class ClassSimpleNameMethod extends Method {
ClassSimpleNameMethod() {
hasName("getSimpleName") and
getDeclaringType() instanceof TypeClass
}
}
/** The method `Math.abs`. */
class MethodAbs extends Method {
MethodAbs() {
this.getDeclaringType() instanceof TypeMath and
this.getName() = "abs"
}
}
/** The method `Math.min`. */
class MethodMathMin extends Method {
MethodMathMin() {
this.getDeclaringType() instanceof TypeMath and
this.getName() = "min"
}
}
/** The method `Math.min`. */
class MethodMathMax extends Method {
MethodMathMax() {
this.getDeclaringType() instanceof TypeMath and
this.getName() = "max"
}
}
// --- Standard fields ---
/** The field `System.in`. */
class SystemIn extends Field {
SystemIn() {
hasName("in") and
getDeclaringType() instanceof TypeSystem
}
}
/** The field `System.out`. */
class SystemOut extends Field {
SystemOut() {
hasName("out") and
getDeclaringType() instanceof TypeSystem
}
}
/** The field `System.err`. */
class SystemErr extends Field {
SystemErr() {
hasName("err") and
getDeclaringType() instanceof TypeSystem
}
}
// --- User-defined methods with a particular meaning ---
/** A method with the same signature as `java.lang.Object.equals`. */
class EqualsMethod extends Method {
EqualsMethod() {
this.hasName("equals") and
this.getNumberOfParameters() = 1 and
this.getParameter(0).getType().(RefType).hasQualifiedName("java.lang", "Object")
}
/** Gets the single parameter of this method. */
Parameter getParameter() { result = this.getAParameter() }
}
/** A method with the same signature as `java.lang.Object.hashCode`. */
class HashCodeMethod extends Method {
HashCodeMethod() {
this.hasName("hashCode") and
this.hasNoParameters()
}
}
/** A method with the same signature as `java.lang.Object.clone`. */
class CloneMethod extends Method {
CloneMethod() {
this.hasName("clone") and
this.hasNoParameters()
}
}
/** A method with the same signature as `java.lang.Object.toString`. */
class ToStringMethod extends Method {
ToStringMethod() {
this.hasName("toString") and
this.hasNoParameters()
}
}
/**
* The public static `main` method, with a single formal parameter
* of type `String[]` and return type `void`.
*/
class MainMethod extends Method {
MainMethod() {
this.isPublic() and
this.isStatic() and
this.getReturnType().hasName("void") and
this.hasName("main") and
this.getNumberOfParameters() = 1 and
exists(Array a |
a = this.getAParameter().getType() and
a.getDimension() = 1 and
a.getElementType() instanceof TypeString
)
}
}
/** A premain method is an agent entry-point. */
class PreMainMethod extends Method {
PreMainMethod() {
this.isPublic() and
this.isStatic() and
this.getReturnType().hasName("void") and
this.getNumberOfParameters() < 3 and
this.getParameter(0).getType() instanceof TypeString and
(exists(this.getParameter(1)) implies this.getParameter(1).getType().hasName("Instrumentation"))
}
}
/** The `length` field of the array type. */
class ArrayLengthField extends Field {
ArrayLengthField() {
this.getDeclaringType() instanceof Array and
this.hasName("length")
}
}
/** A (reflexive, transitive) subtype of `java.lang.Throwable`. */
class ThrowableType extends RefType {
ThrowableType() { exists(TypeThrowable throwable | hasSubtype*(throwable, this)) }
}
/** An unchecked exception. That is, a (reflexive, transitive) subtype of `java.lang.Error` or `java.lang.RuntimeException`. */
class UncheckedThrowableType extends RefType {
UncheckedThrowableType() {
exists(TypeError e | hasSubtype*(e, this)) or
exists(TypeRuntimeException e | hasSubtype*(e, this))
}
}

View File

@@ -0,0 +1,132 @@
/**
* Provides classes that represent standard annotations from the JDK.
*/
import java
/** A `@Deprecated` annotation. */
class DeprecatedAnnotation extends Annotation {
DeprecatedAnnotation() { this.getType().hasQualifiedName("java.lang", "Deprecated") }
}
/** An `@Override` annotation. */
class OverrideAnnotation extends Annotation {
OverrideAnnotation() { this.getType().hasQualifiedName("java.lang", "Override") }
}
/** A `@SuppressWarnings` annotation. */
class SuppressWarningsAnnotation extends Annotation {
SuppressWarningsAnnotation() { this.getType().hasQualifiedName("java.lang", "SuppressWarnings") }
/** Gets the `StringLiteral` of a warning suppressed by this annotation. */
StringLiteral getASuppressedWarningLiteral() {
result = this.getAValue() or
result = this.getAValue().(ArrayInit).getAnInit()
}
/** Gets the name of a warning suppressed by this annotation. */
string getASuppressedWarning() {
result = this.getAValue().(StringLiteral).getLiteral() or
result = this.getAValue().(ArrayInit).getAnInit().(StringLiteral).getLiteral()
}
}
/** A `@Target` annotation. */
class TargetAnnotation extends Annotation {
TargetAnnotation() { this.getType().hasQualifiedName("java.lang.annotation", "Target") }
/**
* Gets a target expression within this annotation.
*
* For example, the field access `ElementType.FIELD` is a target expression in
* `@Target({ElementType.FIELD, ElementType.METHOD})`.
*/
Expr getATargetExpression() {
not result instanceof ArrayInit and
(
result = this.getAValue() or
result = this.getAValue().(ArrayInit).getAnInit()
)
}
/**
* Gets the name of a target element type.
*
* For example, `METHOD` is the name of a target element type in
* `@Target({ElementType.FIELD, ElementType.METHOD})`.
*/
string getATargetElementType() {
exists(EnumConstant ec |
ec = this.getATargetExpression().(VarAccess).getVariable() and
ec.getDeclaringType().hasQualifiedName("java.lang.annotation", "ElementType")
|
result = ec.getName()
)
}
}
/** A `@Retention` annotation. */
class RetentionAnnotation extends Annotation {
RetentionAnnotation() { this.getType().hasQualifiedName("java.lang.annotation", "Retention") }
/**
* Gets the retention policy expression within this annotation.
*
* For example, the field access `RetentionPolicy.RUNTIME` is the
* retention policy expression in `@Retention(RetentionPolicy.RUNTIME)`.
*/
Expr getRetentionPolicyExpression() { result = this.getValue("value") }
/**
* Gets the name of the retention policy of this annotation.
*
* For example, `RUNTIME` is the name of the retention policy
* in `@Retention(RetentionPolicy.RUNTIME)`.
*/
string getRetentionPolicy() {
exists(EnumConstant ec |
ec = this.getRetentionPolicyExpression().(VarAccess).getVariable() and
ec.getDeclaringType().hasQualifiedName("java.lang.annotation", "RetentionPolicy")
|
result = ec.getName()
)
}
}
/**
* An annotation suggesting that the annotated element may be accessed reflectively.
*
* This is implemented by negation of a white-list of standard annotations that are
* known not to be reflection-related; all other annotations are assumed to potentially
* be reflection-related.
*
* A typical use-case is the exclusion of results relating to various frameworks,
* where an exhaustive list of all annotations for all frameworks that may exist
* can be difficult to obtain and maintain.
*/
class ReflectiveAccessAnnotation extends Annotation {
ReflectiveAccessAnnotation() {
// We conservatively white-list a few standard annotations that have nothing to do
// with reflection, and assume that any other annotation may be reflection-related.
not this instanceof NonReflectiveAnnotation
}
}
/**
* An annotation that does not indicate that a field may be accessed reflectively.
*
* Any annotation that is not a subclass of `NonReflectiveAnnotation` is assumed to
* allow for reflective access.
*/
abstract class NonReflectiveAnnotation extends Annotation { }
library class StandardNonReflectiveAnnotation extends NonReflectiveAnnotation {
StandardNonReflectiveAnnotation() {
exists(AnnotationType anntp | anntp = this.getType() |
anntp.hasQualifiedName("java.lang", "Override") or
anntp.hasQualifiedName("java.lang", "Deprecated") or
anntp.hasQualifiedName("java.lang", "SuppressWarnings") or
anntp.hasQualifiedName("java.lang", "SafeVarargs")
)
}
}

View File

@@ -0,0 +1,105 @@
/**
* Provides classes and predicates for working with JMX bean types.
*/
import Type
/** A managed bean. */
abstract class ManagedBean extends Interface { }
/** An `MBean`. */
class MBean extends ManagedBean {
MBean() { this.getQualifiedName().matches("%MBean%") }
}
/** An `MXBean`. */
class MXBean extends ManagedBean {
MXBean() {
this.getQualifiedName().matches("%MXBean%") or
this.getAnAnnotation().getType().hasQualifiedName("javax.management", "MXBean")
}
}
/**
* An managed bean implementation which is seen to be registered with the `MBeanServer`, directly or
* indirectly.
*/
class RegisteredManagedBeanImpl extends Class {
RegisteredManagedBeanImpl() {
getAnAncestor() instanceof ManagedBean and
exists(JMXRegistrationCall registerCall | registerCall.getObjectArgument().getType() = this)
}
/**
* Gets a managed bean that this registered bean class implements.
*/
ManagedBean getAnImplementedManagedBean() { result = getAnAncestor() }
}
/**
* A call that registers an object with the `MBeanServer`, directly or indirectly.
*/
class JMXRegistrationCall extends MethodAccess {
JMXRegistrationCall() { getCallee() instanceof JMXRegistrationMethod }
/**
* Gets the argument that represents the object in the registration call.
*/
Expr getObjectArgument() {
result = getArgument(getCallee().(JMXRegistrationMethod).getObjectPosition())
}
}
/**
* A method used to register `MBean` and `MXBean` instances with the `MBeanServer`.
*
* This is either the `registerMBean` method on `MBeanServer`, or it is a wrapper around that
* registration method.
*/
class JMXRegistrationMethod extends Method {
JMXRegistrationMethod() {
// A direct registration with the `MBeanServer`.
getDeclaringType().hasQualifiedName("javax.management", "MBeanServer") and
getName() = "registerMBean"
or
// The `MBeanServer` is often wrapped by an application specific management class, so identify
// methods that wrap a call to another `JMXRegistrationMethod`.
exists(JMXRegistrationCall c |
// This must be a call to another JMX registration method, where the object argument is an access
// of one of the parameters of this method.
c.getObjectArgument().(VarAccess).getVariable() = getAParameter()
)
}
/**
* Gets the position of the parameter through which the "object" to be registered is passed.
*/
int getObjectPosition() {
// Passed as the first argument to `registerMBean`.
getDeclaringType().hasQualifiedName("javax.management", "MBeanServer") and
getName() = "registerMBean" and
result = 0
or
// Identify the position in this method where the object parameter should be passed.
exists(JMXRegistrationCall c |
c.getObjectArgument().(VarAccess).getVariable() = getParameter(result)
)
}
}
/** The class `javax.management.remote.JMXConnectorFactory`. */
class TypeJMXConnectorFactory extends Class {
TypeJMXConnectorFactory() {
this.hasQualifiedName("javax.management.remote", "JMXConnectorFactory")
}
}
/** The class `javax.management.remote.JMXServiceURL`. */
class TypeJMXServiceURL extends Class {
TypeJMXServiceURL() { this.hasQualifiedName("javax.management.remote", "JMXServiceURL") }
}
/** The class `javax.management.remote.rmi.RMIConnector`. */
class TypeRMIConnector extends Class {
TypeRMIConnector() { this.hasQualifiedName("javax.management.remote.rmi", "RMIConnector") }
}

View File

@@ -0,0 +1,148 @@
/**
* Provides classes and predicates for working with Javadoc documentation.
*/
import semmle.code.Location
import Element
/** A Javadoc parent is an element whose child can be some Javadoc documentation. */
class JavadocParent extends @javadocParent, Top {
/** Gets a documentation element attached to this parent. */
JavadocElement getAChild() { result.getParent() = this }
/** Gets the child documentation element at the specified (zero-based) position. */
JavadocElement getChild(int index) { result = this.getAChild() and result.getIndex() = index }
/** Gets the number of documentation elements attached to this parent. */
int getNumChild() { result = count(getAChild()) }
/** Gets a documentation element with the specified Javadoc tag name. */
JavadocTag getATag(string name) { result = this.getAChild() and result.getTagName() = name }
/*abstract*/ override string toString() { result = "Javadoc" }
}
/** A Javadoc comment. */
class Javadoc extends JavadocParent, @javadoc {
/** Gets the number of lines in this Javadoc comment. */
int getNumberOfLines() { result = this.getLocation().getNumberOfCommentLines() }
/** Gets the value of the `@version` tag, if any. */
string getVersion() { result = this.getATag("@version").getChild(0).toString() }
/** Gets the value of the `@author` tag, if any. */
string getAuthor() { result = this.getATag("@author").getChild(0).toString() }
override string toString() { result = toStringPrefix() + getChild(0) + toStringPostfix() }
private string toStringPrefix() {
if isEolComment(this)
then result = "//"
else (
if isNormalComment(this) then result = "/* " else result = "/** "
)
}
private string toStringPostfix() {
if isEolComment(this)
then result = ""
else (
if strictcount(getAChild()) = 1 then result = " */" else result = " ... */"
)
}
/** Gets the Java code element that is commented by this piece of Javadoc. */
Documentable getCommentedElement() { result.getJavadoc() = this }
override string getAPrimaryQlClass() { result = "Javadoc" }
}
/** A documentable element that can have an attached Javadoc comment. */
class Documentable extends Element, @member {
/** Gets the Javadoc comment attached to this element. */
Javadoc getJavadoc() { hasJavadoc(this, result) and not isNormalComment(result) }
/** Gets the name of the author(s) of this element, if any. */
string getAuthor() { result = this.getJavadoc().getAuthor() }
}
/** A common super-class for Javadoc elements, which may be either tags or text. */
abstract class JavadocElement extends @javadocElement, Top {
/** Gets the parent of this Javadoc element. */
JavadocParent getParent() { javadocTag(this, _, result, _) or javadocText(this, _, result, _) }
/** Gets the index of this child element relative to its parent. */
int getIndex() { javadocTag(this, _, _, result) or javadocText(this, _, _, result) }
/** Gets a printable representation of this Javadoc element. */
/*abstract*/ override string toString() { result = "Javadoc element" }
/** Gets the line of text associated with this Javadoc element. */
abstract string getText();
}
/** A Javadoc block tag. This does not include inline tags. */
class JavadocTag extends JavadocElement, JavadocParent, @javadocTag {
/** Gets the name of this Javadoc tag. */
string getTagName() { javadocTag(this, result, _, _) }
/** Gets a printable representation of this Javadoc tag. */
override string toString() { result = this.getTagName() }
/** Gets the text associated with this Javadoc tag. */
override string getText() { result = this.getChild(0).toString() }
override string getAPrimaryQlClass() { result = "JavadocTag" }
}
/** A Javadoc `@param` tag. */
class ParamTag extends JavadocTag {
ParamTag() { this.getTagName() = "@param" }
/** Gets the name of the parameter. */
string getParamName() { result = this.getChild(0).toString() }
/** Gets the documentation for the parameter. */
override string getText() { result = this.getChild(1).toString() }
}
/** A Javadoc `@throws` or `@exception` tag. */
class ThrowsTag extends JavadocTag {
ThrowsTag() { this.getTagName() = "@throws" or this.getTagName() = "@exception" }
/** Gets the name of the exception. */
string getExceptionName() { result = this.getChild(0).toString() }
/** Gets the documentation for the exception. */
override string getText() { result = this.getChild(1).toString() }
}
/** A Javadoc `@see` tag. */
class SeeTag extends JavadocTag {
SeeTag() { getTagName() = "@see" }
/** Gets the name of the entity referred to. */
string getReference() { result = getChild(0).toString() }
}
/** A Javadoc `@author` tag. */
class AuthorTag extends JavadocTag {
AuthorTag() { this.getTagName() = "@author" }
/** Gets the name of the author. */
string getAuthorName() { result = this.getChild(0).toString() }
}
/** A piece of Javadoc text. */
class JavadocText extends JavadocElement, @javadocText {
/** Gets the Javadoc comment that contains this piece of text. */
Javadoc getJavadoc() { result.getAChild+() = this }
/** Gets the text itself. */
override string getText() { javadocText(this, result, _, _) }
/** Gets a printable representation of this Javadoc element. */
override string toString() { result = this.getText() }
override string getAPrimaryQlClass() { result = "JavadocText" }
}

View File

@@ -0,0 +1,86 @@
/**
* Provides classes and predicates for reasoning about instances of
* `java.util.Map` and their methods.
*/
import java
import Collections
/** A reference type that extends a parameterization of `java.util.Map`. */
class MapType extends RefType {
MapType() {
this.getSourceDeclaration().getASourceSupertype*().hasQualifiedName("java.util", "Map")
}
/** Gets the type of keys stored in this map. */
RefType getKeyType() {
exists(GenericInterface map | map.hasQualifiedName("java.util", "Map") |
indirectlyInstantiates(this, map, 0, result)
)
}
/** Gets the type of values stored in this map. */
RefType getValueType() {
exists(GenericInterface map | map.hasQualifiedName("java.util", "Map") |
indirectlyInstantiates(this, map, 1, result)
)
}
}
/** A method declared in a map type. */
class MapMethod extends Method {
MapMethod() { this.getDeclaringType() instanceof MapType }
/** Gets the type of keys of the map to which this method belongs. */
RefType getReceiverKeyType() { result = this.getDeclaringType().(MapType).getKeyType() }
/** Gets the type of values of the map to which this method belongs. */
RefType getReceiverValueType() { result = this.getDeclaringType().(MapType).getValueType() }
}
/** A method that mutates the map it belongs to. */
class MapMutator extends MapMethod {
MapMutator() { this.getName().regexpMatch("(put.*|remove|clear)") }
}
/** The `size` method of `java.util.Map`. */
class MapSizeMethod extends MapMethod {
MapSizeMethod() { this.hasName("size") and this.hasNoParameters() }
}
/** A method call that mutates a map. */
class MapMutation extends MethodAccess {
MapMutation() { this.getMethod() instanceof MapMutator }
/** Holds if the result of this call is not immediately discarded. */
predicate resultIsChecked() { not this.getParent() instanceof ExprStmt }
}
/** A method that queries the contents of the map it belongs to without mutating it. */
class MapQueryMethod extends MapMethod {
MapQueryMethod() {
this.getName().regexpMatch("get|containsKey|containsValue|entrySet|keySet|values|isEmpty|size")
}
}
/** A `new` expression that allocates a fresh, empty map. */
class FreshMap extends ClassInstanceExpr {
FreshMap() {
this.getConstructedType() instanceof MapType and
this.getNumArgument() = 0 and
not exists(this.getAnonymousClass())
}
}
/**
* A call to `Map.put(key, value)`.
*/
class MapPutCall extends MethodAccess {
MapPutCall() { getCallee().(MapMethod).hasName("put") }
/** Gets the key argument of this call. */
Expr getKey() { result = getArgument(0) }
/** Gets the value argument of this call. */
Expr getValue() { result = getArgument(1) }
}

View File

@@ -0,0 +1,659 @@
/**
* Provides classes and predicates for working with members of Java classes and interfaces,
* that is, methods, constructors, fields and nested types.
*/
import Element
import Type
import Annotation
import Exception
import metrics.MetricField
private import dispatch.VirtualDispatch
/**
* A common abstraction for type member declarations,
* including methods, constructors, fields, and nested types.
*/
class Member extends Element, Annotatable, Modifiable, @member {
Member() { declaresMember(_, this) }
/** Gets the type in which this member is declared. */
RefType getDeclaringType() { declaresMember(result, this) }
/** Gets the qualified name of this member. */
string getQualifiedName() { result = getDeclaringType().getName() + "." + getName() }
/**
* Holds if this member has the specified name and is declared in the
* specified package and type.
*/
predicate hasQualifiedName(string package, string type, string name) {
this.getDeclaringType().hasQualifiedName(package, type) and this.hasName(name)
}
/** Holds if this member is package protected, that is, neither public nor private nor protected. */
predicate isPackageProtected() {
not isPrivate() and
not isProtected() and
not isPublic()
}
/**
* Gets the immediately enclosing callable, if this member is declared in
* an anonymous or local class.
*/
Callable getEnclosingCallable() {
exists(NestedClass nc | this.getDeclaringType() = nc |
nc.(AnonymousClass).getClassInstanceExpr().getEnclosingCallable() = result or
nc.(LocalClass).getLocalClassDeclStmt().getEnclosingCallable() = result
)
}
}
/** A callable is a method or constructor. */
class Callable extends StmtParent, Member, @callable {
/**
* Gets the declared return type of this callable (`void` for
* constructors).
*/
Type getReturnType() {
constrs(this, _, _, result, _, _) or
methods(this, _, _, result, _, _)
}
/**
* Gets a callee that may be called from this callable.
*/
Callable getACallee() { this.calls(result) }
/** Gets the call site of a call from this callable to a callee. */
Call getACallSite(Callable callee) {
result.getCaller() = this and
result.getCallee() = callee
}
/**
* Gets the bytecode method descriptor, encoding parameter and return types,
* but not the name of the callable.
*/
string getMethodDescriptor() {
exists(string return | return = this.getReturnType().getTypeDescriptor() |
result = "(" + descriptorUpTo(this.getNumberOfParameters()) + ")" + return
)
}
private string descriptorUpTo(int n) {
n = 0 and result = ""
or
exists(Parameter p | p = this.getParameter(n - 1) |
result = descriptorUpTo(n - 1) + p.getType().getTypeDescriptor()
)
}
/** Holds if this callable calls `target`. */
predicate calls(Callable target) { exists(getACallSite(target)) }
/**
* Holds if this callable calls `target`
* using a `super(...)` constructor call.
*/
predicate callsSuperConstructor(Constructor target) {
getACallSite(target) instanceof SuperConstructorInvocationStmt
}
/**
* Holds if this callable calls `target`
* using a `this(...)` constructor call.
*/
predicate callsThis(Constructor target) {
getACallSite(target) instanceof ThisConstructorInvocationStmt
}
/**
* Holds if this callable calls `target`
* using a `super` method call.
*/
predicate callsSuper(Method target) { getACallSite(target) instanceof SuperMethodAccess }
/**
* Holds if this callable calls `c` using
* either a `super(...)` constructor call
* or a `this(...)` constructor call.
*/
predicate callsConstructor(Constructor c) { this.callsSuperConstructor(c) or this.callsThis(c) }
/**
* Holds if this callable may call the specified callable,
* taking virtual dispatch into account.
*
* This includes both static call targets and dynamic dispatch targets.
*/
predicate polyCalls(Callable m) { this.calls(m) or this.callsImpl(m) }
/**
* Holds if `c` is a viable implementation of a callable called by this
* callable, taking virtual dispatch resolution into account.
*/
predicate callsImpl(Callable c) {
exists(Call call |
call.getCaller() = this and
viableCallable(call) = c
)
}
/**
* Holds if field `f` may be assigned a value
* within the body of this callable.
*/
predicate writes(Field f) { f.getAnAccess().(LValue).getEnclosingCallable() = this }
/**
* Holds if field `f` may be read
* within the body of this callable.
*/
predicate reads(Field f) { f.getAnAccess().(RValue).getEnclosingCallable() = this }
/**
* Holds if field `f` may be either read or written
* within the body of this callable.
*/
predicate accesses(Field f) { this.writes(f) or this.reads(f) }
/**
* Gets a field accessed in this callable.
*/
Field getAnAccessedField() { this.accesses(result) }
/** Gets the type of a formal parameter of this callable. */
Type getAParamType() { result = getParameterType(_) }
/** Holds if this callable does not have any formal parameters. */
predicate hasNoParameters() { not exists(getAParameter()) }
/** Gets the number of formal parameters of this callable. */
int getNumberOfParameters() { result = count(getAParameter()) }
/** Gets a formal parameter of this callable. */
Parameter getAParameter() { result.getCallable() = this }
/** Gets the formal parameter at the specified (zero-based) position. */
Parameter getParameter(int n) { params(result, _, n, this, _) }
/** Gets the type of the formal parameter at the specified (zero-based) position. */
Type getParameterType(int n) { params(_, result, n, this, _) }
/**
* Gets the signature of this callable, including its name and the types of all
* its parameters, identified by their simple (unqualified) names.
*
* The format of the string is `<name><params>`, where `<name>` is the result of
* the predicate `getName()` and `<params>` is the result of `paramsString()`.
* For example, the method `void printf(java.lang.String, java.lang.Object...)`
* has the string signature `printf(String, Object[])`.
*
* Use `getSignature` to obtain a signature including fully qualified type names.
*/
string getStringSignature() { result = this.getName() + this.paramsString() }
/**
* Gets a parenthesized string containing all parameter types of this callable,
* separated by a comma and space. For the parameter types the unqualified string
* representation is used. If this callable has no parameters, the result is `()`.
*
* For example, the method `void printf(java.lang.String, java.lang.Object...)`
* has the params string `(String, Object[])`.
*/
pragma[nomagic]
string paramsString() {
exists(int n | n = getNumberOfParameters() |
n = 0 and result = "()"
or
n > 0 and result = "(" + this.paramUpTo(n - 1) + ")"
)
}
/**
* Gets a string containing the parameter types of this callable
* from left to right, up to (and including) the `n`-th parameter.
*/
private string paramUpTo(int n) {
n = 0 and result = getParameterType(0).toString()
or
n > 0 and result = paramUpTo(n - 1) + ", " + getParameterType(n)
}
/**
* Holds if this callable has the specified string signature.
*
* This predicate simply tests if `sig` is equal to the result of the
* `getStringSignature()` predicate.
*/
predicate hasStringSignature(string sig) { sig = this.getStringSignature() }
/** Gets an exception that occurs in the `throws` clause of this callable. */
Exception getAnException() { exceptions(result, _, this) }
/** Gets an exception type that occurs in the `throws` clause of this callable. */
RefType getAThrownExceptionType() { result = getAnException().getType() }
/** Gets a call site that references this callable. */
Call getAReference() { result.getCallee() = this }
/** Gets the body of this callable, if any. */
BlockStmt getBody() { result.getParent() = this }
/**
* Gets the source declaration of this callable.
*
* For parameterized instances of generic methods, the
* source declaration is the corresponding generic method.
*
* For non-parameterized callables declared inside a parameterized
* instance of a generic type, the source declaration is the
* corresponding callable in the generic type.
*
* For all other callables, the source declaration is the callable itself.
*/
Callable getSourceDeclaration() { result = this }
/** Holds if this callable is the same as its source declaration. */
predicate isSourceDeclaration() { this.getSourceDeclaration() = this }
/** Cast this callable to a class that provides access to metrics information. */
MetricCallable getMetrics() { result = this }
/** Holds if the last parameter of this callable is a varargs (variable arity) parameter. */
predicate isVarargs() { this.getAParameter().isVarargs() }
/**
* Gets the signature of this callable, where all types in the signature have a fully-qualified name.
* The parameter types are only separated by a comma (without space). If this callable has
* no parameters, the callable name is followed by `()`.
*
* For example, method `void m(String s, int i)` has the signature `m(java.lang.String,int)`.
*/
string getSignature() {
constrs(this, _, result, _, _, _) or
methods(this, _, result, _, _, _)
}
}
/** Holds if method `m1` overrides method `m2`. */
private predicate overrides(Method m1, Method m2) {
exists(RefType t1, RefType t2 | overridesIgnoringAccess(m1, t1, m2, t2) |
m2.isPublic()
or
m2.isProtected()
or
m2.isPackageProtected() and t1.getPackage() = t2.getPackage()
)
}
/**
* Auxiliary predicate: whether method `m1` overrides method `m2`,
* ignoring any access modifiers. Additionally, this predicate binds
* `t1` to the type declaring `m1` and `t2` to the type declaring `m2`.
*/
pragma[noopt]
predicate overridesIgnoringAccess(Method m1, RefType t1, Method m2, RefType t2) {
exists(string sig |
virtualMethodWithSignature(sig, t1, m1) and
t1.extendsOrImplements+(t2) and
virtualMethodWithSignature(sig, t2, m2)
)
}
private predicate virtualMethodWithSignature(string sig, RefType t, Method m) {
methods(m, _, _, _, t, _) and
sig = m.getSignature() and
m.isVirtual()
}
private predicate potentialInterfaceImplementationWithSignature(string sig, RefType t, Method impl) {
t.hasMethod(impl, _) and
sig = impl.getSignature() and
impl.isVirtual() and
impl.isPublic() and
not t instanceof Interface and
not t.isAbstract()
}
pragma[nomagic]
private predicate implementsInterfaceMethod(SrcMethod impl, SrcMethod m) {
exists(RefType t, Interface i, Method minst, Method implinst |
m = minst.getSourceDeclaration() and
i = minst.getDeclaringType() and
t.extendsOrImplements+(i) and
t.isSourceDeclaration() and
potentialInterfaceImplementationWithSignature(minst.getSignature(), t, implinst) and
impl = implinst.getSourceDeclaration()
)
}
/** A method is a particular kind of callable. */
class Method extends Callable, @method {
/** Holds if this method (directly) overrides the specified callable. */
predicate overrides(Method m) { overrides(this, m) }
/**
* Holds if this method either overrides `m`, or `m` is the
* source declaration of this method (and not equal to it).
*/
predicate overridesOrInstantiates(Method m) {
this.overrides(m)
or
this.getSourceDeclaration() = m and this != m
}
/** Gets a method (directly or transitively) overridden by this method. */
Method getAnOverride() { this.overrides+(result) }
/** Gets the source declaration of a method overridden by this method. */
SrcMethod getASourceOverriddenMethod() {
exists(Method m | this.overrides(m) and result = m.getSourceDeclaration())
}
override string getSignature() { methods(this, _, result, _, _, _) }
/**
* Holds if this method and method `m` are declared in the same type
* and have the same parameter types.
*/
predicate sameParamTypes(Method m) {
// `this` and `m` are different methods,
this != m and
// `this` and `m` are declared in the same type,
this.getDeclaringType() = m.getDeclaringType() and
// `this` and `m` are of the same arity, and
this.getNumberOfParameters() = m.getNumberOfParameters() and
// there does not exist a pair of parameters whose types differ.
not exists(int n | this.getParameterType(n) != m.getParameterType(n))
}
override SrcMethod getSourceDeclaration() { methods(this, _, _, _, _, result) }
/**
* All the methods that could possibly be called when this method
* is called. For class methods this includes the method itself and all its
* overriding methods (if any), and for interface methods this includes
* matching methods defined on or inherited by implementing classes.
*
* Only includes method implementations, not abstract or non-default interface methods.
* Native methods are included, since they have an implementation (just not in Java).
*/
SrcMethod getAPossibleImplementation() {
this.getSourceDeclaration().getAPossibleImplementationOfSrcMethod() = result
}
override MethodAccess getAReference() { result = Callable.super.getAReference() }
override predicate isPublic() {
Callable.super.isPublic()
or
// JLS 9.4: Every method declaration in the body of an interface without an
// access modifier is implicitly public.
getDeclaringType() instanceof Interface and
not this.isPrivate()
or
exists(FunctionalExpr func | func.asMethod() = this)
}
override predicate isAbstract() {
Callable.super.isAbstract()
or
// JLS 9.4: An interface method lacking a `private`, `default`, or `static` modifier
// is implicitly abstract.
this.getDeclaringType() instanceof Interface and
not this.isPrivate() and
not this.isDefault() and
not this.isStatic()
}
override predicate isStrictfp() {
Callable.super.isStrictfp()
or
// JLS 8.1.1.3, JLS 9.1.1.2
getDeclaringType().isStrictfp()
}
/**
* Holds if this method is neither private nor a static interface method
* nor an initializer method, and hence could be inherited.
*/
predicate isInheritable() {
not isPrivate() and
not (isStatic() and getDeclaringType() instanceof Interface) and
not this instanceof InitializerMethod
}
/**
* Holds if this method is neither private nor static, and hence
* uses dynamic dispatch.
*/
predicate isVirtual() { not isPrivate() and not isStatic() }
/** Holds if this method can be overridden. */
predicate isOverridable() {
isVirtual() and
not isFinal() and
not getDeclaringType().isFinal()
}
override string getAPrimaryQlClass() { result = "Method" }
}
/** A method that is the same as its source declaration. */
class SrcMethod extends Method {
SrcMethod() { methods(_, _, _, _, _, this) }
/**
* All the methods that could possibly be called when this method
* is called. For class methods this includes the method itself and all its
* overriding methods (if any), and for interface methods this includes
* matching methods defined on or inherited by implementing classes.
*
* Only includes method implementations, not abstract or non-default interface methods.
* Native methods are included, since they have an implementation (just not in Java).
*/
SrcMethod getAPossibleImplementationOfSrcMethod() {
(
if this.getDeclaringType() instanceof Interface and this.isVirtual()
then implementsInterfaceMethod(result, this)
else result.getASourceOverriddenMethod*() = this
) and
(exists(result.getBody()) or result.hasModifier("native"))
}
}
/**
* A _setter_ method is a method with the following properties:
*
* - it has exactly one parameter,
* - its body contains exactly one statement
* that assigns the value of the method parameter to a field
* declared in the same type as the method.
*/
class SetterMethod extends Method {
SetterMethod() {
this.getNumberOfParameters() = 1 and
exists(ExprStmt s, Assignment a |
s = this.getBody().(SingletonBlock).getStmt() and a = s.getExpr()
|
exists(Field f | f.getDeclaringType() = this.getDeclaringType() |
a.getDest() = f.getAnAccess() and
a.getSource() = this.getAParameter().getAnAccess()
)
)
}
/** Gets the field assigned by this setter method. */
Field getField() {
exists(Assignment a | a.getEnclosingCallable() = this | a.getDest() = result.getAnAccess())
}
}
/**
* A _getter_ method is a method with the following properties:
*
* - it has no parameters,
* - its body contains exactly one statement
* that returns the value of a field.
*/
class GetterMethod extends Method {
GetterMethod() {
this.hasNoParameters() and
exists(ReturnStmt s, Field f | s = this.getBody().(SingletonBlock).getStmt() |
s.getResult() = f.getAnAccess()
)
}
/** Gets the field whose value is returned by this getter method. */
Field getField() {
exists(ReturnStmt r | r.getEnclosingCallable() = this | r.getResult() = result.getAnAccess())
}
}
/**
* A finalizer method, with name `finalize`,
* return type `void` and no parameters.
*/
class FinalizeMethod extends Method {
FinalizeMethod() {
this.hasName("finalize") and
this.getReturnType().hasName("void") and
this.hasNoParameters()
}
}
/** A constructor is a particular kind of callable. */
class Constructor extends Callable, @constructor {
/** Holds if this is a default constructor, not explicitly declared in source code. */
predicate isDefaultConstructor() { isDefConstr(this) }
override Constructor getSourceDeclaration() { constrs(this, _, _, _, _, result) }
override string getSignature() { constrs(this, _, result, _, _, _) }
override string getAPrimaryQlClass() { result = "Constructor" }
}
/**
* A compiler-generated initializer method (could be static or
* non-static), which is used to hold (static or non-static) field
* initializers, as well as explicit initializer blocks.
*/
abstract class InitializerMethod extends Method { }
/**
* A static initializer is a method that contains all static
* field initializations and static initializer blocks.
*/
class StaticInitializer extends InitializerMethod {
StaticInitializer() { hasName("<clinit>") }
}
/**
* An instance initializer is a method that contains field initializations
* and explicit instance initializer blocks.
*/
class InstanceInitializer extends InitializerMethod {
InstanceInitializer() { this.hasName("<obinit>") }
}
/** A field declaration that declares one or more class or instance fields. */
class FieldDeclaration extends ExprParent, @fielddecl, Annotatable {
/** Gets the access to the type of the field(s) in this declaration. */
Expr getTypeAccess() { result.getParent() = this }
/** Gets a field declared in this declaration. */
Field getAField() { fieldDeclaredIn(result, this, _) }
/** Gets the field declared at the specified (zero-based) position in this declaration */
Field getField(int idx) { fieldDeclaredIn(result, this, idx) }
/** Gets the number of fields declared in this declaration. */
int getNumField() { result = max(int idx | fieldDeclaredIn(_, this, idx) | idx) + 1 }
override string toString() {
if this.getNumField() = 0
then result = this.getTypeAccess() + " " + this.getField(0) + ";"
else result = this.getTypeAccess() + " " + this.getField(0) + ", ...;"
}
override string getAPrimaryQlClass() { result = "FieldDeclaration" }
}
/** A class or instance field. */
class Field extends Member, ExprParent, @field, Variable {
/** Gets the declared type of this field. */
override Type getType() { fields(this, _, result, _, _) }
/** Gets the type in which this field is declared. */
override RefType getDeclaringType() { fields(this, _, _, result, _) }
/**
* Gets the field declaration in which this field is declared.
*
* Note that this declaration is only available if the field occurs in source code.
*/
FieldDeclaration getDeclaration() { result.getAField() = this }
/** Gets the initializer expression of this field, if any. */
override Expr getInitializer() {
exists(AssignExpr e, InitializerMethod im |
e.getDest() = this.getAnAccess() and
e.getSource() = result and
pragma[only_bind_out](result).getEnclosingCallable() = im and
// This rules out updates in explicit initializer blocks as they are nested inside the compiler generated initializer blocks.
pragma[only_bind_out](e.getEnclosingStmt().getParent()) = pragma[only_bind_out](im.getBody())
)
}
/**
* Gets the source declaration of this field.
*
* For fields that are members of a parameterized
* instance of a generic type, the source declaration is the
* corresponding field in the generic type.
*
* For all other fields, the source declaration is the field itself.
*/
Field getSourceDeclaration() { fields(this, _, _, _, result) }
/** Holds if this field is the same as its source declaration. */
predicate isSourceDeclaration() { this.getSourceDeclaration() = this }
override predicate isPublic() {
Member.super.isPublic()
or
// JLS 9.3: Every field declaration in the body of an interface is
// implicitly public, static, and final
getDeclaringType() instanceof Interface
}
override predicate isStatic() {
Member.super.isStatic()
or
// JLS 9.3: Every field declaration in the body of an interface is
// implicitly public, static, and final
this.getDeclaringType() instanceof Interface
}
override predicate isFinal() {
Member.super.isFinal()
or
// JLS 9.3: Every field declaration in the body of an interface is
// implicitly public, static, and final
this.getDeclaringType() instanceof Interface
}
/** Cast this field to a class that provides access to metrics information. */
MetricField getMetrics() { result = this }
override string getAPrimaryQlClass() { result = "Field" }
}
/** An instance field. */
class InstanceField extends Field {
InstanceField() { not this.isStatic() }
}

View File

@@ -0,0 +1,71 @@
/**
* Provides classes and predicates for working with Java modifiers.
*/
import Element
/** A modifier such as `private`, `static` or `abstract`. */
class Modifier extends Element, @modifier {
/** Gets the element to which this modifier applies. */
Element getElement() { hasModifier(result, this) }
override string getAPrimaryQlClass() { result = "Modifier" }
}
/** An element of the Java syntax tree that may have a modifier. */
abstract class Modifiable extends Element {
/**
* Holds if this element has modifier `m`.
*
* For most purposes, the more specialized predicates `isAbstract`, `isPublic`, etc.
* should be used.
*
* Both this method and those specialized predicates take implicit modifiers into account.
* For instance, non-default instance methods in interfaces are implicitly
* abstract, so `isAbstract()` will hold for them even if `hasModifier("abstract")`
* does not.
*/
predicate hasModifier(string m) { modifiers(getAModifier(), m) }
/** Holds if this element has no modifier. */
predicate hasNoModifier() { not hasModifier(this, _) }
/** Gets a modifier of this element. */
Modifier getAModifier() { this = result.getElement() }
/** Holds if this element has an `abstract` modifier or is implicitly abstract. */
predicate isAbstract() { hasModifier("abstract") }
/** Holds if this element has a `static` modifier or is implicitly static. */
predicate isStatic() { hasModifier("static") }
/** Holds if this element has a `final` modifier or is implicitly final. */
predicate isFinal() { hasModifier("final") }
/** Holds if this element has a `public` modifier or is implicitly public. */
predicate isPublic() { hasModifier("public") }
/** Holds if this element has a `protected` modifier. */
predicate isProtected() { hasModifier("protected") }
/** Holds if this element has a `private` modifier or is implicitly private. */
predicate isPrivate() { hasModifier("private") }
/** Holds if this element has a `volatile` modifier. */
predicate isVolatile() { hasModifier("volatile") }
/** Holds if this element has a `synchronized` modifier. */
predicate isSynchronized() { hasModifier("synchronized") }
/** Holds if this element has a `native` modifier. */
predicate isNative() { hasModifier("native") }
/** Holds if this element has a `default` modifier. */
predicate isDefault() { this.hasModifier("default") }
/** Holds if this element has a `transient` modifier. */
predicate isTransient() { this.hasModifier("transient") }
/** Holds if this element has a `strictfp` modifier. */
predicate isStrictfp() { this.hasModifier("strictfp") }
}

View File

@@ -0,0 +1,197 @@
/**
* Provides classes for working with Java modules.
*/
import CompilationUnit
/**
* A module.
*/
class Module extends @module {
Module() { modules(this, _) }
/**
* Gets the name of this module.
*/
string getName() { modules(this, result) }
/**
* Holds if this module is an `open` module, that is,
* it grants access _at run time_ to types in all its packages,
* as if all packages had been exported.
*/
predicate isOpen() { isOpen(this) }
/**
* Gets a directive of this module.
*/
Directive getADirective() { directives(this, result) }
/**
* Gets a compilation unit associated with this module.
*/
CompilationUnit getACompilationUnit() { cumodule(result, this) }
/** Gets a textual representation of this module. */
string toString() { modules(this, result) }
}
/**
* A directive in a module declaration.
*/
abstract class Directive extends @directive {
/** Gets a textual representation of this directive. */
abstract string toString();
}
/**
* A `requires` directive in a module declaration.
*/
class RequiresDirective extends Directive, @requires {
RequiresDirective() { requires(this, _) }
/**
* Holds if this `requires` directive is `transitive`,
* that is, any module that depends on this module
* has an implicitly declared dependency on the
* module specified in this `requires` directive.
*/
predicate isTransitive() { isTransitive(this) }
/**
* Holds if this `requires` directive is `static`,
* that is, the dependence specified by this `requires`
* directive is only mandatory at compile time but
* optional at run time.
*/
predicate isStatic() { isStatic(this) }
/**
* Gets the module on which this module depends.
*/
Module getTargetModule() { requires(this, result) }
override string toString() {
exists(string transitive, string static |
(if isTransitive() then transitive = "transitive " else transitive = "") and
(if isStatic() then static = "static " else static = "")
|
result = "requires " + transitive + static + getTargetModule() + ";"
)
}
}
/**
* An `exports` directive in a module declaration.
*/
class ExportsDirective extends Directive, @exports {
ExportsDirective() { exports(this, _) }
/**
* Gets the package exported by this `exports` directive.
*/
Package getExportedPackage() { exports(this, result) }
/**
* Holds if this `exports` directive is qualified, that is,
* it contains a `to` clause.
*
* For qualified `exports` directives, exported types and members
* are accessible only to code in the specified modules.
* For unqualified `exports` directives, they are accessible
* to code in any module.
*/
predicate isQualified() { exportsTo(this, _) }
/**
* Gets a module specified in the `to` clause of this
* `exports` directive, if any.
*/
Module getATargetModule() { exportsTo(this, result) }
override string toString() {
exists(string toClause |
if isQualified()
then toClause = (" to " + concat(getATargetModule().getName(), ", "))
else toClause = ""
|
result = "exports " + getExportedPackage() + toClause + ";"
)
}
}
/**
* An `opens` directive in a module declaration.
*/
class OpensDirective extends Directive, @opens {
OpensDirective() { opens(this, _) }
/**
* Gets the package opened by this `opens` directive.
*/
Package getOpenedPackage() { opens(this, result) }
/**
* Holds if this `opens` directive is qualified, that is,
* it contains a `to` clause.
*
* For qualified `opens` directives, opened types and members
* are accessible only to code in the specified modules.
* For unqualified `opens` directives, they are accessible
* to code in any module.
*/
predicate isQualified() { opensTo(this, _) }
/**
* Gets a module specified in the `to` clause of this
* `opens` directive, if any.
*/
Module getATargetModule() { opensTo(this, result) }
override string toString() {
exists(string toClause |
if isQualified()
then toClause = (" to " + concat(getATargetModule().getName(), ", "))
else toClause = ""
|
result = "opens " + getOpenedPackage() + toClause + ";"
)
}
}
/**
* A `uses` directive in a module declaration.
*/
class UsesDirective extends Directive, @uses {
UsesDirective() { uses(this, _) }
/**
* Gets the qualified name of the service interface specified in this `uses` directive.
*/
string getServiceInterfaceName() { uses(this, result) }
override string toString() { result = "uses " + getServiceInterfaceName() + ";" }
}
/**
* A `provides` directive in a module declaration.
*/
class ProvidesDirective extends Directive, @provides {
ProvidesDirective() { provides(this, _) }
/**
* Gets the qualified name of the service interface specified in this `provides` directive.
*/
string getServiceInterfaceName() { provides(this, result) }
/**
* Gets the qualified name of a service implementation specified in this `provides` directive.
*/
string getServiceImplementationName() { providesWith(this, result) }
override string toString() {
result =
"provides " + getServiceInterfaceName() + " with " +
concat(getServiceImplementationName(), ", ") + ";"
}
}

View File

@@ -0,0 +1,73 @@
/** Provides classes and predicates for reasoning about `java.lang.NumberFormatException`. */
import java
/** A call to a string to number conversion. */
private class SpecialMethodAccess extends MethodAccess {
predicate isValueOfMethod(string klass) {
this.getMethod().getName() = "valueOf" and
this.getQualifier().getType().(RefType).hasQualifiedName("java.lang", klass) and
this.getAnArgument().getType().(RefType).hasQualifiedName("java.lang", "String")
}
predicate isParseMethod(string klass, string name) {
this.getMethod().getName() = name and
this.getQualifier().getType().(RefType).hasQualifiedName("java.lang", klass)
}
predicate throwsNFE() {
this.isParseMethod("Byte", "parseByte") or
this.isParseMethod("Short", "parseShort") or
this.isParseMethod("Integer", "parseInt") or
this.isParseMethod("Long", "parseLong") or
this.isParseMethod("Float", "parseFloat") or
this.isParseMethod("Double", "parseDouble") or
this.isParseMethod("Byte", "decode") or
this.isParseMethod("Short", "decode") or
this.isParseMethod("Integer", "decode") or
this.isParseMethod("Long", "decode") or
this.isValueOfMethod("Byte") or
this.isValueOfMethod("Short") or
this.isValueOfMethod("Integer") or
this.isValueOfMethod("Long") or
this.isValueOfMethod("Float") or
this.isValueOfMethod("Double")
}
}
/** A `ClassInstanceExpr` that constructs a number from its string representation. */
private class SpecialClassInstanceExpr extends ClassInstanceExpr {
predicate isStringConstructor(string klass) {
this.getType().(RefType).hasQualifiedName("java.lang", klass) and
this.getAnArgument().getType().(RefType).hasQualifiedName("java.lang", "String") and
this.getNumArgument() = 1
}
predicate throwsNFE() {
this.isStringConstructor("Byte") or
this.isStringConstructor("Short") or
this.isStringConstructor("Integer") or
this.isStringConstructor("Long") or
this.isStringConstructor("Float") or
this.isStringConstructor("Double")
}
}
/** The class `java.lang.NumberFormatException`. */
class NumberFormatException extends RefType {
NumberFormatException() { this.hasQualifiedName("java.lang", "NumberFormatException") }
}
/** Holds if `java.lang.NumberFormatException` is caught. */
predicate catchesNFE(TryStmt t) {
exists(CatchClause cc, LocalVariableDeclExpr v |
t.getACatchClause() = cc and
cc.getVariable() = v and
v.getType().(RefType).getASubtype*() instanceof NumberFormatException
)
}
/** Holds if `java.lang.NumberFormatException` can be thrown. */
predicate throwsNFE(Expr e) {
e.(SpecialClassInstanceExpr).throwsNFE() or e.(SpecialMethodAccess).throwsNFE()
}

View File

@@ -0,0 +1,34 @@
/**
* Provides classes and predicates for working with Java packages.
*/
import Element
import Type
import metrics.MetricPackage
/**
* A package may be used to abstract over all of its members,
* regardless of which compilation unit they are defined in.
*/
class Package extends Element, Annotatable, @package {
/** Gets a top level type in this package. */
TopLevelType getATopLevelType() { result.getPackage() = this }
/** Holds if at least one reference type in this package originates from source code. */
override predicate fromSource() { exists(RefType t | t.fromSource() and t.getPackage() = this) }
/** Cast this package to a class that provides access to metrics information. */
MetricPackage getMetrics() { result = this }
/**
* A dummy URL for packages.
*
* This declaration is required to allow selection of packages in QL queries.
* Without it, an implicit call to `Package.getLocation()` would be generated
* when selecting a package, which would result in a compile-time error
* since packages do not have locations.
*/
string getURL() { result = "file://:0:0:0:0" }
override string getAPrimaryQlClass() { result = "Package" }
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,20 @@
/**
* @name Print AST
* @description Outputs a representation of the Abstract Syntax Tree.
* @id java/print-ast
* @kind graph
*/
import java
import PrintAst
/**
* Temporarily tweak this class or make a copy to control which functions are
* printed.
*/
class PrintAstConfigurationOverride extends PrintAstConfiguration {
/**
* TWEAK THIS PREDICATE AS NEEDED.
*/
override predicate shouldPrint(Element e, Location l) { super.shouldPrint(e, l) }
}

View File

@@ -0,0 +1,813 @@
/**
* Provides queries to pretty-print a Java AST as a graph.
*
* By default, this will print the AST for all elements in the database. To change this behavior,
* extend `PrintAstConfiguration` and override `shouldPrint` to hold for only the elements
* you wish to view the AST for.
*/
import java
private newtype TPrintAstConfiguration = MkPrintAstConfiguration()
/**
* The query can extend this class to control which elements are printed.
*/
class PrintAstConfiguration extends TPrintAstConfiguration {
/**
* Gets a textual representation of this `PrintAstConfiguration`.
*/
string toString() { result = "PrintAstConfiguration" }
/**
* Controls whether the `Element` should be considered for AST printing.
* By default it checks whether the `Element` `e` belongs to `Location` `l`.
*/
predicate shouldPrint(Element e, Location l) { e.fromSource() and l = e.getLocation() }
}
private predicate shouldPrint(Element e, Location l) {
exists(PrintAstConfiguration config | config.shouldPrint(e, l))
}
private class ExprOrStmt extends Element {
ExprOrStmt() { this instanceof Expr or this instanceof Stmt }
ExprOrStmt getParent() {
result = this.(Expr).getParent()
or
result = this.(Stmt).getParent()
}
Callable getEnclosingCallable() {
result = this.(Expr).getEnclosingCallable()
or
result = this.(Stmt).getEnclosingCallable()
}
}
/** Holds if the given element does not need to be rendered in the AST, due to being compiler-generated. */
private predicate isNotNeeded(Element el) {
exists(InitializerMethod im |
el = im
or
exists(ExprOrStmt e | e = el |
e.getEnclosingCallable() = im and
not e.getParent*() = any(Field f).getInitializer() and
not isInitBlock(im.getDeclaringType(), e.getParent*())
)
)
or
exists(Constructor c | c.isDefaultConstructor() |
el = c
or
el.(ExprOrStmt).getEnclosingCallable() = c
)
or
exists(Constructor c, int sline, int eline, int scol, int ecol |
el.(ExprOrStmt).getEnclosingCallable() = c
|
el.getLocation().hasLocationInfo(_, sline, eline, scol, ecol) and
c.getLocation().hasLocationInfo(_, sline, eline, scol, ecol)
// simply comparing their getLocation() doesn't work as they have distinct but equivalent locations
)
or
isNotNeeded(el.(Expr).getParent*().(Annotation).getAnnotatedElement())
or
isNotNeeded(el.(Parameter).getCallable())
}
/** Holds if the given field would have the same javadoc and annotations as another field declared in the same declaration */
private predicate duplicateMetadata(Field f) {
exists(FieldDeclaration fd |
f = fd.getAField() and
not f = fd.getField(0)
)
}
/**
* Retrieves the canonical QL class(es) for entity `el`
*/
private string getQlClass(Top el) {
result = "[" + concat(el.getAPrimaryQlClass(), ",") + "] "
// Alternative implementation -- do not delete. It is useful for QL class discovery.
// result = "[" + concat(el.getAQlClass(), ",") + "] "
}
private predicate locationSortKeys(Element ast, string file, int line, int column) {
exists(Location loc |
loc = ast.getLocation() and
file = loc.getFile().toString() and
line = loc.getStartLine() and
column = loc.getStartColumn()
)
or
not exists(ast.getLocation()) and
file = "" and
line = 0 and
column = 0
}
/**
* Printed AST nodes are mostly `Element`s of the underlying AST.
*/
private newtype TPrintAstNode =
TElementNode(Element el) { shouldPrint(el, _) } or
TForInitNode(ForStmt fs) { shouldPrint(fs, _) and exists(fs.getAnInit()) } or
TLocalVarDeclNode(LocalVariableDeclExpr lvde) {
shouldPrint(lvde, _) and lvde.getParent() instanceof SingleLocalVarDeclParent
} or
TAnnotationsNode(Annotatable ann) {
shouldPrint(ann, _) and ann.hasAnnotation() and not partOfAnnotation(ann)
} or
TParametersNode(Callable c) { shouldPrint(c, _) and not c.hasNoParameters() } or
TBaseTypesNode(ClassOrInterface ty) { shouldPrint(ty, _) } or
TGenericTypeNode(GenericType ty) { shouldPrint(ty, _) } or
TGenericCallableNode(GenericCallable c) { shouldPrint(c, _) } or
TDocumentableNode(Documentable d) { shouldPrint(d, _) and exists(d.getJavadoc()) } or
TJavadocNode(Javadoc jd) { exists(Documentable d | d.getJavadoc() = jd | shouldPrint(d, _)) } or
TJavadocElementNode(JavadocElement jd) {
exists(Documentable d | d.getJavadoc() = jd.getParent*() | shouldPrint(d, _))
} or
TImportsNode(CompilationUnit cu) {
shouldPrint(cu, _) and exists(Import i | i.getCompilationUnit() = cu)
}
/**
* A node in the output tree.
*/
class PrintAstNode extends TPrintAstNode {
/**
* Gets a textual representation of this node in the PrintAst output tree.
*/
string toString() { none() }
/**
* Gets the child node at index `childIndex`. Child indices must be unique,
* but need not be contiguous.
*/
PrintAstNode getChild(int childIndex) { none() }
/**
* Gets a child of this node.
*/
final PrintAstNode getAChild() { result = getChild(_) }
/**
* Gets the parent of this node, if any.
*/
final PrintAstNode getParent() { result.getAChild() = this }
/**
* Gets the location of this node in the source code.
*/
Location getLocation() { none() }
/**
* 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()
}
}
/** A top-level AST node. */
class TopLevelPrintAstNode extends PrintAstNode {
TopLevelPrintAstNode() { not exists(this.getParent()) }
private int getOrder() {
this =
rank[result](TopLevelPrintAstNode n, Location l |
l = n.getLocation()
|
n
order by
l.getFile().getRelativePath(), l.getStartLine(), l.getStartColumn(), l.getEndLine(),
l.getEndColumn()
)
}
override string getProperty(string key) {
result = super.getProperty(key)
or
key = "semmle.order" and
result = this.getOrder().toString()
}
}
/**
* A node representing an AST node with an underlying `Element`.
*/
abstract class ElementNode extends PrintAstNode, TElementNode {
Element element;
ElementNode() { this = TElementNode(element) and not isNotNeeded(element) }
override string toString() { result = getQlClass(element) + element.toString() }
override Location getLocation() { result = element.getLocation() }
/**
* Gets the `Element` represented by this node.
*/
final Element getElement() { result = element }
}
/**
* A node representing an `Expr` or a `Stmt`.
*/
class ExprStmtNode extends ElementNode {
ExprStmtNode() { element instanceof ExprOrStmt }
override PrintAstNode getChild(int childIndex) {
exists(Element el | result.(ElementNode).getElement() = el |
el.(Expr).isNthChildOf(element, childIndex)
or
el.(Stmt).isNthChildOf(element, childIndex)
)
}
}
/**
* Holds if the given expression is part of an annotation.
*/
private predicate partOfAnnotation(Expr e) {
e instanceof Annotation
or
e instanceof ArrayInit and
partOfAnnotation(e.getParent())
}
/**
* A node representing an `Expr` that is part of an annotation.
*/
final class AnnotationPartNode extends ExprStmtNode {
AnnotationPartNode() { partOfAnnotation(element) }
override ElementNode getChild(int childIndex) {
result.getElement() =
rank[childIndex](Element ch, string file, int line, int column |
ch = getAnAnnotationChild() and locationSortKeys(ch, file, line, column)
|
ch order by file, line, column
)
}
private Expr getAnAnnotationChild() {
result = element.(Annotation).getValue(_)
or
result = element.(ArrayInit).getAnInit()
or
result = element.(ArrayInit).(Annotatable).getAnAnnotation()
}
}
/**
* A node representing a `LocalVariableDeclExpr`.
*/
final class LocalVarDeclExprNode extends ExprStmtNode {
LocalVarDeclExprNode() { element instanceof LocalVariableDeclExpr }
override PrintAstNode getChild(int childIndex) {
result = super.getChild(childIndex)
or
childIndex = -2 and
result.(AnnotationsNode).getAnnotated() = element.(LocalVariableDeclExpr).getVariable()
}
}
/**
* A node representing a `ClassInstanceExpr`.
*/
final class ClassInstanceExprNode extends ExprStmtNode {
ClassInstanceExprNode() { element instanceof ClassInstanceExpr }
override ElementNode getChild(int childIndex) {
result = super.getChild(childIndex)
or
childIndex = -4 and
result.getElement() = element.(ClassInstanceExpr).getAnonymousClass()
}
}
/**
* A node representing a `LocalClassDeclStmt`.
*/
final class LocalClassDeclStmtNode extends ExprStmtNode {
LocalClassDeclStmtNode() { element instanceof LocalClassDeclStmt }
override ElementNode getChild(int childIndex) {
result = super.getChild(childIndex)
or
childIndex = 0 and
result.getElement() = element.(LocalClassDeclStmt).getLocalClass()
}
}
/**
* A node representing a `ForStmt`.
*/
final class ForStmtNode extends ExprStmtNode {
ForStmtNode() { element instanceof ForStmt }
override PrintAstNode getChild(int childIndex) {
childIndex >= 1 and
result = super.getChild(childIndex)
or
childIndex = 0 and
result.(ForInitNode).getForStmt() = element
}
}
/**
* An element that can be the parent of up to one `LocalVariableDeclExpr` for which we want
* to use a synthetic node to hold the variable declaration and its `TypeAccess`.
*/
private class SingleLocalVarDeclParent extends ExprOrStmt {
SingleLocalVarDeclParent() {
this instanceof EnhancedForStmt or
this instanceof CatchClause or
this.(InstanceOfExpr).isPattern()
}
/** Gets the variable declaration that this element contains */
LocalVariableDeclExpr getVariable() { result.getParent() = this }
/** Gets the type access of the variable */
Expr getTypeAccess() { result = getVariable().getTypeAccess() }
}
/**
* A node representing an element that can be the parent of up to one `LocalVariableDeclExpr` for which we
* want to use a synthetic node to variable declaration and its type access.
*
* Excludes `LocalVariableDeclStmt` and `ForStmt`, as they can hold multiple declarations.
* For these cases, either a synthetic node is not necassary or a different synthetic node is used.
*/
final class SingleLocalVarDeclParentNode extends ExprStmtNode {
SingleLocalVarDeclParent lvdp;
SingleLocalVarDeclParentNode() { lvdp = element }
override PrintAstNode getChild(int childIndex) {
result = super.getChild(childIndex) and
not result.(ElementNode).getElement() = [lvdp.getVariable(), lvdp.getTypeAccess()]
or
childIndex = lvdp.getVariable().getIndex() and
result.(LocalVarDeclSynthNode).getVariable() = lvdp.getVariable()
}
}
/**
* A node representing a `Callable`, such as method declaration.
*/
final class CallableNode extends ElementNode {
Callable callable;
CallableNode() { callable = element }
override PrintAstNode getChild(int childIndex) {
childIndex = 0 and
result.(DocumentableNode).getDocumentable() = callable
or
childIndex = 1 and
result.(AnnotationsNode).getAnnotated() = callable
or
childIndex = 2 and
result.(GenericCallableNode).getCallable() = callable
or
childIndex = 3 and
result.(ElementNode).getElement().(Expr).isNthChildOf(callable, -1) // return type
or
childIndex = 4 and
result.(ParametersNode).getCallable() = callable
or
childIndex = 5 and
result.(ElementNode).getElement() = callable.getBody()
}
}
/**
* A node representing a `Parameter` of a `Callable`.
*/
final class ParameterNode extends ElementNode {
Parameter p;
ParameterNode() { p = element }
override PrintAstNode getChild(int childIndex) {
childIndex = -1 and
result.(AnnotationsNode).getAnnotated() = p
or
childIndex = 0 and
result.(ElementNode).getElement().(Expr).isNthChildOf(p, -1) // type
}
}
private predicate isInitBlock(Class c, BlockStmt b) {
exists(InitializerMethod m | b.getParent() = m.getBody() and m.getDeclaringType() = c)
}
/**
* A node representing a `Class` or an `Interface`.
*/
final class ClassInterfaceNode extends ElementNode {
ClassOrInterface ty;
ClassInterfaceNode() { ty = element }
private Element getADeclaration() {
result.(Callable).getDeclaringType() = ty
or
result.(FieldDeclaration).getAField().getDeclaringType() = ty
or
result.(MemberType).getEnclosingType().getSourceDeclaration() = ty
or
isInitBlock(ty, result)
}
override PrintAstNode getChild(int childIndex) {
childIndex = -4 and
result.(DocumentableNode).getDocumentable() = ty
or
childIndex = -3 and
result.(AnnotationsNode).getAnnotated() = ty
or
childIndex = -2 and
result.(GenericTypeNode).getType() = ty
or
childIndex = -1 and
result.(BaseTypesNode).getClassOrInterface() = ty
or
childIndex >= 0 and
result.(ElementNode).getElement() =
rank[childIndex](Element e, string file, int line, int column |
e = getADeclaration() and locationSortKeys(e, file, line, column)
|
e order by file, line, column
)
}
}
/**
* A node representing a `FieldDeclaration`.
*/
final class FieldDeclNode extends ElementNode {
FieldDeclaration decl;
FieldDeclNode() { decl = element }
override PrintAstNode getChild(int childIndex) {
childIndex = -3 and
result.(DocumentableNode).getDocumentable() = decl.getField(0)
or
childIndex = -2 and
result.(AnnotationsNode).getAnnotated() = decl.getField(0)
or
childIndex = -1 and
result.(ElementNode).getElement() = decl.getTypeAccess()
or
childIndex >= 0 and
result.(ElementNode).getElement() = decl.getField(childIndex).getInitializer()
}
}
/**
* A node representing a `CompilationUnit`.
*/
final class CompilationUnitNode extends ElementNode {
CompilationUnit cu;
CompilationUnitNode() { cu = element }
private Element getADeclaration() { cu.hasChildElement(result) }
override PrintAstNode getChild(int childIndex) {
childIndex = -1 and
result.(ImportsNode).getCompilationUnit() = cu
or
childIndex >= 0 and
result.(ElementNode).getElement() =
rank[childIndex](Element e, string file, int line, int column |
e = getADeclaration() and locationSortKeys(e, file, line, column)
|
e order by file, line, column
)
}
}
/**
* A node representing an `Import`.
*/
final class ImportNode extends ElementNode {
ImportNode() { element instanceof Import }
}
/**
* A node representing a `TypeVariable`.
*/
final class TypeVariableNode extends ElementNode {
TypeVariableNode() { element instanceof TypeVariable }
override ElementNode getChild(int childIndex) {
result.getElement().(Expr).isNthChildOf(element, childIndex)
}
}
/**
* A node representing the initializers of a `ForStmt`.
*/
final class ForInitNode extends PrintAstNode, TForInitNode {
ForStmt fs;
ForInitNode() { this = TForInitNode(fs) }
override string toString() { result = "(For Initializers) " }
override ElementNode getChild(int childIndex) {
childIndex >= 0 and
result.getElement().(Expr).isNthChildOf(fs, -childIndex)
}
/**
* Gets the underlying `ForStmt`.
*/
ForStmt getForStmt() { result = fs }
}
/**
* A synthetic node holding a `LocalVariableDeclExpr` and its type access.
*/
final class LocalVarDeclSynthNode extends PrintAstNode, TLocalVarDeclNode {
LocalVariableDeclExpr lvde;
LocalVarDeclSynthNode() { this = TLocalVarDeclNode(lvde) }
override string toString() { result = "(Single Local Variable Declaration)" }
override ElementNode getChild(int childIndex) {
childIndex = 0 and
result.getElement() = lvde.getTypeAccess()
or
childIndex = 1 and
result.getElement() = lvde
}
/**
* Gets the underlying `LocalVariableDeclExpr`
*/
LocalVariableDeclExpr getVariable() { result = lvde }
}
/**
* A node representing the annotations of an `Annotatable`.
* Only rendered if there is at least one annotation.
*/
final class AnnotationsNode extends PrintAstNode, TAnnotationsNode {
Annotatable ann;
AnnotationsNode() {
this = TAnnotationsNode(ann) and not isNotNeeded(ann) and not duplicateMetadata(ann)
}
override string toString() { result = "(Annotations)" }
override Location getLocation() { none() }
override ElementNode getChild(int childIndex) {
result.getElement() =
rank[childIndex](Element e, string file, int line, int column |
e = ann.getAnAnnotation() and locationSortKeys(e, file, line, column)
|
e order by file, line, column
)
}
/**
* Gets the underlying `Annotatable`.
*/
Annotatable getAnnotated() { result = ann }
}
/**
* A node representing the parameters of a `Callable`.
* Only rendered if there is at least one parameter.
*/
final class ParametersNode extends PrintAstNode, TParametersNode {
Callable c;
ParametersNode() { this = TParametersNode(c) and not isNotNeeded(c) }
override string toString() { result = "(Parameters)" }
override Location getLocation() { none() }
override ElementNode getChild(int childIndex) { result.getElement() = c.getParameter(childIndex) }
/**
* Gets the underlying `Callable`.
*/
Callable getCallable() { result = c }
}
/**
* A node representing the base types of a `Class` or `Interface` that it extends or implements.
* Only redered if there is at least one such type.
*/
final class BaseTypesNode extends PrintAstNode, TBaseTypesNode {
ClassOrInterface ty;
BaseTypesNode() { this = TBaseTypesNode(ty) and exists(TypeAccess ta | ta.getParent() = ty) }
override string toString() { result = "(Base Types)" }
override Location getLocation() { none() }
override ElementNode getChild(int childIndex) {
result.getElement().(TypeAccess).isNthChildOf(ty, -2 - childIndex)
}
/**
* Gets the underlying `Class` or `Interface`.
*/
ClassOrInterface getClassOrInterface() { result = ty }
}
/**
* A node representing the type parameters of a `Class` or `Interface`.
* Only rendered when the type in question is indeed generic.
*/
final class GenericTypeNode extends PrintAstNode, TGenericTypeNode {
GenericType ty;
GenericTypeNode() { this = TGenericTypeNode(ty) }
override string toString() { result = "(Generic Parameters)" }
override Location getLocation() { none() }
override ElementNode getChild(int childIndex) {
result.getElement().(TypeVariable) = ty.getTypeParameter(childIndex)
}
/**
* Gets the underlying `GenericType`.
*/
GenericType getType() { result = ty }
}
/**
* A node representing the type parameters of a `Callable`.
* Only rendered when the callable in question is indeed generic.
*/
final class GenericCallableNode extends PrintAstNode, TGenericCallableNode {
GenericCallable c;
GenericCallableNode() { this = TGenericCallableNode(c) }
override string toString() { result = "(Generic Parameters)" }
override ElementNode getChild(int childIndex) {
result.getElement().(TypeVariable) = c.getTypeParameter(childIndex)
}
/**
* Gets the underlying `GenericCallable`.
*/
GenericCallable getCallable() { result = c }
}
/**
* A node representing the documentation of a `Documentable`.
* Only rendered if there is at least one `Javadoc` attatched to it.
*/
final class DocumentableNode extends PrintAstNode, TDocumentableNode {
Documentable d;
DocumentableNode() { this = TDocumentableNode(d) and not duplicateMetadata(d) }
override string toString() { result = "(Javadoc)" }
override Location getLocation() { none() }
override JavadocNode getChild(int childIndex) {
result.getJavadoc() =
rank[childIndex](Javadoc jd, string file, int line, int column |
jd.getCommentedElement() = d and jd.getLocation().hasLocationInfo(file, line, column, _, _)
|
jd order by file, line, column
)
}
/**
* Gets the underlying `Documentable`.
*/
Documentable getDocumentable() { result = d }
}
/**
* A node representing a `Javadoc`.
* Only rendered if it is the javadoc of some `Documentable`.
*/
final class JavadocNode extends PrintAstNode, TJavadocNode {
Javadoc jd;
JavadocNode() { this = TJavadocNode(jd) }
override string toString() { result = getQlClass(jd) + jd.toString() }
override Location getLocation() { result = jd.getLocation() }
override JavadocElementNode getChild(int childIndex) {
result.getJavadocElement() = jd.getChild(childIndex)
}
/**
* Gets the `Javadoc` represented by this node.
*/
Javadoc getJavadoc() { result = jd }
}
/**
* A node representing a `JavadocElement`.
* Only rendered if it is part of the javadoc of some `Documentable`.
*/
final class JavadocElementNode extends PrintAstNode, TJavadocElementNode {
JavadocElement jd;
JavadocElementNode() { this = TJavadocElementNode(jd) }
override string toString() { result = getQlClass(jd) + jd.toString() }
override Location getLocation() { result = jd.getLocation() }
override JavadocElementNode getChild(int childIndex) {
result.getJavadocElement() = jd.(JavadocParent).getChild(childIndex)
}
/**
* Gets the `JavadocElement` represented by this node.
*/
JavadocElement getJavadocElement() { result = jd }
}
/**
* A node representing the `Import`s of a `CompilationUnit`.
* Only rendered if there is at least one import.
*/
final class ImportsNode extends PrintAstNode, TImportsNode {
CompilationUnit cu;
ImportsNode() { this = TImportsNode(cu) }
override string toString() { result = "(Imports)" }
override ElementNode getChild(int childIndex) {
result.getElement() =
rank[childIndex](Import im, string file, int line, int column |
im.getCompilationUnit() = cu and locationSortKeys(im, file, line, column)
|
im order by file, line, column
)
}
/**
* Gets the underlying CompilationUnit.
*/
CompilationUnit getCompilationUnit() { result = cu }
}
/** Holds if `node` belongs to the output tree, and its property `key` has the given `value`. */
query predicate nodes(PrintAstNode node, string key, string value) { value = node.getProperty(key) }
/**
* Holds if `target` is a child of `source` in the AST, and property `key` of the edge has the
* given `value`.
*/
query predicate edges(PrintAstNode source, PrintAstNode target, string key, string value) {
exists(int childIndex |
target = source.getChild(childIndex) and
(
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,403 @@
/**
* Provides classes and predicates for working with Java Reflection.
*/
import java
import JDKAnnotations
import Serializability
import semmle.code.java.dataflow.DefUse
/** Holds if `f` is a field that may be read by reflection. */
predicate reflectivelyRead(Field f) {
f instanceof SerializableField or
f.getAnAnnotation() instanceof ReflectiveAccessAnnotation or
referencedInXmlFile(f)
}
/** Holds if `f` is a field that may be written by reflection. */
predicate reflectivelyWritten(Field f) {
f instanceof DeserializableField or
f.getAnAnnotation() instanceof ReflectiveAccessAnnotation or
referencedInXmlFile(f)
}
/**
* Holds if a field's name and declaring type are referenced in an XML file.
* Usually, this implies that the field may be accessed reflectively.
*/
predicate referencedInXmlFile(Field f) {
elementReferencingField(f).getParent*() = elementReferencingType(f.getDeclaringType())
}
/**
* Gets an XML element with an attribute whose value is the name of `f`,
* suggesting that it might reference `f`.
*/
private XMLElement elementReferencingField(Field f) {
exists(elementReferencingType(f.getDeclaringType())) and
result.getAnAttribute().getValue() = f.getName()
}
/**
* Gets an XML element with an attribute whose value is the fully qualified
* name of `rt`, suggesting that it might reference `rt`.
*/
private XMLElement elementReferencingType(RefType rt) {
result.getAnAttribute().getValue() = rt.getSourceDeclaration().getQualifiedName()
}
abstract private class ReflectiveClassIdentifier extends Expr {
/**
* Gets the type of a class identified by this expression.
*/
abstract RefType getReflectivelyIdentifiedClass();
}
private class ReflectiveClassIdentifierLiteral extends ReflectiveClassIdentifier, TypeLiteral {
override RefType getReflectivelyIdentifiedClass() {
result = getReferencedType().(RefType).getSourceDeclaration()
}
}
/**
* A call to a Java standard library method which constructs or returns a `Class<T>` from a `String`.
*/
class ReflectiveClassIdentifierMethodAccess extends ReflectiveClassIdentifier, MethodAccess {
ReflectiveClassIdentifierMethodAccess() {
// A call to `Class.forName(...)`, from which we can infer `T` in the returned type `Class<T>`.
getCallee().getDeclaringType() instanceof TypeClass and getCallee().hasName("forName")
or
// A call to `ClassLoader.loadClass(...)`, from which we can infer `T` in the returned type `Class<T>`.
getCallee().getDeclaringType().hasQualifiedName("java.lang", "ClassLoader") and
getCallee().hasName("loadClass")
}
/**
* If the argument to this call is a `StringLiteral`, then return that string.
*/
string getTypeName() { result = getArgument(0).(StringLiteral).getRepresentedString() }
override RefType getReflectivelyIdentifiedClass() {
// We only handle cases where the class is specified as a string literal to this call.
result.getQualifiedName() = getTypeName()
}
}
/**
* Gets a `ReflectiveClassIdentifier` that we believe may represent the value of `expr`.
*/
private ReflectiveClassIdentifier pointsToReflectiveClassIdentifier(Expr expr) {
// If this is an expression creating a `Class<T>`, return the inferred `T` from the creation expression.
result = expr
or
// Or if this is an access of a variable which was defined as an expression creating a `Class<T>`,
// return the inferred `T` from the definition expression.
exists(RValue use, VariableAssign assign |
use = expr and
defUsePair(assign, use) and
// The source of the assignment must be a `ReflectiveClassIdentifier`.
result = assign.getSource()
)
}
/**
* Holds if `type` is considered to be "overly" generic.
*/
private predicate overlyGenericType(Type type) {
type instanceof TypeObject or
type instanceof TypeSerializable
}
/**
* Identify "catch-all" bounded types, where the upper bound is an overly generic type, such as
* `? extends Object` and `? extends Serializable`.
*/
private predicate catchallType(BoundedType type) {
exists(Type upperBound | upperBound = type.getUpperBoundType() | overlyGenericType(upperBound))
}
/**
* Given `Class<X>` or `Constructor<X>`, return all types `T`, such that
* `Class<T>` or `Constructor<T>` is, or is a sub-type of, `type`.
*
* In the case that `X` is a bounded type with an upper bound, and that upper bound is `Object` or
* `Serializable`, we return no sub-types.
*/
pragma[nomagic]
private Type parameterForSubTypes(ParameterizedType type) {
(type instanceof TypeClass or type instanceof TypeConstructor) and
// Only report "real" types.
not result instanceof TypeVariable and
// Identify which types the type argument `arg` could represent.
exists(Type arg |
arg = type.getTypeArgument(0) and
// Must not be a catch-all.
not catchallType(arg)
|
// Simple case - this type is not a bounded type, so must represent exactly the `arg` class.
not arg instanceof BoundedType and result = arg
or
exists(RefType upperBound |
// Upper bound case
upperBound = arg.(BoundedType).getUpperBoundType()
|
// `T extends Foo` implies that `Foo`, or any sub-type of `Foo`, may be represented.
result.(RefType).getAnAncestor() = upperBound
)
or
exists(RefType lowerBound |
// Lower bound case
lowerBound = arg.(Wildcard).getLowerBoundType()
|
// `T super Foo` implies that `Foo`, or any super-type of `Foo`, may be represented.
lowerBound.(RefType).getAnAncestor() = result
)
)
}
/**
* Given an expression whose type is `Class<T>`, infer a possible set of types for `T`.
*/
Type inferClassParameterType(Expr expr) {
// Must be of type `Class` or `Class<T>`.
expr.getType() instanceof TypeClass and
(
// If this `expr` is a `VarAccess` of a final or effectively final parameter, then look at the
// arguments to calls to this method, to see if we can infer anything from that case.
exists(Parameter p |
p = expr.(VarAccess).getVariable() and
p.isEffectivelyFinal()
|
result = inferClassParameterType(p.getAnArgument())
)
or
if exists(pointsToReflectiveClassIdentifier(expr).getReflectivelyIdentifiedClass())
then
// We've been able to identify where this `Class` instance was created, and identified the
// particular class that was loaded.
result = pointsToReflectiveClassIdentifier(expr).getReflectivelyIdentifiedClass()
else
// If we haven't been able to find where the value for this expression was defined, then we
// resort to the type `T` in `Class<T>`.
//
// If `T` refers to a bounded type with an upper bound, then we return all sub-types of the upper
// bound as possibilities for the instantiation, so long as this is not a catch-all type.
//
// A "catch-all" type is something like `? extends Object` or `? extends Serialization`, which
// would return too many sub-types.
result = parameterForSubTypes(expr.getType())
)
}
/**
* Given an expression whose type is `Constructor<T>`, infer a possible set of types for `T`.
*/
private Type inferConstructorParameterType(Expr expr) {
expr.getType() instanceof TypeConstructor and
// Return all the possible sub-types that could be instantiated.
// Not a catch-all `Constructor`, for example, `? extends Object` or `? extends Serializable`.
result = parameterForSubTypes(expr.getType())
}
/**
* Holds if a `Constructor.newInstance(...)` call for this type would expect an enclosing instance
* argument in the first position.
*/
private predicate expectsEnclosingInstance(RefType r) {
r instanceof NestedType and
not r.(NestedType).isStatic()
}
/**
* A call to `Class.newInstance()` or `Constructor.newInstance()`.
*/
class NewInstance extends MethodAccess {
NewInstance() {
(
getCallee().getDeclaringType() instanceof TypeClass or
getCallee().getDeclaringType() instanceof TypeConstructor
) and
getCallee().hasName("newInstance")
}
/**
* Gets the `Constructor` that we believe will be invoked when this `newInstance()` method is
* called.
*/
Constructor getInferredConstructor() {
result = getInferredConstructedType().getAConstructor() and
if getCallee().getDeclaringType() instanceof TypeClass
then result.getNumberOfParameters() = 0
else
if getNumArgument() = 1 and getArgument(0).getType() instanceof Array
then
// This is a var-args array argument. If array argument is initialized inline, then identify
// the number of arguments specified in the array.
if exists(getArgument(0).(ArrayCreationExpr).getInit())
then
// Count the number of elements in the initializer, and find the matching constructors.
matchConstructorArguments(result,
count(getArgument(0).(ArrayCreationExpr).getInit().getAnInit()))
else
// Could be any of the constructors on this class.
any()
else
// No var-args in play, just use the number of arguments to the `newInstance(..)` to determine
// which constructors may be called.
matchConstructorArguments(result, getNumArgument())
}
/**
* Use the number of arguments to a `newInstance(..)` call to determine which constructor might be
* called.
*
* If the `Constructor` is for a non-static nested type, an extra argument is expected to be
* provided for the enclosing instance.
*/
private predicate matchConstructorArguments(Constructor c, int numArguments) {
if expectsEnclosingInstance(c.getDeclaringType())
then c.getNumberOfParameters() = numArguments - 1
else c.getNumberOfParameters() = numArguments
}
/**
* Gets an inferred type for the constructed class.
*
* To infer the constructed type we infer a type `T` for `Class<T>` or `Constructor<T>`, by inspecting
* points to results.
*/
RefType getInferredConstructedType() {
// Inferred type cannot be abstract.
not result.isAbstract() and
// `TypeVariable`s cannot be constructed themselves.
not result instanceof TypeVariable and
(
// If this is called on a `Class<T>` instance, return the inferred type `T`.
result = inferClassParameterType(getQualifier())
or
// If this is called on a `Constructor<T>` instance, return the inferred type `T`.
result = inferConstructorParameterType(getQualifier())
or
// If the result of this is cast to a particular type, then use that type.
result = getCastInferredConstructedTypes()
)
}
/**
* If the result of this `newInstance` call is casted, infer the types that we could have
* constructed based on the cast. If the cast is to `Object` or `Serializable`, then we ignore the
* cast.
*/
private Type getCastInferredConstructedTypes() {
exists(CastExpr cast | cast.getExpr() = this |
result = cast.getType()
or
// If we cast the result of this method, then this is either the type specified, or a
// sub-type of that type. Make sure we exclude overly generic types such as `Object`.
not overlyGenericType(cast.getType()) and
hasSubtype*(cast.getType(), result)
)
}
}
/**
* A `MethodAccess` on a `Class` element.
*/
class ClassMethodAccess extends MethodAccess {
ClassMethodAccess() { this.getCallee().getDeclaringType() instanceof TypeClass }
/**
* Gets an inferred type for the `Class` represented by this expression.
*/
RefType getInferredClassType() {
// `TypeVariable`s do not have methods themselves.
not result instanceof TypeVariable and
// If this is called on a `Class<T>` instance, return the inferred type `T`.
result = inferClassParameterType(getQualifier())
}
}
/**
* A call to `Class.getConstructors(..)` or `Class.getDeclaredConstructors(..)`.
*/
class ReflectiveConstructorsAccess extends ClassMethodAccess {
ReflectiveConstructorsAccess() {
this.getCallee().hasName("getConstructors") or
this.getCallee().hasName("getDeclaredConstructors")
}
}
/**
* A call to `Class.getMethods(..)` or `Class.getDeclaredMethods(..)`.
*/
class ReflectiveMethodsAccess extends ClassMethodAccess {
ReflectiveMethodsAccess() {
this.getCallee().hasName("getMethods") or
this.getCallee().hasName("getDeclaredMethods")
}
}
/**
* A call to `Class.getMethod(..)` or `Class.getDeclaredMethod(..)`.
*/
class ReflectiveMethodAccess extends ClassMethodAccess {
ReflectiveMethodAccess() {
this.getCallee().hasName("getMethod") or
this.getCallee().hasName("getDeclaredMethod")
}
/**
* Gets a `Method` that is inferred to be accessed by this reflective use of `getMethod(..)`.
*/
Method inferAccessedMethod() {
(
if this.getCallee().hasName("getDeclaredMethod")
then
// The method must be declared on the type itself.
result.getDeclaringType() = getInferredClassType()
else
// The method may be declared on an inferred type or a super-type.
getInferredClassType().inherits(result)
) and
// Only consider instances where the method name is provided as a `StringLiteral`.
result.hasName(getArgument(0).(StringLiteral).getRepresentedString())
}
}
/**
* A call to `Class.getAnnotation(..)`.
*/
class ReflectiveAnnotationAccess extends ClassMethodAccess {
ReflectiveAnnotationAccess() { this.getCallee().hasName("getAnnotation") }
/**
* Gets a possible annotation type for this reflective annotation access.
*/
AnnotationType getAPossibleAnnotationType() { result = inferClassParameterType(getArgument(0)) }
}
/**
* A call to `Class.getField(..)` that accesses a field.
*/
class ReflectiveFieldAccess extends ClassMethodAccess {
ReflectiveFieldAccess() {
this.getCallee().hasName("getField") or
this.getCallee().hasName("getDeclaredField")
}
/** Gets the field accessed by this call. */
Field inferAccessedField() {
(
if this.getCallee().hasName("getDeclaredField")
then
// Declared fields must be on the type itself.
result.getDeclaringType() = getInferredClassType()
else (
// This field must be public, and be inherited by one of the inferred class types.
result.isPublic() and
getInferredClassType().inherits(result)
)
) and
result.hasName(getArgument(0).(StringLiteral).getRepresentedString())
}
}

View File

@@ -0,0 +1,30 @@
/**
* Provides classes and predicates for working with Java Serialization.
*/
import java
private import frameworks.jackson.JacksonSerializability
private import frameworks.google.GoogleHttpClientApi
/**
* A serializable field may be read without code referencing it,
* due to the use of serialization.
*/
abstract class SerializableField extends Field { }
/**
* A deserializable field may be written without code referencing it,
* due to the use of serialization.
*/
abstract class DeserializableField extends Field { }
/**
* A non-`transient` field in a type that (directly or indirectly) implements the `Serializable` interface
* and may be read or written via serialization.
*/
library class StandardSerializableField extends SerializableField, DeserializableField {
StandardSerializableField() {
this.getDeclaringType().getASupertype*() instanceof TypeSerializable and
not this.isTransient()
}
}

View File

@@ -0,0 +1,882 @@
/**
* Provides classes and predicates for working with Java statements.
*/
import Expr
import metrics.MetricStmt
/** A common super-class of all statements. */
class Stmt extends StmtParent, ExprParent, @stmt {
/*abstract*/ override string toString() { result = "stmt" }
/** Gets a printable representation of this statement. May include more detail than `toString()`. */
string pp() { result = "stmt" }
/**
* Gets the immediately enclosing callable (method or constructor)
* whose body contains this statement.
*/
Callable getEnclosingCallable() { stmts(this, _, _, _, result) }
/** Gets the index of this statement as a child of its parent. */
int getIndex() { stmts(this, _, _, result, _) }
/** Gets the parent of this statement. */
StmtParent getParent() { stmts(this, _, result, _, _) }
/**
* Gets the statement containing this statement, if any.
*/
Stmt getEnclosingStmt() {
result = this.getParent() or
result = this.getParent().(SwitchExpr).getEnclosingStmt()
}
/** Holds if this statement is the child of the specified parent at the specified (zero-based) position. */
predicate isNthChildOf(StmtParent parent, int index) {
this.getParent() = parent and this.getIndex() = index
}
/** Gets the compilation unit in which this statement occurs. */
CompilationUnit getCompilationUnit() { result = this.getFile() }
/** Gets a child of this statement, if any. */
Stmt getAChild() { result.getParent() = this }
/** Gets the basic block in which this statement occurs. */
BasicBlock getBasicBlock() { result.getANode() = this }
/** Gets the `ControlFlowNode` corresponding to this statement. */
ControlFlowNode getControlFlowNode() { result = this }
/** Cast this statement to a class that provides access to metrics information. */
MetricStmt getMetrics() { result = this }
/** This statement's Halstead ID (used to compute Halstead metrics). */
string getHalsteadID() { result = "Stmt" }
}
/** A statement parent is any element that can have a statement as its child. */
class StmtParent extends @stmtparent, Top { }
/** A block of statements. */
class BlockStmt extends Stmt, @block {
/** Gets a statement that is an immediate child of this block. */
Stmt getAStmt() { result.getParent() = this }
/** Gets the immediate child statement of this block that occurs at the specified (zero-based) position. */
Stmt getStmt(int index) { result.getIndex() = index and result.getParent() = this }
/** Gets the number of immediate child statements in this block. */
int getNumStmt() { result = count(this.getAStmt()) }
/** Gets the last statement in this block. */
Stmt getLastStmt() { result = getStmt(getNumStmt() - 1) }
override string pp() { result = "{ ... }" }
override string toString() { result = "{ ... }" }
override string getHalsteadID() { result = "BlockStmt" }
override string getAPrimaryQlClass() { result = "BlockStmt" }
}
/**
* DEPRECATED: This is now called `BlockStmt` to avoid confusion with
* `BasicBlock`.
*/
deprecated class Block = BlockStmt;
/** A block with only a single statement. */
class SingletonBlock extends BlockStmt {
SingletonBlock() { this.getNumStmt() = 1 }
/** Gets the single statement in this block. */
Stmt getStmt() { result = getStmt(0) }
}
/**
* A conditional statement, including `if`, `for`,
* `while` and `dowhile` statements.
*/
abstract class ConditionalStmt extends Stmt {
/** Gets the boolean condition of this conditional statement. */
abstract Expr getCondition();
/**
* Gets the statement that is executed whenever the condition
* of this branch statement evaluates to `true`.
*
* DEPRECATED: use `ConditionNode.getATrueSuccessor()` instead.
*/
abstract deprecated Stmt getTrueSuccessor();
}
/** An `if` statement. */
class IfStmt extends ConditionalStmt, @ifstmt {
/** Gets the boolean condition of this `if` statement. */
override Expr getCondition() { result.isNthChildOf(this, 0) }
/** Gets the `then` branch of this `if` statement. */
Stmt getThen() { result.isNthChildOf(this, 1) }
/**
* Gets the statement that is executed whenever the condition
* of this branch statement evaluates to `true`.
*/
deprecated override Stmt getTrueSuccessor() { result = getThen() }
/** Gets the `else` branch of this `if` statement. */
Stmt getElse() { result.isNthChildOf(this, 2) }
override string pp() {
result = "if (...) " + this.getThen().pp() + " else " + this.getElse().pp()
or
not exists(this.getElse()) and result = "if (...) " + this.getThen().pp()
}
override string toString() { result = "if (...)" }
override string getHalsteadID() { result = "IfStmt" }
override string getAPrimaryQlClass() { result = "IfStmt" }
}
/** A `for` loop. */
class ForStmt extends ConditionalStmt, @forstmt {
/**
* Gets an initializer expression of the loop.
*
* This may be an assignment expression or a
* local variable declaration expression.
*/
Expr getAnInit() { exists(int index | result.isNthChildOf(this, index) | index <= -1) }
/** Gets the initializer expression of the loop at the specified (zero-based) position. */
Expr getInit(int index) {
result = getAnInit() and
index = -1 - result.getIndex()
}
/** Gets the boolean condition of this `for` loop. */
override Expr getCondition() { result.isNthChildOf(this, 1) }
/** Gets an update expression of this `for` loop. */
Expr getAnUpdate() { exists(int index | result.isNthChildOf(this, index) | index >= 3) }
/** Gets the update expression of this loop at the specified (zero-based) position. */
Expr getUpdate(int index) {
result = getAnUpdate() and
index = result.getIndex() - 3
}
/** Gets the body of this `for` loop. */
Stmt getStmt() { result.getParent() = this and result.getIndex() = 2 }
/**
* Gets the statement that is executed whenever the condition
* of this branch statement evaluates to true.
*/
deprecated override Stmt getTrueSuccessor() { result = getStmt() }
/**
* Gets a variable that is used as an iteration variable: it is defined,
* updated or tested in the head of the `for` statement.
*
* This only returns variables that are quite certainly loop variables;
* for complex iterations, it may not return anything.
*
* More precisely, it returns variables that are both accessed in the
* condition of this `for` statement and updated in the update expression
* of this for statement but may be initialized elsewhere.
*/
Variable getAnIterationVariable() {
// Check that the variable is assigned to, incremented or decremented in the update expression, and...
exists(Expr update | update = getAnUpdate().getAChildExpr*() |
update.(UnaryAssignExpr).getExpr() = result.getAnAccess() or
update = result.getAnAssignedValue()
) and
// ...that it is checked or used in the condition.
getCondition().getAChildExpr*() = result.getAnAccess()
}
override string pp() { result = "for (...;...;...) " + this.getStmt().pp() }
override string toString() { result = "for (...;...;...)" }
override string getHalsteadID() { result = "ForStmt" }
override string getAPrimaryQlClass() { result = "ForStmt" }
}
/** An enhanced `for` loop. (Introduced in Java 5.) */
class EnhancedForStmt extends Stmt, @enhancedforstmt {
/** Gets the local variable declaration expression of this enhanced `for` loop. */
LocalVariableDeclExpr getVariable() { result.getParent() = this }
/** Gets the expression over which this enhanced `for` loop iterates. */
Expr getExpr() { result.isNthChildOf(this, 1) }
/** Gets the body of this enhanced `for` loop. */
Stmt getStmt() { result.getParent() = this }
override string pp() { result = "for (... : ...) " + this.getStmt().pp() }
override string toString() { result = "for (... : ...)" }
override string getHalsteadID() { result = "EnhancedForStmt" }
override string getAPrimaryQlClass() { result = "EnhancedForStmt" }
}
/** A `while` loop. */
class WhileStmt extends ConditionalStmt, @whilestmt {
/** Gets the boolean condition of this `while` loop. */
override Expr getCondition() { result.getParent() = this }
/** Gets the body of this `while` loop. */
Stmt getStmt() { result.getParent() = this }
/**
* Gets the statement that is executed whenever the condition
* of this branch statement evaluates to true.
*/
deprecated override Stmt getTrueSuccessor() { result = getStmt() }
override string pp() { result = "while (...) " + this.getStmt().pp() }
override string toString() { result = "while (...)" }
override string getHalsteadID() { result = "WhileStmt" }
override string getAPrimaryQlClass() { result = "WhileStmt" }
}
/** A `do` loop. */
class DoStmt extends ConditionalStmt, @dostmt {
/** Gets the condition of this `do` loop. */
override Expr getCondition() { result.getParent() = this }
/** Gets the body of this `do` loop. */
Stmt getStmt() { result.getParent() = this }
/**
* Gets the statement that is executed whenever the condition
* of this branch statement evaluates to `true`.
*/
deprecated override Stmt getTrueSuccessor() { result = getStmt() }
override string pp() { result = "do " + this.getStmt().pp() + " while (...)" }
override string toString() { result = "do ... while (...)" }
override string getHalsteadID() { result = "DoStmt" }
override string getAPrimaryQlClass() { result = "DoStmt" }
}
/**
* A loop statement, including `for`, enhanced `for`,
* `while` and `do` statements.
*/
class LoopStmt extends Stmt {
LoopStmt() {
this instanceof ForStmt or
this instanceof EnhancedForStmt or
this instanceof WhileStmt or
this instanceof DoStmt
}
/** Gets the body of this loop statement. */
Stmt getBody() {
result = this.(ForStmt).getStmt() or
result = this.(EnhancedForStmt).getStmt() or
result = this.(WhileStmt).getStmt() or
result = this.(DoStmt).getStmt()
}
/** Gets the boolean condition of this loop statement. */
Expr getCondition() {
result = this.(ForStmt).getCondition() or
result = this.(WhileStmt).getCondition() or
result = this.(DoStmt).getCondition()
}
}
/** A `try` statement. */
class TryStmt extends Stmt, @trystmt {
/** Gets the block of the `try` statement. */
Stmt getBlock() { result.isNthChildOf(this, -1) }
/** Gets a `catch` clause of this `try` statement. */
CatchClause getACatchClause() { result.getParent() = this }
/**
* Gets the `catch` clause at the specified (zero-based) position
* in this `try` statement.
*/
CatchClause getCatchClause(int index) {
result = this.getACatchClause() and
result.getIndex() = index
}
/** Gets the `finally` block, if any. */
BlockStmt getFinally() { result.isNthChildOf(this, -2) }
/** Gets a resource variable declaration, if any. */
LocalVariableDeclStmt getAResourceDecl() { result.getParent() = this and result.getIndex() <= -3 }
/** Gets the resource variable declaration at the specified position in this `try` statement. */
LocalVariableDeclStmt getResourceDecl(int index) {
result = this.getAResourceDecl() and
index = -3 - result.getIndex()
}
/** Gets a resource expression, if any. */
VarAccess getAResourceExpr() { result.getParent() = this and result.getIndex() <= -3 }
/** Gets the resource expression at the specified position in this `try` statement. */
VarAccess getResourceExpr(int index) {
result = this.getAResourceExpr() and
index = -3 - result.getIndex()
}
/** Gets a resource in this `try` statement, if any. */
ExprParent getAResource() { result = getAResourceDecl() or result = getAResourceExpr() }
/** Gets the resource at the specified position in this `try` statement. */
ExprParent getResource(int index) {
result = getResourceDecl(index) or result = getResourceExpr(index)
}
/** Gets a resource variable, if any, either from a resource variable declaration or resource expression. */
Variable getAResourceVariable() {
result = getAResourceDecl().getAVariable().getVariable() or
result = getAResourceExpr().getVariable()
}
override string pp() { result = "try " + this.getBlock().pp() + " catch (...)" }
override string toString() { result = "try ..." }
override string getHalsteadID() { result = "TryStmt" }
override string getAPrimaryQlClass() { result = "TryStmt" }
}
/** A `catch` clause in a `try` statement. */
class CatchClause extends Stmt, @catchclause {
/** Gets the block of this `catch` clause. */
BlockStmt getBlock() { result.getParent() = this }
/** Gets the `try` statement in which this `catch` clause occurs. */
TryStmt getTry() { this = result.getACatchClause() }
/** Gets the parameter of this `catch` clause. */
LocalVariableDeclExpr getVariable() { result.getParent() = this }
/** Holds if this `catch` clause is a _multi_-`catch` clause. */
predicate isMultiCatch() { this.getVariable().getTypeAccess() instanceof UnionTypeAccess }
/** Gets a type caught by this `catch` clause. */
RefType getACaughtType() {
exists(Expr ta | ta = getVariable().getTypeAccess() |
result = ta.(TypeAccess).getType() or
result = ta.(UnionTypeAccess).getAnAlternative().getType()
)
}
override string pp() { result = "catch (...) " + this.getBlock().pp() }
override string toString() { result = "catch (...)" }
override string getHalsteadID() { result = "CatchClause" }
override string getAPrimaryQlClass() { result = "CatchClause" }
}
/** A `switch` statement. */
class SwitchStmt extends Stmt, @switchstmt {
/** Gets an immediate child statement of this `switch` statement. */
Stmt getAStmt() { result.getParent() = this }
/**
* Gets the immediate child statement of this `switch` statement
* that occurs at the specified (zero-based) position.
*/
Stmt getStmt(int index) { result = this.getAStmt() and result.getIndex() = index }
/**
* Gets a case of this `switch` statement,
* which may be either a normal `case` or a `default`.
*/
SwitchCase getACase() { result = getAConstCase() or result = getDefaultCase() }
/** Gets a (non-default) `case` of this `switch` statement. */
ConstCase getAConstCase() { result.getParent() = this }
/** Gets the `default` case of this switch statement, if any. */
DefaultCase getDefaultCase() { result.getParent() = this }
/** Gets the expression of this `switch` statement. */
Expr getExpr() { result.getParent() = this }
override string pp() { result = "switch (...)" }
override string toString() { result = "switch (...)" }
override string getHalsteadID() { result = "SwitchStmt" }
override string getAPrimaryQlClass() { result = "SwitchStmt" }
}
/**
* A case of a `switch` statement or expression.
*
* This includes both normal `case`s and the `default` case.
*/
class SwitchCase extends Stmt, @case {
/** Gets the switch statement to which this case belongs, if any. */
SwitchStmt getSwitch() { result.getACase() = this }
/**
* Gets the switch expression to which this case belongs, if any.
*/
SwitchExpr getSwitchExpr() { result.getACase() = this }
/**
* Gets the expression of the surrounding switch that this case is compared
* against.
*/
Expr getSelectorExpr() {
result = this.getSwitch().getExpr() or result = this.getSwitchExpr().getExpr()
}
/**
* Holds if this `case` is a switch labeled rule of the form `... -> ...`.
*/
predicate isRule() {
exists(Expr e | e.getParent() = this | e.getIndex() = -1)
or
exists(Stmt s | s.getParent() = this | s.getIndex() = -1)
}
/**
* Gets the expression on the right-hand side of the arrow, if any.
*/
Expr getRuleExpression() { result.getParent() = this and result.getIndex() = -1 }
/**
* Gets the statement on the right-hand side of the arrow, if any.
*/
Stmt getRuleStatement() { result.getParent() = this and result.getIndex() = -1 }
}
/** A constant `case` of a switch statement. */
class ConstCase extends SwitchCase {
ConstCase() { exists(Expr e | e.getParent() = this | e.getIndex() >= 0) }
/** Gets the `case` constant at index 0. */
Expr getValue() { result.getParent() = this and result.getIndex() = 0 }
/**
* Gets the `case` constant at the specified index.
*/
Expr getValue(int i) { result.getParent() = this and result.getIndex() = i and i >= 0 }
override string pp() { result = "case ..." }
override string toString() { result = "case ..." }
override string getHalsteadID() { result = "ConstCase" }
override string getAPrimaryQlClass() { result = "ConstCase" }
}
/** A `default` case of a `switch` statement */
class DefaultCase extends SwitchCase {
DefaultCase() { not exists(Expr e | e.getParent() = this | e.getIndex() >= 0) }
override string pp() { result = "default" }
override string toString() { result = "default" }
override string getHalsteadID() { result = "DefaultCase" }
override string getAPrimaryQlClass() { result = "DefaultCase" }
}
/** A `synchronized` statement. */
class SynchronizedStmt extends Stmt, @synchronizedstmt {
/** Gets the expression on which this `synchronized` statement synchronizes. */
Expr getExpr() { result.getParent() = this }
/** Gets the block of this `synchronized` statement. */
Stmt getBlock() { result.getParent() = this }
override string pp() { result = "synchronized (...) " + this.getBlock().pp() }
override string toString() { result = "synchronized (...)" }
override string getHalsteadID() { result = "SynchronizedStmt" }
override string getAPrimaryQlClass() { result = "SynchronizedStmt" }
}
/** A `return` statement. */
class ReturnStmt extends Stmt, @returnstmt {
/** Gets the expression returned by this `return` statement, if any. */
Expr getResult() { result.getParent() = this }
override string pp() { result = "return ..." }
override string toString() { result = "return ..." }
override string getHalsteadID() { result = "ReturnStmt" }
override string getAPrimaryQlClass() { result = "ReturnStmt" }
}
/** A `throw` statement. */
class ThrowStmt extends Stmt, @throwstmt {
/** Gets the expression thrown by this `throw` statement. */
Expr getExpr() { result.getParent() = this }
override string pp() { result = "throw ..." }
override string toString() { result = "throw ..." }
override string getHalsteadID() { result = "ThrowStmt" }
/** Gets the type of the expression thrown by this `throw` statement. */
RefType getThrownExceptionType() { result = getExpr().getType() }
/**
* Gets the `catch` clause that catches the exception
* thrown by this `throw` statement and occurs
* in the same method as this `throw` statement,
* provided such a `catch` exists.
*/
CatchClause getLexicalCatchIfAny() {
exists(TryStmt try | try = findEnclosing() and result = catchClauseForThis(try))
}
private Stmt findEnclosing() {
result = getEnclosingStmt()
or
exists(Stmt mid |
mid = findEnclosing() and
not exists(this.catchClauseForThis(mid.(TryStmt))) and
result = mid.getEnclosingStmt()
)
}
private CatchClause catchClauseForThis(TryStmt try) {
result = try.getACatchClause() and
result.getEnclosingCallable() = this.getEnclosingCallable() and
getExpr().getType().(RefType).hasSupertype*(result.getVariable().getType().(RefType)) and
not this.getEnclosingStmt+() = result
}
override string getAPrimaryQlClass() { result = "ThrowStmt" }
}
/** A `break`, `yield` or `continue` statement. */
class JumpStmt extends Stmt {
JumpStmt() {
this instanceof BreakStmt or
this instanceof YieldStmt or
this instanceof ContinueStmt
}
/**
* Gets the labeled statement that this `break` or
* `continue` statement refers to, if any.
*/
LabeledStmt getTargetLabel() {
this.getEnclosingStmt+() = result and
namestrings(result.getLabel(), _, this)
}
private Stmt getLabelTarget() { result = getTargetLabel().getStmt() }
private Stmt getAPotentialTarget() {
this.getEnclosingStmt+() = result and
(
result instanceof LoopStmt
or
this instanceof BreakStmt and result instanceof SwitchStmt
)
}
private SwitchExpr getSwitchExprTarget() { result = this.(YieldStmt).getParent+() }
private StmtParent getEnclosingTarget() {
result = getSwitchExprTarget()
or
not exists(getSwitchExprTarget()) and
result = getAPotentialTarget() and
not exists(Stmt other | other = getAPotentialTarget() | other.getEnclosingStmt+() = result)
}
/**
* Gets the statement or `switch` expression that this `break`, `yield` or `continue` jumps to.
*/
StmtParent getTarget() {
result = getLabelTarget()
or
not exists(getLabelTarget()) and result = getEnclosingTarget()
}
}
/** A `break` statement. */
class BreakStmt extends Stmt, @breakstmt {
/** Gets the label targeted by this `break` statement, if any. */
string getLabel() { namestrings(result, _, this) }
/** Holds if this `break` statement has an explicit label. */
predicate hasLabel() { exists(string s | s = this.getLabel()) }
override string pp() {
if this.hasLabel() then result = "break " + this.getLabel() else result = "break"
}
override string toString() { result = "break" }
override string getHalsteadID() { result = "BreakStmt" }
override string getAPrimaryQlClass() { result = "BreakStmt" }
}
/**
* A `yield` statement.
*/
class YieldStmt extends Stmt, @yieldstmt {
/**
* Gets the value of this `yield` statement.
*/
Expr getValue() { result.getParent() = this }
override string pp() { result = "yield ..." }
override string toString() { result = "yield ..." }
override string getHalsteadID() { result = "YieldStmt" }
override string getAPrimaryQlClass() { result = "YieldStmt" }
}
/** A `continue` statement. */
class ContinueStmt extends Stmt, @continuestmt {
/** Gets the label targeted by this `continue` statement, if any. */
string getLabel() { namestrings(result, _, this) }
/** Holds if this `continue` statement has an explicit label. */
predicate hasLabel() { exists(string s | s = this.getLabel()) }
override string pp() {
if this.hasLabel() then result = "continue " + this.getLabel() else result = "continue"
}
override string toString() { result = "continue" }
override string getHalsteadID() { result = "ContinueStmt" }
override string getAPrimaryQlClass() { result = "ContinueStmt" }
}
/** The empty statement. */
class EmptyStmt extends Stmt, @emptystmt {
override string pp() { result = ";" }
override string toString() { result = ";" }
override string getHalsteadID() { result = "EmptyStmt" }
override string getAPrimaryQlClass() { result = "EmptyStmt" }
}
/**
* An expression statement.
*
* Certain kinds of expressions may be used as statements by appending a semicolon.
*/
class ExprStmt extends Stmt, @exprstmt {
/** Gets the expression of this expression statement. */
Expr getExpr() { result.getParent() = this }
override string pp() { result = "<Expr>;" }
override string toString() { result = "<Expr>;" }
override string getHalsteadID() { result = "ExprStmt" }
/** Holds if this statement represents a field declaration with an initializer. */
predicate isFieldDecl() {
getEnclosingCallable() instanceof InitializerMethod and
exists(FieldDeclaration fd, Location fdl, Location sl |
fdl = fd.getLocation() and sl = getLocation()
|
fdl.getFile() = sl.getFile() and
fdl.getStartLine() = sl.getStartLine() and
fdl.getStartColumn() = sl.getStartColumn()
)
}
override string getAPrimaryQlClass() { result = "ExprStmt" }
}
/** A labeled statement. */
class LabeledStmt extends Stmt, @labeledstmt {
/** Gets the statement of this labeled statement. */
Stmt getStmt() { result.getParent() = this }
/** Gets the label of this labeled statement. */
string getLabel() { namestrings(result, _, this) }
override string pp() { result = this.getLabel() + ": " + this.getStmt().pp() }
override string getHalsteadID() { result = this.getLabel() + ":" }
override string toString() { result = "<Label>: ..." }
override string getAPrimaryQlClass() { result = "LabeledStmt" }
}
/** An `assert` statement. */
class AssertStmt extends Stmt, @assertstmt {
/** Gets the boolean expression of this `assert` statement. */
Expr getExpr() { exprs(result, _, _, this, _) and result.getIndex() = 0 }
/** Gets the assertion message expression, if any. */
Expr getMessage() { exprs(result, _, _, this, _) and result.getIndex() = 1 }
override string pp() {
if exists(this.getMessage()) then result = "assert ... : ..." else result = "assert ..."
}
override string toString() { result = "assert ..." }
override string getHalsteadID() { result = "AssertStmt" }
override string getAPrimaryQlClass() { result = "AssertStmt" }
}
/** A statement that declares one or more local variables. */
class LocalVariableDeclStmt extends Stmt, @localvariabledeclstmt {
/** Gets a declared variable. */
LocalVariableDeclExpr getAVariable() { result.getParent() = this }
/** Gets the variable declared at the specified (one-based) position in this local variable declaration statement. */
LocalVariableDeclExpr getVariable(int index) {
result = this.getAVariable() and
result.getIndex() = index
}
/** Gets an index of a variable declared in this local variable declaration statement. */
int getAVariableIndex() { exists(getVariable(result)) }
override string pp() { result = "var ...;" }
override string toString() { result = "var ...;" }
override string getHalsteadID() { result = "LocalVariableDeclStmt" }
override string getAPrimaryQlClass() { result = "LocalVariableDeclStmt" }
}
/** A statement that declares a local class. */
class LocalClassDeclStmt extends Stmt, @localclassdeclstmt {
/** Gets the local class declared by this statement. */
LocalClass getLocalClass() { isLocalClass(result, this) }
override string pp() { result = "class " + this.getLocalClass().toString() }
override string toString() { result = "class ..." }
override string getHalsteadID() { result = "LocalClassDeclStmt" }
override string getAPrimaryQlClass() { result = "LocalClassDeclStmt" }
}
/** An explicit `this(...)` constructor invocation. */
class ThisConstructorInvocationStmt extends Stmt, ConstructorCall, @constructorinvocationstmt {
/** Gets an argument of this constructor invocation. */
override Expr getAnArgument() { result.getIndex() >= 0 and result.getParent() = this }
/** Gets the argument at the specified (zero-based) position in this constructor invocation. */
override Expr getArgument(int index) {
result = this.getAnArgument() and
result.getIndex() = index
}
/** Gets a type argument of this constructor invocation. */
Expr getATypeArgument() { result.getIndex() <= -2 and result.getParent() = this }
/** Gets the type argument at the specified (zero-based) position in this constructor invocation. */
Expr getTypeArgument(int index) {
result = this.getATypeArgument() and
(-2 - result.getIndex()) = index
}
/** Gets the constructor invoked by this constructor invocation. */
override Constructor getConstructor() { callableBinding(this, result) }
override Expr getQualifier() { none() }
/** Gets the immediately enclosing callable of this constructor invocation. */
override Callable getEnclosingCallable() { result = Stmt.super.getEnclosingCallable() }
/** Gets the immediately enclosing statement of this constructor invocation. */
override Stmt getEnclosingStmt() { result = this }
override string pp() { result = "this(...)" }
override string toString() { result = "this(...)" }
override string getHalsteadID() { result = "ConstructorInvocationStmt" }
override string getAPrimaryQlClass() { result = "ThisConstructorInvocationStmt" }
}
/** An explicit `super(...)` constructor invocation. */
class SuperConstructorInvocationStmt extends Stmt, ConstructorCall, @superconstructorinvocationstmt {
/** Gets an argument of this constructor invocation. */
override Expr getAnArgument() { result.getIndex() >= 0 and result.getParent() = this }
/** Gets the argument at the specified (zero-based) position in this constructor invocation. */
override Expr getArgument(int index) {
result = this.getAnArgument() and
result.getIndex() = index
}
/** Gets a type argument of this constructor invocation. */
Expr getATypeArgument() { result.getIndex() <= -2 and result.getParent() = this }
/** Gets the type argument at the specified (zero-based) position in this constructor invocation. */
Expr getTypeArgument(int index) {
result = this.getATypeArgument() and
(-2 - result.getIndex()) = index
}
/** Gets the constructor invoked by this constructor invocation. */
override Constructor getConstructor() { callableBinding(this, result) }
/** Gets the qualifier expression of this `super(...)` constructor invocation, if any. */
override Expr getQualifier() { result.isNthChildOf(this, -1) }
/** Gets the immediately enclosing callable of this constructor invocation. */
override Callable getEnclosingCallable() { result = Stmt.super.getEnclosingCallable() }
/** Gets the immediately enclosing statement of this constructor invocation. */
override Stmt getEnclosingStmt() { result = this }
override string pp() { result = "super(...)" }
override string toString() { result = "super(...)" }
override string getHalsteadID() { result = "SuperConstructorInvocationStmt" }
override string getAPrimaryQlClass() { result = "SuperConstructorInvocationStmt" }
}

View File

@@ -0,0 +1,488 @@
/**
* Provides classes and predicates for reasoning about string formatting.
*/
import java
import dataflow.DefUse
/**
* A library method that formats a number of its arguments according to a
* format string.
*/
abstract private class FormatMethod extends Method {
/** Gets the index of the format string argument. */
abstract int getFormatStringIndex();
}
/**
* A library method that acts like `String.format` by formatting a number of
* its arguments according to a format string.
*/
class StringFormatMethod extends FormatMethod {
StringFormatMethod() {
(
this.hasName("format") or
this.hasName("formatted") or
this.hasName("printf") or
this.hasName("readLine") or
this.hasName("readPassword")
) and
(
this.getDeclaringType().hasQualifiedName("java.lang", "String") or
this.getDeclaringType().hasQualifiedName("java.io", "PrintStream") or
this.getDeclaringType().hasQualifiedName("java.io", "PrintWriter") or
this.getDeclaringType().hasQualifiedName("java.io", "Console") or
this.getDeclaringType().hasQualifiedName("java.util", "Formatter")
)
}
override int getFormatStringIndex() {
result = 0 and this.getSignature() = "format(java.lang.String,java.lang.Object[])"
or
result = -1 and this.getSignature() = "formatted(java.lang.Object[])"
or
result = 0 and this.getSignature() = "printf(java.lang.String,java.lang.Object[])"
or
result = 1 and
this.getSignature() = "format(java.util.Locale,java.lang.String,java.lang.Object[])"
or
result = 1 and
this.getSignature() = "printf(java.util.Locale,java.lang.String,java.lang.Object[])"
or
result = 0 and this.getSignature() = "readLine(java.lang.String,java.lang.Object[])"
or
result = 0 and this.getSignature() = "readPassword(java.lang.String,java.lang.Object[])"
}
}
/**
* A format method using the `org.slf4j.Logger` format string syntax. That is,
* the placeholder string is `"{}"`.
*/
class LoggerFormatMethod extends FormatMethod {
LoggerFormatMethod() {
(
this.hasName("debug") or
this.hasName("error") or
this.hasName("info") or
this.hasName("trace") or
this.hasName("warn")
) and
this.getDeclaringType().getASourceSupertype*().hasQualifiedName("org.slf4j", "Logger")
}
override int getFormatStringIndex() {
(result = 0 or result = 1) and
this.getParameterType(result) instanceof TypeString
}
}
private newtype TFmtSyntax =
TFmtPrintf() or
TFmtLogger()
/** A syntax for format strings. */
class FmtSyntax extends TFmtSyntax {
/** Gets a textual representation of this format string syntax. */
string toString() {
result = "printf (%) syntax" and this = TFmtPrintf()
or
result = "logger ({}) syntax" and this = TFmtLogger()
}
/** Holds if this syntax is logger ({}) syntax. */
predicate isLogger() { this = TFmtLogger() }
}
private Expr getArgumentOrQualifier(Call c, int i) {
result = c.getArgument(i)
or
result = c.getQualifier() and i = -1
}
/**
* Holds if `c` wraps a call to a `StringFormatMethod`, such that `fmtix` is
* the index of the format string argument to `c` and the following and final
* argument is the `Object[]` that holds the arguments to be formatted.
*/
private predicate formatWrapper(Callable c, int fmtix, FmtSyntax syntax) {
exists(Parameter fmt, Parameter args, Call fmtcall, int i |
fmt = c.getParameter(fmtix) and
fmt.getType() instanceof TypeString and
args = c.getParameter(fmtix + 1) and
args.getType().(Array).getElementType() instanceof TypeObject and
c.getNumberOfParameters() = fmtix + 2 and
fmtcall.getEnclosingCallable() = c and
(
formatWrapper(fmtcall.getCallee(), i, syntax)
or
fmtcall.getCallee().(StringFormatMethod).getFormatStringIndex() = i and syntax = TFmtPrintf()
or
fmtcall.getCallee().(LoggerFormatMethod).getFormatStringIndex() = i and syntax = TFmtLogger()
) and
getArgumentOrQualifier(fmtcall, i) = fmt.getAnAccess() and
fmtcall.getArgument(i + 1) = args.getAnAccess()
)
}
/**
* A call to a `StringFormatMethod` or a callable wrapping a `StringFormatMethod`.
*/
class FormattingCall extends Call {
FormattingCall() {
this.getCallee() instanceof FormatMethod or
formatWrapper(this.getCallee(), _, _)
}
/** Gets the index of the format string argument. */
private int getFormatStringIndex() {
this.getCallee().(FormatMethod).getFormatStringIndex() = result or
formatWrapper(this.getCallee(), result, _)
}
/** Gets the format string syntax used by this call. */
FmtSyntax getSyntax() {
this.getCallee() instanceof StringFormatMethod and result = TFmtPrintf()
or
this.getCallee() instanceof LoggerFormatMethod and result = TFmtLogger()
or
formatWrapper(this.getCallee(), _, result)
}
private Expr getLastArg() {
exists(Expr last | last = this.getArgument(this.getNumArgument() - 1) |
if this.hasExplicitVarargsArray()
then result = last.(ArrayCreationExpr).getInit().getInit(getVarargsCount() - 1)
else result = last
)
}
/** Holds if this uses the "logger ({})" format syntax and the last argument is a `Throwable`. */
predicate hasTrailingThrowableArgument() {
getSyntax() = TFmtLogger() and
getLastArg().getType().(RefType).getASourceSupertype*() instanceof TypeThrowable
}
/** Gets the argument to this call in the position of the format string */
Expr getFormatArgument() { result = getArgumentOrQualifier(this, this.getFormatStringIndex()) }
/** Gets an argument to be formatted. */
Expr getAnArgumentToBeFormatted() {
exists(int i |
result = this.getArgument(i) and
i > this.getFormatStringIndex() and
not hasExplicitVarargsArray()
)
}
/** Gets the `i`th argument to be formatted. The index `i` is one-based. */
Expr getArgumentToBeFormatted(int i) {
i >= 1 and
if this.hasExplicitVarargsArray()
then
result =
this.getArgument(1 + this.getFormatStringIndex())
.(ArrayCreationExpr)
.getInit()
.getInit(i - 1)
else result = this.getArgument(this.getFormatStringIndex() + i)
}
/** Holds if the varargs argument is given as an explicit array. */
private predicate hasExplicitVarargsArray() {
this.getNumArgument() = this.getFormatStringIndex() + 2 and
this.getArgument(1 + this.getFormatStringIndex()).getType() instanceof Array
}
/** Gets the length of the varargs array if it can determined. */
int getVarargsCount() {
if this.hasExplicitVarargsArray()
then
exists(Expr arg | arg = this.getArgument(1 + this.getFormatStringIndex()) |
result = arg.(ArrayCreationExpr).getFirstDimensionSize() or
result =
arg.(VarAccess)
.getVariable()
.getAnAssignedValue()
.(ArrayCreationExpr)
.getFirstDimensionSize()
)
else result = this.getNumArgument() - this.getFormatStringIndex() - 1
}
/** Gets a `FormatString` that is used by this call. */
FormatString getAFormatString() { result.getAFormattingUse() = this }
}
/** Holds if `m` calls `toString()` on its `i`th argument. */
private predicate printMethod(Method m, int i) {
exists(RefType t |
t = m.getDeclaringType() and
m.getParameterType(i) instanceof TypeObject
|
(t.hasQualifiedName("java.io", "PrintWriter") or t.hasQualifiedName("java.io", "PrintStream")) and
(m.hasName("print") or m.hasName("println"))
or
t instanceof StringBuildingType and
(m.hasName("append") or m.hasName("insert"))
or
t instanceof TypeString and m.hasName("valueOf")
)
}
/**
* Holds if `e` occurs in a position where it may be converted to a string by
* an implicit call to `toString()`.
*/
predicate implicitToStringCall(Expr e) {
not e.getType() instanceof TypeString and
(
exists(FormattingCall fmtcall | fmtcall.getAnArgumentToBeFormatted() = e)
or
exists(AddExpr add | add.getType() instanceof TypeString and add.getAnOperand() = e)
or
exists(MethodAccess ma, Method m, int i |
ma.getMethod() = m and
ma.getArgument(i) = e and
printMethod(m, i)
)
)
}
/**
* A call to a `format` or `printf` method.
*/
class StringFormat extends MethodAccess, FormattingCall {
StringFormat() { this.getCallee() instanceof StringFormatMethod }
}
/**
* Holds if `fmt` is used as part of a format string.
*/
private predicate formatStringFragment(Expr fmt) {
any(FormattingCall call).getFormatArgument() = fmt
or
exists(Expr e | formatStringFragment(e) |
e.(VarAccess).getVariable().getAnAssignedValue() = fmt or
e.(AddExpr).getLeftOperand() = fmt or
e.(AddExpr).getRightOperand() = fmt or
e.(ChooseExpr).getAResultExpr() = fmt
)
}
/**
* Holds if `e` is a part of a format string with the approximate value
* `fmtvalue`. The value is approximated by ignoring details that are
* irrelevant for determining the number of format specifiers in the resulting
* string.
*/
private predicate formatStringValue(Expr e, string fmtvalue) {
formatStringFragment(e) and
(
e.(StringLiteral).getRepresentedString() = fmtvalue
or
e.getType() instanceof IntegralType and fmtvalue = "1" // dummy value
or
e.getType() instanceof BooleanType and fmtvalue = "x" // dummy value
or
e.getType() instanceof EnumType and fmtvalue = "x" // dummy value
or
exists(Variable v |
e = v.getAnAccess() and
v.isFinal() and
v.getType() instanceof TypeString and
formatStringValue(v.getInitializer(), fmtvalue)
)
or
exists(LocalVariableDecl v |
e = v.getAnAccess() and
not exists(AssignAddExpr aa | aa.getDest() = v.getAnAccess()) and
1 = count(v.getAnAssignedValue()) and
v.getType() instanceof TypeString and
formatStringValue(v.getAnAssignedValue(), fmtvalue)
)
or
exists(AddExpr add, string left, string right |
add = e and
add.getType() instanceof TypeString and
formatStringValue(add.getLeftOperand(), left) and
formatStringValue(add.getRightOperand(), right) and
fmtvalue = left + right
)
or
formatStringValue(e.(ChooseExpr).getAResultExpr(), fmtvalue)
or
exists(Method getprop, MethodAccess ma, string prop |
e = ma and
ma.getMethod() = getprop and
getprop.hasName("getProperty") and
getprop.getDeclaringType().hasQualifiedName("java.lang", "System") and
getprop.getNumberOfParameters() = 1 and
ma.getAnArgument().(StringLiteral).getRepresentedString() = prop and
(prop = "line.separator" or prop = "file.separator" or prop = "path.separator") and
fmtvalue = "x" // dummy value
)
or
exists(Field f |
e = f.getAnAccess() and
f.getDeclaringType().hasQualifiedName("java.io", "File") and
fmtvalue = "x" // dummy value
|
f.hasName("pathSeparator") or
f.hasName("pathSeparatorChar") or
f.hasName("separator") or
f.hasName("separatorChar")
)
)
}
/**
* A string that is used as the format string in a `FormattingCall`.
*/
class FormatString extends string {
FormatString() { formatStringValue(_, this) }
/** Gets a `FormattingCall` that uses this as its format string. */
FormattingCall getAFormattingUse() {
exists(Expr fmt | formatStringValue(fmt, this) |
result.getFormatArgument() = fmt
or
exists(VariableAssign va |
defUsePair(va, result.getFormatArgument()) and va.getSource() = fmt
)
or
result.getFormatArgument().(FieldAccess).getField().getAnAssignedValue() = fmt
)
}
/**
* Gets the largest argument index (1-indexed) that is referred by a format
* specifier. Gets the value 0 if there are no format specifiers.
*/
/*abstract*/ int getMaxFmtSpecIndex() { none() }
/**
* Gets an argument index (1-indexed) less than `getMaxFmtSpecIndex()` that
* is not referred by any format specifier.
*/
/*abstract*/ int getASkippedFmtSpecIndex() { none() }
/**
* Gets an offset (zero-based) in this format string where argument `argNo` (1-based) will be interpolated, if any.
*/
int getAnArgUsageOffset(int argNo) { none() }
}
private class PrintfFormatString extends FormatString {
PrintfFormatString() { this.getAFormattingUse().getSyntax() = TFmtPrintf() }
/**
* Gets a boolean value that indicates whether the `%` character at index `i`
* is an escaped percentage sign or a format specifier.
*/
private boolean isEscapedPct(int i) {
this.charAt(i) = "%" and
if this.charAt(i - 1) = "%"
then result = this.isEscapedPct(i - 1).booleanNot()
else result = false
}
/** Holds if the format specifier at index `i` is a reference to an argument. */
private predicate fmtSpecIsRef(int i) {
false = this.isEscapedPct(i) and
this.charAt(i) = "%" and
exists(string c |
c = this.charAt(i + 1) and
c != "%" and
c != "n"
)
}
/**
* Holds if the format specifier at index `i` refers to the same argument as
* the preceding format specifier.
*/
private predicate fmtSpecRefersToPrevious(int i) {
this.fmtSpecIsRef(i) and
"<" = this.charAt(i + 1)
}
/**
* Gets the index of the specific argument (1-indexed) that the format
* specifier at index `i` refers to, if any.
*/
private int fmtSpecRefersToSpecificIndex(int i) {
this.fmtSpecIsRef(i) and
exists(string num | result = num.toInt() |
num = this.charAt(i + 1) and "$" = this.charAt(i + 2)
or
num = this.charAt(i + 1) + this.charAt(i + 2) and "$" = this.charAt(i + 3)
)
}
/**
* Holds if the format specifier at index `i` refers to the next argument in
* sequential order.
*/
private predicate fmtSpecRefersToSequentialIndex(int i) {
this.fmtSpecIsRef(i) and
not exists(this.fmtSpecRefersToSpecificIndex(i)) and
not this.fmtSpecRefersToPrevious(i)
}
override int getMaxFmtSpecIndex() {
result =
max(int ix |
ix = fmtSpecRefersToSpecificIndex(_) or
ix = count(int i | fmtSpecRefersToSequentialIndex(i))
)
}
override int getASkippedFmtSpecIndex() {
result in [1 .. getMaxFmtSpecIndex()] and
result > count(int i | fmtSpecRefersToSequentialIndex(i)) and
not result = fmtSpecRefersToSpecificIndex(_)
}
private int getFmtSpecRank(int specOffset) {
rank[result](int i | this.fmtSpecIsRef(i)) = specOffset
}
override int getAnArgUsageOffset(int argNo) {
argNo = fmtSpecRefersToSpecificIndex(result)
or
result = rank[argNo](int i | fmtSpecRefersToSequentialIndex(i))
or
fmtSpecRefersToPrevious(result) and
exists(int previousOffset |
getFmtSpecRank(previousOffset) = getFmtSpecRank(result) - 1 and
previousOffset = getAnArgUsageOffset(argNo)
)
}
}
private class LoggerFormatString extends FormatString {
LoggerFormatString() { this.getAFormattingUse().getSyntax() = TFmtLogger() }
/**
* Gets a boolean value that indicates whether the `\` character at index `i`
* is an unescaped backslash.
*/
private boolean isUnescapedBackslash(int i) {
this.charAt(i) = "\\" and
if this.charAt(i - 1) = "\\"
then result = this.isUnescapedBackslash(i - 1).booleanNot()
else result = true
}
/** Holds if an unescaped placeholder `{}` occurs at index `i`. */
private predicate fmtPlaceholder(int i) {
this.charAt(i) = "{" and
this.charAt(i + 1) = "}" and
not true = isUnescapedBackslash(i - 1)
}
override int getMaxFmtSpecIndex() { result = count(int i | fmtPlaceholder(i)) }
override int getAnArgUsageOffset(int argNo) { result = rank[argNo](int i | fmtPlaceholder(i)) }
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,322 @@
/**
* Provides classes and predicates for working with test classes and methods.
*/
import Type
import Member
import semmle.code.java.frameworks.JUnitAnnotations
/** The Java class `junit.framework.TestCase`. */
class TypeJUnitTestCase extends RefType {
TypeJUnitTestCase() { this.hasQualifiedName("junit.framework", "TestCase") }
}
/** The Java interface `junit.framework.Test`. */
class TypeJUnitTest extends RefType {
TypeJUnitTest() { this.hasQualifiedName("junit.framework", "Test") }
}
/** The Java class `junit.framework.TestSuite`. */
class TypeJUnitTestSuite extends RefType {
TypeJUnitTestSuite() { this.hasQualifiedName("junit.framework", "TestSuite") }
}
/** A JUnit 3.8 test class. */
class JUnit38TestClass extends Class {
JUnit38TestClass() { exists(TypeJUnitTestCase tc | this.hasSupertype+(tc)) }
}
/** A JUnit 3.8 `tearDown` method. */
class TearDownMethod extends Method {
TearDownMethod() {
this.hasName("tearDown") and
this.hasNoParameters() and
this.getReturnType().hasName("void") and
exists(Method m | m.getDeclaringType() instanceof TypeJUnitTestCase | this.overrides*(m))
}
}
private class TestRelatedAnnotation extends Annotation {
TestRelatedAnnotation() {
this.getType().getPackage().hasName("org.testng.annotations") or
this.getType().getPackage().hasName("org.junit") or
this.getType().getPackage().hasName("org.junit.runner") or
this.getType().getPackage().hasName("org.junit.jupiter.api") or
this.getType().getPackage().hasName("org.junit.jupiter.params")
}
}
private class TestRelatedMethod extends Method {
TestRelatedMethod() { this.getAnAnnotation() instanceof TestRelatedAnnotation }
}
/**
* A class detected to be a test class, either because it or one of its super-types
* and/or enclosing types contains a test method or method with a unit-test-related
* annotation.
*/
class TestClass extends Class {
TestClass() {
this instanceof JUnit38TestClass or
exists(TestMethod m | m.getDeclaringType() = this) or
exists(TestRelatedMethod m | m.getDeclaringType() = this) or
this.getASourceSupertype() instanceof TestClass or
this.getEnclosingType() instanceof TestClass
}
}
/**
* A test method declared within a JUnit 3.8 test class.
*/
class JUnit3TestMethod extends Method {
JUnit3TestMethod() {
this.isPublic() and
this.getDeclaringType() instanceof JUnit38TestClass and
this.getName().matches("test%") and
this.getReturnType().hasName("void") and
this.hasNoParameters()
}
}
/**
* A JUnit 3.8 test suite method.
*/
class JUnit3TestSuite extends Method {
JUnit3TestSuite() {
this.isPublic() and
this.isStatic() and
(
this.getDeclaringType() instanceof JUnit38TestClass or
this.getDeclaringType().getAnAncestor() instanceof TypeJUnitTestSuite
) and
this.hasName("suite") and
this.getReturnType() instanceof TypeJUnitTest and
this.hasNoParameters()
}
}
/**
* A JUnit test method that is annotated with the `org.junit.Test` annotation.
*/
class JUnit4TestMethod extends Method {
JUnit4TestMethod() { this.getAnAnnotation().getType().hasQualifiedName("org.junit", "Test") }
}
/**
* A JUnit test method that is annotated with the `org.junit.jupiter.api.Test` annotation.
*/
class JUnitJupiterTestMethod extends Method {
JUnitJupiterTestMethod() {
this.getAnAnnotation().getType().hasQualifiedName("org.junit.jupiter.api", "Test")
}
}
/**
* A JUnit `@Ignore` annotation.
*/
class JUnitIgnoreAnnotation extends Annotation {
JUnitIgnoreAnnotation() { getType().hasQualifiedName("org.junit", "Ignore") }
}
/**
* A method which, directly or indirectly, is treated as ignored by JUnit due to a `@Ignore`
* annotation.
*/
class JUnitIgnoredMethod extends Method {
JUnitIgnoredMethod() {
getAnAnnotation() instanceof JUnitIgnoreAnnotation
or
exists(Class c | c = this.getDeclaringType() |
c.getAnAnnotation() instanceof JUnitIgnoreAnnotation
)
}
}
/**
* An annotation in TestNG.
*/
class TestNGAnnotation extends Annotation {
TestNGAnnotation() { getType().getPackage().hasName("org.testng.annotations") }
}
/**
* An annotation of type `org.test.ng.annotations.Test`.
*/
class TestNGTestAnnotation extends TestNGAnnotation {
TestNGTestAnnotation() { getType().hasName("Test") }
}
/**
* A TestNG test method, annotated with the `org.testng.annotations.Test` annotation.
*/
class TestNGTestMethod extends Method {
TestNGTestMethod() { this.getAnAnnotation() instanceof TestNGTestAnnotation }
/**
* Identify a possible `DataProvider` for this method, if the annotation includes a `dataProvider`
* value.
*/
TestNGDataProviderMethod getADataProvider() {
exists(TestNGTestAnnotation testAnnotation |
testAnnotation = getAnAnnotation() and
// The data provider must have the same name as the referenced data provider
result.getDataProviderName() =
testAnnotation.getValue("dataProvider").(StringLiteral).getRepresentedString()
|
// Either the data provider should be on the current class, or a supertype
getDeclaringType().getAnAncestor() = result.getDeclaringType()
or
// Or the data provider class should be declared
result.getDeclaringType() =
testAnnotation.getValue("dataProviderClass").(TypeLiteral).getReferencedType()
)
}
}
/**
* Any method detected to be a test method of a common testing framework,
* including JUnit and TestNG.
*/
class TestMethod extends Method {
TestMethod() {
this instanceof JUnit3TestMethod or
this instanceof JUnit4TestMethod or
this instanceof JUnitJupiterTestMethod or
this instanceof TestNGTestMethod
}
}
/**
* A TestNG annotation used to mark a method that runs "before".
*/
class TestNGBeforeAnnotation extends TestNGAnnotation {
TestNGBeforeAnnotation() { getType().getName().matches("Before%") }
}
/**
* A TestNG annotation used to mark a method that runs "after".
*/
class TestNGAfterAnnotation extends TestNGAnnotation {
TestNGAfterAnnotation() { getType().getName().matches("After%") }
}
/**
* An annotation of type `org.testng.annotations.DataProvider` which is applied to methods to mark
* them as data provider methods for TestNG.
*/
class TestNGDataProviderAnnotation extends TestNGAnnotation {
TestNGDataProviderAnnotation() { getType().hasName("DataProvider") }
}
/**
* An annotation of type `org.testng.annotations.Factory` which is applied to methods to mark
* them as factory methods for TestNG.
*/
class TestNGFactoryAnnotation extends TestNGAnnotation {
TestNGFactoryAnnotation() { getType().hasName("Factory") }
}
/**
* An annotation of type `org.testng.annotations.Listeners` which is applied to classes to define
* which listeners apply to them.
*/
class TestNGListenersAnnotation extends TestNGAnnotation {
TestNGListenersAnnotation() { getType().hasName("Listeners") }
/**
* Gets a listener defined in this annotation.
*/
TestNGListenerImpl getAListener() {
result = getAValue("value").(TypeLiteral).getReferencedType()
}
}
/**
* A concrete implementation class of one or more of the TestNG listener interfaces.
*/
class TestNGListenerImpl extends Class {
TestNGListenerImpl() { getAnAncestor().hasQualifiedName("org.testng", "ITestNGListener") }
}
/**
* A method annotated with `org.testng.annotations.DataProvider` marking it as a data provider method
* for TestNG.
*
* This data provider method can be referenced by "name", and used by the test framework to provide
* an instance of a particular value when running a test method.
*/
class TestNGDataProviderMethod extends Method {
TestNGDataProviderMethod() { getAnAnnotation() instanceof TestNGDataProviderAnnotation }
/**
* Gets the name associated with this data provider.
*/
string getDataProviderName() {
result =
getAnAnnotation()
.(TestNGDataProviderAnnotation)
.getValue("name")
.(StringLiteral)
.getRepresentedString()
}
}
/**
* A constructor or method annotated with `org.testng.annotations.Factory` marking it as a factory
* for TestNG.
*
* This factory callable is used to generate instances of parameterized test classes.
*/
class TestNGFactoryCallable extends Callable {
TestNGFactoryCallable() { getAnAnnotation() instanceof TestNGFactoryAnnotation }
}
/**
* A class that will be run using the `org.junit.runners.Parameterized` JUnit runner.
*/
class ParameterizedJUnitTest extends Class {
ParameterizedJUnitTest() {
getAnAnnotation()
.(RunWithAnnotation)
.getRunner()
.(Class)
.hasQualifiedName("org.junit.runners", "Parameterized")
}
}
/**
* A `@Category` annotation on a class or method, that categorizes the annotated test.
*/
class JUnitCategoryAnnotation extends Annotation {
JUnitCategoryAnnotation() {
getType().hasQualifiedName("org.junit.experimental.categories", "Category")
}
/**
* One of the categories that this test is categorized as.
*/
Type getACategory() {
exists(TypeLiteral literal, Expr value |
value = getValue("value") and
(
literal = value or
literal = value.(ArrayCreationExpr).getInit().getAnInit()
)
|
result = literal.getReferencedType()
)
}
}
/**
* A test class that will be run with theories.
*/
class JUnitTheoryTest extends Class {
JUnitTheoryTest() {
getAnAnnotation()
.(RunWithAnnotation)
.getRunner()
.(Class)
.hasQualifiedName("org.junit.experimental.theories", "Theories")
}
}

View File

@@ -0,0 +1,103 @@
/**
* Provides classes and predicates for working with Java variables and their declarations.
*/
import Element
/** A variable is a field, a local variable or a parameter. */
class Variable extends @variable, Annotatable, Element, Modifiable {
/** Gets the type of this variable. */
/*abstract*/ Type getType() { none() }
/** Gets an access to this variable. */
VarAccess getAnAccess() { variableBinding(result, this) }
/** Gets an expression on the right-hand side of an assignment to this variable. */
Expr getAnAssignedValue() {
exists(LocalVariableDeclExpr e | e.getVariable() = this and result = e.getInit())
or
exists(AssignExpr e | e.getDest() = this.getAnAccess() and result = e.getSource())
}
/** Gets the initializer expression of this variable. */
Expr getInitializer() { none() }
/** Gets a printable representation of this variable together with its type. */
string pp() { result = this.getType().getName() + " " + this.getName() }
}
/** A locally scoped variable, that is, either a local variable or a parameter. */
class LocalScopeVariable extends Variable, @localscopevariable {
/** Gets the callable in which this variable is declared. */
abstract Callable getCallable();
}
/** A local variable declaration */
class LocalVariableDecl extends @localvar, LocalScopeVariable {
/** Gets the type of this local variable. */
override Type getType() { localvars(this, _, result, _) }
/** Gets the expression declaring this variable. */
LocalVariableDeclExpr getDeclExpr() { localvars(this, _, _, result) }
/** Gets the parent of this declaration. */
Expr getParent() { localvars(this, _, _, result) }
/** Gets the callable in which this declaration occurs. */
override Callable getCallable() { result = this.getParent().getEnclosingCallable() }
/** Gets the callable in which this declaration occurs. */
Callable getEnclosingCallable() { result = getCallable() }
override string toString() { result = this.getType().getName() + " " + this.getName() }
/** Gets the initializer expression of this local variable declaration. */
override Expr getInitializer() { result = getDeclExpr().getInit() }
override string getAPrimaryQlClass() { result = "LocalVariableDecl" }
}
/** A formal parameter of a callable. */
class Parameter extends Element, @param, LocalScopeVariable {
/** Gets the type of this formal parameter. */
override Type getType() { params(this, result, _, _, _) }
/** Holds if the parameter is never assigned a value in the body of the callable. */
predicate isEffectivelyFinal() { not exists(getAnAssignedValue()) }
/** Gets the (zero-based) index of this formal parameter. */
int getPosition() { params(this, _, result, _, _) }
/** Gets the callable that declares this formal parameter. */
override Callable getCallable() { params(this, _, _, result, _) }
/** Gets the source declaration of this formal parameter. */
Parameter getSourceDeclaration() { params(this, _, _, _, result) }
/** Holds if this formal parameter is the same as its source declaration. */
predicate isSourceDeclaration() { this.getSourceDeclaration() = this }
/** Holds if this formal parameter is a variable arity parameter. */
predicate isVarargs() { isVarargsParam(this) }
/**
* Gets an argument for this parameter in any call to the callable that declares this formal
* parameter.
*
* Varargs parameters will have no results for this method.
*/
Expr getAnArgument() {
not isVarargs() and
result = getACallArgument(getPosition())
}
pragma[noinline]
private Expr getACallArgument(int i) {
exists(Call call |
result = call.getArgument(i) and
call.getCallee().getSourceDeclaration().getAParameter() = this
)
}
override string getAPrimaryQlClass() { result = "Parameter" }
}

View File

@@ -0,0 +1,119 @@
import java
/** A subclass of `PrimitiveType` with width-based ordering methods. */
class OrdPrimitiveType extends PrimitiveType {
predicate widerThan(OrdPrimitiveType that) { getWidthRank() > that.getWidthRank() }
predicate widerThanOrEqualTo(OrdPrimitiveType that) { getWidthRank() >= that.getWidthRank() }
OrdPrimitiveType maxType(OrdPrimitiveType that) {
this.widerThan(that) and result = this
or
not this.widerThan(that) and result = that
}
int getWidthRank() {
this.getName() = "byte" and result = 1
or
this.getName() = "short" and result = 2
or
this.getName() = "int" and result = 3
or
this.getName() = "long" and result = 4
or
this.getName() = "float" and result = 5
or
this.getName() = "double" and result = 6
}
float getMaxValue() {
this.getName() = "byte" and result = 127.0
or
this.getName() = "short" and result = 32767.0
or
this.getName() = "int" and result = 2147483647.0
or
// Long.MAX_VALUE is 9223372036854775807 but floating point only has 53 bits of precision.
this.getName() = "long" and result = 9223372036854776000.0
// don't try for floats and doubles
}
float getMinValue() {
this.getName() = "byte" and result = -128.0
or
this.getName() = "short" and result = -32768.0
or
this.getName() = "int" and result = -2147483648.0
or
// Long.MIN_VALUE is -9223372036854775808 but floating point only has 53 bits of precision.
this.getName() = "long" and result = -9223372036854776000.0
// don't try for floats and doubles
}
}
class NumType extends Type {
NumType() {
this instanceof PrimitiveType or
this instanceof BoxedType
}
/** Gets the width-ordered primitive type corresponding to this type. */
OrdPrimitiveType getOrdPrimitiveType() {
this instanceof PrimitiveType and result = this
or
this instanceof BoxedType and result = this.(BoxedType).getPrimitiveType()
}
predicate widerThan(NumType that) {
this.getOrdPrimitiveType().widerThan(that.getOrdPrimitiveType())
}
predicate widerThanOrEqualTo(NumType that) {
this.getOrdPrimitiveType().widerThanOrEqualTo(that.getOrdPrimitiveType())
}
int getWidthRank() { result = this.getOrdPrimitiveType().getWidthRank() }
}
class ArithExpr extends Expr {
ArithExpr() {
(
this instanceof UnaryAssignExpr or
this instanceof AddExpr or
this instanceof MulExpr or
this instanceof SubExpr or
this instanceof DivExpr
) and
forall(Expr e | e = this.(BinaryExpr).getAnOperand() or e = this.(UnaryAssignExpr).getExpr() |
e.getType() instanceof NumType
)
}
OrdPrimitiveType getOrdPrimitiveType() {
exists(OrdPrimitiveType t1, OrdPrimitiveType t2 |
t1 = this.getLeftOperand().getType().(NumType).getOrdPrimitiveType() and
t2 = this.getRightOperand().getType().(NumType).getOrdPrimitiveType() and
result = t1.maxType(t2)
)
}
/**
* Gets the left-hand operand of a binary expression
* or the operand of a unary assignment expression.
*/
Expr getLeftOperand() {
result = this.(BinaryExpr).getLeftOperand() or
result = this.(UnaryAssignExpr).getExpr()
}
/**
* Gets the right-hand operand if this is a binary expression.
*/
Expr getRightOperand() { result = this.(BinaryExpr).getRightOperand() }
/** Gets an operand of this arithmetic expression. */
Expr getAnOperand() {
result = this.(BinaryExpr).getAnOperand() or
result = this.(UnaryAssignExpr).getExpr()
}
}

View File

@@ -0,0 +1,27 @@
import java
/**
* If `e1` evaluates to `b1` then the direct subexpression `e2` evaluates to `b2`.
*
* Used as basis for the transitive closure in `exprImplies`.
*/
private predicate exprImpliesStep(Expr e1, boolean b1, Expr e2, boolean b2) {
e1.(LogNotExpr).getExpr() = e2 and
b2 = b1.booleanNot() and
(b1 = true or b1 = false)
or
b1 = true and e1.(AndLogicalExpr).getAnOperand() = e2 and b2 = true
or
b1 = false and e1.(OrLogicalExpr).getAnOperand() = e2 and b2 = false
}
/** If `e1` evaluates to `b1` then the subexpression `e2` evaluates to `b2`. */
predicate exprImplies(Expr e1, boolean b1, Expr e2, boolean b2) {
e1 = e2 and
b1 = b2 and
(b1 = true or b1 = false)
or
exists(Expr emid, boolean bmid |
exprImplies(e1, b1, emid, bmid) and exprImpliesStep(emid, bmid, e2, b2)
)
}

View File

@@ -0,0 +1,69 @@
/**
* Provides classes and predicates for working with basic blocks in Java.
*/
import java
import Dominance
import semmle.code.java.ControlFlowGraph
/**
* A control-flow node that represents the start of a basic block.
*
* A basic block is a series of nodes with no control-flow branching, which can
* often be treated as a unit in analyses.
*/
class BasicBlock extends ControlFlowNode {
BasicBlock() {
not exists(this.getAPredecessor()) and exists(this.getASuccessor())
or
strictcount(this.getAPredecessor()) > 1
or
exists(ControlFlowNode pred | pred = this.getAPredecessor() |
strictcount(pred.getASuccessor()) > 1
)
}
/** Gets an immediate successor of this basic block. */
cached
BasicBlock getABBSuccessor() { result = getLastNode().getASuccessor() }
/** Gets an immediate predecessor of this basic block. */
BasicBlock getABBPredecessor() { result.getABBSuccessor() = this }
/** Gets a control-flow node contained in this basic block. */
ControlFlowNode getANode() { result = getNode(_) }
/** Gets the control-flow node at a specific (zero-indexed) position in this basic block. */
cached
ControlFlowNode getNode(int pos) {
result = this and pos = 0
or
exists(ControlFlowNode mid, int mid_pos | pos = mid_pos + 1 |
getNode(mid_pos) = mid and
mid.getASuccessor() = result and
not result instanceof BasicBlock
)
}
/** Gets the first control-flow node in this basic block. */
ControlFlowNode getFirstNode() { result = this }
/** Gets the last control-flow node in this basic block. */
ControlFlowNode getLastNode() { result = getNode(length() - 1) }
/** Gets the number of control-flow nodes contained in this basic block. */
cached
int length() { result = strictcount(getANode()) }
/** Holds if this basic block strictly dominates `node`. */
predicate bbStrictlyDominates(BasicBlock node) { bbStrictlyDominates(this, node) }
/** Holds if this basic block dominates `node`. (This is reflexive.) */
predicate bbDominates(BasicBlock node) { bbDominates(this, node) }
/** Holds if this basic block strictly post-dominates `node`. */
predicate bbStrictlyPostDominates(BasicBlock node) { bbStrictlyPostDominates(this, node) }
/** Holds if this basic block post-dominates `node`. (This is reflexive.) */
predicate bbPostDominates(BasicBlock node) { bbPostDominates(this, node) }
}

View File

@@ -0,0 +1,147 @@
/**
* Provides classes and predicates for control-flow graph dominance.
*/
import java
private import semmle.code.java.ControlFlowGraph
/*
* Predicates for basic-block-level dominance.
*/
/** Entry points for control-flow. */
private predicate flowEntry(Stmt entry) {
exists(Callable c | entry = c.getBody())
or
// This disjunct is technically superfluous, but safeguards against extractor problems.
entry instanceof BlockStmt and
not exists(entry.getEnclosingCallable()) and
not entry.getParent() instanceof Stmt
}
/** The successor relation for basic blocks. */
private predicate bbSucc(BasicBlock pre, BasicBlock post) { post = pre.getABBSuccessor() }
/** The immediate dominance relation for basic blocks. */
cached
predicate bbIDominates(BasicBlock dom, BasicBlock node) =
idominance(flowEntry/1, bbSucc/2)(_, dom, node)
/** Holds if the dominance relation is calculated for `bb`. */
predicate hasDominanceInformation(BasicBlock bb) {
exists(BasicBlock entry | flowEntry(entry) and bbSucc*(entry, bb))
}
/** Exit points for control-flow. */
private predicate flowExit(Callable exit) { exists(ControlFlowNode s | s.getASuccessor() = exit) }
/** Exit points for basic-block control-flow. */
private predicate bbSink(BasicBlock exit) { flowExit(exit.getLastNode()) }
/** Reversed `bbSucc`. */
private predicate bbPred(BasicBlock post, BasicBlock pre) { post = pre.getABBSuccessor() }
/** The immediate post-dominance relation on basic blocks. */
cached
predicate bbIPostDominates(BasicBlock dominator, BasicBlock node) =
idominance(bbSink/1, bbPred/2)(_, dominator, node)
/** Holds if `dom` strictly dominates `node`. */
predicate bbStrictlyDominates(BasicBlock dom, BasicBlock node) { bbIDominates+(dom, node) }
/** Holds if `dom` dominates `node`. (This is reflexive.) */
predicate bbDominates(BasicBlock dom, BasicBlock node) {
bbStrictlyDominates(dom, node) or dom = node
}
/** Holds if `dom` strictly post-dominates `node`. */
predicate bbStrictlyPostDominates(BasicBlock dom, BasicBlock node) { bbIPostDominates+(dom, node) }
/** Holds if `dom` post-dominates `node`. (This is reflexive.) */
predicate bbPostDominates(BasicBlock dom, BasicBlock node) {
bbStrictlyPostDominates(dom, node) or dom = node
}
/**
* The dominance frontier relation for basic blocks.
*
* This is equivalent to:
*
* ```
* bbDominates(x, w.getABBPredecessor()) and not bbStrictlyDominates(x, w)
* ```
*/
predicate dominanceFrontier(BasicBlock x, BasicBlock w) {
x = w.getABBPredecessor() and not bbIDominates(x, w)
or
exists(BasicBlock prev | dominanceFrontier(prev, w) |
bbIDominates(x, prev) and
not bbIDominates(x, w)
)
}
/**
* Holds if `(bb1, bb2)` is an edge that dominates `bb2`, that is, all other
* predecessors of `bb2` are dominated by `bb2`. This implies that `bb1` is the
* immediate dominator of `bb2`.
*
* This is a necessary and sufficient condition for an edge to dominate anything,
* and in particular `dominatingEdge(bb1, bb2) and bb2.bbDominates(bb3)` means
* that the edge `(bb1, bb2)` dominates `bb3`.
*/
predicate dominatingEdge(BasicBlock bb1, BasicBlock bb2) {
bbIDominates(bb1, bb2) and
bb1.getABBSuccessor() = bb2 and
forall(BasicBlock pred | pred = bb2.getABBPredecessor() and pred != bb1 | bbDominates(bb2, pred))
}
/*
* Predicates for expression-level dominance.
*/
/** Immediate dominance relation on control-flow graph nodes. */
predicate iDominates(ControlFlowNode dominator, ControlFlowNode node) {
exists(BasicBlock bb, int i | dominator = bb.getNode(i) and node = bb.getNode(i + 1))
or
exists(BasicBlock dom, BasicBlock bb |
bbIDominates(dom, bb) and
dominator = dom.getLastNode() and
node = bb.getFirstNode()
)
}
/** Holds if `dom` strictly dominates `node`. */
pragma[inline]
predicate strictlyDominates(ControlFlowNode dom, ControlFlowNode node) {
// This predicate is gigantic, so it must be inlined.
bbStrictlyDominates(dom.getBasicBlock(), node.getBasicBlock())
or
exists(BasicBlock b, int i, int j | dom = b.getNode(i) and node = b.getNode(j) and i < j)
}
/** Holds if `dom` dominates `node`. (This is reflexive.) */
pragma[inline]
predicate dominates(ControlFlowNode dom, ControlFlowNode node) {
// This predicate is gigantic, so it must be inlined.
bbStrictlyDominates(dom.getBasicBlock(), node.getBasicBlock())
or
exists(BasicBlock b, int i, int j | dom = b.getNode(i) and node = b.getNode(j) and i <= j)
}
/** Holds if `dom` strictly post-dominates `node`. */
pragma[inline]
predicate strictlyPostDominates(ControlFlowNode dom, ControlFlowNode node) {
// This predicate is gigantic, so it must be inlined.
bbStrictlyPostDominates(dom.getBasicBlock(), node.getBasicBlock())
or
exists(BasicBlock b, int i, int j | dom = b.getNode(i) and node = b.getNode(j) and i > j)
}
/** Holds if `dom` post-dominates `node`. (This is reflexive.) */
pragma[inline]
predicate postDominates(ControlFlowNode dom, ControlFlowNode node) {
// This predicate is gigantic, so it must be inlined.
bbStrictlyPostDominates(dom.getBasicBlock(), node.getBasicBlock())
or
exists(BasicBlock b, int i, int j | dom = b.getNode(i) and node = b.getNode(j) and i >= j)
}

View File

@@ -0,0 +1,277 @@
/**
* Provides classes and predicates for reasoning about guards and the control
* flow elements controlled by those guards.
*/
import java
private import semmle.code.java.controlflow.Dominance
private import semmle.code.java.controlflow.internal.GuardsLogic
private import semmle.code.java.controlflow.internal.Preconditions
/**
* A basic block that terminates in a condition, splitting the subsequent control flow.
*/
class ConditionBlock extends BasicBlock {
ConditionBlock() { this.getLastNode() instanceof ConditionNode }
/** Gets the last node of this basic block. */
ConditionNode getConditionNode() { result = this.getLastNode() }
/** Gets the condition of the last node of this basic block. */
Expr getCondition() { result = this.getConditionNode().getCondition() }
/** Gets a `true`- or `false`-successor of the last node of this basic block. */
BasicBlock getTestSuccessor(boolean testIsTrue) {
result = this.getConditionNode().getABranchSuccessor(testIsTrue)
}
/*
* For this block to control the block `controlled` with `testIsTrue` the following must be true:
* Execution must have passed through the test i.e. `this` must strictly dominate `controlled`.
* Execution must have passed through the `testIsTrue` edge leaving `this`.
*
* Although "passed through the true edge" implies that `this.getATrueSuccessor()` dominates `controlled`,
* the reverse is not true, as flow may have passed through another edge to get to `this.getATrueSuccessor()`
* so we need to assert that `this.getATrueSuccessor()` dominates `controlled` *and* that
* all predecessors of `this.getATrueSuccessor()` are either `this` or dominated by `this.getATrueSuccessor()`.
*
* For example, in the following java snippet:
* ```
* if (x)
* controlled;
* false_successor;
* uncontrolled;
* ```
* `false_successor` dominates `uncontrolled`, but not all of its predecessors are `this` (`if (x)`)
* or dominated by itself. Whereas in the following code:
* ```
* if (x)
* while (controlled)
* also_controlled;
* false_successor;
* uncontrolled;
* ```
* the block `while controlled` is controlled because all of its predecessors are `this` (`if (x)`)
* or (in the case of `also_controlled`) dominated by itself.
*
* The additional constraint on the predecessors of the test successor implies
* that `this` strictly dominates `controlled` so that isn't necessary to check
* directly.
*/
/**
* Holds if `controlled` is a basic block controlled by this condition, that
* is, a basic blocks for which the condition is `testIsTrue`.
*/
predicate controls(BasicBlock controlled, boolean testIsTrue) {
exists(BasicBlock succ |
succ = this.getTestSuccessor(testIsTrue) and
dominatingEdge(this, succ) and
succ.bbDominates(controlled)
)
}
}
/**
* A condition that can be evaluated to either true or false. This can either
* be an `Expr` of boolean type that isn't a boolean literal, or a case of a
* switch statement, or a method access that acts as a precondition check.
*
* Evaluating a switch case to true corresponds to taking that switch case, and
* evaluating it to false corresponds to taking some other branch.
*/
class Guard extends ExprParent {
Guard() {
this.(Expr).getType() instanceof BooleanType and not this instanceof BooleanLiteral
or
this instanceof SwitchCase
or
conditionCheck(this, _)
}
/** Gets the immediately enclosing callable whose body contains this guard. */
Callable getEnclosingCallable() {
result = this.(Expr).getEnclosingCallable() or
result = this.(SwitchCase).getEnclosingCallable()
}
/** Gets the statement containing this guard. */
Stmt getEnclosingStmt() {
result = this.(Expr).getEnclosingStmt() or
result = this.(SwitchCase).getSwitch() or
result = this.(SwitchCase).getSwitchExpr().getEnclosingStmt()
}
/**
* Gets the basic block containing this guard or the basic block containing
* the switch expression if the guard is a switch case.
*/
BasicBlock getBasicBlock() {
result = this.(Expr).getBasicBlock() or
result = this.(SwitchCase).getSwitch().getExpr().getBasicBlock() or
result = this.(SwitchCase).getSwitchExpr().getExpr().getBasicBlock()
}
/**
* Holds if this guard is an equality test between `e1` and `e2`. The test
* can be either `==`, `!=`, `.equals`, or a switch case. If the test is
* negated, that is `!=`, then `polarity` is false, otherwise `polarity` is
* true.
*/
predicate isEquality(Expr e1, Expr e2, boolean polarity) {
exists(Expr exp1, Expr exp2 | equalityGuard(this, exp1, exp2, polarity) |
e1 = exp1 and e2 = exp2
or
e2 = exp1 and e1 = exp2
)
}
/**
* Holds if the evaluation of this guard to `branch` corresponds to the edge
* from `bb1` to `bb2`.
*/
predicate hasBranchEdge(BasicBlock bb1, BasicBlock bb2, boolean branch) {
exists(ConditionBlock cb |
cb = bb1 and
cb.getCondition() = this and
bb2 = cb.getTestSuccessor(branch)
)
or
exists(SwitchCase sc, ControlFlowNode pred |
sc = this and
branch = true and
bb2.getFirstNode() = sc.getControlFlowNode() and
pred = sc.getControlFlowNode().getAPredecessor() and
pred.(Expr).getParent*() = sc.getSelectorExpr() and
bb1 = pred.getBasicBlock()
)
or
preconditionBranchEdge(this, bb1, bb2, branch)
}
/**
* Holds if this guard evaluating to `branch` directly controls the block
* `controlled`. That is, the `true`- or `false`-successor of this guard (as
* given by `branch`) dominates `controlled`.
*/
predicate directlyControls(BasicBlock controlled, boolean branch) {
exists(ConditionBlock cb |
cb.getCondition() = this and
cb.controls(controlled, branch)
)
or
switchCaseControls(this, controlled) and branch = true
or
preconditionControls(this, controlled, branch)
}
/**
* Holds if this guard evaluating to `branch` directly or indirectly controls
* the block `controlled`. That is, the evaluation of `controlled` is
* dominated by this guard evaluating to `branch`.
*/
predicate controls(BasicBlock controlled, boolean branch) {
guardControls_v3(this, controlled, branch)
}
}
private predicate switchCaseControls(SwitchCase sc, BasicBlock bb) {
exists(BasicBlock caseblock, Expr selector |
selector = sc.getSelectorExpr() and
caseblock.getFirstNode() = sc.getControlFlowNode() and
caseblock.bbDominates(bb) and
forall(ControlFlowNode pred | pred = sc.getControlFlowNode().getAPredecessor() |
pred.(Expr).getParent*() = selector
)
)
}
private predicate preconditionBranchEdge(
MethodAccess ma, BasicBlock bb1, BasicBlock bb2, boolean branch
) {
conditionCheck(ma, branch) and
bb1.getLastNode() = ma.getControlFlowNode() and
bb2 = bb1.getLastNode().getANormalSuccessor()
}
private predicate preconditionControls(MethodAccess ma, BasicBlock controlled, boolean branch) {
exists(BasicBlock check, BasicBlock succ |
preconditionBranchEdge(ma, check, succ, branch) and
dominatingEdge(check, succ) and
succ.bbDominates(controlled)
)
}
/**
* INTERNAL: Use `Guards.controls` instead.
*
* Holds if `guard.controls(controlled, branch)`, except this only relies on
* BaseSSA-based reasoning.
*/
predicate guardControls_v1(Guard guard, BasicBlock controlled, boolean branch) {
guard.directlyControls(controlled, branch)
or
exists(Guard g, boolean b |
guardControls_v1(g, controlled, b) and
implies_v1(g, b, guard, branch)
)
}
/**
* INTERNAL: Use `Guards.controls` instead.
*
* Holds if `guard.controls(controlled, branch)`, except this doesn't rely on
* RangeAnalysis.
*/
predicate guardControls_v2(Guard guard, BasicBlock controlled, boolean branch) {
guard.directlyControls(controlled, branch)
or
exists(Guard g, boolean b |
guardControls_v2(g, controlled, b) and
implies_v2(g, b, guard, branch)
)
}
private predicate guardControls_v3(Guard guard, BasicBlock controlled, boolean branch) {
guard.directlyControls(controlled, branch)
or
exists(Guard g, boolean b |
guardControls_v3(g, controlled, b) and
implies_v3(g, b, guard, branch)
)
}
private predicate equalityGuard(Guard g, Expr e1, Expr e2, boolean polarity) {
exists(EqualityTest eqtest |
eqtest = g and
polarity = eqtest.polarity() and
eqtest.hasOperands(e1, e2)
)
or
exists(MethodAccess ma |
ma = g and
ma.getMethod() instanceof EqualsMethod and
polarity = true and
ma.getAnArgument() = e1 and
ma.getQualifier() = e2
)
or
exists(MethodAccess ma, Method equals |
ma = g and
ma.getMethod() = equals and
polarity = true and
equals.hasName("equals") and
equals.getNumberOfParameters() = 2 and
equals.getDeclaringType().hasQualifiedName("java.util", "Objects") and
ma.getArgument(0) = e1 and
ma.getArgument(1) = e2
)
or
exists(ConstCase cc |
cc = g and
polarity = true and
cc.getSelectorExpr() = e1 and
cc.getValue() = e2 and
strictcount(cc.getValue(_)) = 1
)
}

View File

@@ -0,0 +1,87 @@
/**
* This library provides predicates for reasoning about the set of all paths
* through a callable.
*/
import java
import semmle.code.java.dispatch.VirtualDispatch
/**
* A configuration to define an "action". The member predicates
* `callableAlwaysPerformsAction` and `callAlwaysPerformsAction` then gives all
* the callables and calls that always performs an action taking
* inter-procedural flow into account.
*/
abstract class ActionConfiguration extends string {
bindingset[this]
ActionConfiguration() { any() }
/** Holds if `node` is an action. */
abstract predicate isAction(ControlFlowNode node);
/** Holds if every path through `callable` goes through at least one action node. */
final predicate callableAlwaysPerformsAction(Callable callable) {
callableAlwaysPerformsAction(callable, this)
}
/** Holds if every path through `call` goes through at least one action node. */
final predicate callAlwaysPerformsAction(Call call) { callAlwaysPerformsAction(call, this) }
}
/** Gets a `BasicBlock` that contains an action. */
private BasicBlock actionBlock(ActionConfiguration conf) {
exists(ControlFlowNode node | result = node.getBasicBlock() |
conf.isAction(node) or
callAlwaysPerformsAction(node, conf)
)
}
/** Holds if every path through `call` goes through at least one action node. */
private predicate callAlwaysPerformsAction(Call call, ActionConfiguration conf) {
forex(Callable callable | callable = viableCallable(call) |
callableAlwaysPerformsAction(callable, conf)
)
}
/** Holds if an action dominates the exit of the callable. */
private predicate actionDominatesExit(Callable callable, ActionConfiguration conf) {
exists(BasicBlock exit |
exit.getLastNode() = callable and
actionBlock(conf).bbDominates(exit)
)
}
/** Gets a `BasicBlock` that contains an action that does not dominate the exit. */
private BasicBlock nonDominatingActionBlock(ActionConfiguration conf) {
exists(BasicBlock exit |
result = actionBlock(conf) and
exit.getLastNode() = result.getEnclosingCallable() and
not result.bbDominates(exit)
)
}
private class JoinBlock extends BasicBlock {
JoinBlock() { 2 <= strictcount(this.getABBPredecessor()) }
}
/**
* Holds if `bb` is a block that is collectively dominated by a set of one or
* more actions that individually does not dominate the exit.
*/
private predicate postActionBlock(BasicBlock bb, ActionConfiguration conf) {
bb = nonDominatingActionBlock(conf)
or
if bb instanceof JoinBlock
then forall(BasicBlock pred | pred = bb.getABBPredecessor() | postActionBlock(pred, conf))
else postActionBlock(bb.getABBPredecessor(), conf)
}
/** Holds if every path through `callable` goes through at least one action node. */
private predicate callableAlwaysPerformsAction(Callable callable, ActionConfiguration conf) {
actionDominatesExit(callable, conf)
or
exists(BasicBlock exit |
exit.getLastNode() = callable and
postActionBlock(exit, conf)
)
}

View File

@@ -0,0 +1,242 @@
/**
* Provides classes and predicates for identifying unreachable blocks under a "closed-world" assumption.
*/
import java
import semmle.code.java.controlflow.Guards
/**
* A field which contains a constant of an immutable type.
*
* This only considers fields which are assigned once.
*/
class ConstantField extends Field {
ConstantField() {
getType() instanceof ImmutableType and
// Assigned once
count(getAnAssignedValue()) = 1 and
// And that assignment is either in the appropriate initializer, or, for instance fields on
// classes with one constructor, in the constructor.
forall(FieldWrite fa | fa = getAnAccess() |
if isStatic()
then fa.getEnclosingCallable() instanceof StaticInitializer
else (
// Defined in the instance initializer.
fa.getEnclosingCallable() instanceof InstanceInitializer
or
// It can be defined in the constructor if there is only one constructor.
fa.getEnclosingCallable() instanceof Constructor and
count(getDeclaringType().getAConstructor()) = 1
)
)
}
/**
* Gets the constant value assigned to the field.
*
* Note: although this value is constant, we may not be able to statically determine the value.
*/
ConstantExpr getConstantValue() { result = getAnAssignedValue() }
}
/**
* A method that returns a single constant value, and is not overridden.
*/
class ConstantMethod extends Method {
ConstantMethod() {
// Just one return statement
count(ReturnStmt rs | rs.getEnclosingCallable() = this) = 1 and
// Which returns a constant expr
exists(ReturnStmt rs | rs.getEnclosingCallable() = this |
rs.getResult() instanceof ConstantExpr
) and
// And this method is not overridden
not exists(Method m | m.overrides(this))
}
/**
* Gets the expression representing the constant value returned.
*/
ConstantExpr getConstantValue() {
exists(ReturnStmt returnStmt | returnStmt.getEnclosingCallable() = this |
result = returnStmt.getResult()
)
}
}
/**
* A field that appears constant, but should not be considered constant when determining
* `ConstantExpr`, and, consequently, in the unreachable blocks analysis.
*/
abstract class ExcludedConstantField extends ConstantField { }
/**
* An expression that evaluates to a constant at runtime.
*
* This includes all JLS compile-time constants, plus expressions that can be deduced to be
* constant by making a closed world assumption.
*/
class ConstantExpr extends Expr {
ConstantExpr() {
// Ignore reads of excluded fields.
not this.(FieldRead).getField() instanceof ExcludedConstantField and
(
// A JLS compile time constant expr
this instanceof CompileTimeConstantExpr
or
// A call to a constant method
this.(Call).getCallee() instanceof ConstantMethod
or
// A read of a constant field
exists(this.(FieldRead).getField().(ConstantField).getConstantValue())
or
// A binary expression where both sides are constant
this.(BinaryExpr).getLeftOperand() instanceof ConstantExpr and
this.(BinaryExpr).getRightOperand() instanceof ConstantExpr
)
}
/**
* Gets the inferred boolean value for this constant boolean expression.
*/
boolean getBooleanValue() {
result = this.(CompileTimeConstantExpr).getBooleanValue()
or
result = this.(Call).getCallee().(ConstantMethod).getConstantValue().getBooleanValue()
or
result = this.(FieldRead).getField().(ConstantField).getConstantValue().getBooleanValue()
or
// Handle binary expressions that have integer operands and a boolean result.
exists(BinaryExpr b, int left, int right |
b = this and
left = b.getLeftOperand().(ConstantExpr).getIntValue() and
right = b.getRightOperand().(ConstantExpr).getIntValue()
|
(
b instanceof LTExpr and
if left < right then result = true else result = false
)
or
(
b instanceof LEExpr and
if left <= right then result = true else result = false
)
or
(
b instanceof GTExpr and
if left > right then result = true else result = false
)
or
(
b instanceof GEExpr and
if left >= right then result = true else result = false
)
or
(
b instanceof EQExpr and
if left = right then result = true else result = false
)
or
(
b instanceof NEExpr and
if left != right then result = true else result = false
)
)
}
/**
* Gets the inferred int value for this constant int expression.
*/
int getIntValue() {
result = this.(CompileTimeConstantExpr).getIntValue() or
result = this.(Call).getCallee().(ConstantMethod).getConstantValue().getIntValue() or
result = this.(FieldRead).getField().(ConstantField).getConstantValue().getIntValue()
}
}
/**
* A switch statement that always selects the same case.
*/
class ConstSwitchStmt extends SwitchStmt {
ConstSwitchStmt() { this.getExpr() instanceof ConstantExpr }
/** Gets the `ConstCase` that matches, if any. */
ConstCase getMatchingConstCase() {
result = getAConstCase() and
// Only handle the int case for now
result.getValue().(ConstantExpr).getIntValue() = getExpr().(ConstantExpr).getIntValue()
}
/** Gets the matching case, if it can be deduced. */
SwitchCase getMatchingCase() {
// Must be a value we can deduce
exists(getExpr().(ConstantExpr).getIntValue()) and
if exists(getMatchingConstCase())
then result = getMatchingConstCase()
else result = getDefaultCase()
}
/**
* Gets a case that never matches.
*
* This only has values if we found the matching case.
*/
SwitchCase getAFailingCase() {
exists(SwitchCase matchingCase |
// We must have found the matching case, otherwise we can't deduce which cases are not matched
matchingCase = getMatchingCase() and
result = getACase() and
result != matchingCase
)
}
}
/**
* An unreachable basic block is one that is dominated by a condition that never holds.
*/
class UnreachableBasicBlock extends BasicBlock {
UnreachableBasicBlock() {
// Condition blocks with a constant condition that causes a true/false successor to be
// unreachable. Note: conditions including a single boolean literal e.g. if (false) are not
// modeled as a ConditionBlock - this case is covered by the blocks-without-a-predecessor
// check below.
exists(ConditionBlock conditionBlock, boolean constant |
constant = conditionBlock.getCondition().(ConstantExpr).getBooleanValue()
|
conditionBlock.controls(this, constant.booleanNot())
)
or
// This block is not reachable in the CFG, and is not a callable, a body of a callable, an
// expression in an annotation, an expression in an assert statement, or a catch clause.
forall(BasicBlock bb | bb = getABBPredecessor() | bb instanceof UnreachableBasicBlock) and
not exists(Callable c | c.getBody() = this) and
not this instanceof Callable and
not exists(Annotation a | a.getAChildExpr*() = this) and
not exists(AssertStmt a | a = this.(Expr).getEnclosingStmt()) and
not this instanceof CatchClause
or
// Switch statements with a constant comparison expression may have unreachable cases.
exists(ConstSwitchStmt constSwitchStmt, BasicBlock failingCaseBlock |
failingCaseBlock = constSwitchStmt.getAFailingCase().getBasicBlock()
|
// Not accessible from the successful case
not constSwitchStmt.getMatchingCase().getBasicBlock().getABBSuccessor*() = failingCaseBlock and
// Blocks dominated by the failing case block are unreachable
constSwitchStmt.getAFailingCase().getBasicBlock().bbDominates(this)
)
}
}
/**
* An unreachable expression is an expression contained in an `UnreachableBasicBlock`.
*/
class UnreachableExpr extends Expr {
UnreachableExpr() { getBasicBlock() instanceof UnreachableBasicBlock }
}
/**
* An unreachable statement is a statement contained in an `UnreachableBasicBlock`.
*/
class UnreachableStmt extends Stmt {
UnreachableStmt() { getBasicBlock() instanceof UnreachableBasicBlock }
}

View File

@@ -0,0 +1,384 @@
/**
* Provides predicates for working with the internal logic of the `Guards`
* library.
*/
import java
import semmle.code.java.controlflow.Guards
private import Preconditions
private import semmle.code.java.dataflow.SSA
private import semmle.code.java.dataflow.internal.BaseSSA
private import semmle.code.java.dataflow.NullGuards
private import semmle.code.java.dataflow.IntegerGuards
/**
* Holds if the assumption that `g1` has been evaluated to `b1` implies that
* `g2` has been evaluated to `b2`, that is, the evaluation of `g2` to `b2`
* dominates the evaluation of `g1` to `b1`.
*
* Restricted to BaseSSA-based reasoning.
*/
predicate implies_v1(Guard g1, boolean b1, Guard g2, boolean b2) {
g1.(AndBitwiseExpr).getAnOperand() = g2 and b1 = true and b2 = true
or
g1.(OrBitwiseExpr).getAnOperand() = g2 and b1 = false and b2 = false
or
g1.(AndLogicalExpr).getAnOperand() = g2 and b1 = true and b2 = true
or
g1.(OrLogicalExpr).getAnOperand() = g2 and b1 = false and b2 = false
or
g1.(LogNotExpr).getExpr() = g2 and
b1 = b2.booleanNot() and
(b1 = true or b1 = false)
or
exists(EqualityTest eqtest, boolean polarity, BooleanLiteral boollit |
eqtest = g1 and
eqtest.hasOperands(g2, boollit) and
eqtest.polarity() = polarity and
(b1 = true or b1 = false) and
b2 = b1.booleanXor(polarity).booleanXor(boollit.getBooleanValue())
)
or
exists(ConditionalExpr cond, boolean branch, BooleanLiteral boollit, boolean boolval |
cond.getBranchExpr(branch) = boollit and
cond = g1 and
boolval = boollit.getBooleanValue() and
b1 = boolval.booleanNot() and
(
g2 = cond.getCondition() and b2 = branch.booleanNot()
or
g2 = cond.getABranchExpr() and b2 = b1
)
)
or
g1.(DefaultCase).getSwitch().getAConstCase() = g2 and b1 = true and b2 = false
or
exists(MethodAccess check | check = g1 |
conditionCheck(check, _) and
g2 = check.getArgument(0) and
(b1 = true or b1 = false) and
b2 = b1
)
or
exists(BaseSsaUpdate vbool |
vbool.getAUse() = g1 and
vbool.getDefiningExpr().(VariableAssign).getSource() = g2 and
(b1 = true or b1 = false) and
b2 = b1
)
}
/**
* Holds if the assumption that `g1` has been evaluated to `b1` implies that
* `g2` has been evaluated to `b2`, that is, the evaluation of `g2` to `b2`
* dominates the evaluation of `g1` to `b1`.
*
* Allows full use of SSA but is restricted to pre-RangeAnalysis reasoning.
*/
predicate implies_v2(Guard g1, boolean b1, Guard g2, boolean b2) {
implies_v1(g1, b1, g2, b2)
or
exists(SsaExplicitUpdate vbool |
vbool.getAUse() = g1 and
vbool.getDefiningExpr().(VariableAssign).getSource() = g2 and
(b1 = true or b1 = false) and
b2 = b1
)
or
exists(SsaVariable v, AbstractValue k |
// If `v = g2 ? k : ...` or `v = g2 ? ... : k` then a guard
// proving `v != k` ensures that `g2` evaluates to `b2`.
conditionalAssignVal(v, g2, b2.booleanNot(), k) and
guardImpliesNotEqual1(g1, b1, v, k)
)
or
exists(SsaVariable v, Expr e, AbstractValue k |
// If `v = g2 ? k : ...` and all other assignments to `v` are different from
// `k` then a guard proving `v == k` ensures that `g2` evaluates to `b2`.
uniqueValue(v, e, k) and
guardImpliesEqual(g1, b1, v, k) and
g2.directlyControls(e.getBasicBlock(), b2) and
not g2.directlyControls(g1.getBasicBlock(), b2)
)
}
/**
* Holds if the assumption that `g1` has been evaluated to `b1` implies that
* `g2` has been evaluated to `b2`, that is, the evaluation of `g2` to `b2`
* dominates the evaluation of `g1` to `b1`.
*/
cached
predicate implies_v3(Guard g1, boolean b1, Guard g2, boolean b2) {
implies_v2(g1, b1, g2, b2)
or
exists(SsaVariable v, AbstractValue k |
// If `v = g2 ? k : ...` or `v = g2 ? ... : k` then a guard
// proving `v != k` ensures that `g2` evaluates to `b2`.
conditionalAssignVal(v, g2, b2.booleanNot(), k) and
guardImpliesNotEqual2(g1, b1, v, k)
)
or
exists(SsaVariable v |
conditionalAssign(v, g2, b2.booleanNot(), clearlyNotNullExpr()) and
guardImpliesEqual(g1, b1, v, TAbsValNull())
)
}
private newtype TAbstractValue =
TAbsValNull() or
TAbsValInt(int i) { exists(CompileTimeConstantExpr c | c.getIntValue() = i) } or
TAbsValChar(string c) { exists(CharacterLiteral lit | lit.getValue() = c) } or
TAbsValString(string s) { exists(StringLiteral lit | lit.getValue() = s) } or
TAbsValEnum(EnumConstant c)
/** The value of a constant expression. */
abstract private class AbstractValue extends TAbstractValue {
abstract string toString();
/** Gets an expression whose value is this abstract value. */
abstract Expr getExpr();
}
private class AbsValNull extends AbstractValue, TAbsValNull {
override string toString() { result = "null" }
override Expr getExpr() { result = alwaysNullExpr() }
}
private class AbsValInt extends AbstractValue, TAbsValInt {
int i;
AbsValInt() { this = TAbsValInt(i) }
override string toString() { result = i.toString() }
override Expr getExpr() { result.(CompileTimeConstantExpr).getIntValue() = i }
}
private class AbsValChar extends AbstractValue, TAbsValChar {
string c;
AbsValChar() { this = TAbsValChar(c) }
override string toString() { result = c }
override Expr getExpr() { result.(CharacterLiteral).getValue() = c }
}
private class AbsValString extends AbstractValue, TAbsValString {
string s;
AbsValString() { this = TAbsValString(s) }
override string toString() { result = s }
override Expr getExpr() { result.(CompileTimeConstantExpr).getStringValue() = s }
}
private class AbsValEnum extends AbstractValue, TAbsValEnum {
EnumConstant c;
AbsValEnum() { this = TAbsValEnum(c) }
override string toString() { result = c.toString() }
override Expr getExpr() { result = c.getAnAccess() }
}
/**
* Holds if `v` can have a value that is not representable as an `AbstractValue`.
*/
private predicate hasPossibleUnknownValue(SsaVariable v) {
exists(SsaVariable def | v.getAnUltimateDefinition() = def |
def instanceof SsaImplicitUpdate
or
def instanceof SsaImplicitInit
or
exists(VariableUpdate upd | upd = def.(SsaExplicitUpdate).getDefiningExpr() |
not exists(upd.(VariableAssign).getSource())
)
or
exists(VariableAssign a, Expr e |
a = def.(SsaExplicitUpdate).getDefiningExpr() and
e = possibleValue(a.getSource()) and
not exists(AbstractValue val | val.getExpr() = e)
)
)
}
/**
* Gets a sub-expression of `e` whose value can flow to `e` through
* `ConditionalExpr`s.
*/
private Expr possibleValue(Expr e) {
result = possibleValue(e.(ConditionalExpr).getABranchExpr())
or
result = e and not e instanceof ConditionalExpr
}
/**
* Gets an ultimate definition of `v` that is not itself a phi node. The
* boolean `fromBackEdge` indicates whether the flow from `result` to `v` goes
* through a back edge.
*/
SsaVariable getADefinition(SsaVariable v, boolean fromBackEdge) {
result = v and not v instanceof SsaPhiNode and fromBackEdge = false
or
exists(SsaVariable inp, BasicBlock bb, boolean fbe |
v.(SsaPhiNode).hasInputFromBlock(inp, bb) and
result = getADefinition(inp, fbe) and
(if v.getBasicBlock().bbDominates(bb) then fromBackEdge = true else fromBackEdge = fbe)
)
}
/**
* Holds if `e` equals `k` and may be assigned to `v`. The boolean
* `fromBackEdge` indicates whether the flow from `e` to `v` goes through a
* back edge.
*/
private predicate possibleValue(SsaVariable v, boolean fromBackEdge, Expr e, AbstractValue k) {
not hasPossibleUnknownValue(v) and
exists(SsaExplicitUpdate def |
def = getADefinition(v, fromBackEdge) and
e = possibleValue(def.getDefiningExpr().(VariableAssign).getSource()) and
k.getExpr() = e
)
}
/**
* Holds if `e` equals `k` and may be assigned to `v` without going through
* back edges, and all other possible ultimate definitions of `v` are different
* from `k`. The trivial case where `v` is an `SsaExplicitUpdate` with `e` as
* the only possible value is excluded.
*/
private predicate uniqueValue(SsaVariable v, Expr e, AbstractValue k) {
possibleValue(v, false, e, k) and
forex(Expr other, AbstractValue otherval | possibleValue(v, _, other, otherval) and other != e |
otherval != k
)
}
/**
* Holds if `v1` and `v2` have the same value in `bb`.
*/
private predicate equalVarsInBlock(BasicBlock bb, SsaVariable v1, SsaVariable v2) {
exists(Guard guard, boolean branch |
guard.isEquality(v1.getAUse(), v2.getAUse(), branch) and
guardControls_v1(guard, bb, branch)
)
}
/**
* Holds if `guard` evaluating to `branch` implies that `v` equals `k`.
*/
private predicate guardImpliesEqual(Guard guard, boolean branch, SsaVariable v, AbstractValue k) {
exists(SsaVariable v0 |
guard.isEquality(v0.getAUse(), k.getExpr(), branch) and
(v = v0 or equalVarsInBlock(guard.getBasicBlock(), v0, v))
)
}
private ControlFlowNode getAGuardBranchSuccessor(Guard g, boolean branch) {
result = g.(Expr).getControlFlowNode().(ConditionNode).getABranchSuccessor(branch)
or
result = g.(SwitchCase).getControlFlowNode() and branch = true
}
/**
* Holds if `guard` dominates `phi` and `guard` evaluating to `branch` controls the definition
* `upd = e` where `upd` is a possible input to `phi`.
*/
private predicate guardControlsPhiBranch(
SsaExplicitUpdate upd, SsaPhiNode phi, Guard guard, boolean branch, Expr e
) {
guard.directlyControls(upd.getBasicBlock(), branch) and
upd.getDefiningExpr().(VariableAssign).getSource() = e and
upd = phi.getAPhiInput() and
guard.getBasicBlock().bbStrictlyDominates(phi.getBasicBlock())
}
/**
* Holds if `v` is conditionally assigned `e` under the condition that `guard` evaluates to `branch`.
*
* The evaluation of `guard` dominates the definition of `v` and `guard` evaluating to `branch`
* implies that `e` is assigned to `v`. In particular, this allows us to conclude that if `v` has
* a value different from `e` then `guard` must have evaluated to `branch.booleanNot()`.
*/
private predicate conditionalAssign(SsaVariable v, Guard guard, boolean branch, Expr e) {
exists(ConditionalExpr c |
v.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource() = c and
guard = c.getCondition()
|
e = c.getBranchExpr(branch)
)
or
exists(SsaExplicitUpdate upd, SsaPhiNode phi |
phi = v and
guardControlsPhiBranch(upd, phi, guard, branch, e) and
not guard.directlyControls(phi.getBasicBlock(), branch) and
forall(SsaVariable other | other != upd and other = phi.getAPhiInput() |
guard.directlyControls(other.getBasicBlock(), branch.booleanNot())
or
other.getBasicBlock().bbDominates(guard.getBasicBlock()) and
not other.isLiveAtEndOfBlock(getAGuardBranchSuccessor(guard, branch))
)
)
}
/**
* Holds if `v` is conditionally assigned `val` under the condition that `guard` evaluates to `branch`.
*/
private predicate conditionalAssignVal(SsaVariable v, Guard guard, boolean branch, AbstractValue val) {
conditionalAssign(v, guard, branch, val.getExpr())
}
private predicate relevantEq(SsaVariable v, AbstractValue val) {
conditionalAssignVal(v, _, _, val)
or
exists(SsaVariable v0 |
conditionalAssignVal(v0, _, _, val) and
equalVarsInBlock(_, v0, v)
)
}
/**
* Holds if the evaluation of `guard` to `branch` implies that `v` does not have the value `val`.
*/
private predicate guardImpliesNotEqual1(
Guard guard, boolean branch, SsaVariable v, AbstractValue val
) {
exists(SsaVariable v0 |
relevantEq(v0, val) and
(
guard.isEquality(v0.getAUse(), val.getExpr(), branch.booleanNot())
or
exists(AbstractValue val2 |
guard.isEquality(v0.getAUse(), val2.getExpr(), branch) and
val != val2
)
or
guard.(InstanceOfExpr).getExpr() = sameValue(v0, _) and branch = true and val = TAbsValNull()
) and
(v = v0 or equalVarsInBlock(guard.getBasicBlock(), v0, v))
)
}
/**
* Holds if the evaluation of `guard` to `branch` implies that `v` does not have the value `val`.
*/
private predicate guardImpliesNotEqual2(
Guard guard, boolean branch, SsaVariable v, AbstractValue val
) {
exists(SsaVariable v0 |
relevantEq(v0, val) and
(
guard = directNullGuard(v0, branch, false) and val = TAbsValNull()
or
exists(int k |
guard = integerGuard(v0.getAUse(), branch, k, false) and
val = TAbsValInt(k)
)
) and
(v = v0 or equalVarsInBlock(guard.getBasicBlock(), v0, v))
)
}

View File

@@ -0,0 +1,60 @@
/**
* Provides predicates for identifying precondition checks like
* `com.google.common.base.Preconditions` and
* `org.apache.commons.lang3.Validate`.
*/
import java
/**
* Holds if `m` is a non-overridable method that checks that its first argument
* is equal to `checkTrue` and throws otherwise.
*/
predicate conditionCheckMethod(Method m, boolean checkTrue) {
m.getDeclaringType().hasQualifiedName("com.google.common.base", "Preconditions") and
checkTrue = true and
(m.hasName("checkArgument") or m.hasName("checkState"))
or
m.getDeclaringType().hasQualifiedName("org.apache.commons.lang3", "Validate") and
checkTrue = true and
(m.hasName("isTrue") or m.hasName("validState"))
or
exists(Parameter p, IfStmt ifstmt, Expr cond |
p = m.getParameter(0) and
not m.isOverridable() and
p.getType() instanceof BooleanType and
m.getBody().getStmt(0) = ifstmt and
ifstmt.getCondition() = cond and
(
cond.(LogNotExpr).getExpr().(VarAccess).getVariable() = p and checkTrue = true
or
cond.(VarAccess).getVariable() = p and checkTrue = false
) and
(
ifstmt.getThen() instanceof ThrowStmt or
ifstmt.getThen().(SingletonBlock).getStmt() instanceof ThrowStmt
)
)
or
exists(Parameter p, MethodAccess ma, boolean ct, Expr arg |
p = m.getParameter(0) and
not m.isOverridable() and
m.getBody().getStmt(0).(ExprStmt).getExpr() = ma and
conditionCheck(ma, ct) and
ma.getArgument(0) = arg and
(
arg.(LogNotExpr).getExpr().(VarAccess).getVariable() = p and
checkTrue = ct.booleanNot()
or
arg.(VarAccess).getVariable() = p and checkTrue = ct
)
)
}
/**
* Holds if `ma` is an access to a non-overridable method that checks that its
* first argument is equal to `checkTrue` and throws otherwise.
*/
predicate conditionCheck(MethodAccess ma, boolean checkTrue) {
conditionCheckMethod(ma.getMethod().getSourceDeclaration(), checkTrue)
}

View File

@@ -0,0 +1,39 @@
import java
import semmle.code.java.controlflow.UnreachableBlocks
/**
* Exclude from the unreachable block analysis constant fields that look like they are flags for
* controlling debugging, profiling or logging features.
*
* Debugging, profiling and logging flags that are compile time constants are usually intended to be
* toggled by the developer at compile time to provide extra information when developing the
* application, or when triaging a problem. By including this sub-class, blocks that are unreachable
* because they are guarded by a check of such a flag are considered reachable.
*
* Note: we explicitly limit this to debugging, profiling and logging flags. True feature toggles
* are treated as constant true/false, because it is much less likely that they are toggled in
* practice.
*/
class ExcludeDebuggingProfilingLogging extends ExcludedConstantField {
ExcludeDebuggingProfilingLogging() {
exists(string validFieldName |
validFieldName = "debug" or
validFieldName = "profiling" or
validFieldName = "profile" or
validFieldName = "time" or
validFieldName = "verbose" or
validFieldName = "report" or
validFieldName = "dbg" or
validFieldName = "timing" or
validFieldName = "assert" or
validFieldName = "log"
|
getName().regexpMatch(".*(?i)" + validFieldName + ".*")
) and
// Boolean type
(
getType().hasName("boolean") or
getType().(BoxedType).hasQualifiedName("java.lang", "Boolean")
)
}
}

View File

@@ -0,0 +1,78 @@
/**
* Provides classes for representing abstract bounds for use in, for example, range analysis.
*/
private import internal.rangeanalysis.BoundSpecific
private newtype TBound =
TBoundZero() or
TBoundSsa(SsaVariable v) { v.getSourceVariable().getType() instanceof IntegralType } or
TBoundExpr(Expr e) {
interestingExprBound(e) and
not exists(SsaVariable v | e = v.getAUse())
}
/**
* A bound that may be inferred for an expression plus/minus an integer delta.
*/
abstract class Bound extends TBound {
/** Gets a textual representation of this bound. */
abstract string toString();
/** Gets an expression that equals this bound plus `delta`. */
abstract Expr getExpr(int delta);
/** Gets an expression that equals this bound. */
Expr getExpr() { result = getExpr(0) }
/**
* Holds if this element is at the specified location.
* The location spans column `sc` of line `sl` to
* column `ec` of line `el` in file `path`.
* For more information, see
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
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
}
}
/**
* The bound that corresponds to the integer 0. This is used to represent all
* integer bounds as bounds are always accompanied by an added integer delta.
*/
class ZeroBound extends Bound, TBoundZero {
override string toString() { result = "0" }
override Expr getExpr(int delta) { result.(ConstantIntegerExpr).getIntValue() = delta }
}
/**
* A bound corresponding to the value of an SSA variable.
*/
class SsaBound extends Bound, TBoundSsa {
/** Gets the SSA variable that equals this bound. */
SsaVariable getSsa() { this = TBoundSsa(result) }
override string toString() { result = getSsa().toString() }
override Expr getExpr(int delta) { result = getSsa().getAUse() and delta = 0 }
override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
getSsa().getLocation().hasLocationInfo(path, sl, sc, el, ec)
}
}
/**
* A bound that corresponds to the value of a specific expression that might be
* interesting, but isn't otherwise represented by the value of an SSA variable.
*/
class ExprBound extends Bound, TBoundExpr {
override string toString() { result = getExpr().toString() }
override Expr getExpr(int delta) { this = TBoundExpr(result) and delta = 0 }
override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
getExpr().getLocation().hasLocationInfo(path, sl, sc, el, ec)
}
}

View File

@@ -0,0 +1,10 @@
/**
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) data flow analyses.
*/
import java
module DataFlow {
import semmle.code.java.dataflow.internal.DataFlowImpl
}

View File

@@ -0,0 +1,10 @@
/**
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) data flow analyses.
*/
import java
module DataFlow2 {
import semmle.code.java.dataflow.internal.DataFlowImpl2
}

View File

@@ -0,0 +1,10 @@
/**
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) data flow analyses.
*/
import java
module DataFlow3 {
import semmle.code.java.dataflow.internal.DataFlowImpl3
}

View File

@@ -0,0 +1,10 @@
/**
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) data flow analyses.
*/
import java
module DataFlow4 {
import semmle.code.java.dataflow.internal.DataFlowImpl4
}

View File

@@ -0,0 +1,10 @@
/**
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) data flow analyses.
*/
import java
module DataFlow5 {
import semmle.code.java.dataflow.internal.DataFlowImpl5
}

View File

@@ -0,0 +1,10 @@
/**
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) data flow analyses.
*/
import java
module DataFlow6 {
import semmle.code.java.dataflow.internal.DataFlowImpl6
}

View File

@@ -0,0 +1,50 @@
/**
* Provides classes and predicates for def-use and use-use pairs. Built on top of the SSA library for
* maximal precision.
*/
import java
private import SSA
/**
* Holds if `use1` and `use2` form a use-use-pair of the same SSA variable,
* that is, the value read in `use1` can reach `use2` without passing through
* any SSA definition of the variable.
*
* This is the transitive closure of `adjacentUseUseSameVar`.
*/
predicate useUsePairSameVar(RValue use1, RValue use2) { adjacentUseUseSameVar+(use1, use2) }
/**
* Holds if `use1` and `use2` form a use-use-pair of the same
* `SsaSourceVariable`, that is, the value read in `use1` can reach `use2`
* without passing through any SSA definition of the variable except for phi
* nodes and uncertain implicit updates.
*
* This is the transitive closure of `adjacentUseUse`.
*/
predicate useUsePair(RValue use1, RValue use2) { adjacentUseUse+(use1, use2) }
/**
* Holds if there exists a path from `def` to `use` without passing through another
* `VariableUpdate` of the `LocalScopeVariable` that they both refer to.
*
* Other paths may also exist, so the SSA variables in `def` and `use` can be different.
*/
predicate defUsePair(VariableUpdate def, RValue use) {
exists(SsaVariable v |
v.getAUse() = use and v.getAnUltimateDefinition().(SsaExplicitUpdate).getDefiningExpr() = def
)
}
/**
* Holds if there exists a path from the entry-point of the callable to `use` without
* passing through a `VariableUpdate` of the parameter `p` that `use` refers to.
*
* Other paths may also exist, so the SSA variables can be different.
*/
predicate parameterDefUsePair(Parameter p, RValue use) {
exists(SsaVariable v |
v.getAUse() = use and v.getAnUltimateDefinition().(SsaImplicitInit).isParameterDefinition(p)
)
}

View File

@@ -0,0 +1,734 @@
/**
* INTERNAL use only. This is an experimental API subject to change without notice.
*
* Provides classes and predicates for dealing with flow models specified in CSV format.
*
* The CSV specification has the following columns:
* - Sources:
* `namespace; type; subtypes; name; signature; ext; output; kind`
* - Sinks:
* `namespace; type; subtypes; name; signature; ext; input; kind`
* - Summaries:
* `namespace; type; subtypes; name; signature; ext; input; output; kind`
*
* The interpretation of a row is similar to API-graphs with a left-to-right
* reading.
* 1. The `namespace` column selects a package.
* 2. The `type` column selects a type within that package.
* 3. The `subtypes` is a boolean that indicates whether to jump to an
* arbitrary subtype of that type.
* 4. The `name` column optionally selects a specific named member of the type.
* 5. The `signature` column optionally restricts the named member. If
* `signature` is blank then no such filtering is done. The format of the
* signature is a comma-separated list of types enclosed in parentheses. The
* types can be short names or fully qualified names (mixing these two options
* is not allowed within a single signature).
* 6. The `ext` column specifies additional API-graph-like edges. Currently
* there are only two valid values: "" and "Annotated". The empty string has no
* effect. "Annotated" applies if `name` and `signature` were left blank and
* acts by selecting an element that is annotated by the annotation type
* selected by the first 4 columns. This can be another member such as a field
* or method, or a parameter.
* 7. The `input` column specifies how data enters the element selected by the
* first 6 columns, and the `output` column specifies how data leaves the
* element selected by the first 6 columns. An `input` can be either "",
* "Argument[n]", "Argument[n1..n2]", "ReturnValue":
* - "": Selects a write to the selected element in case this is a field.
* - "Argument[n]": Selects an argument in a call to the selected element.
* The arguments are zero-indexed, and `-1` specifies the qualifier.
* - "Argument[n1..n2]": Similar to "Argument[n]" but select any argument in
* the given range. The range is inclusive at both ends.
* - "ReturnValue": Selects a value being returned by the selected element.
* This requires that the selected element is a method with a body.
*
* An `output` can be either "", "Argument[n]", "Argument[n1..n2]", "Parameter",
* "Parameter[n]", "Parameter[n1..n2]", or "ReturnValue":
* - "": Selects a read of a selected field, or a selected parameter.
* - "Argument[n]": Selects the post-update value of an argument in a call to the
* selected element. That is, the value of the argument after the call returns.
* The arguments are zero-indexed, and `-1` specifies the qualifier.
* - "Argument[n1..n2]": Similar to "Argument[n]" but select any argument in
* the given range. The range is inclusive at both ends.
* - "Parameter": Selects the value of a parameter of the selected element.
* "Parameter" is also allowed in case the selected element is already a
* parameter itself.
* - "Parameter[n]": Similar to "Parameter" but restricted to a specific
* numbered parameter (zero-indexed, and `-1` specifies the value of `this`).
* - "Parameter[n1..n2]": Similar to "Parameter[n]" but selects any parameter
* in the given range. The range is inclusive at both ends.
* - "ReturnValue": Selects the return value of a call to the selected element.
* 8. The `kind` column is a tag that can be referenced from QL to determine to
* which classes the interpreted elements should be added. For example, for
* sources "remote" indicates a default remote flow source, and for summaries
* "taint" indicates a default additional taint step and "value" indicates a
* globally applicable value-preserving step.
*/
import java
private import semmle.code.java.dataflow.DataFlow::DataFlow
private import internal.DataFlowPrivate
private import internal.FlowSummaryImpl::Private::External
private import internal.FlowSummaryImplSpecific
private import FlowSummary
/**
* A module importing the frameworks that provide external flow data,
* ensuring that they are visible to the taint tracking / data flow library.
*/
private module Frameworks {
private import internal.ContainerFlow
private import semmle.code.java.frameworks.android.XssSinks
private import semmle.code.java.frameworks.ApacheHttp
private import semmle.code.java.frameworks.apache.Collections
private import semmle.code.java.frameworks.apache.Lang
private import semmle.code.java.frameworks.guava.Guava
private import semmle.code.java.frameworks.jackson.JacksonSerializability
private import semmle.code.java.frameworks.JavaxJson
private import semmle.code.java.frameworks.JaxWS
private import semmle.code.java.frameworks.JoddJson
private import semmle.code.java.frameworks.JsonJava
private import semmle.code.java.frameworks.Optional
private import semmle.code.java.frameworks.spring.SpringCache
private import semmle.code.java.frameworks.spring.SpringHttp
private import semmle.code.java.frameworks.spring.SpringUtil
private import semmle.code.java.frameworks.spring.SpringUi
private import semmle.code.java.frameworks.spring.SpringValidation
private import semmle.code.java.frameworks.spring.SpringWebClient
private import semmle.code.java.frameworks.spring.SpringBeans
private import semmle.code.java.frameworks.spring.SpringWebMultipart
private import semmle.code.java.frameworks.spring.SpringWebUtil
private import semmle.code.java.security.ResponseSplitting
private import semmle.code.java.security.InformationLeak
private import semmle.code.java.security.GroovyInjection
private import semmle.code.java.security.JexlInjectionSinkModels
private import semmle.code.java.security.JndiInjection
private import semmle.code.java.security.LdapInjection
private import semmle.code.java.security.MvelInjection
private import semmle.code.java.security.OgnlInjection
private import semmle.code.java.security.XPath
private import semmle.code.java.frameworks.android.SQLite
private import semmle.code.java.frameworks.Jdbc
private import semmle.code.java.frameworks.SpringJdbc
private import semmle.code.java.frameworks.MyBatis
private import semmle.code.java.frameworks.Hibernate
private import semmle.code.java.frameworks.jOOQ
private import semmle.code.java.frameworks.spring.SpringHttp
}
private predicate sourceModelCsv(string row) {
row =
[
// org.springframework.security.web.savedrequest.SavedRequest
"org.springframework.security.web.savedrequest;SavedRequest;true;getRedirectUrl;;;ReturnValue;remote",
"org.springframework.security.web.savedrequest;SavedRequest;true;getCookies;;;ReturnValue;remote",
"org.springframework.security.web.savedrequest;SavedRequest;true;getHeaderValues;;;ReturnValue;remote",
"org.springframework.security.web.savedrequest;SavedRequest;true;getHeaderNames;;;ReturnValue;remote",
"org.springframework.security.web.savedrequest;SavedRequest;true;getParameterValues;;;ReturnValue;remote",
"org.springframework.security.web.savedrequest;SavedRequest;true;getParameterMap;;;ReturnValue;remote",
// ServletRequestGetParameterMethod
"javax.servlet;ServletRequest;false;getParameter;(String);;ReturnValue;remote",
"javax.servlet;ServletRequest;false;getParameterValues;(String);;ReturnValue;remote",
"javax.servlet.http;HttpServletRequest;false;getParameter;(String);;ReturnValue;remote",
"javax.servlet.http;HttpServletRequest;false;getParameterValues;(String);;ReturnValue;remote",
// ServletRequestGetParameterMapMethod
"javax.servlet;ServletRequest;false;getParameterMap;();;ReturnValue;remote",
"javax.servlet.http;HttpServletRequest;false;getParameterMap;();;ReturnValue;remote",
// ServletRequestGetParameterNamesMethod
"javax.servlet;ServletRequest;false;getParameterNames;();;ReturnValue;remote",
"javax.servlet.http;HttpServletRequest;false;getParameterNames;();;ReturnValue;remote",
// HttpServletRequestGetQueryStringMethod
"javax.servlet.http;HttpServletRequest;false;getQueryString;();;ReturnValue;remote",
//
// URLConnectionGetInputStreamMethod
"java.net;URLConnection;false;getInputStream;();;ReturnValue;remote",
// SocketGetInputStreamMethod
"java.net;Socket;false;getInputStream;();;ReturnValue;remote",
// BeanValidationSource
"javax.validation;ConstraintValidator;true;isValid;;;Parameter[0];remote",
// SpringMultipartRequestSource
"org.springframework.web.multipart;MultipartRequest;true;getFile;(String);;ReturnValue;remote",
"org.springframework.web.multipart;MultipartRequest;true;getFileMap;();;ReturnValue;remote",
"org.springframework.web.multipart;MultipartRequest;true;getFileNames;();;ReturnValue;remote",
"org.springframework.web.multipart;MultipartRequest;true;getFiles;(String);;ReturnValue;remote",
"org.springframework.web.multipart;MultipartRequest;true;getMultiFileMap;();;ReturnValue;remote",
"org.springframework.web.multipart;MultipartRequest;true;getMultipartContentType;(String);;ReturnValue;remote",
// SpringMultipartFileSource
"org.springframework.web.multipart;MultipartFile;true;getBytes;();;ReturnValue;remote",
"org.springframework.web.multipart;MultipartFile;true;getContentType;();;ReturnValue;remote",
"org.springframework.web.multipart;MultipartFile;true;getInputStream;();;ReturnValue;remote",
"org.springframework.web.multipart;MultipartFile;true;getName;();;ReturnValue;remote",
"org.springframework.web.multipart;MultipartFile;true;getOriginalFilename;();;ReturnValue;remote",
"org.springframework.web.multipart;MultipartFile;true;getResource;();;ReturnValue;remote",
// HttpServletRequest.get*
"javax.servlet.http;HttpServletRequest;false;getHeader;(String);;ReturnValue;remote",
"javax.servlet.http;HttpServletRequest;false;getHeaders;(String);;ReturnValue;remote",
"javax.servlet.http;HttpServletRequest;false;getHeaderNames;();;ReturnValue;remote",
"javax.servlet.http;HttpServletRequest;false;getPathInfo;();;ReturnValue;remote",
"javax.servlet.http;HttpServletRequest;false;getRequestURI;();;ReturnValue;remote",
"javax.servlet.http;HttpServletRequest;false;getRequestURL;();;ReturnValue;remote",
"javax.servlet.http;HttpServletRequest;false;getRemoteUser;();;ReturnValue;remote",
// SpringWebRequestGetMethod
"org.springframework.web.context.request;WebRequest;false;getDescription;;;ReturnValue;remote",
"org.springframework.web.context.request;WebRequest;false;getHeader;;;ReturnValue;remote",
"org.springframework.web.context.request;WebRequest;false;getHeaderNames;;;ReturnValue;remote",
"org.springframework.web.context.request;WebRequest;false;getHeaderValues;;;ReturnValue;remote",
"org.springframework.web.context.request;WebRequest;false;getParameter;;;ReturnValue;remote",
"org.springframework.web.context.request;WebRequest;false;getParameterMap;;;ReturnValue;remote",
"org.springframework.web.context.request;WebRequest;false;getParameterNames;;;ReturnValue;remote",
"org.springframework.web.context.request;WebRequest;false;getParameterValues;;;ReturnValue;remote",
// TODO consider org.springframework.web.context.request.WebRequest.getRemoteUser
// ServletRequestGetBodyMethod
"javax.servlet;ServletRequest;false;getInputStream;();;ReturnValue;remote",
"javax.servlet;ServletRequest;false;getReader;();;ReturnValue;remote",
// CookieGet*
"javax.servlet.http;Cookie;false;getValue;();;ReturnValue;remote",
"javax.servlet.http;Cookie;false;getName;();;ReturnValue;remote",
"javax.servlet.http;Cookie;false;getComment;();;ReturnValue;remote",
// ApacheHttp*
"org.apache.http;HttpMessage;false;getParams;();;ReturnValue;remote",
"org.apache.http;HttpEntity;false;getContent;();;ReturnValue;remote",
// In the setting of Android we assume that XML has been transmitted over
// the network, so may be tainted.
// XmlPullGetMethod
"org.xmlpull.v1;XmlPullParser;false;getName;();;ReturnValue;remote",
"org.xmlpull.v1;XmlPullParser;false;getNamespace;();;ReturnValue;remote",
"org.xmlpull.v1;XmlPullParser;false;getText;();;ReturnValue;remote",
// XmlAttrSetGetMethod
"android.util;AttributeSet;false;getAttributeBooleanValue;;;ReturnValue;remote",
"android.util;AttributeSet;false;getAttributeCount;;;ReturnValue;remote",
"android.util;AttributeSet;false;getAttributeFloatValue;;;ReturnValue;remote",
"android.util;AttributeSet;false;getAttributeIntValue;;;ReturnValue;remote",
"android.util;AttributeSet;false;getAttributeListValue;;;ReturnValue;remote",
"android.util;AttributeSet;false;getAttributeName;;;ReturnValue;remote",
"android.util;AttributeSet;false;getAttributeNameResource;;;ReturnValue;remote",
"android.util;AttributeSet;false;getAttributeNamespace;;;ReturnValue;remote",
"android.util;AttributeSet;false;getAttributeResourceValue;;;ReturnValue;remote",
"android.util;AttributeSet;false;getAttributeUnsignedIntValue;;;ReturnValue;remote",
"android.util;AttributeSet;false;getAttributeValue;;;ReturnValue;remote",
"android.util;AttributeSet;false;getClassAttribute;;;ReturnValue;remote",
"android.util;AttributeSet;false;getIdAttribute;;;ReturnValue;remote",
"android.util;AttributeSet;false;getIdAttributeResourceValue;;;ReturnValue;remote",
"android.util;AttributeSet;false;getPositionDescription;;;ReturnValue;remote",
"android.util;AttributeSet;false;getStyleAttribute;;;ReturnValue;remote",
// The current URL in a browser may be untrusted or uncontrolled.
// WebViewGetUrlMethod
"android.webkit;WebView;false;getUrl;();;ReturnValue;remote",
"android.webkit;WebView;false;getOriginalUrl;();;ReturnValue;remote",
// SpringRestTemplateResponseEntityMethod
"org.springframework.web.client;RestTemplate;false;exchange;;;ReturnValue;remote",
"org.springframework.web.client;RestTemplate;false;getForEntity;;;ReturnValue;remote",
"org.springframework.web.client;RestTemplate;false;postForEntity;;;ReturnValue;remote",
// WebSocketMessageParameterSource
"java.net.http;WebSocket$Listener;true;onText;(WebSocket,CharSequence,boolean);;Parameter[1];remote",
// PlayRequestGetMethod
"play.mvc;Http$RequestHeader;false;queryString;;;ReturnValue;remote",
"play.mvc;Http$RequestHeader;false;getQueryString;;;ReturnValue;remote",
"play.mvc;Http$RequestHeader;false;header;;;ReturnValue;remote",
"play.mvc;Http$RequestHeader;false;getHeader;;;ReturnValue;remote"
]
}
private predicate sinkModelCsv(string row) {
row =
[
// Open URL
"java.net;URL;false;openConnection;;;Argument[-1];open-url",
"java.net;URL;false;openStream;;;Argument[-1];open-url",
"java.net.http;HttpRequest;false;newBuilder;;;Argument[0];open-url",
"java.net.http;HttpRequest$Builder;false;uri;;;Argument[0];open-url",
"java.net;URLClassLoader;false;URLClassLoader;(URL[]);;Argument[0];open-url",
"java.net;URLClassLoader;false;URLClassLoader;(URL[],ClassLoader);;Argument[0];open-url",
"java.net;URLClassLoader;false;URLClassLoader;(URL[],ClassLoader,URLStreamHandlerFactory);;Argument[0];open-url",
"java.net;URLClassLoader;false;URLClassLoader;(String,URL[],ClassLoader);;Argument[1];open-url",
"java.net;URLClassLoader;false;URLClassLoader;(String,URL[],ClassLoader,URLStreamHandlerFactory);;Argument[1];open-url",
"java.net;URLClassLoader;false;newInstance;;;Argument[0];open-url",
// Create file
"java.io;FileOutputStream;false;FileOutputStream;;;Argument[0];create-file",
"java.io;RandomAccessFile;false;RandomAccessFile;;;Argument[0];create-file",
"java.io;FileWriter;false;FileWriter;;;Argument[0];create-file",
"java.nio.file;Files;false;move;;;Argument[1];create-file",
"java.nio.file;Files;false;copy;;;Argument[1];create-file",
"java.nio.file;Files;false;newOutputStream;;;Argument[0];create-file",
"java.nio.file;Files;false;newBufferedReader;;;Argument[0];create-file",
"java.nio.file;Files;false;createDirectory;;;Argument[0];create-file",
"java.nio.file;Files;false;createFile;;;Argument[0];create-file",
"java.nio.file;Files;false;createLink;;;Argument[0];create-file",
"java.nio.file;Files;false;createSymbolicLink;;;Argument[0];create-file",
"java.nio.file;Files;false;createTempDirectory;;;Argument[0];create-file",
"java.nio.file;Files;false;createTempFile;;;Argument[0];create-file",
// Bean validation
"javax.validation;ConstraintValidatorContext;true;buildConstraintViolationWithTemplate;;;Argument[0];bean-validation",
// Set hostname
"javax.net.ssl;HttpsURLConnection;true;setDefaultHostnameVerifier;;;Argument[0];set-hostname-verifier",
"javax.net.ssl;HttpsURLConnection;true;setHostnameVerifier;;;Argument[0];set-hostname-verifier"
]
}
private predicate summaryModelCsv(string row) {
row =
[
// qualifier to arg
"java.io;InputStream;true;read;(byte[]);;Argument[-1];Argument[0];taint",
"java.io;InputStream;true;read;(byte[],int,int);;Argument[-1];Argument[0];taint",
"java.io;InputStream;true;readNBytes;(byte[],int,int);;Argument[-1];Argument[0];taint",
"java.io;InputStream;true;transferTo;(OutputStream);;Argument[-1];Argument[0];taint",
"java.io;ByteArrayOutputStream;false;writeTo;;;Argument[-1];Argument[0];taint",
"java.io;Reader;true;read;;;Argument[-1];Argument[0];taint",
// qualifier to return
"java.io;ByteArrayOutputStream;false;toByteArray;;;Argument[-1];ReturnValue;taint",
"java.io;ByteArrayOutputStream;false;toString;;;Argument[-1];ReturnValue;taint",
"java.io;InputStream;true;readAllBytes;;;Argument[-1];ReturnValue;taint",
"java.io;InputStream;true;readNBytes;(int);;Argument[-1];ReturnValue;taint",
"java.util;StringTokenizer;false;nextElement;();;Argument[-1];ReturnValue;taint",
"java.util;StringTokenizer;false;nextToken;;;Argument[-1];ReturnValue;taint",
"javax.xml.transform.sax;SAXSource;false;getInputSource;;;Argument[-1];ReturnValue;taint",
"javax.xml.transform.stream;StreamSource;false;getInputStream;;;Argument[-1];ReturnValue;taint",
"java.nio;ByteBuffer;false;get;;;Argument[-1];ReturnValue;taint",
"java.net;URI;false;toURL;;;Argument[-1];ReturnValue;taint",
"java.net;URI;false;toString;;;Argument[-1];ReturnValue;taint",
"java.net;URI;false;toAsciiString;;;Argument[-1];ReturnValue;taint",
"java.io;File;false;toURI;;;Argument[-1];ReturnValue;taint",
"java.io;File;false;toPath;;;Argument[-1];ReturnValue;taint",
"java.nio;ByteBuffer;false;array;();;Argument[-1];ReturnValue;taint",
"java.nio.file;Path;false;toFile;;;Argument[-1];ReturnValue;taint",
"java.io;BufferedReader;true;readLine;;;Argument[-1];ReturnValue;taint",
"java.io;Reader;true;read;();;Argument[-1];ReturnValue;taint",
// arg to return
"java.nio;ByteBuffer;false;wrap;(byte[]);;Argument[0];ReturnValue;taint",
"java.util;Base64$Encoder;false;encode;(byte[]);;Argument[0];ReturnValue;taint",
"java.util;Base64$Encoder;false;encode;(ByteBuffer);;Argument[0];ReturnValue;taint",
"java.util;Base64$Encoder;false;encodeToString;(byte[]);;Argument[0];ReturnValue;taint",
"java.util;Base64$Encoder;false;wrap;(OutputStream);;Argument[0];ReturnValue;taint",
"java.util;Base64$Decoder;false;decode;(byte[]);;Argument[0];ReturnValue;taint",
"java.util;Base64$Decoder;false;decode;(ByteBuffer);;Argument[0];ReturnValue;taint",
"java.util;Base64$Decoder;false;decode;(String);;Argument[0];ReturnValue;taint",
"java.util;Base64$Decoder;false;wrap;(InputStream);;Argument[0];ReturnValue;taint",
"org.apache.commons.codec;Encoder;true;encode;(Object);;Argument[0];ReturnValue;taint",
"org.apache.commons.codec;Decoder;true;decode;(Object);;Argument[0];ReturnValue;taint",
"org.apache.commons.codec;BinaryEncoder;true;encode;(byte[]);;Argument[0];ReturnValue;taint",
"org.apache.commons.codec;BinaryDecoder;true;decode;(byte[]);;Argument[0];ReturnValue;taint",
"org.apache.commons.codec;StringEncoder;true;encode;(String);;Argument[0];ReturnValue;taint",
"org.apache.commons.codec;StringDecoder;true;decode;(String);;Argument[0];ReturnValue;taint",
"org.apache.commons.io;IOUtils;false;buffer;;;Argument[0];ReturnValue;taint",
"org.apache.commons.io;IOUtils;false;readLines;;;Argument[0];ReturnValue;taint",
"org.apache.commons.io;IOUtils;false;readFully;(InputStream,int);;Argument[0];ReturnValue;taint",
"org.apache.commons.io;IOUtils;false;toBufferedInputStream;;;Argument[0];ReturnValue;taint",
"org.apache.commons.io;IOUtils;false;toBufferedReader;;;Argument[0];ReturnValue;taint",
"org.apache.commons.io;IOUtils;false;toByteArray;;;Argument[0];ReturnValue;taint",
"org.apache.commons.io;IOUtils;false;toCharArray;;;Argument[0];ReturnValue;taint",
"org.apache.commons.io;IOUtils;false;toInputStream;;;Argument[0];ReturnValue;taint",
"org.apache.commons.io;IOUtils;false;toString;;;Argument[0];ReturnValue;taint",
"java.net;URLDecoder;false;decode;;;Argument[0];ReturnValue;taint",
"java.net;URI;false;create;;;Argument[0];ReturnValue;taint",
"javax.xml.transform.sax;SAXSource;false;sourceToInputSource;;;Argument[0];ReturnValue;taint",
// arg to arg
"java.lang;System;false;arraycopy;;;Argument[0];Argument[2];taint",
"org.apache.commons.io;IOUtils;false;copy;;;Argument[0];Argument[1];taint",
"org.apache.commons.io;IOUtils;false;copyLarge;;;Argument[0];Argument[1];taint",
"org.apache.commons.io;IOUtils;false;read;;;Argument[0];Argument[1];taint",
"org.apache.commons.io;IOUtils;false;readFully;(InputStream,byte[]);;Argument[0];Argument[1];taint",
"org.apache.commons.io;IOUtils;false;readFully;(InputStream,byte[],int,int);;Argument[0];Argument[1];taint",
"org.apache.commons.io;IOUtils;false;readFully;(InputStream,ByteBuffer);;Argument[0];Argument[1];taint",
"org.apache.commons.io;IOUtils;false;readFully;(ReadableByteChannel,ByteBuffer);;Argument[0];Argument[1];taint",
"org.apache.commons.io;IOUtils;false;readFully;(Reader,char[]);;Argument[0];Argument[1];taint",
"org.apache.commons.io;IOUtils;false;readFully;(Reader,char[],int,int);;Argument[0];Argument[1];taint",
"org.apache.commons.io;IOUtils;false;write;;;Argument[0];Argument[1];taint",
"org.apache.commons.io;IOUtils;false;writeChunked;;;Argument[0];Argument[1];taint",
"org.apache.commons.io;IOUtils;false;writeLines;;;Argument[0];Argument[2];taint",
"org.apache.commons.io;IOUtils;false;writeLines;;;Argument[1];Argument[2];taint",
// constructor flow
"java.io;File;false;File;;;Argument[0];Argument[-1];taint",
"java.io;File;false;File;;;Argument[1];Argument[-1];taint",
"java.net;URI;false;URI;(String);;Argument[0];Argument[-1];taint",
"java.net;URL;false;URL;(String);;Argument[0];Argument[-1];taint",
"javax.xml.transform.stream;StreamSource;false;StreamSource;;;Argument[0];Argument[-1];taint",
"javax.xml.transform.sax;SAXSource;false;SAXSource;(InputSource);;Argument[0];Argument[-1];taint",
"javax.xml.transform.sax;SAXSource;false;SAXSource;(XMLReader,InputSource);;Argument[1];Argument[-1];taint",
"org.xml.sax;InputSource;false;InputSource;;;Argument[0];Argument[-1];taint",
"javax.servlet.http;Cookie;false;Cookie;;;Argument[0];Argument[-1];taint",
"javax.servlet.http;Cookie;false;Cookie;;;Argument[1];Argument[-1];taint",
"java.util.zip;ZipInputStream;false;ZipInputStream;;;Argument[0];Argument[-1];taint",
"java.util.zip;GZIPInputStream;false;GZIPInputStream;;;Argument[0];Argument[-1];taint",
"java.util;StringTokenizer;false;StringTokenizer;;;Argument[0];Argument[-1];taint",
"java.beans;XMLDecoder;false;XMLDecoder;;;Argument[0];Argument[-1];taint",
"com.esotericsoftware.kryo.io;Input;false;Input;;;Argument[0];Argument[-1];taint",
"com.esotericsoftware.kryo5.io;Input;false;Input;;;Argument[0];Argument[-1];taint",
"java.io;BufferedInputStream;false;BufferedInputStream;;;Argument[0];Argument[-1];taint",
"java.io;DataInputStream;false;DataInputStream;;;Argument[0];Argument[-1];taint",
"java.io;ByteArrayInputStream;false;ByteArrayInputStream;;;Argument[0];Argument[-1];taint",
"java.io;ObjectInputStream;false;ObjectInputStream;;;Argument[0];Argument[-1];taint",
"java.io;StringReader;false;StringReader;;;Argument[0];Argument[-1];taint",
"java.io;CharArrayReader;false;CharArrayReader;;;Argument[0];Argument[-1];taint",
"java.io;BufferedReader;false;BufferedReader;;;Argument[0];Argument[-1];taint",
"java.io;InputStreamReader;false;InputStreamReader;;;Argument[0];Argument[-1];taint"
]
}
/**
* A unit class for adding additional source model rows.
*
* Extend this class to add additional source definitions.
*/
class SourceModelCsv extends Unit {
/** Holds if `row` specifies a source definition. */
abstract predicate row(string row);
}
/**
* A unit class for adding additional sink model rows.
*
* Extend this class to add additional sink definitions.
*/
class SinkModelCsv extends Unit {
/** Holds if `row` specifies a sink definition. */
abstract predicate row(string row);
}
/**
* A unit class for adding additional summary model rows.
*
* Extend this class to add additional flow summary definitions.
*/
class SummaryModelCsv extends Unit {
/** Holds if `row` specifies a summary definition. */
abstract predicate row(string row);
}
private predicate sourceModel(string row) {
sourceModelCsv(row) or
any(SourceModelCsv s).row(row)
}
private predicate sinkModel(string row) {
sinkModelCsv(row) or
any(SinkModelCsv s).row(row)
}
private predicate summaryModel(string row) {
summaryModelCsv(row) or
any(SummaryModelCsv s).row(row)
}
/** Holds if a source model exists for the given parameters. */
predicate sourceModel(
string namespace, string type, boolean subtypes, string name, string signature, string ext,
string output, string kind
) {
exists(string row |
sourceModel(row) and
row.splitAt(";", 0) = namespace and
row.splitAt(";", 1) = type and
row.splitAt(";", 2) = subtypes.toString() and
subtypes = [true, false] and
row.splitAt(";", 3) = name and
row.splitAt(";", 4) = signature and
row.splitAt(";", 5) = ext and
row.splitAt(";", 6) = output and
row.splitAt(";", 7) = kind
)
}
/** Holds if a sink model exists for the given parameters. */
predicate sinkModel(
string namespace, string type, boolean subtypes, string name, string signature, string ext,
string input, string kind
) {
exists(string row |
sinkModel(row) and
row.splitAt(";", 0) = namespace and
row.splitAt(";", 1) = type and
row.splitAt(";", 2) = subtypes.toString() and
subtypes = [true, false] and
row.splitAt(";", 3) = name and
row.splitAt(";", 4) = signature and
row.splitAt(";", 5) = ext and
row.splitAt(";", 6) = input and
row.splitAt(";", 7) = kind
)
}
/** Holds if a summary model exists for the given parameters. */
predicate summaryModel(
string namespace, string type, boolean subtypes, string name, string signature, string ext,
string input, string output, string kind
) {
summaryModel(namespace, type, subtypes, name, signature, ext, input, output, kind, _)
}
/** Holds if a summary model `row` exists for the given parameters. */
predicate summaryModel(
string namespace, string type, boolean subtypes, string name, string signature, string ext,
string input, string output, string kind, string row
) {
summaryModel(row) and
row.splitAt(";", 0) = namespace and
row.splitAt(";", 1) = type and
row.splitAt(";", 2) = subtypes.toString() and
subtypes = [true, false] and
row.splitAt(";", 3) = name and
row.splitAt(";", 4) = signature and
row.splitAt(";", 5) = ext and
row.splitAt(";", 6) = input and
row.splitAt(";", 7) = output and
row.splitAt(";", 8) = kind
}
private predicate relevantPackage(string package) {
sourceModel(package, _, _, _, _, _, _, _) or
sinkModel(package, _, _, _, _, _, _, _) or
summaryModel(package, _, _, _, _, _, _, _, _)
}
private predicate packageLink(string shortpkg, string longpkg) {
relevantPackage(shortpkg) and
relevantPackage(longpkg) and
longpkg.prefix(longpkg.indexOf(".")) = shortpkg
}
private predicate canonicalPackage(string package) {
relevantPackage(package) and not packageLink(_, package)
}
private predicate canonicalPkgLink(string package, string subpkg) {
canonicalPackage(package) and
(subpkg = package or packageLink(package, subpkg))
}
/**
* Holds if CSV framework coverage of `package` is `n` api endpoints of the
* kind `(kind, part)`.
*/
predicate modelCoverage(string package, int pkgs, string kind, string part, int n) {
pkgs = strictcount(string subpkg | canonicalPkgLink(package, subpkg)) and
(
part = "source" and
n =
strictcount(string subpkg, string type, boolean subtypes, string name, string signature,
string ext, string output |
canonicalPkgLink(package, subpkg) and
sourceModel(subpkg, type, subtypes, name, signature, ext, output, kind)
)
or
part = "sink" and
n =
strictcount(string subpkg, string type, boolean subtypes, string name, string signature,
string ext, string input |
canonicalPkgLink(package, subpkg) and
sinkModel(subpkg, type, subtypes, name, signature, ext, input, kind)
)
or
part = "summary" and
n =
strictcount(string subpkg, string type, boolean subtypes, string name, string signature,
string ext, string input, string output |
canonicalPkgLink(package, subpkg) and
summaryModel(subpkg, type, subtypes, name, signature, ext, input, output, kind)
)
)
}
/** Provides a query predicate to check the CSV data for validation errors. */
module CsvValidation {
/** Holds if some row in a CSV-based flow model appears to contain typos. */
query predicate invalidModelRow(string msg) {
exists(string pred, string namespace, string type, string name, string signature, string ext |
sourceModel(namespace, type, _, name, signature, ext, _, _) and pred = "source"
or
sinkModel(namespace, type, _, name, signature, ext, _, _) and pred = "sink"
or
summaryModel(namespace, type, _, name, signature, ext, _, _, _) and pred = "summary"
|
not namespace.regexpMatch("[a-zA-Z0-9_\\.]+") and
msg = "Dubious namespace \"" + namespace + "\" in " + pred + " model."
or
not type.regexpMatch("[a-zA-Z0-9_\\$<>]+") and
msg = "Dubious type \"" + type + "\" in " + pred + " model."
or
not name.regexpMatch("[a-zA-Z0-9_]*") and
msg = "Dubious name \"" + name + "\" in " + pred + " model."
or
not signature.regexpMatch("|\\([a-zA-Z0-9_\\.\\$<>,\\[\\]]*\\)") and
msg = "Dubious signature \"" + signature + "\" in " + pred + " model."
or
not ext.regexpMatch("|Annotated") and
msg = "Unrecognized extra API graph element \"" + ext + "\" in " + pred + " model."
)
or
exists(string pred, string input, string part |
sinkModel(_, _, _, _, _, _, input, _) and pred = "sink"
or
summaryModel(_, _, _, _, _, _, input, _, _) and pred = "summary"
|
(
invalidSpecComponent(input, part) and
not part = "" and
not (part = "Argument" and pred = "sink") and
not parseArg(part, _)
or
specSplit(input, part, _) and
parseParam(part, _)
) and
msg = "Unrecognized input specification \"" + part + "\" in " + pred + " model."
)
or
exists(string pred, string output, string part |
sourceModel(_, _, _, _, _, _, output, _) and pred = "source"
or
summaryModel(_, _, _, _, _, _, _, output, _) and pred = "summary"
|
invalidSpecComponent(output, part) and
not part = "" and
not (part = ["Argument", "Parameter"] and pred = "source") and
msg = "Unrecognized output specification \"" + part + "\" in " + pred + " model."
)
or
exists(string pred, string row, int expect |
sourceModel(row) and expect = 8 and pred = "source"
or
sinkModel(row) and expect = 8 and pred = "sink"
or
summaryModel(row) and expect = 9 and pred = "summary"
|
exists(int cols |
cols = 1 + max(int n | exists(row.splitAt(";", n))) and
cols != expect and
msg =
"Wrong number of columns in " + pred + " model row, expected " + expect + ", got " + cols +
"."
)
or
exists(string b |
b = row.splitAt(";", 2) and
not b = ["true", "false"] and
msg = "Invalid boolean \"" + b + "\" in " + pred + " model."
)
)
}
}
pragma[nomagic]
private predicate elementSpec(
string namespace, string type, boolean subtypes, string name, string signature, string ext
) {
sourceModel(namespace, type, subtypes, name, signature, ext, _, _) or
sinkModel(namespace, type, subtypes, name, signature, ext, _, _) or
summaryModel(namespace, type, subtypes, name, signature, ext, _, _, _)
}
private string paramsStringPart(Callable c, int i) {
i = -1 and result = "("
or
exists(int n, string p | c.getParameterType(n).getErasure().toString() = p |
i = 2 * n and result = p
or
i = 2 * n - 1 and result = "," and n != 0
)
or
i = 2 * c.getNumberOfParameters() and result = ")"
}
private string paramsString(Callable c) {
result = concat(int i | | paramsStringPart(c, i) order by i)
}
private Element interpretElement0(
string namespace, string type, boolean subtypes, string name, string signature
) {
elementSpec(namespace, type, subtypes, name, signature, _) and
exists(RefType t | t.hasQualifiedName(namespace, type) |
exists(Member m |
(
result = m
or
subtypes = true and result.(SrcMethod).overridesOrInstantiates+(m)
) and
m.getDeclaringType() = t and
m.hasName(name)
|
signature = "" or
m.(Callable).getSignature() = any(string nameprefix) + signature or
paramsString(m) = signature
)
or
(if subtypes = true then result.(SrcRefType).getASourceSupertype*() = t else result = t) and
name = "" and
signature = ""
)
}
/** Gets the source/sink/summary element corresponding to the supplied parameters. */
Element interpretElement(
string namespace, string type, boolean subtypes, string name, string signature, string ext
) {
elementSpec(namespace, type, subtypes, name, signature, ext) and
exists(Element e | e = interpretElement0(namespace, type, subtypes, name, signature) |
ext = "" and result = e
or
ext = "Annotated" and result.(Annotatable).getAnAnnotation().getType() = e
)
}
private predicate parseField(string c, FieldContent f) {
specSplit(_, c, _) and
exists(string fieldRegex, string package, string className, string fieldName |
fieldRegex = "^Field\\[(.*)\\.([^.]+)\\.([^.]+)\\]$" and
package = c.regexpCapture(fieldRegex, 1) and
className = c.regexpCapture(fieldRegex, 2) and
fieldName = c.regexpCapture(fieldRegex, 3) and
f.getField().hasQualifiedName(package, className, fieldName)
)
}
/** A string representing a synthetic instance field. */
class SyntheticField extends string {
SyntheticField() { parseSynthField(_, this) }
/**
* Gets the type of this field. The default type is `Object`, but this can be
* overridden.
*/
Type getType() { result instanceof TypeObject }
}
private predicate parseSynthField(string c, string f) {
specSplit(_, c, _) and
c.regexpCapture("SyntheticField\\[([.a-zA-Z0-9]+)\\]", 1) = f
}
/** Holds if the specification component parses as a `Content`. */
predicate parseContent(string component, Content content) {
parseField(component, content)
or
parseSynthField(component, content.(SyntheticFieldContent).getField())
or
component = "ArrayElement" and content instanceof ArrayContent
or
component = "Element" and content instanceof CollectionContent
or
component = "MapKey" and content instanceof MapKeyContent
or
component = "MapValue" and content instanceof MapValueContent
}
cached
private module Cached {
/**
* Holds if `node` is specified as a source with the given kind in a CSV flow
* model.
*/
cached
predicate sourceNode(Node node, string kind) {
exists(InterpretNode n | isSourceNode(n, kind) and n.asNode() = node)
}
/**
* Holds if `node` is specified as a sink with the given kind in a CSV flow
* model.
*/
cached
predicate sinkNode(Node node, string kind) {
exists(InterpretNode n | isSinkNode(n, kind) and n.asNode() = node)
}
}
import Cached

View File

@@ -0,0 +1,248 @@
/**
* Provides classes representing various flow sources for taint tracking.
*/
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.DefUse
import semmle.code.java.frameworks.Jdbc
import semmle.code.java.frameworks.Networking
import semmle.code.java.frameworks.Properties
import semmle.code.java.frameworks.Rmi
import semmle.code.java.frameworks.Servlets
import semmle.code.java.frameworks.ApacheHttp
import semmle.code.java.frameworks.android.XmlParsing
import semmle.code.java.frameworks.android.WebView
import semmle.code.java.frameworks.JaxWS
import semmle.code.java.frameworks.javase.WebSocket
import semmle.code.java.frameworks.android.Android
import semmle.code.java.frameworks.android.Intent
import semmle.code.java.frameworks.play.Play
import semmle.code.java.frameworks.spring.SpringWeb
import semmle.code.java.frameworks.spring.SpringController
import semmle.code.java.frameworks.spring.SpringWebClient
import semmle.code.java.frameworks.Guice
import semmle.code.java.frameworks.struts.StrutsActions
import semmle.code.java.frameworks.Thrift
private import semmle.code.java.dataflow.ExternalFlow
/** A data flow source of remote user input. */
abstract class RemoteFlowSource extends DataFlow::Node {
/** Gets a string that describes the type of this remote flow source. */
abstract string getSourceType();
}
private class ExternalRemoteFlowSource extends RemoteFlowSource {
ExternalRemoteFlowSource() { sourceNode(this, "remote") }
override string getSourceType() { result = "external" }
}
private class RmiMethodParameterSource extends RemoteFlowSource {
RmiMethodParameterSource() {
exists(RemoteCallableMethod method |
method.getAParameter() = this.asParameter() and
(
getType() instanceof PrimitiveType or
getType() instanceof TypeString
)
)
}
override string getSourceType() { result = "RMI method parameter" }
}
private class JaxWsMethodParameterSource extends RemoteFlowSource {
JaxWsMethodParameterSource() {
exists(JaxWsEndpoint endpoint |
endpoint.getARemoteMethod().getAParameter() = this.asParameter()
)
}
override string getSourceType() { result = "Jax WS method parameter" }
}
private class JaxRsMethodParameterSource extends RemoteFlowSource {
JaxRsMethodParameterSource() {
exists(JaxRsResourceClass service |
service.getAnInjectableCallable().getAParameter() = this.asParameter() or
service.getAnInjectableField().getAnAccess() = this.asExpr()
)
}
override string getSourceType() { result = "Jax Rs method parameter" }
}
private predicate variableStep(Expr tracked, VarAccess sink) {
exists(VariableAssign def |
def.getSource() = tracked and
defUsePair(def, sink)
)
}
private class ReverseDnsSource extends RemoteFlowSource {
ReverseDnsSource() {
// Try not to trigger on `localhost`.
exists(MethodAccess m | m = this.asExpr() |
m.getMethod() instanceof ReverseDNSMethod and
not exists(MethodAccess l |
(variableStep(l, m.getQualifier()) or l = m.getQualifier()) and
l.getMethod().getName() = "getLocalHost"
)
)
}
override string getSourceType() { result = "reverse DNS lookup" }
}
private class MessageBodyReaderParameterSource extends RemoteFlowSource {
MessageBodyReaderParameterSource() {
exists(MessageBodyReaderRead m |
m.getParameter(4) = this.asParameter() or
m.getParameter(5) = this.asParameter()
)
}
override string getSourceType() { result = "MessageBodyReader parameter" }
}
private class PlayParameterSource extends RemoteFlowSource {
PlayParameterSource() { exists(PlayActionMethodQueryParameter p | p = this.asParameter()) }
override string getSourceType() { result = "Play Query Parameters" }
}
private class SpringServletInputParameterSource extends RemoteFlowSource {
SpringServletInputParameterSource() {
this.asParameter() = any(SpringRequestMappingParameter srmp | srmp.isTaintedInput())
}
override string getSourceType() { result = "Spring servlet input parameter" }
}
private class GuiceRequestParameterSource extends RemoteFlowSource {
GuiceRequestParameterSource() {
exists(GuiceRequestParametersAnnotation a |
a = this.asParameter().getAnAnnotation() or
a = this.asExpr().(FieldRead).getField().getAnAnnotation()
)
}
override string getSourceType() { result = "Guice request parameter" }
}
private class Struts2ActionSupportClassFieldReadSource extends RemoteFlowSource {
Struts2ActionSupportClassFieldReadSource() {
exists(Struts2ActionSupportClass c |
c.getASetterMethod().getField() = this.asExpr().(FieldRead).getField()
)
}
override string getSourceType() { result = "Struts2 ActionSupport field" }
}
private class ThriftIfaceParameterSource extends RemoteFlowSource {
ThriftIfaceParameterSource() {
exists(ThriftIface i | i.getAnImplementingMethod().getAParameter() = this.asParameter())
}
override string getSourceType() { result = "Thrift Iface parameter" }
}
/** Class for `tainted` user input. */
abstract class UserInput extends DataFlow::Node { }
/**
* Input that may be controlled by a remote user.
*/
private class RemoteUserInput extends UserInput {
RemoteUserInput() { this instanceof RemoteFlowSource }
}
/** A node with input that may be controlled by a local user. */
abstract class LocalUserInput extends UserInput { }
/**
* A node with input from the local environment, such as files, standard in,
* environment variables, and main method parameters.
*/
class EnvInput extends LocalUserInput {
EnvInput() {
// Parameters to a main method.
exists(MainMethod main | this.asParameter() = main.getParameter(0))
or
// Args4j arguments.
exists(Field f | this.asExpr() = f.getAnAccess() |
f.getAnAnnotation().getType().getQualifiedName() = "org.kohsuke.args4j.Argument"
)
or
// Results from various specific methods.
this.asExpr().(MethodAccess).getMethod() instanceof EnvReadMethod
or
// Access to `System.in`.
exists(Field f | this.asExpr() = f.getAnAccess() | f instanceof SystemIn)
or
// Access to files.
this.asExpr()
.(ConstructorCall)
.getConstructedType()
.hasQualifiedName("java.io", "FileInputStream")
}
}
/** A node with input from a database. */
class DatabaseInput extends LocalUserInput {
DatabaseInput() { this.asExpr().(MethodAccess).getMethod() instanceof ResultSetGetStringMethod }
}
/** A method that reads from the environment, such as `System.getProperty` or `System.getenv`. */
class EnvReadMethod extends Method {
EnvReadMethod() {
this instanceof MethodSystemGetenv or
this instanceof PropertiesGetPropertyMethod or
this instanceof MethodSystemGetProperty
}
}
/** The type `java.net.InetAddress`. */
class TypeInetAddr extends RefType {
TypeInetAddr() { this.getQualifiedName() = "java.net.InetAddress" }
}
/** A reverse DNS method. */
class ReverseDNSMethod extends Method {
ReverseDNSMethod() {
this.getDeclaringType() instanceof TypeInetAddr and
(
this.getName() = "getHostName" or
this.getName() = "getCanonicalHostName"
)
}
}
/** Android `Intent` that may have come from a hostile application. */
class AndroidIntentInput extends DataFlow::Node {
Type receiverType;
AndroidIntentInput() {
exists(MethodAccess ma, AndroidGetIntentMethod m |
ma.getMethod().overrides*(m) and
this.asExpr() = ma and
receiverType = ma.getReceiverType()
)
or
exists(Method m, AndroidReceiveIntentMethod rI |
m.overrides*(rI) and
this.asParameter() = m.getParameter(1) and
receiverType = m.getDeclaringType()
)
}
}
/** Exported Android `Intent` that may have come from a hostile application. */
class ExportedAndroidIntentInput extends RemoteFlowSource, AndroidIntentInput {
ExportedAndroidIntentInput() { receiverType.(ExportableAndroidComponent).isExported() }
override string getSourceType() { result = "Exported Android intent source" }
}

View File

@@ -0,0 +1,199 @@
/**
* Provides classes representing various flow steps for taint tracking.
*/
private import java
private import semmle.code.java.dataflow.DataFlow
/**
* A module importing the frameworks that implement additional flow steps,
* ensuring that they are visible to the taint tracking library.
*/
private module Frameworks {
private import semmle.code.java.frameworks.jackson.JacksonSerializability
private import semmle.code.java.frameworks.android.Intent
private import semmle.code.java.frameworks.android.SQLite
private import semmle.code.java.frameworks.Guice
private import semmle.code.java.frameworks.Protobuf
private import semmle.code.java.frameworks.guava.Guava
private import semmle.code.java.frameworks.apache.Lang
private import semmle.code.java.frameworks.ApacheHttp
}
/**
* A method that returns the exact value of one of its parameters or the qualifier.
*
* Extend this class and override `returnsValue` to add additional value-preserving steps through a
* method that should be added to the basic local flow step relation.
*
* These steps will be visible for all global data-flow purposes, as well as via
* `DataFlow::Node.getASuccessor` and other related functions exposing intraprocedural dataflow.
*/
abstract class ValuePreservingMethod extends Method {
/**
* Holds if this method returns precisely the value passed into argument `arg`.
* `arg` is a parameter index, or is -1 to indicate the qualifier.
*/
abstract predicate returnsValue(int arg);
}
/**
* A method that returns the exact value of its qualifier (e.g., `return this;`)
*
* Extend this class to add additional value-preserving steps from qualifier to return value through a
* method that should be added to the basic local flow step relation.
*
* These steps will be visible for all global data-flow purposes, as well as via
* `DataFlow::Node.getASuccessor` and other related functions exposing intraprocedural dataflow.
*/
abstract class FluentMethod extends ValuePreservingMethod {
override predicate returnsValue(int arg) { arg = -1 }
}
private class StandardLibraryValuePreservingMethod extends ValuePreservingMethod {
int returnsArgNo;
StandardLibraryValuePreservingMethod() {
this.getDeclaringType().hasQualifiedName("java.util", "Objects") and
(
this.hasName(["requireNonNull", "requireNonNullElseGet"]) and returnsArgNo = 0
or
this.hasName("requireNonNullElse") and returnsArgNo = [0 .. this.getNumberOfParameters() - 1]
or
this.hasName("toString") and returnsArgNo = 1
)
or
this.getDeclaringType().getASourceSupertype*().hasQualifiedName("java.util", "Stack") and
this.hasName("push") and
returnsArgNo = 0
}
override predicate returnsValue(int argNo) { argNo = returnsArgNo }
}
/**
* 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);
}
/**
* A method or constructor that preserves taint.
*
* Extend this class and override at least one of `returnsTaintFrom` or `transfersTaint`
* to add additional taint steps through a method that should apply to all taint configurations.
*/
abstract class TaintPreservingCallable extends Callable {
/**
* Holds if this callable returns tainted data when `arg` tainted.
* `arg` is a parameter index, or is -1 to indicate the qualifier.
*/
predicate returnsTaintFrom(int arg) { none() }
/**
* Holds if this callable writes tainted data to `sink` when `src` is tainted.
* `src` and `sink` are parameter indices, or -1 to indicate the qualifier.
*/
predicate transfersTaint(int src, int sink) { none() }
}
private class StringTaintPreservingMethod extends TaintPreservingCallable {
StringTaintPreservingMethod() {
this.getDeclaringType() instanceof TypeString and
(
this.hasName([
"concat", "copyValueOf", "endsWith", "format", "formatted", "getBytes", "indent",
"intern", "join", "repeat", "split", "strip", "stripIndent", "stripLeading",
"stripTrailing", "substring", "toCharArray", "toLowerCase", "toString", "toUpperCase",
"trim"
])
or
this.hasName("valueOf") and this.getParameterType(0) instanceof Array
)
}
override predicate returnsTaintFrom(int arg) {
arg = -1 and not this.isStatic()
or
this.hasName(["concat", "copyValueOf", "valueOf"]) and arg = 0
or
this.hasName(["format", "formatted", "join"]) and arg = [0 .. getNumberOfParameters()]
}
}
private class StringTaintPreservingConstructor extends Constructor, TaintPreservingCallable {
StringTaintPreservingConstructor() { this.getDeclaringType() instanceof TypeString }
override predicate returnsTaintFrom(int arg) { arg = 0 }
}
private class NumberTaintPreservingCallable extends TaintPreservingCallable {
int argument;
NumberTaintPreservingCallable() {
this.getDeclaringType().getASupertype*().hasQualifiedName("java.lang", "Number") and
(
this instanceof Constructor and
argument = 0
or
this.getName().matches(["to%String", "toByteArray", "%Value"]) and
argument = -1
or
this.getName().matches(["parse%", "valueOf%", "to%String", "decode"]) and
argument = 0
)
}
override predicate returnsTaintFrom(int arg) { arg = argument }
}
/** Holds for the types `StringBuilder`, `StringBuffer`, and `StringWriter`. */
private predicate stringBuilderType(RefType t) {
t instanceof StringBuildingType or
t.hasQualifiedName("java.io", "StringWriter")
}
private class StringBuilderTaintPreservingCallable extends TaintPreservingCallable {
StringBuilderTaintPreservingCallable() {
exists(Method m |
this.(Method).overrides*(m) and
stringBuilderType(m.getDeclaringType()) and
m.hasName(["append", "insert", "replace", "toString", "write"])
)
or
this.(Constructor).getParameterType(0) instanceof RefType and
stringBuilderType(this.getDeclaringType())
}
override predicate returnsTaintFrom(int arg) {
arg = -1 and
not this instanceof Constructor
or
this instanceof Constructor and arg = 0
or
this.hasName("append") and arg = 0
or
this.hasName("insert") and arg = 1
or
this.hasName("replace") and arg = 2
}
override predicate transfersTaint(int src, int sink) {
returnsTaintFrom(src) and
sink = -1 and
src != -1 and
not this instanceof Constructor
or
this.hasName("write") and
src = 0 and
sink = -1
}
}

View File

@@ -0,0 +1,51 @@
/**
* Provides classes and predicates for definining flow summaries.
*/
import java
private import internal.FlowSummaryImpl as Impl
private import internal.DataFlowDispatch
private import internal.DataFlowUtil
// import all instances of SummarizedCallable below
private module Summaries {
private import semmle.code.java.dataflow.ExternalFlow
}
class SummaryComponent = Impl::Public::SummaryComponent;
/** Provides predicates for constructing summary components. */
module SummaryComponent {
import Impl::Public::SummaryComponent
/** Gets a summary component that represents a qualifier. */
SummaryComponent qualifier() { result = argument(-1) }
/** Gets a summary component for field `f`. */
SummaryComponent field(Field f) { result = content(any(FieldContent c | c.getField() = f)) }
/** Gets a summary component that represents the return value of a call. */
SummaryComponent return() { result = return(_) }
}
class SummaryComponentStack = Impl::Public::SummaryComponentStack;
/** Provides predicates for constructing stacks of summary components. */
module SummaryComponentStack {
import Impl::Public::SummaryComponentStack
/** Gets a singleton stack representing a qualifier. */
SummaryComponentStack qualifier() { result = singleton(SummaryComponent::qualifier()) }
/** Gets a stack representing a field `f` of `object`. */
SummaryComponentStack fieldOf(Field f, SummaryComponentStack object) {
result = push(SummaryComponent::field(f), object)
}
/** Gets a singleton stack representing a (normal) return. */
SummaryComponentStack return() { result = singleton(SummaryComponent::return()) }
}
class SummarizedCallable = Impl::Public::SummarizedCallable;
class RequiredSummaryComponentStack = Impl::Public::RequiredSummaryComponentStack;

View File

@@ -0,0 +1,267 @@
/**
* Provides classes and predicates for reasoning about explicit and implicit
* instance accesses.
*/
import java
/**
* Holds if `cc` constructs an inner class that holds a reference to its
* enclosing class `t` and the enclosing instance is not given explicitly as a
* qualifier of the constructor.
*/
private predicate implicitSetEnclosingInstance(ConstructorCall cc, RefType t) {
exists(InnerClass ic |
ic = cc.getConstructedType().getSourceDeclaration() and
ic.hasEnclosingInstance() and
ic.getEnclosingType() = t and
not cc instanceof ThisConstructorInvocationStmt and
not exists(cc.getQualifier())
)
}
/**
* Holds if `cc` implicitly sets the enclosing instance of the constructed
* inner class to `this`.
*/
private predicate implicitSetEnclosingInstanceToThis(ConstructorCall cc) {
exists(RefType t |
implicitSetEnclosingInstance(cc, t) and
cc.getEnclosingCallable().getDeclaringType().getASourceSupertype*() = t
)
}
/**
* Gets the closest enclosing type of `ic` that is also a subtype of `t`.
*/
private RefType getEnclosing(InnerClass ic, RefType t) {
exists(RefType enclosing | enclosing = ic.getEnclosingType() |
if enclosing.getASourceSupertype*() = t
then result = enclosing
else result = getEnclosing(enclosing, t)
)
}
/**
* Holds if `cc` implicitly sets the enclosing instance of type `t2` of the
* constructed inner class to `t1.this`.
*/
private predicate implicitEnclosingThisCopy(ConstructorCall cc, RefType t1, RefType t2) {
implicitSetEnclosingInstance(cc, t2) and
not implicitSetEnclosingInstanceToThis(cc) and
t1 = getEnclosing(cc.getEnclosingCallable().getDeclaringType(), t2)
}
/**
* Holds if an enclosing instance of the form `t.this` is accessed by `e`.
*/
private predicate enclosingInstanceAccess(ExprParent e, RefType t) {
e.(InstanceAccess).isEnclosingInstanceAccess(t)
or
exists(MethodAccess ma |
ma.isEnclosingMethodAccess(t) and ma = e and not exists(ma.getQualifier())
)
or
exists(FieldAccess fa | fa.isEnclosingFieldAccess(t) and fa = e and not exists(fa.getQualifier()))
or
implicitEnclosingThisCopy(e, t, _)
}
/**
* Holds if an enclosing instance of the form `t2.this` is accessed by `e`, and
* this desugars into `this.enclosing.enclosing...enclosing`. The prefix of the
* desugared access with `i` enclosing instance field accesses has type `t1`.
*/
private predicate derivedInstanceAccess(ExprParent e, int i, RefType t1, RefType t2) {
enclosingInstanceAccess(e, t2) and
i = 0 and
exists(Callable c | c = e.(Expr).getEnclosingCallable() or c = e.(Stmt).getEnclosingCallable() |
t1 = c.getDeclaringType()
)
or
exists(InnerClass ic |
derivedInstanceAccess(e, i - 1, ic, t2) and
ic.getEnclosingType() = t1 and
ic != t2
)
}
cached
private newtype TInstanceAccessExt =
TExplicitInstanceAccess(InstanceAccess ia) or
TThisQualifier(FieldAccess fa) { fa.isOwnFieldAccess() and not exists(fa.getQualifier()) } or
TThisArgument(Call c) {
c instanceof ThisConstructorInvocationStmt
or
c instanceof SuperConstructorInvocationStmt
or
c.(MethodAccess).isOwnMethodAccess() and not exists(c.getQualifier())
} or
TThisEnclosingInstanceCapture(ConstructorCall cc) { implicitSetEnclosingInstanceToThis(cc) } or
TEnclosingInstanceAccess(ExprParent e, RefType t) {
enclosingInstanceAccess(e, t) and not e instanceof InstanceAccess
} or
TInstanceAccessQualifier(ExprParent e, int i, RefType t1, RefType t2) {
derivedInstanceAccess(e, i, t1, t2) and t1 != t2
}
/**
* A generalization of `InstanceAccess` that includes implicit accesses.
*
* The accesses can be divided into 6 kinds:
* - Explicit: Represented by an `InstanceAccess`.
* - Implicit field qualifier: The implicit access associated with an
* unqualified `FieldAccess` to a non-static field.
* - Implicit method qualifier: The implicit access associated with an
* unqualified `MethodAccess` to a non-static method.
* - Implicit this constructor argument: The implicit argument of the value of
* `this` to a constructor call of the form `this()` or `super()`.
* - Implicit enclosing instance capture: The implicit capture of the value of
* the directly enclosing instance of a constructed inner class. This is
* associated with an unqualified constructor call.
* - Implicit enclosing instance qualifier: The instance access that occurs as
* the implicit qualifier of a desugared enclosing instance access.
*
* Of these 6 kinds, the fourth (implicit this constructor argument) is always
* an `OwnInstanceAccess`, whereas the other 5 can be either `OwnInstanceAccess`
* or `EnclosingInstanceAccess`.
*/
class InstanceAccessExt extends TInstanceAccessExt {
private string ppBase() {
exists(EnclosingInstanceAccess enc | enc = this |
result = enc.getQualifier().toString() + "(" + enc.getType() + ")enclosing"
)
or
isOwnInstanceAccess() and result = "this"
}
private string ppKind() {
isExplicit(_) and result = " <" + getAssociatedExprOrStmt().toString() + ">"
or
isImplicitFieldQualifier(_) and result = " <.field>"
or
isImplicitMethodQualifier(_) and result = " <.method>"
or
isImplicitThisConstructorArgument(_) and result = " <constr(this)>"
or
isImplicitEnclosingInstanceCapture(_) and result = " <.new>"
or
isImplicitEnclosingInstanceQualifier(_) and result = "."
}
/** Gets a textual representation of this element. */
string toString() { result = ppBase() + ppKind() }
/** Gets the source location for this element. */
Location getLocation() { result = getAssociatedExprOrStmt().getLocation() }
private ExprParent getAssociatedExprOrStmt() {
this = TExplicitInstanceAccess(result) or
this = TThisQualifier(result) or
this = TThisArgument(result) or
this = TThisEnclosingInstanceCapture(result) or
this = TEnclosingInstanceAccess(result, _) or
this = TInstanceAccessQualifier(result, _, _, _)
}
/** Gets the callable in which this instance access occurs. */
Callable getEnclosingCallable() {
result = getAssociatedExprOrStmt().(Expr).getEnclosingCallable() or
result = getAssociatedExprOrStmt().(Stmt).getEnclosingCallable()
}
/** Holds if this is the explicit instance access `ia`. */
predicate isExplicit(InstanceAccess ia) { this = TExplicitInstanceAccess(ia) }
/** Holds if this is the implicit qualifier of `fa`. */
predicate isImplicitFieldQualifier(FieldAccess fa) {
this = TThisQualifier(fa) or
this = TEnclosingInstanceAccess(fa, _)
}
/** Holds if this is the implicit qualifier of `ma`. */
predicate isImplicitMethodQualifier(MethodAccess ma) {
this = TThisArgument(ma) or
this = TEnclosingInstanceAccess(ma, _)
}
/**
* Holds if this is the implicit `this` argument of `cc`, which is either a
* `ThisConstructorInvocationStmt` or a `SuperConstructorInvocationStmt`.
*/
predicate isImplicitThisConstructorArgument(ConstructorCall cc) { this = TThisArgument(cc) }
/** Holds if this is the implicit qualifier of `cc`. */
predicate isImplicitEnclosingInstanceCapture(ConstructorCall cc) {
this = TThisEnclosingInstanceCapture(cc) or
this = TEnclosingInstanceAccess(cc, _)
}
/**
* Holds if this is the implicit qualifier of the desugared enclosing
* instance access `enc`.
*/
predicate isImplicitEnclosingInstanceQualifier(EnclosingInstanceAccess enc) {
enc.getQualifier() = this
}
/** Holds if this is an access to an object's own instance. */
predicate isOwnInstanceAccess() { not isEnclosingInstanceAccess(_) }
/** Holds if this is an access to an enclosing instance. */
predicate isEnclosingInstanceAccess(RefType t) {
exists(InstanceAccess ia |
this = TExplicitInstanceAccess(ia) and ia.isEnclosingInstanceAccess(t)
)
or
this = TEnclosingInstanceAccess(_, t)
or
exists(int i | this = TInstanceAccessQualifier(_, i, t, _) and i > 0)
}
/** Gets the type of this instance access. */
RefType getType() {
isEnclosingInstanceAccess(result)
or
isOwnInstanceAccess() and result = getEnclosingCallable().getDeclaringType()
}
/** Gets the control flow node associated with this instance access. */
ControlFlowNode getCfgNode() {
exists(ExprParent e | e = getAssociatedExprOrStmt() |
e instanceof Call and result = e
or
e instanceof InstanceAccess and result = e
or
exists(FieldAccess fa | fa = e |
if fa instanceof RValue then fa = result else result.(AssignExpr).getDest() = fa
)
)
}
}
/**
* An access to an object's own instance.
*/
class OwnInstanceAccess extends InstanceAccessExt {
OwnInstanceAccess() { isOwnInstanceAccess() }
}
/**
* An access to an enclosing instance.
*/
class EnclosingInstanceAccess extends InstanceAccessExt {
EnclosingInstanceAccess() { isEnclosingInstanceAccess(_) }
/** Gets the implicit qualifier of this in the desugared representation. */
InstanceAccessExt getQualifier() {
exists(ExprParent e, int i | result = TInstanceAccessQualifier(e, i, _, _) |
this = TInstanceAccessQualifier(e, i + 1, _, _)
or
exists(RefType t | derivedInstanceAccess(e, i + 1, t, t) |
this = TEnclosingInstanceAccess(e, t) or
this = TExplicitInstanceAccess(e)
)
)
}
}

View File

@@ -0,0 +1,166 @@
/**
* Provides classes and predicates for integer guards.
*/
import java
private import SSA
private import RangeUtils
private import RangeAnalysis
/** Gets an expression that might have the value `i`. */
private Expr exprWithIntValue(int i) {
result.(ConstantIntegerExpr).getIntValue() = i or
result.(ChooseExpr).getAResultExpr() = exprWithIntValue(i)
}
/**
* An expression for which the predicate `integerGuard` is relevant.
* This includes `RValue` and `MethodAccess`.
*/
class IntComparableExpr extends Expr {
IntComparableExpr() { this instanceof RValue or this instanceof MethodAccess }
/** Gets an integer that is directly assigned to the expression in case of a variable; or zero. */
int relevantInt() {
exists(SsaExplicitUpdate ssa, SsaSourceVariable v |
this = v.getAnAccess() and
ssa.getSourceVariable() = v and
ssa.getDefiningExpr().(VariableAssign).getSource() = exprWithIntValue(result)
)
or
result = 0
}
}
/**
* An expression that directly tests whether a given expression is equal to `k` or not.
* The set of `k`s is restricted to those that are relevant for the expression or
* have a direct comparison with the expression.
*
* If `result` evaluates to `branch`, then `e` is guaranteed to be equal to `k` if `is_k`
* is true, and different from `k` if `is_k` is false.
*/
pragma[nomagic]
Expr integerGuard(IntComparableExpr e, boolean branch, int k, boolean is_k) {
exists(EqualityTest eqtest, boolean polarity |
eqtest = result and
eqtest.hasOperands(e, any(ConstantIntegerExpr c | c.getIntValue() = k)) and
polarity = eqtest.polarity() and
(
branch = true and is_k = polarity
or
branch = false and is_k = polarity.booleanNot()
)
)
or
exists(EqualityTest eqtest, int val, Expr c, boolean upper |
k = e.relevantInt() and
eqtest = result and
eqtest.hasOperands(e, c) and
bounded(c, any(ZeroBound zb), val, upper, _) and
is_k = false and
(
upper = true and val < k
or
upper = false and val > k
) and
branch = eqtest.polarity()
)
or
exists(ComparisonExpr comp, Expr c, int val, boolean upper |
k = e.relevantInt() and
comp = result and
comp.hasOperands(e, c) and
bounded(c, any(ZeroBound zb), val, upper, _) and
is_k = false
|
// k <= val <= c < e, so e != k
comp.getLesserOperand() = c and
comp.isStrict() and
branch = true and
val >= k and
upper = false
or
comp.getLesserOperand() = c and
comp.isStrict() and
branch = false and
val < k and
upper = true
or
comp.getLesserOperand() = c and
not comp.isStrict() and
branch = true and
val > k and
upper = false
or
comp.getLesserOperand() = c and
not comp.isStrict() and
branch = false and
val <= k and
upper = true
or
comp.getGreaterOperand() = c and
comp.isStrict() and
branch = true and
val <= k and
upper = true
or
comp.getGreaterOperand() = c and
comp.isStrict() and
branch = false and
val > k and
upper = false
or
comp.getGreaterOperand() = c and
not comp.isStrict() and
branch = true and
val < k and
upper = true
or
comp.getGreaterOperand() = c and
not comp.isStrict() and
branch = false and
val >= k and
upper = false
)
}
/**
* A guard that splits the values of a variable into one range with an upper bound of `k-1`
* and one with a lower bound of `k`.
*
* If `branch_with_lower_bound_k` is true then `result` is equivalent to `k <= x`
* and if it is false then `result` is equivalent to `k > x`.
*/
Expr intBoundGuard(RValue x, boolean branch_with_lower_bound_k, int k) {
exists(ComparisonExpr comp, ConstantIntegerExpr c, int val |
comp = result and
comp.hasOperands(x, c) and
c.getIntValue() = val and
x.getVariable().getType() instanceof IntegralType
|
// c < x
comp.getLesserOperand() = c and
comp.isStrict() and
branch_with_lower_bound_k = true and
val + 1 = k
or
// c <= x
comp.getLesserOperand() = c and
not comp.isStrict() and
branch_with_lower_bound_k = true and
val = k
or
// x < c
comp.getGreaterOperand() = c and
comp.isStrict() and
branch_with_lower_bound_k = false and
val = k
or
// x <= c
comp.getGreaterOperand() = c and
not comp.isStrict() and
branch_with_lower_bound_k = false and
val + 1 = k
)
}

View File

@@ -0,0 +1,288 @@
/**
* Provides inferences of the form: `e` equals `b + v` modulo `m` where `e` is
* an expression, `b` is a `Bound` (typically zero or the value of an SSA
* variable), and `v` is an integer in the range `[0 .. m-1]`.
*/
private import internal.rangeanalysis.ModulusAnalysisSpecific::Private
private import Bound
private import internal.rangeanalysis.SsaReadPositionCommon
/**
* Holds if `e + delta` equals `v` at `pos`.
*/
private predicate valueFlowStepSsa(SsaVariable v, SsaReadPosition pos, Expr e, int delta) {
ssaUpdateStep(v, e, delta) and pos.hasReadOfVar(v)
or
exists(Guard guard, boolean testIsTrue |
pos.hasReadOfVar(v) and
guard = eqFlowCond(v, e, delta, true, testIsTrue) and
guardDirectlyControlsSsaRead(guard, pos, testIsTrue)
)
}
/**
* Holds if `add` is the addition of `larg` and `rarg`, neither of which are
* `ConstantIntegerExpr`s.
*/
private predicate nonConstAddition(Expr add, Expr larg, Expr rarg) {
exists(AddExpr a | a = add |
larg = a.getLhs() and
rarg = a.getRhs()
) and
not larg instanceof ConstantIntegerExpr and
not rarg instanceof ConstantIntegerExpr
}
/**
* Holds if `sub` is the subtraction of `larg` and `rarg`, where `rarg` is not
* a `ConstantIntegerExpr`.
*/
private predicate nonConstSubtraction(Expr sub, Expr larg, Expr rarg) {
exists(SubExpr s | s = sub |
larg = s.getLhs() and
rarg = s.getRhs()
) and
not rarg instanceof ConstantIntegerExpr
}
/** Gets an expression that is the remainder modulo `mod` of `arg`. */
private Expr modExpr(Expr arg, int mod) {
exists(RemExpr rem |
result = rem and
arg = rem.getLeftOperand() and
rem.getRightOperand().(ConstantIntegerExpr).getIntValue() = mod and
mod >= 2
)
or
exists(ConstantIntegerExpr c |
mod = 2.pow([1 .. 30]) and
c.getIntValue() = mod - 1 and
result.(BitwiseAndExpr).hasOperands(arg, c)
)
}
/**
* Gets a guard that tests whether `v` is congruent with `val` modulo `mod` on
* its `testIsTrue` branch.
*/
private Guard moduloCheck(SsaVariable v, int val, int mod, boolean testIsTrue) {
exists(Expr rem, ConstantIntegerExpr c, int r, boolean polarity |
result.isEquality(rem, c, polarity) and
c.getIntValue() = r and
rem = modExpr(v.getAUse(), mod) and
(
testIsTrue = polarity and val = r
or
testIsTrue = polarity.booleanNot() and
mod = 2 and
val = 1 - r and
(r = 0 or r = 1)
)
)
}
/**
* Holds if a guard ensures that `v` at `pos` is congruent with `val` modulo `mod`.
*/
private predicate moduloGuardedRead(SsaVariable v, SsaReadPosition pos, int val, int mod) {
exists(Guard guard, boolean testIsTrue |
pos.hasReadOfVar(v) and
guard = moduloCheck(v, val, mod, testIsTrue) and
guardControlsSsaRead(guard, pos, testIsTrue)
)
}
/** Holds if `factor` is a power of 2 that divides `mask`. */
bindingset[mask]
private predicate andmaskFactor(int mask, int factor) {
mask % factor = 0 and
factor = 2.pow([1 .. 30])
}
/** Holds if `e` is evenly divisible by `factor`. */
private predicate evenlyDivisibleExpr(Expr e, int factor) {
exists(ConstantIntegerExpr c, int k | k = c.getIntValue() |
e.(MulExpr).getAnOperand() = c and factor = k.abs() and factor >= 2
or
e.(LShiftExpr).getRhs() = c and factor = 2.pow(k) and k > 0
or
e.(BitwiseAndExpr).getAnOperand() = c and factor = max(int f | andmaskFactor(k, f))
)
}
/**
* Holds if `rix` is the number of input edges to `phi`.
*/
private predicate maxPhiInputRank(SsaPhiNode phi, int rix) {
rix = max(int r | rankedPhiInput(phi, _, _, r))
}
/**
* Gets the remainder of `val` modulo `mod`.
*
* For `mod = 0` the result equals `val` and for `mod > 1` the result is within
* the range `[0 .. mod-1]`.
*/
bindingset[val, mod]
private int remainder(int val, int mod) {
mod = 0 and result = val
or
mod > 1 and result = ((val % mod) + mod) % mod
}
/**
* Holds if `inp` is an input to `phi` and equals `phi` modulo `mod` along `edge`.
*/
private predicate phiSelfModulus(
SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, int mod
) {
exists(SsaBound phibound, int v, int m |
edge.phiInput(phi, inp) and
phibound.getSsa() = phi and
ssaModulus(inp, edge, phibound, v, m) and
mod = m.gcd(v) and
mod != 1
)
}
/**
* Holds if `b + val` modulo `mod` is a candidate congruence class for `phi`.
*/
private predicate phiModulusInit(SsaPhiNode phi, Bound b, int val, int mod) {
exists(SsaVariable inp, SsaReadPositionPhiInputEdge edge |
edge.phiInput(phi, inp) and
ssaModulus(inp, edge, b, val, mod)
)
}
/**
* Holds if all inputs to `phi` numbered `1` to `rix` are equal to `b + val` modulo `mod`.
*/
private predicate phiModulusRankStep(SsaPhiNode phi, Bound b, int val, int mod, int rix) {
rix = 0 and
phiModulusInit(phi, b, val, mod)
or
exists(SsaVariable inp, SsaReadPositionPhiInputEdge edge, int v1, int m1 |
mod != 1 and
val = remainder(v1, mod)
|
exists(int v2, int m2 |
rankedPhiInput(phi, inp, edge, rix) and
phiModulusRankStep(phi, b, v1, m1, rix - 1) and
ssaModulus(inp, edge, b, v2, m2) and
mod = m1.gcd(m2).gcd(v1 - v2)
)
or
exists(int m2 |
rankedPhiInput(phi, inp, edge, rix) and
phiModulusRankStep(phi, b, v1, m1, rix - 1) and
phiSelfModulus(phi, inp, edge, m2) and
mod = m1.gcd(m2)
)
)
}
/**
* Holds if `phi` is equal to `b + val` modulo `mod`.
*/
private predicate phiModulus(SsaPhiNode phi, Bound b, int val, int mod) {
exists(int r |
maxPhiInputRank(phi, r) and
phiModulusRankStep(phi, b, val, mod, r)
)
}
/**
* Holds if `v` at `pos` is equal to `b + val` modulo `mod`.
*/
private predicate ssaModulus(SsaVariable v, SsaReadPosition pos, Bound b, int val, int mod) {
phiModulus(v, b, val, mod) and pos.hasReadOfVar(v)
or
b.(SsaBound).getSsa() = v and pos.hasReadOfVar(v) and val = 0 and mod = 0
or
exists(Expr e, int val0, int delta |
exprModulus(e, b, val0, mod) and
valueFlowStepSsa(v, pos, e, delta) and
val = remainder(val0 + delta, mod)
)
or
moduloGuardedRead(v, pos, val, mod) and b instanceof ZeroBound
}
/**
* Holds if `e` is equal to `b + val` modulo `mod`.
*
* There are two cases for the modulus:
* - `mod = 0`: The equality `e = b + val` is an ordinary equality.
* - `mod > 1`: `val` lies within the range `[0 .. mod-1]`.
*/
cached
predicate exprModulus(Expr e, Bound b, int val, int mod) {
e = b.getExpr(val) and mod = 0
or
evenlyDivisibleExpr(e, mod) and val = 0 and b instanceof ZeroBound
or
exists(SsaVariable v, SsaReadPositionBlock bb |
ssaModulus(v, bb, b, val, mod) and
e = v.getAUse() and
getABasicBlockExpr(bb.getBlock()) = e
)
or
exists(Expr mid, int val0, int delta |
exprModulus(mid, b, val0, mod) and
valueFlowStep(e, mid, delta) and
val = remainder(val0 + delta, mod)
)
or
exists(ConditionalExpr cond, int v1, int v2, int m1, int m2 |
cond = e and
condExprBranchModulus(cond, true, b, v1, m1) and
condExprBranchModulus(cond, false, b, v2, m2) and
mod = m1.gcd(m2).gcd(v1 - v2) and
mod != 1 and
val = remainder(v1, mod)
)
or
exists(Bound b1, Bound b2, int v1, int v2, int m1, int m2 |
addModulus(e, true, b1, v1, m1) and
addModulus(e, false, b2, v2, m2) and
mod = m1.gcd(m2) and
mod != 1 and
val = remainder(v1 + v2, mod)
|
b = b1 and b2 instanceof ZeroBound
or
b = b2 and b1 instanceof ZeroBound
)
or
exists(int v1, int v2, int m1, int m2 |
subModulus(e, true, b, v1, m1) and
subModulus(e, false, any(ZeroBound zb), v2, m2) and
mod = m1.gcd(m2) and
mod != 1 and
val = remainder(v1 - v2, mod)
)
}
private predicate condExprBranchModulus(
ConditionalExpr cond, boolean branch, Bound b, int val, int mod
) {
exprModulus(cond.getBranchExpr(branch), b, val, mod)
}
private predicate addModulus(Expr add, boolean isLeft, Bound b, int val, int mod) {
exists(Expr larg, Expr rarg | nonConstAddition(add, larg, rarg) |
exprModulus(larg, b, val, mod) and isLeft = true
or
exprModulus(rarg, b, val, mod) and isLeft = false
)
}
private predicate subModulus(Expr sub, boolean isLeft, Bound b, int val, int mod) {
exists(Expr larg, Expr rarg | nonConstSubtraction(sub, larg, rarg) |
exprModulus(larg, b, val, mod) and isLeft = true
or
exprModulus(rarg, b, val, mod) and isLeft = false
)
}

View File

@@ -0,0 +1,293 @@
/**
* Provides classes and predicates for null guards.
*/
import java
import SSA
private import semmle.code.java.controlflow.internal.GuardsLogic
private import semmle.code.java.frameworks.apache.Collections
private import RangeUtils
private import IntegerGuards
/** Gets an expression that is always `null`. */
Expr alwaysNullExpr() {
result instanceof NullLiteral or
result.(CastExpr).getExpr() = alwaysNullExpr()
}
/** Gets an equality test between an expression `e` and an enum constant `c`. */
Expr enumConstEquality(Expr e, boolean polarity, EnumConstant c) {
exists(EqualityTest eqtest |
eqtest = result and
eqtest.hasOperands(e, c.getAnAccess()) and
polarity = eqtest.polarity()
)
}
/** Gets an instanceof expression of `v` with type `type` */
InstanceOfExpr instanceofExpr(SsaVariable v, RefType type) {
result.getCheckedType() = type and
result.getExpr() = v.getAUse()
}
/**
* Gets an expression of the form `v1 == v2` or `v1 != v2`.
* The predicate is symmetric in `v1` and `v2`.
*/
EqualityTest varEqualityTestExpr(SsaVariable v1, SsaVariable v2, boolean isEqualExpr) {
result.hasOperands(v1.getAUse(), v2.getAUse()) and
isEqualExpr = result.polarity()
}
/** Gets an expression that is provably not `null`. */
Expr clearlyNotNullExpr(Expr reason) {
result instanceof ClassInstanceExpr and reason = result
or
result instanceof ArrayCreationExpr and reason = result
or
result instanceof TypeLiteral and reason = result
or
result instanceof ThisAccess and reason = result
or
result instanceof StringLiteral and reason = result
or
result instanceof AddExpr and result.getType() instanceof TypeString and reason = result
or
exists(Field f |
result = f.getAnAccess() and
(f.hasName("TRUE") or f.hasName("FALSE")) and
f.getDeclaringType().hasQualifiedName("java.lang", "Boolean") and
reason = result
)
or
result.(CastExpr).getExpr() = clearlyNotNullExpr(reason)
or
result.(AssignExpr).getSource() = clearlyNotNullExpr(reason)
or
exists(ConditionalExpr c, Expr r1, Expr r2 |
c = result and
c.getTrueExpr() = clearlyNotNullExpr(r1) and
c.getFalseExpr() = clearlyNotNullExpr(r2) and
(reason = r1 or reason = r2)
)
or
exists(SsaVariable v, boolean branch, RValue rval, Guard guard |
guard = directNullGuard(v, branch, false) and
guard.controls(rval.getBasicBlock(), branch) and
reason = guard and
rval = v.getAUse() and
result = rval
)
or
exists(SsaVariable v | clearlyNotNull(v, reason) and result = v.getAUse())
or
exists(Method m | m = result.(MethodAccess).getMethod() and reason = result |
m.getDeclaringType().hasQualifiedName("com.google.common.base", "Strings") and
m.hasName("nullToEmpty")
)
}
/** Holds if `v` is an SSA variable that is provably not `null`. */
predicate clearlyNotNull(SsaVariable v, Expr reason) {
exists(Expr src |
src = v.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource() and
src = clearlyNotNullExpr(reason)
)
or
exists(CatchClause cc, LocalVariableDeclExpr decl |
decl = cc.getVariable() and
decl = v.(SsaExplicitUpdate).getDefiningExpr() and
reason = decl
)
or
exists(SsaVariable captured |
v.(SsaImplicitInit).captures(captured) and
clearlyNotNull(captured, reason)
)
or
exists(Field f |
v.getSourceVariable().getVariable() = f and
f.isFinal() and
f.getInitializer() = clearlyNotNullExpr(reason)
)
}
/** Gets an expression that is provably not `null`. */
Expr clearlyNotNullExpr() { result = clearlyNotNullExpr(_) }
/** Holds if `v` is an SSA variable that is provably not `null`. */
predicate clearlyNotNull(SsaVariable v) { clearlyNotNull(v, _) }
/**
* Holds if the evaluation of a call to `m` resulting in the value `branch`
* implies that the argument to the call is guaranteed to be null if `isnull`
* is true, and non-null if `isnull` is false.
*/
predicate nullCheckMethod(Method m, boolean branch, boolean isnull) {
exists(boolean polarity |
m.getDeclaringType().hasQualifiedName("java.util", "Objects") and
(
m.hasName("isNull") and polarity = true
or
m.hasName("nonNull") and polarity = false
) and
(
branch = true and isnull = polarity
or
branch = false and isnull = polarity.booleanNot()
)
)
or
m instanceof EqualsMethod and branch = true and isnull = false
or
m.getDeclaringType().hasQualifiedName("org.apache.commons.lang3", "StringUtils") and
m.hasName("isBlank") and
branch = false and
isnull = false
or
m instanceof MethodApacheCollectionsIsEmpty and
branch = false and
isnull = false
or
m instanceof MethodApacheCollectionsIsNotEmpty and
branch = true and
isnull = false
or
m.getDeclaringType().hasQualifiedName("com.google.common.base", "Strings") and
m.hasName("isNullOrEmpty") and
branch = false and
isnull = false
}
/**
* Gets an expression that directly tests whether a given expression, `e`, is null or not.
*
* If `result` evaluates to `branch`, then `e` is guaranteed to be null if `isnull`
* is true, and non-null if `isnull` is false.
*/
Expr basicNullGuard(Expr e, boolean branch, boolean isnull) {
exists(EqualityTest eqtest, boolean polarity |
eqtest = result and
eqtest.hasOperands(e, any(NullLiteral n)) and
polarity = eqtest.polarity() and
(
branch = true and isnull = polarity
or
branch = false and isnull = polarity.booleanNot()
)
)
or
result.(InstanceOfExpr).getExpr() = e and branch = true and isnull = false
or
exists(MethodAccess call |
call = result and
call.getAnArgument() = e and
nullCheckMethod(call.getMethod(), branch, isnull)
)
or
exists(EqualityTest eqtest |
eqtest = result and
eqtest.hasOperands(e, clearlyNotNullExpr()) and
isnull = false and
branch = eqtest.polarity()
)
or
result = enumConstEquality(e, branch, _) and isnull = false
}
/**
* Gets an expression that directly tests whether a given expression, `e`, is null or not.
*
* If `result` evaluates to `branch`, then `e` is guaranteed to be null if `isnull`
* is true, and non-null if `isnull` is false.
*/
Expr basicOrCustomNullGuard(Expr e, boolean branch, boolean isnull) {
result = basicNullGuard(e, branch, isnull)
or
exists(MethodAccess call, Method m, int ix |
call = result and
call.getArgument(ix) = e and
call.getMethod().getSourceDeclaration() = m and
m = customNullGuard(ix, branch, isnull)
)
}
/**
* Gets an expression that directly tests whether a given SSA variable is null or not.
*
* If `result` evaluates to `branch`, then `v` is guaranteed to be null if `isnull`
* is true, and non-null if `isnull` is false.
*/
Expr directNullGuard(SsaVariable v, boolean branch, boolean isnull) {
result = basicOrCustomNullGuard(sameValue(v, _), branch, isnull)
}
/**
* Gets a `Guard` that tests (possibly indirectly) whether a given SSA variable is null or not.
*
* If `result` evaluates to `branch`, then `v` is guaranteed to be null if `isnull`
* is true, and non-null if `isnull` is false.
*/
Guard nullGuard(SsaVariable v, boolean branch, boolean isnull) {
result = directNullGuard(v, branch, isnull) or
exists(boolean branch0 | implies_v3(result, branch, nullGuard(v, branch0, isnull), branch0))
}
/**
* A return statement that on a return value of `retval` allows the conclusion that the
* parameter `p` either is null or non-null as specified by `isnull`.
*/
private predicate validReturnInCustomNullGuard(
ReturnStmt ret, Parameter p, boolean retval, boolean isnull
) {
exists(Method m |
ret.getEnclosingCallable() = m and
p.getCallable() = m and
m.getReturnType().(PrimitiveType).hasName("boolean")
) and
exists(SsaImplicitInit ssa | ssa.isParameterDefinition(p) |
nullGuardedReturn(ret, ssa, isnull) and
(retval = true or retval = false)
or
exists(Expr res | res = ret.getResult() | res = nullGuard(ssa, retval, isnull))
)
}
private predicate nullGuardedReturn(ReturnStmt ret, SsaImplicitInit ssa, boolean isnull) {
exists(boolean branch |
nullGuard(ssa, branch, isnull).directlyControls(ret.getBasicBlock(), branch)
)
}
/**
* Gets a non-overridable method with a boolean return value that performs a null-check
* on the `index`th parameter. A return value equal to `retval` allows us to conclude
* that the argument either is null or non-null as specified by `isnull`.
*/
private Method customNullGuard(int index, boolean retval, boolean isnull) {
exists(Parameter p |
result.getReturnType().(PrimitiveType).hasName("boolean") and
not result.isOverridable() and
p.getCallable() = result and
not p.isVarargs() and
p.getType() instanceof RefType and
p.getPosition() = index and
forex(ReturnStmt ret |
ret.getEnclosingCallable() = result and
exists(Expr res | res = ret.getResult() |
not res.(BooleanLiteral).getBooleanValue() = retval.booleanNot()
)
|
validReturnInCustomNullGuard(ret, p, retval, isnull)
)
)
}
/**
* `guard` is a guard expression that suggests that `v` might be null.
*
* This is equivalent to `guard = basicNullGuard(sameValue(v, _), _, true)`.
*/
predicate guardSuggestsVarMaybeNull(Expr guard, SsaVariable v) {
guard = basicNullGuard(sameValue(v, _), _, true)
}

View File

@@ -0,0 +1,831 @@
/**
* Provides classes and predicates for nullness analysis.
*
* Local variables that may be null are tracked to see if they might reach
* a dereference and cause a NullPointerException. Assertions are assumed to
* hold, so results guarded by, for example, `assert x != null;` or
* `if (x == null) { assert false; }` are excluded.
*/
/*
* Implementation details:
*
* The three exported predicates, `nullDeref`, `alwaysNullDeref`, and
* `superfluousNullGuard`, compute potential null dereferences, definite null
* dereferences, and superfluous null checks, respectively. The bulk of the
* library supports `nullDeref`, while the latter two are fairly simple in
* comparison.
*
* The NPE (NullPointerException) candidates are computed by
* `nullDerefCandidate` and consist of three parts: A variable definition that
* might be null as computed by `varMaybeNull`, a dereference that can cause a
* NPE as computed by `firstVarDereferenceInBlock`, and a control flow path
* between the two points. The path is computed by `varMaybeNullInBlock`,
* which is the transitive closure of the step relation `nullVarStep`
* originating in a definition given by `varMaybeNull`. The step relation
* `nullVarStep` is essentially just the successor relation on basic blocks
* restricted to exclude edges along which the variable cannot be null.
*
* The step relation `nullVarStep` is then reused twice to produce two
* refinements of the path reachability predicate `varMaybeNullInBlock` in
* order to prune impossible paths that would otherwise lead to a potential
* NPE. These two refinements are `varMaybeNullInBlock_corrCond` and
* `varMaybeNullInBlock_trackVar` and are described in further detail below.
*/
import java
private import SSA
private import semmle.code.java.controlflow.Guards
private import RangeUtils
private import IntegerGuards
private import NullGuards
private import semmle.code.java.Collections
private import semmle.code.java.frameworks.Assertions
/** Gets an expression that may be `null`. */
Expr nullExpr() {
result instanceof NullLiteral or
result.(ChooseExpr).getAResultExpr() = nullExpr() or
result.(AssignExpr).getSource() = nullExpr() or
result.(CastExpr).getExpr() = nullExpr()
}
/** An expression of a boxed type that is implicitly unboxed. */
private predicate unboxed(Expr e) {
e.getType() instanceof BoxedType and
(
exists(ArrayAccess aa | aa.getIndexExpr() = e)
or
exists(ArrayCreationExpr ace | ace.getADimension() = e)
or
exists(LocalVariableDeclExpr decl |
decl.getVariable().getType() instanceof PrimitiveType and decl.getInit() = e
)
or
exists(AssignExpr assign |
assign.getDest().getType() instanceof PrimitiveType and assign.getSource() = e
)
or
exists(AssignOp assign | assign.getSource() = e and assign.getType() instanceof PrimitiveType)
or
exists(EqualityTest eq |
eq.getAnOperand() = e and eq.getAnOperand().getType() instanceof PrimitiveType
)
or
exists(BinaryExpr bin |
bin.getAnOperand() = e and
not bin instanceof EqualityTest and
bin.getType() instanceof PrimitiveType
)
or
exists(UnaryExpr un | un.getExpr() = e)
or
exists(ChooseExpr cond | cond.getType() instanceof PrimitiveType | cond.getAResultExpr() = e)
or
exists(ConditionNode cond | cond.getCondition() = e)
or
exists(Parameter p | p.getType() instanceof PrimitiveType and p.getAnArgument() = e)
or
exists(ReturnStmt ret |
ret.getEnclosingCallable().getReturnType() instanceof PrimitiveType and ret.getResult() = e
)
)
}
/** An expression that is being dereferenced. These are the points where `NullPointerException`s can occur. */
predicate dereference(Expr e) {
exists(EnhancedForStmt for | for.getExpr() = e)
or
exists(SynchronizedStmt synch | synch.getExpr() = e)
or
exists(SwitchStmt switch | switch.getExpr() = e)
or
exists(SwitchExpr switch | switch.getExpr() = e)
or
exists(FieldAccess fa, Field f | fa.getQualifier() = e and fa.getField() = f and not f.isStatic())
or
exists(MethodAccess ma, Method m |
ma.getQualifier() = e and ma.getMethod() = m and not m.isStatic()
)
or
exists(ClassInstanceExpr cie | cie.getQualifier() = e)
or
exists(ArrayAccess aa | aa.getArray() = e)
or
exists(CastExpr cast |
cast.getExpr() = e and
e.getType() instanceof BoxedType and
cast.getType() instanceof PrimitiveType
)
or
unboxed(e)
}
/**
* Gets the `ControlFlowNode` in which the given SSA variable is being dereferenced.
*
* The `VarAccess` is included for nicer error reporting.
*/
private ControlFlowNode varDereference(SsaVariable v, VarAccess va) {
dereference(result) and
result = sameValue(v, va)
}
/**
* A `ControlFlowNode` that ensures that the SSA variable is not null in any
* subsequent use, either by dereferencing it or by an assertion.
*/
private ControlFlowNode ensureNotNull(SsaVariable v) {
result = varDereference(v, _)
or
result.(AssertStmt).getExpr() = nullGuard(v, true, false)
or
exists(AssertTrueMethod m | result = m.getACheck(nullGuard(v, true, false)))
or
exists(AssertFalseMethod m | result = m.getACheck(nullGuard(v, false, false)))
or
exists(AssertNotNullMethod m | result = m.getACheck(v.getAUse()))
or
exists(AssertThatMethod m, MethodAccess ma |
result = m.getACheck(v.getAUse()) and ma.getControlFlowNode() = result
|
ma.getAnArgument().(MethodAccess).getMethod().getName() = "notNullValue"
)
}
/**
* A variable dereference that cannot be reached by a `null` value, because of an earlier
* dereference or assertion in the same `BasicBlock`.
*/
private predicate unreachableVarDereference(BasicBlock bb, SsaVariable v, ControlFlowNode varDeref) {
exists(ControlFlowNode n, int i, int j |
(n = ensureNotNull(v) or assertFail(bb, n)) and
varDeref = varDereference(v, _) and
bb.getNode(i) = n and
bb.getNode(j) = varDeref and
i < j
)
}
/**
* The first dereference of a variable in a given `BasicBlock` excluding those dereferences
* that are preceded by a not-null assertion or a trivially failing assertion.
*/
private predicate firstVarDereferenceInBlock(BasicBlock bb, SsaVariable v, VarAccess va) {
exists(ControlFlowNode n |
varDereference(v, va) = n and
n.getBasicBlock() = bb and
not unreachableVarDereference(bb, v, n)
)
}
/** A variable suspected of being `null`. */
private predicate varMaybeNull(SsaVariable v, string msg, Expr reason) {
// A variable compared to null might be null.
exists(Expr e |
reason = e and
msg = "as suggested by $@ null guard" and
guardSuggestsVarMaybeNull(e, v) and
not v instanceof SsaPhiNode and
not clearlyNotNull(v) and
// Comparisons in finally blocks are excluded since missing exception edges in the CFG could otherwise yield FPs.
not exists(TryStmt try | try.getFinally() = e.getEnclosingStmt().getEnclosingStmt*()) and
(
e = any(ConditionalExpr c).getCondition().getAChildExpr*() or
not exists(MethodAccess ma | ma.getAnArgument().getAChildExpr*() = e)
) and
// Don't use a guard as reason if there is a null assignment.
not v.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource() = nullExpr()
)
or
// A parameter might be null if there is a null argument somewhere.
exists(Parameter p, Expr arg |
v.(SsaImplicitInit).isParameterDefinition(p) and
p.getAnArgument() = arg and
reason = arg and
msg = "because of $@ null argument" and
arg = nullExpr() and
not arg.getEnclosingCallable().getEnclosingCallable*() instanceof TestMethod
)
or
// If the source of a variable is null then the variable may be null.
exists(VariableAssign def |
v.(SsaExplicitUpdate).getDefiningExpr() = def and
def.getSource() = nullExpr() and
reason = def and
msg = "because of $@ assignment"
)
}
/** Gets an array or collection that contains at least one element. */
private Expr nonEmptyExpr() {
// An array creation with a known positive size is trivially non-empty.
result.(ArrayCreationExpr).getFirstDimensionSize() > 0
or
exists(SsaVariable v |
// A use of an array variable is non-empty if...
result = v.getAUse() and
v.getSourceVariable().getType() instanceof Array
|
// ...its definition is non-empty...
v.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource() = nonEmptyExpr()
or
// ...or it is guarded by a condition proving its length to be non-zero.
exists(ConditionBlock cond, boolean branch, FieldAccess length |
cond.controls(result.getBasicBlock(), branch) and
cond.getCondition() = integerGuard(length, branch, 0, false) and
length.getField().hasName("length") and
length.getQualifier() = v.getAUse()
)
)
or
exists(SsaVariable v |
// A use of a Collection variable is non-empty if...
result = v.getAUse() and
v.getSourceVariable().getType() instanceof CollectionType and
exists(ConditionBlock cond, boolean branch, Expr c |
// ...it is guarded by a condition...
cond.controls(result.getBasicBlock(), branch) and
// ...and it isn't modified in the scope of the condition...
forall(MethodAccess ma, Method m |
m = ma.getMethod() and
ma.getQualifier() = v.getSourceVariable().getAnAccess() and
cond.controls(ma.getBasicBlock(), branch)
|
m instanceof CollectionQueryMethod
) and
cond.getCondition() = c
|
// ...and the condition proves that it is non-empty, either by using the `isEmpty` method...
c.(MethodAccess).getMethod().hasName("isEmpty") and
branch = false and
c.(MethodAccess).getQualifier() = v.getAUse()
or
// ...or a check on its `size`.
exists(MethodAccess size |
c = integerGuard(size, branch, 0, false) and
size.getMethod().hasName("size") and
size.getQualifier() = v.getAUse()
)
)
)
}
/** The control flow edge that exits an enhanced for loop if the `Iterable` is empty. */
private predicate enhancedForEarlyExit(EnhancedForStmt for, ControlFlowNode n1, ControlFlowNode n2) {
exists(Expr forExpr |
n1.getANormalSuccessor() = n2 and
for.getExpr() = forExpr and
forExpr.getAChildExpr*() = n1 and
not forExpr.getAChildExpr*() = n2 and
n1.getANormalSuccessor() = for.getVariable() and
not n2 = for.getVariable()
)
}
/** A control flow edge that cannot be taken. */
private predicate impossibleEdge(BasicBlock bb1, BasicBlock bb2) {
exists(EnhancedForStmt for |
enhancedForEarlyExit(for, bb1.getANode(), bb2.getANode()) and
for.getExpr() = nonEmptyExpr()
)
}
/** A control flow edge that leaves a finally-block. */
private predicate leavingFinally(BasicBlock bb1, BasicBlock bb2, boolean normaledge) {
exists(TryStmt try, BlockStmt finally |
try.getFinally() = finally and
bb1.getABBSuccessor() = bb2 and
bb1.getEnclosingStmt().getEnclosingStmt*() = finally and
not bb2.getEnclosingStmt().getEnclosingStmt*() = finally and
if bb1.getLastNode().getANormalSuccessor() = bb2.getFirstNode()
then normaledge = true
else normaledge = false
)
}
private predicate ssaSourceVarMaybeNull(SsaSourceVariable v) {
varMaybeNull(v.getAnSsaVariable(), _, _)
}
/**
* The step relation for propagating that a given SSA variable might be `null` in a given `BasicBlock`.
*
* If `midssa` is null in `mid` then `ssa` might be null in `bb`. The SSA variables share the same
* `SsaSourceVariable`.
*
* A boolean flag tracks whether a non-normal completion is waiting to resume upon the exit of a finally-block.
* If the flag is set, then the normal edge out of the finally-block is prohibited, but if it is not set then
* no knowledge is assumed of any potentially waiting completions. `midstoredcompletion` is the flag before
* the step and `storedcompletion` is the flag after the step.
*/
private predicate nullVarStep(
SsaVariable midssa, BasicBlock mid, boolean midstoredcompletion, SsaVariable ssa, BasicBlock bb,
boolean storedcompletion
) {
exists(SsaSourceVariable v |
ssaSourceVarMaybeNull(v) and
midssa.getSourceVariable() = v
|
ssa.(SsaPhiNode).getAPhiInput() = midssa and ssa.getBasicBlock() = bb
or
ssa = midssa and
not exists(SsaPhiNode phi | phi.getSourceVariable() = v and phi.getBasicBlock() = bb)
) and
(midstoredcompletion = true or midstoredcompletion = false) and
midssa.isLiveAtEndOfBlock(mid) and
not ensureNotNull(midssa).getBasicBlock() = mid and
not assertFail(mid, _) and
bb = mid.getABBSuccessor() and
not impossibleEdge(mid, bb) and
not exists(boolean branch | nullGuard(midssa, branch, false).hasBranchEdge(mid, bb, branch)) and
not (leavingFinally(mid, bb, true) and midstoredcompletion = true) and
if bb.getFirstNode() = any(TryStmt try | | try.getFinally())
then
if bb.getFirstNode() = mid.getLastNode().getANormalSuccessor()
then storedcompletion = false
else storedcompletion = true
else
if leavingFinally(mid, bb, _)
then storedcompletion = false
else storedcompletion = midstoredcompletion
}
/**
* The transitive closure of `nullVarStep` originating from `varMaybeNull`. That is, those `BasicBlock`s
* for which the SSA variable is suspected of being `null`.
*/
private predicate varMaybeNullInBlock(
SsaVariable ssa, SsaSourceVariable v, BasicBlock bb, boolean storedcompletion
) {
varMaybeNull(ssa, _, _) and
bb = ssa.getBasicBlock() and
storedcompletion = false and
v = ssa.getSourceVariable()
or
exists(BasicBlock mid, SsaVariable midssa, boolean midstoredcompletion |
varMaybeNullInBlock(midssa, v, mid, midstoredcompletion) and
nullVarStep(midssa, mid, midstoredcompletion, ssa, bb, storedcompletion)
)
}
/**
* Holds if `v` is a source variable that might reach a potential `null`
* dereference.
*/
private predicate nullDerefCandidateVariable(SsaSourceVariable v) {
exists(SsaVariable ssa, BasicBlock bb |
firstVarDereferenceInBlock(bb, ssa, _) and
varMaybeNullInBlock(ssa, v, bb, _)
)
}
private predicate varMaybeNullInBlock_origin(
SsaVariable origin, SsaVariable ssa, BasicBlock bb, boolean storedcompletion
) {
nullDerefCandidateVariable(ssa.getSourceVariable()) and
varMaybeNull(ssa, _, _) and
bb = ssa.getBasicBlock() and
storedcompletion = false and
origin = ssa
or
exists(BasicBlock mid, SsaVariable midssa, boolean midstoredcompletion |
varMaybeNullInBlock_origin(origin, midssa, mid, midstoredcompletion) and
nullVarStep(midssa, mid, midstoredcompletion, ssa, bb, storedcompletion)
)
}
/**
* A potential `null` dereference. That is, the first dereference of a variable in a block
* where it is suspected of being `null`.
*/
private predicate nullDerefCandidate(SsaVariable origin, VarAccess va) {
exists(SsaVariable ssa, BasicBlock bb |
firstVarDereferenceInBlock(bb, ssa, va) and
varMaybeNullInBlock_origin(origin, ssa, bb, _)
)
}
/*
* In the following, the potential `null` dereference candidates are pruned by proving that
* a `NullPointerException` (NPE) cannot occur. This is done by pruning the control-flow paths
* that lead to the NPE candidate in two ways:
*
* 1. For each set of correlated conditions that are passed by the path, consistent
* branches must be taken. For example, the following code is safe due to the two tests on
* `flag` begin correlated.
* ```
* x = null;
* if (flag) x = new A();
* if (flag) x.m();
* ```
*
* 2. For each other variable that changes its value alongside the potential NPE candidate,
* the passed conditions must be consistent with its value. For example, the following
* code is safe due to the value of `t`.
* ```
* x = null;
* t = null;
* if (...) { x = new A(); t = new B(); }
* if (t != null) x.m();
* ```
* We call such a variable a _tracking variable_ as it tracks the null-ness of `x`.
*/
/** A variable that is assigned `null` if the given condition takes the given branch. */
private predicate varConditionallyNull(SsaExplicitUpdate v, ConditionBlock cond, boolean branch) {
exists(ConditionalExpr condexpr |
v.getDefiningExpr().(VariableAssign).getSource() = condexpr and
condexpr.getCondition() = cond.getCondition()
|
condexpr.getBranchExpr(branch) = nullExpr() and
not condexpr.getBranchExpr(branch.booleanNot()) = nullExpr()
)
or
v.getDefiningExpr().(VariableAssign).getSource() = nullExpr() and
cond.controls(v.getBasicBlock(), branch)
}
/**
* A condition that might be useful in proving an NPE candidate safe.
*
* This is a condition along the path that found the NPE candidate.
*/
private predicate interestingCond(SsaSourceVariable npecand, ConditionBlock cond) {
nullDerefCandidateVariable(npecand) and
(
varMaybeNullInBlock(_, npecand, cond, _) or
varConditionallyNull(npecand.getAnSsaVariable(), cond, _)
) and
not cond.getCondition().getAChildExpr*() = npecand.getAnAccess()
}
/** A pair of correlated conditions for a given NPE candidate. */
private predicate correlatedConditions(
SsaSourceVariable npecand, ConditionBlock cond1, ConditionBlock cond2, boolean inverted
) {
interestingCond(npecand, cond1) and
interestingCond(npecand, cond2) and
cond1 != cond2 and
(
exists(SsaVariable v |
cond1.getCondition() = v.getAUse() and
cond2.getCondition() = v.getAUse() and
inverted = false
)
or
exists(SsaVariable v, boolean branch1, boolean branch2 |
cond1.getCondition() = nullGuard(v, branch1, true) and
cond1.getCondition() = nullGuard(v, branch1.booleanNot(), false) and
cond2.getCondition() = nullGuard(v, branch2, true) and
cond2.getCondition() = nullGuard(v, branch2.booleanNot(), false) and
inverted = branch1.booleanXor(branch2)
)
or
exists(SsaVariable v, RValue rv1, RValue rv2, int k, boolean branch1, boolean branch2 |
rv1 = v.getAUse() and
rv2 = v.getAUse() and
cond1.getCondition() = integerGuard(rv1, branch1, k, true) and
cond1.getCondition() = integerGuard(rv1, branch1.booleanNot(), k, false) and
cond2.getCondition() = integerGuard(rv2, branch2, k, true) and
cond2.getCondition() = integerGuard(rv2, branch2.booleanNot(), k, false) and
inverted = branch1.booleanXor(branch2)
)
or
exists(SsaVariable v, int k, boolean branch1, boolean branch2 |
cond1.getCondition() = intBoundGuard(v.getAUse(), branch1, k) and
cond2.getCondition() = intBoundGuard(v.getAUse(), branch2, k) and
inverted = branch1.booleanXor(branch2)
)
or
exists(SsaVariable v, EnumConstant c, boolean pol1, boolean pol2 |
cond1.getCondition() = enumConstEquality(v.getAUse(), pol1, c) and
cond2.getCondition() = enumConstEquality(v.getAUse(), pol2, c) and
inverted = pol1.booleanXor(pol2)
)
or
exists(SsaVariable v, RefType type |
cond1.getCondition() = instanceofExpr(v, type) and
cond2.getCondition() = instanceofExpr(v, type) and
inverted = false
)
or
exists(SsaVariable v1, SsaVariable v2, boolean branch1, boolean branch2 |
cond1.getCondition() = varEqualityTestExpr(v1, v2, branch1) and
cond2.getCondition() = varEqualityTestExpr(v1, v2, branch2) and
inverted = branch1.booleanXor(branch2)
)
)
}
/**
* This is again the transitive closure of `nullVarStep` similarly to `varMaybeNullInBlock`, but
* this time restricted based on pairs of correlated conditions consistent with `cond1`
* evaluating to `branch`.
*/
private predicate varMaybeNullInBlock_corrCond(
SsaVariable origin, SsaVariable ssa, BasicBlock bb, boolean storedcompletion,
ConditionBlock cond1, boolean branch
) {
exists(SsaSourceVariable npecand | npecand = ssa.getSourceVariable() |
nullDerefCandidateVariable(npecand) and correlatedConditions(npecand, cond1, _, _)
) and
(
varConditionallyNull(ssa, cond1, branch)
or
not varConditionallyNull(ssa, cond1, _) and
(branch = true or branch = false)
) and
varMaybeNull(ssa, _, _) and
bb = ssa.getBasicBlock() and
storedcompletion = false and
origin = ssa
or
exists(BasicBlock mid, SsaVariable midssa, boolean midstoredcompletion |
varMaybeNullInBlock_corrCond(origin, midssa, mid, midstoredcompletion, cond1, branch) and
(
cond1 = mid and cond1.getTestSuccessor(branch) = bb
or
exists(ConditionBlock cond2, boolean inverted, boolean branch2 |
cond2 = mid and
correlatedConditions(_, cond1, cond2, inverted) and
cond2.getTestSuccessor(branch2) = bb and
branch = branch2.booleanXor(inverted)
)
or
cond1 != mid and
not exists(ConditionBlock cond2 | cond2 = mid and correlatedConditions(_, cond1, cond2, _))
) and
nullVarStep(midssa, mid, midstoredcompletion, ssa, bb, storedcompletion)
)
}
/*
* A tracking variable has its possible values divided into two sets, A and B, for
* which we can attribute at least one direct assignment to be contained in either
* A or B.
* Four kinds are supported:
* - null: A means null and B means non-null.
* - boolean: A means true and B means false.
* - enum: A means a specific enum constant and B means any other value.
* - int: A means a specific integer value and B means any other value.
*/
private newtype TrackVarKind =
TrackVarKindNull() or
TrackVarKindBool() or
TrackVarKindEnum() or
TrackVarKindInt()
/** A variable that might be relevant as a tracking variable for the NPE candidate. */
private predicate trackingVar(
SsaSourceVariable npecand, SsaExplicitUpdate trackssa, SsaSourceVariable trackvar,
TrackVarKind kind, Expr init
) {
exists(ConditionBlock cond |
interestingCond(npecand, cond) and
varMaybeNullInBlock(_, npecand, cond, _) and
cond.getCondition().getAChildExpr*() = trackvar.getAnAccess() and
trackssa.getSourceVariable() = trackvar and
trackssa.getDefiningExpr().(VariableAssign).getSource() = init
|
init instanceof NullLiteral and kind = TrackVarKindNull()
or
init = clearlyNotNullExpr() and kind = TrackVarKindNull()
or
init instanceof BooleanLiteral and kind = TrackVarKindBool()
or
init.(VarAccess).getVariable() instanceof EnumConstant and kind = TrackVarKindEnum()
or
exists(init.(ConstantIntegerExpr).getIntValue()) and kind = TrackVarKindInt()
)
}
/** Gets an expression that tests the value of a given tracking variable. */
private Expr trackingVarGuard(
SsaVariable trackssa, SsaSourceVariable trackvar, TrackVarKind kind, boolean branch, boolean isA
) {
exists(Expr init | trackingVar(_, trackssa, trackvar, kind, init) |
result = basicOrCustomNullGuard(trackvar.getAnAccess(), branch, isA) and
kind = TrackVarKindNull()
or
result = trackvar.getAnAccess() and
kind = TrackVarKindBool() and
(branch = true or branch = false) and
isA = branch
or
exists(boolean polarity, EnumConstant c, EnumConstant initc |
initc.getAnAccess() = init and
kind = TrackVarKindEnum() and
result = enumConstEquality(trackvar.getAnAccess(), polarity, c) and
(
initc = c and branch = polarity.booleanNot() and isA = false
or
initc = c and branch = polarity and isA = true
or
initc != c and branch = polarity and isA = false
)
)
or
exists(int k |
init.(ConstantIntegerExpr).getIntValue() = k and
kind = TrackVarKindInt()
|
result = integerGuard(trackvar.getAnAccess(), branch, k, isA)
or
exists(int k2 |
result = integerGuard(trackvar.getAnAccess(), branch.booleanNot(), k2, true) and
isA = false and
k2 != k
)
or
exists(int bound, boolean branch_with_lower_bound |
result = intBoundGuard(trackvar.getAnAccess(), branch_with_lower_bound, bound) and
isA = false
|
branch = branch_with_lower_bound and k < bound
or
branch = branch_with_lower_bound.booleanNot() and bound <= k
)
)
)
or
exists(EqualityTest eqtest, boolean branch0, boolean polarity, BooleanLiteral boollit |
eqtest = result and
eqtest.hasOperands(trackingVarGuard(trackssa, trackvar, kind, branch0, isA), boollit) and
eqtest.polarity() = polarity and
branch = branch0.booleanXor(polarity).booleanXor(boollit.getBooleanValue())
)
}
/** An update to a tracking variable that is contained fully in either A or B. */
private predicate isReset(
SsaVariable trackssa, SsaSourceVariable trackvar, TrackVarKind kind, SsaExplicitUpdate update,
boolean isA
) {
exists(Expr init, Expr e |
trackingVar(_, trackssa, trackvar, kind, init) and
update.getSourceVariable() = trackvar and
e = update.getDefiningExpr().(VariableAssign).getSource()
|
e instanceof NullLiteral and kind = TrackVarKindNull() and isA = true
or
e = clearlyNotNullExpr() and kind = TrackVarKindNull() and isA = false
or
e.(BooleanLiteral).getBooleanValue() = isA and kind = TrackVarKindBool()
or
e.(VarAccess).getVariable().(EnumConstant) = init.(VarAccess).getVariable() and
kind = TrackVarKindEnum() and
isA = true
or
e.(VarAccess).getVariable().(EnumConstant) != init.(VarAccess).getVariable() and
kind = TrackVarKindEnum() and
isA = false
or
e.(ConstantIntegerExpr).getIntValue() = init.(ConstantIntegerExpr).getIntValue() and
kind = TrackVarKindInt() and
isA = true
or
e.(ConstantIntegerExpr).getIntValue() != init.(ConstantIntegerExpr).getIntValue() and
kind = TrackVarKindInt() and
isA = false
)
}
/** The abstract value of the tracked variable. */
private newtype TrackedValue =
TrackedValueA() or
TrackedValueB() or
TrackedValueUnknown()
private TrackedValue trackValAorB(boolean isA) {
isA = true and result = TrackedValueA()
or
isA = false and result = TrackedValueB()
}
/** A control flow edge passing through a condition that implies a specific value for a tracking variable. */
private predicate stepImplies(
BasicBlock bb1, BasicBlock bb2, SsaVariable trackssa, SsaSourceVariable trackvar,
TrackVarKind kind, boolean isA
) {
exists(ConditionBlock cond, boolean branch |
cond = bb1 and
cond.getTestSuccessor(branch) = bb2 and
cond.getCondition() = trackingVarGuard(trackssa, trackvar, kind, branch, isA)
)
}
/**
* This is again the transitive closure of `nullVarStep` similarly to `varMaybeNullInBlock`, but
* this time restricted based on a tracking variable.
*/
private predicate varMaybeNullInBlock_trackVar(
SsaVariable origin, SsaVariable ssa, BasicBlock bb, boolean storedcompletion,
SsaVariable trackssa, SsaSourceVariable trackvar, TrackVarKind kind, TrackedValue trackvalue
) {
exists(SsaSourceVariable npecand | npecand = ssa.getSourceVariable() |
nullDerefCandidateVariable(npecand) and trackingVar(npecand, trackssa, trackvar, kind, _)
) and
(
exists(SsaVariable init, boolean isA |
init.getSourceVariable() = trackvar and
init.isLiveAtEndOfBlock(bb) and
isReset(trackssa, trackvar, kind, init, isA) and
trackvalue = trackValAorB(isA)
)
or
trackvalue = TrackedValueUnknown() and
not exists(SsaVariable init |
init.getSourceVariable() = trackvar and
init.isLiveAtEndOfBlock(bb) and
isReset(trackssa, trackvar, kind, init, _)
)
) and
varMaybeNull(ssa, _, _) and
bb = ssa.getBasicBlock() and
storedcompletion = false and
origin = ssa
or
exists(BasicBlock mid, SsaVariable midssa, boolean midstoredcompletion, TrackedValue trackvalue0 |
varMaybeNullInBlock_trackVar(origin, midssa, mid, midstoredcompletion, trackssa, trackvar, kind,
trackvalue0) and
nullVarStep(midssa, mid, midstoredcompletion, ssa, bb, storedcompletion) and
(
trackvalue0 = TrackedValueUnknown()
or
// A step that implies a value that contradicts the current value is not allowed.
exists(boolean isA | trackvalue0 = trackValAorB(isA) |
not stepImplies(mid, bb, trackssa, trackvar, kind, isA.booleanNot())
)
) and
(
// If no update occurs then the tracked value is unchanged unless the step implies a given value via a condition.
not exists(SsaUpdate update |
update.getSourceVariable() = trackvar and
update.getBasicBlock() = bb
) and
(
exists(boolean isA | stepImplies(mid, bb, trackssa, trackvar, kind, isA) |
trackvalue = trackValAorB(isA)
)
or
not stepImplies(mid, bb, trackssa, trackvar, kind, _) and trackvalue = trackvalue0
)
or
// If an update occurs then the tracked value is set accordingly.
exists(SsaUpdate update |
update.getSourceVariable() = trackvar and
update.getBasicBlock() = bb
|
exists(boolean isA | isReset(trackssa, trackvar, kind, update, isA) |
trackvalue = trackValAorB(isA)
)
or
not isReset(trackssa, trackvar, kind, update, _) and trackvalue = TrackedValueUnknown()
)
)
)
}
/**
* A potential `null` dereference that has not been proven safe.
*/
predicate nullDeref(SsaSourceVariable v, VarAccess va, string msg, Expr reason) {
exists(SsaVariable origin, SsaVariable ssa, BasicBlock bb |
nullDerefCandidate(origin, va) and
varMaybeNull(origin, msg, reason) and
ssa.getSourceVariable() = v and
firstVarDereferenceInBlock(bb, ssa, va) and
forall(ConditionBlock cond | correlatedConditions(v, cond, _, _) |
varMaybeNullInBlock_corrCond(origin, ssa, bb, _, cond, _)
) and
forall(SsaVariable guardssa, SsaSourceVariable guardvar, TrackVarKind kind |
trackingVar(v, guardssa, guardvar, kind, _)
|
varMaybeNullInBlock_trackVar(origin, ssa, bb, _, guardssa, guardvar, kind, _)
)
)
}
/**
* A dereference of a variable that is always `null`.
*/
predicate alwaysNullDeref(SsaSourceVariable v, VarAccess va) {
exists(BasicBlock bb, SsaVariable ssa |
forall(SsaVariable def | def = ssa.getAnUltimateDefinition() |
def.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource() = alwaysNullExpr()
)
or
exists(boolean branch |
nullGuard(ssa, branch, true).directlyControls(bb, branch) and
not clearlyNotNull(ssa)
)
|
// Exclude fields as they might not have an accurate ssa representation.
not v.getVariable() instanceof Field and
firstVarDereferenceInBlock(bb, ssa, va) and
ssa.getSourceVariable() = v and
not exists(boolean branch | nullGuard(ssa, branch, false).directlyControls(bb, branch))
)
}

View File

@@ -0,0 +1,882 @@
/**
* Provides classes and predicates for range analysis.
*
* An inferred bound can either be a specific integer, the abstract value of an
* SSA variable, or the abstract value of an interesting expression. The latter
* category includes array lengths that are not SSA variables.
*
* If an inferred bound relies directly on a condition, then this condition is
* reported as the reason for the bound.
*/
/*
* This library tackles range analysis as a flow problem. Consider e.g.:
* ```
* len = arr.length;
* if (x < len) { ... y = x-1; ... y ... }
* ```
* In this case we would like to infer `y <= arr.length - 2`, and this is
* accomplished by tracking the bound through a sequence of steps:
* ```
* arr.length --> len = .. --> x < len --> x-1 --> y = .. --> y
* ```
*
* In its simplest form the step relation `E1 --> E2` relates two expressions
* such that `E1 <= B` implies `E2 <= B` for any `B` (with a second separate
* step relation handling lower bounds). Examples of such steps include
* assignments `E2 = E1` and conditions `x <= E1` where `E2` is a use of `x`
* guarded by the condition.
*
* In order to handle subtractions and additions with constants, and strict
* comparisons, the step relation is augmented with an integer delta. With this
* generalization `E1 --(delta)--> E2` relates two expressions and an integer
* such that `E1 <= B` implies `E2 <= B + delta` for any `B`. This corresponds
* to the predicate `boundFlowStep`.
*
* The complete range analysis is then implemented as the transitive closure of
* the step relation summing the deltas along the way. If `E1` transitively
* steps to `E2`, `delta` is the sum of deltas along the path, and `B` is an
* interesting bound equal to the value of `E1` then `E2 <= B + delta`. This
* corresponds to the predicate `bounded`.
*
* Phi nodes need a little bit of extra handling. Consider `x0 = phi(x1, x2)`.
* There are essentially two cases:
* - If `x1 <= B + d1` and `x2 <= B + d2` then `x0 <= B + max(d1,d2)`.
* - If `x1 <= B + d1` and `x2 <= x0 + d2` with `d2 <= 0` then `x0 <= B + d1`.
* The first case is for whenever a bound can be proven without taking looping
* into account. The second case is relevant when `x2` comes from a back-edge
* where we can prove that the variable has been non-increasing through the
* loop-iteration as this means that any upper bound that holds prior to the
* loop also holds for the variable during the loop.
* This generalizes to a phi node with `n` inputs, so if
* `x0 = phi(x1, ..., xn)` and `xi <= B + delta` for one of the inputs, then we
* also have `x0 <= B + delta` if we can prove either:
* - `xj <= B + d` with `d <= delta` or
* - `xj <= x0 + d` with `d <= 0`
* for each input `xj`.
*
* As all inferred bounds can be related directly to a path in the source code
* the only source of non-termination is if successive redundant (and thereby
* increasingly worse) bounds are calculated along a loop in the source code.
* We prevent this by weakening the bound to a small finite set of bounds when
* a path follows a second back-edge (we postpone weakening till the second
* back-edge as a precise bound might require traversing a loop once).
*/
import java
private import SSA
private import RangeUtils
private import semmle.code.java.dataflow.internal.rangeanalysis.SsaReadPositionCommon
private import semmle.code.java.controlflow.internal.GuardsLogic
private import semmle.code.java.security.RandomDataSource
private import SignAnalysis
private import ModulusAnalysis
private import semmle.code.java.Reflection
private import semmle.code.java.Collections
private import semmle.code.java.Maps
import Bound
cached
private module RangeAnalysisCache {
cached
module RangeAnalysisPublic {
/**
* Holds if `b + delta` is a valid bound for `e`.
* - `upper = true` : `e <= b + delta`
* - `upper = false` : `e >= b + delta`
*
* The reason for the bound is given by `reason` and may be either a condition
* or `NoReason` if the bound was proven directly without the use of a bounding
* condition.
*/
cached
predicate bounded(Expr e, Bound b, int delta, boolean upper, Reason reason) {
bounded(e, b, delta, upper, _, _, reason) and
bestBound(e, b, delta, upper)
}
}
/**
* Holds if `guard = boundFlowCond(_, _, _, _, _) or guard = eqFlowCond(_, _, _, _, _)`.
*/
cached
predicate possibleReason(Guard guard) {
guard = boundFlowCond(_, _, _, _, _) or guard = eqFlowCond(_, _, _, _, _)
}
}
private import RangeAnalysisCache
import RangeAnalysisPublic
/**
* Holds if `b + delta` is a valid bound for `e` and this is the best such delta.
* - `upper = true` : `e <= b + delta`
* - `upper = false` : `e >= b + delta`
*/
private predicate bestBound(Expr e, Bound b, int delta, boolean upper) {
delta = min(int d | bounded(e, b, d, upper, _, _, _)) and upper = true
or
delta = max(int d | bounded(e, b, d, upper, _, _, _)) and upper = false
}
/**
* Holds if `comp` corresponds to:
* - `upper = true` : `v <= e + delta` or `v < e + delta`
* - `upper = false` : `v >= e + delta` or `v > e + delta`
*/
private predicate boundCondition(
ComparisonExpr comp, SsaVariable v, Expr e, int delta, boolean upper
) {
comp.getLesserOperand() = ssaRead(v, delta) and e = comp.getGreaterOperand() and upper = true
or
comp.getGreaterOperand() = ssaRead(v, delta) and e = comp.getLesserOperand() and upper = false
or
exists(SubExpr sub, ConstantIntegerExpr c, int d |
// (v - d) - e < c
comp.getLesserOperand() = sub and
comp.getGreaterOperand() = c and
sub.getLeftOperand() = ssaRead(v, d) and
sub.getRightOperand() = e and
upper = true and
delta = d + c.getIntValue()
or
// (v - d) - e > c
comp.getGreaterOperand() = sub and
comp.getLesserOperand() = c and
sub.getLeftOperand() = ssaRead(v, d) and
sub.getRightOperand() = e and
upper = false and
delta = d + c.getIntValue()
or
// e - (v - d) < c
comp.getLesserOperand() = sub and
comp.getGreaterOperand() = c and
sub.getLeftOperand() = e and
sub.getRightOperand() = ssaRead(v, d) and
upper = false and
delta = d - c.getIntValue()
or
// e - (v - d) > c
comp.getGreaterOperand() = sub and
comp.getLesserOperand() = c and
sub.getLeftOperand() = e and
sub.getRightOperand() = ssaRead(v, d) and
upper = true and
delta = d - c.getIntValue()
)
}
/**
* Holds if `comp` is a comparison between `x` and `y` for which `y - x` has a
* fixed value modulo some `mod > 1`, such that the comparison can be
* strengthened by `strengthen` when evaluating to `testIsTrue`.
*/
private predicate modulusComparison(ComparisonExpr comp, boolean testIsTrue, int strengthen) {
exists(
Bound b, int v1, int v2, int mod1, int mod2, int mod, boolean resultIsStrict, int d, int k
|
// If `x <= y` and `x =(mod) b + v1` and `y =(mod) b + v2` then
// `0 <= y - x =(mod) v2 - v1`. By choosing `k =(mod) v2 - v1` with
// `0 <= k < mod` we get `k <= y - x`. If the resulting comparison is
// strict then the strengthening amount is instead `k - 1` modulo `mod`:
// `x < y` means `0 <= y - x - 1 =(mod) k - 1` so `k - 1 <= y - x - 1` and
// thus `k - 1 < y - x` with `0 <= k - 1 < mod`.
exprModulus(comp.getLesserOperand(), b, v1, mod1) and
exprModulus(comp.getGreaterOperand(), b, v2, mod2) and
mod = mod1.gcd(mod2) and
mod != 1 and
(testIsTrue = true or testIsTrue = false) and
(
if comp.isStrict()
then resultIsStrict = testIsTrue
else resultIsStrict = testIsTrue.booleanNot()
) and
(
resultIsStrict = true and d = 1
or
resultIsStrict = false and d = 0
) and
(
testIsTrue = true and k = v2 - v1
or
testIsTrue = false and k = v1 - v2
) and
strengthen = (((k - d) % mod) + mod) % mod
)
}
/**
* Gets a condition that tests whether `v` is bounded by `e + delta`.
*
* If the condition evaluates to `testIsTrue`:
* - `upper = true` : `v <= e + delta`
* - `upper = false` : `v >= e + delta`
*/
private Guard boundFlowCond(SsaVariable v, Expr e, int delta, boolean upper, boolean testIsTrue) {
exists(
ComparisonExpr comp, int d1, int d2, int d3, int strengthen, boolean compIsUpper,
boolean resultIsStrict
|
comp = result and
boundCondition(comp, v, e, d1, compIsUpper) and
(testIsTrue = true or testIsTrue = false) and
upper = compIsUpper.booleanXor(testIsTrue.booleanNot()) and
(
if comp.isStrict()
then resultIsStrict = testIsTrue
else resultIsStrict = testIsTrue.booleanNot()
) and
(
if v.getSourceVariable().getType() instanceof IntegralType
then
upper = true and strengthen = -1
or
upper = false and strengthen = 1
else strengthen = 0
) and
(
exists(int k | modulusComparison(comp, testIsTrue, k) and d2 = strengthen * k)
or
not modulusComparison(comp, testIsTrue, _) and d2 = 0
) and
// A strict inequality `x < y` can be strengthened to `x <= y - 1`.
(
resultIsStrict = true and d3 = strengthen
or
resultIsStrict = false and d3 = 0
) and
delta = d1 + d2 + d3
)
or
exists(boolean testIsTrue0 |
implies_v2(result, testIsTrue, boundFlowCond(v, e, delta, upper, testIsTrue0), testIsTrue0)
)
or
result = eqFlowCond(v, e, delta, true, testIsTrue) and
(upper = true or upper = false)
or
// guard that tests whether `v2` is bounded by `e + delta + d1 - d2` and
// exists a guard `guardEq` such that `v = v2 - d1 + d2`.
exists(SsaVariable v2, Guard guardEq, boolean eqIsTrue, int d1, int d2 |
guardEq = eqFlowCond(v, ssaRead(v2, d1), d2, true, eqIsTrue) and
result = boundFlowCond(v2, e, delta + d1 - d2, upper, testIsTrue) and
// guardEq needs to control guard
guardEq.directlyControls(result.getBasicBlock(), eqIsTrue)
)
}
private newtype TReason =
TNoReason() or
TCondReason(Guard guard) { possibleReason(guard) }
/**
* A reason for an inferred bound. This can either be `CondReason` if the bound
* is due to a specific condition, or `NoReason` if the bound is inferred
* without going through a bounding condition.
*/
abstract class Reason extends TReason {
/** Gets a textual representation of this reason. */
abstract string toString();
}
/**
* A reason for an inferred bound that indicates that the bound is inferred
* without going through a bounding condition.
*/
class NoReason extends Reason, TNoReason {
override string toString() { result = "NoReason" }
}
/** A reason for an inferred bound pointing to a condition. */
class CondReason extends Reason, TCondReason {
/** Gets the condition that is the reason for the bound. */
Guard getCond() { this = TCondReason(result) }
override string toString() { result = getCond().toString() }
}
/**
* Holds if `e + delta` is a valid bound for `v` at `pos`.
* - `upper = true` : `v <= e + delta`
* - `upper = false` : `v >= e + delta`
*/
private predicate boundFlowStepSsa(
SsaVariable v, SsaReadPosition pos, Expr e, int delta, boolean upper, Reason reason
) {
ssaUpdateStep(v, e, delta) and
pos.hasReadOfVar(v) and
(upper = true or upper = false) and
reason = TNoReason()
or
exists(Guard guard, boolean testIsTrue |
pos.hasReadOfVar(v) and
guard = boundFlowCond(v, e, delta, upper, testIsTrue) and
guardDirectlyControlsSsaRead(guard, pos, testIsTrue) and
reason = TCondReason(guard)
)
}
/** Holds if `v != e + delta` at `pos` and `v` is of integral type. */
private predicate unequalFlowStepIntegralSsa(
SsaVariable v, SsaReadPosition pos, Expr e, int delta, Reason reason
) {
v.getSourceVariable().getType() instanceof IntegralType and
exists(Guard guard, boolean testIsTrue |
pos.hasReadOfVar(v) and
guard = eqFlowCond(v, e, delta, false, testIsTrue) and
guardDirectlyControlsSsaRead(guard, pos, testIsTrue) and
reason = TCondReason(guard)
)
}
/**
* Holds if a cast from `fromtyp` to `totyp` can be ignored for the purpose of
* range analysis.
*/
private predicate safeCast(Type fromtyp, Type totyp) {
exists(PrimitiveType pfrom, PrimitiveType pto | pfrom = fromtyp and pto = totyp |
pfrom = pto
or
pfrom.hasName("char") and pto.getName().regexpMatch("int|long|float|double")
or
pfrom.hasName("byte") and pto.getName().regexpMatch("short|int|long|float|double")
or
pfrom.hasName("short") and pto.getName().regexpMatch("int|long|float|double")
or
pfrom.hasName("int") and pto.getName().regexpMatch("long|float|double")
or
pfrom.hasName("long") and pto.getName().regexpMatch("float|double")
or
pfrom.hasName("float") and pto.hasName("double")
or
pfrom.hasName("double") and pto.hasName("float")
)
or
safeCast(fromtyp.(BoxedType).getPrimitiveType(), totyp)
or
safeCast(fromtyp, totyp.(BoxedType).getPrimitiveType())
}
/**
* A cast that can be ignored for the purpose of range analysis.
*/
private class SafeCastExpr extends CastExpr {
SafeCastExpr() { safeCast(getExpr().getType(), getType()) }
}
/**
* Holds if `typ` is a small integral type with the given lower and upper bounds.
*/
private predicate typeBound(Type typ, int lowerbound, int upperbound) {
typ.(PrimitiveType).hasName("byte") and lowerbound = -128 and upperbound = 127
or
typ.(PrimitiveType).hasName("short") and lowerbound = -32768 and upperbound = 32767
or
typ.(PrimitiveType).hasName("char") and lowerbound = 0 and upperbound = 65535
or
typeBound(typ.(BoxedType).getPrimitiveType(), lowerbound, upperbound)
}
/**
* A cast to a small integral type that may overflow or underflow.
*/
private class NarrowingCastExpr extends CastExpr {
NarrowingCastExpr() {
not this instanceof SafeCastExpr and
typeBound(getType(), _, _)
}
/** Gets the lower bound of the resulting type. */
int getLowerBound() { typeBound(getType(), result, _) }
/** Gets the upper bound of the resulting type. */
int getUpperBound() { typeBound(getType(), _, result) }
}
/** Holds if `e >= 1` as determined by sign analysis. */
private predicate strictlyPositiveIntegralExpr(Expr e) {
strictlyPositive(e) and e.getType() instanceof IntegralType
}
/** Holds if `e <= -1` as determined by sign analysis. */
private predicate strictlyNegativeIntegralExpr(Expr e) {
strictlyNegative(e) and e.getType() instanceof IntegralType
}
/**
* Holds if `e1 + delta` is a valid bound for `e2`.
* - `upper = true` : `e2 <= e1 + delta`
* - `upper = false` : `e2 >= e1 + delta`
*/
private predicate boundFlowStep(Expr e2, Expr e1, int delta, boolean upper) {
valueFlowStep(e2, e1, delta) and
(upper = true or upper = false)
or
e2.(SafeCastExpr).getExpr() = e1 and
delta = 0 and
(upper = true or upper = false)
or
exists(Expr x |
e2.(AddExpr).hasOperands(e1, x)
or
exists(AssignAddExpr add | add = e2 |
add.getDest() = e1 and add.getRhs() = x
or
add.getDest() = x and add.getRhs() = e1
)
|
// `x instanceof ConstantIntegerExpr` is covered by valueFlowStep
not x instanceof ConstantIntegerExpr and
not e1 instanceof ConstantIntegerExpr and
if strictlyPositiveIntegralExpr(x)
then upper = false and delta = 1
else
if positive(x)
then upper = false and delta = 0
else
if strictlyNegativeIntegralExpr(x)
then upper = true and delta = -1
else
if negative(x)
then upper = true and delta = 0
else none()
)
or
exists(Expr x |
exists(SubExpr sub |
e2 = sub and
sub.getLeftOperand() = e1 and
sub.getRightOperand() = x
)
or
exists(AssignSubExpr sub |
e2 = sub and
sub.getDest() = e1 and
sub.getRhs() = x
)
|
// `x instanceof ConstantIntegerExpr` is covered by valueFlowStep
not x instanceof ConstantIntegerExpr and
if strictlyPositiveIntegralExpr(x)
then upper = true and delta = -1
else
if positive(x)
then upper = true and delta = 0
else
if strictlyNegativeIntegralExpr(x)
then upper = false and delta = 1
else
if negative(x)
then upper = false and delta = 0
else none()
)
or
e2.(RemExpr).getRightOperand() = e1 and positive(e1) and delta = -1 and upper = true
or
e2.(RemExpr).getLeftOperand() = e1 and positive(e1) and delta = 0 and upper = true
or
e2.(AssignRemExpr).getRhs() = e1 and positive(e1) and delta = -1 and upper = true
or
e2.(AssignRemExpr).getDest() = e1 and positive(e1) and delta = 0 and upper = true
or
e2.(AndBitwiseExpr).getAnOperand() = e1 and positive(e1) and delta = 0 and upper = true
or
e2.(AssignAndExpr).getSource() = e1 and positive(e1) and delta = 0 and upper = true
or
e2.(OrBitwiseExpr).getAnOperand() = e1 and positive(e2) and delta = 0 and upper = false
or
e2.(AssignOrExpr).getSource() = e1 and positive(e2) and delta = 0 and upper = false
or
exists(RandomDataSource rds |
e2 = rds.getOutput() and
(
e1 = rds.getUpperBoundExpr() and
delta = -1 and
upper = true
or
e1 = rds.getLowerBoundExpr() and
delta = 0 and
upper = false
)
)
or
exists(MethodAccess ma, Method m |
e2 = ma and
ma.getMethod() = m and
(
m.hasName("max") and upper = false
or
m.hasName("min") and upper = true
) and
m.getDeclaringType().hasQualifiedName("java.lang", "Math") and
e1 = ma.getAnArgument() and
delta = 0
)
}
/** Holds if `e2 = e1 * factor` and `factor > 0`. */
private predicate boundFlowStepMul(Expr e2, Expr e1, int factor) {
exists(ConstantIntegerExpr c, int k | k = c.getIntValue() and k > 0 |
e2.(MulExpr).hasOperands(e1, c) and factor = k
or
exists(AssignMulExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = k)
or
exists(AssignMulExpr e | e = e2 and e.getDest() = c and e.getRhs() = e1 and factor = k)
or
exists(LShiftExpr e |
e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)
)
or
exists(AssignLShiftExpr e |
e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k)
)
)
}
/**
* Holds if `e2 = e1 / factor` and `factor > 0`.
*
* This conflates division, right shift, and unsigned right shift and is
* therefore only valid for non-negative numbers.
*/
private predicate boundFlowStepDiv(Expr e2, Expr e1, int factor) {
exists(ConstantIntegerExpr c, int k | k = c.getIntValue() and k > 0 |
exists(DivExpr e |
e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = k
)
or
exists(AssignDivExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = k)
or
exists(RShiftExpr e |
e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)
)
or
exists(AssignRShiftExpr e |
e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k)
)
or
exists(URShiftExpr e |
e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)
)
or
exists(AssignURShiftExpr e |
e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k)
)
)
}
/**
* Holds if `b + delta` is a valid bound for `v` at `pos`.
* - `upper = true` : `v <= b + delta`
* - `upper = false` : `v >= b + delta`
*/
private predicate boundedSsa(
SsaVariable v, SsaReadPosition pos, Bound b, int delta, boolean upper, boolean fromBackEdge,
int origdelta, Reason reason
) {
exists(Expr mid, int d1, int d2, Reason r1, Reason r2 |
boundFlowStepSsa(v, pos, mid, d1, upper, r1) and
bounded(mid, b, d2, upper, fromBackEdge, origdelta, r2) and
// upper = true: v <= mid + d1 <= b + d1 + d2 = b + delta
// upper = false: v >= mid + d1 >= b + d1 + d2 = b + delta
delta = d1 + d2 and
(if r1 instanceof NoReason then reason = r2 else reason = r1)
)
or
exists(int d, Reason r1, Reason r2 |
boundedSsa(v, pos, b, d, upper, fromBackEdge, origdelta, r2) or
boundedPhi(v, b, d, upper, fromBackEdge, origdelta, r2)
|
unequalIntegralSsa(v, pos, b, d, r1) and
(
upper = true and delta = d - 1
or
upper = false and delta = d + 1
) and
(
reason = r1
or
reason = r2 and not r2 instanceof NoReason
)
)
}
/**
* Holds if `v != b + delta` at `pos` and `v` is of integral type.
*/
private predicate unequalIntegralSsa(
SsaVariable v, SsaReadPosition pos, Bound b, int delta, Reason reason
) {
exists(Expr e, int d1, int d2 |
unequalFlowStepIntegralSsa(v, pos, e, d1, reason) and
bounded(e, b, d2, true, _, _, _) and
bounded(e, b, d2, false, _, _, _) and
delta = d2 + d1
)
}
/** Weakens a delta to lie in the range `[-1..1]`. */
bindingset[delta, upper]
private int weakenDelta(boolean upper, int delta) {
delta in [-1 .. 1] and result = delta
or
upper = true and result = -1 and delta < -1
or
upper = false and result = 1 and delta > 1
}
/**
* Holds if `b + delta` is a valid bound for `inp` when used as an input to
* `phi` along `edge`.
* - `upper = true` : `inp <= b + delta`
* - `upper = false` : `inp >= b + delta`
*/
private predicate boundedPhiInp(
SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, Bound b, int delta,
boolean upper, boolean fromBackEdge, int origdelta, Reason reason
) {
edge.phiInput(phi, inp) and
exists(int d, boolean fromBackEdge0 |
boundedSsa(inp, edge, b, d, upper, fromBackEdge0, origdelta, reason)
or
boundedPhi(inp, b, d, upper, fromBackEdge0, origdelta, reason)
or
b.(SsaBound).getSsa() = inp and
d = 0 and
(upper = true or upper = false) and
fromBackEdge0 = false and
origdelta = 0 and
reason = TNoReason()
|
if backEdge(phi, inp, edge)
then
fromBackEdge = true and
(
fromBackEdge0 = true and delta = weakenDelta(upper, d - origdelta) + origdelta
or
fromBackEdge0 = false and delta = d
)
else (
delta = d and fromBackEdge = fromBackEdge0
)
)
}
/** Holds if `boundedPhiInp(phi, inp, edge, b, delta, upper, _, _, _)`. */
pragma[noinline]
private predicate boundedPhiInp1(
SsaPhiNode phi, Bound b, boolean upper, SsaVariable inp, SsaReadPositionPhiInputEdge edge,
int delta
) {
boundedPhiInp(phi, inp, edge, b, delta, upper, _, _, _)
}
/**
* Holds if `phi` is a valid bound for `inp` when used as an input to `phi`
* along `edge`.
* - `upper = true` : `inp <= phi`
* - `upper = false` : `inp >= phi`
*/
private predicate selfBoundedPhiInp(
SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, boolean upper
) {
exists(int d, SsaBound phibound |
phibound.getSsa() = phi and
boundedPhiInp(phi, inp, edge, phibound, d, upper, _, _, _) and
(
upper = true and d <= 0
or
upper = false and d >= 0
)
)
}
/**
* Holds if `b + delta` is a valid bound for some input, `inp`, to `phi`, and
* thus a candidate bound for `phi`.
* - `upper = true` : `inp <= b + delta`
* - `upper = false` : `inp >= b + delta`
*/
pragma[noinline]
private predicate boundedPhiCand(
SsaPhiNode phi, boolean upper, Bound b, int delta, boolean fromBackEdge, int origdelta,
Reason reason
) {
exists(SsaVariable inp, SsaReadPositionPhiInputEdge edge |
boundedPhiInp(phi, inp, edge, b, delta, upper, fromBackEdge, origdelta, reason)
)
}
/**
* Holds if the candidate bound `b + delta` for `phi` is valid for the phi input
* `inp` along `edge`.
*/
private predicate boundedPhiCandValidForEdge(
SsaPhiNode phi, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta,
Reason reason, SsaVariable inp, SsaReadPositionPhiInputEdge edge
) {
boundedPhiCand(phi, upper, b, delta, fromBackEdge, origdelta, reason) and
(
exists(int d | boundedPhiInp1(phi, b, upper, inp, edge, d) | upper = true and d <= delta)
or
exists(int d | boundedPhiInp1(phi, b, upper, inp, edge, d) | upper = false and d >= delta)
or
selfBoundedPhiInp(phi, inp, edge, upper)
)
}
/**
* Holds if `b + delta` is a valid bound for `phi`.
* - `upper = true` : `phi <= b + delta`
* - `upper = false` : `phi >= b + delta`
*/
private predicate boundedPhi(
SsaPhiNode phi, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta,
Reason reason
) {
forex(SsaVariable inp, SsaReadPositionPhiInputEdge edge | edge.phiInput(phi, inp) |
boundedPhiCandValidForEdge(phi, b, delta, upper, fromBackEdge, origdelta, reason, inp, edge)
)
}
/**
* Holds if `e` has a lower bound of zero.
*/
private predicate lowerBoundZero(Expr e) {
e.(MethodAccess).getMethod() instanceof StringLengthMethod or
e.(MethodAccess).getMethod() instanceof CollectionSizeMethod or
e.(MethodAccess).getMethod() instanceof MapSizeMethod or
e.(FieldRead).getField() instanceof ArrayLengthField or
positive(e.(AndBitwiseExpr).getAnOperand())
}
/**
* Holds if `e` has an upper (for `upper = true`) or lower
* (for `upper = false`) bound of `b`.
*/
private predicate baseBound(Expr e, int b, boolean upper) {
lowerBoundZero(e) and b = 0 and upper = false
or
exists(Method read |
e.(MethodAccess).getMethod().overrides*(read) and
read.getDeclaringType().hasQualifiedName("java.io", "InputStream") and
read.hasName("read") and
read.getNumberOfParameters() = 0
|
upper = true and b = 255
or
upper = false and b = -1
)
}
/**
* Holds if the value being cast has an upper (for `upper = true`) or lower
* (for `upper = false`) bound within the bounds of the resulting type.
* For `upper = true` this means that the cast will not overflow and for
* `upper = false` this means that the cast will not underflow.
*/
private predicate safeNarrowingCast(NarrowingCastExpr cast, boolean upper) {
exists(int bound | bounded(cast.getExpr(), any(ZeroBound zb), bound, upper, _, _, _) |
upper = true and bound <= cast.getUpperBound()
or
upper = false and bound >= cast.getLowerBound()
)
}
pragma[noinline]
private predicate boundedCastExpr(
NarrowingCastExpr cast, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta,
Reason reason
) {
bounded(cast.getExpr(), b, delta, upper, fromBackEdge, origdelta, reason)
}
/**
* Holds if `b + delta` is a valid bound for `e`.
* - `upper = true` : `e <= b + delta`
* - `upper = false` : `e >= b + delta`
*/
private predicate bounded(
Expr e, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason
) {
e = b.getExpr(delta) and
(upper = true or upper = false) and
fromBackEdge = false and
origdelta = delta and
reason = TNoReason()
or
baseBound(e, delta, upper) and
b instanceof ZeroBound and
fromBackEdge = false and
origdelta = delta and
reason = TNoReason()
or
exists(SsaVariable v, SsaReadPositionBlock bb |
boundedSsa(v, bb, b, delta, upper, fromBackEdge, origdelta, reason) and
e = v.getAUse() and
bb.getBlock() = e.getBasicBlock()
)
or
exists(Expr mid, int d1, int d2 |
boundFlowStep(e, mid, d1, upper) and
// Constants have easy, base-case bounds, so let's not infer any recursive bounds.
not e instanceof ConstantIntegerExpr and
bounded(mid, b, d2, upper, fromBackEdge, origdelta, reason) and
// upper = true: e <= mid + d1 <= b + d1 + d2 = b + delta
// upper = false: e >= mid + d1 >= b + d1 + d2 = b + delta
delta = d1 + d2
)
or
exists(SsaPhiNode phi |
boundedPhi(phi, b, delta, upper, fromBackEdge, origdelta, reason) and
e = phi.getAUse()
)
or
exists(Expr mid, int factor, int d |
boundFlowStepMul(e, mid, factor) and
not e instanceof ConstantIntegerExpr and
bounded(mid, b, d, upper, fromBackEdge, origdelta, reason) and
b instanceof ZeroBound and
delta = d * factor
)
or
exists(Expr mid, int factor, int d |
boundFlowStepDiv(e, mid, factor) and
not e instanceof ConstantIntegerExpr and
bounded(mid, b, d, upper, fromBackEdge, origdelta, reason) and
b instanceof ZeroBound and
d >= 0 and
delta = d / factor
)
or
exists(NarrowingCastExpr cast |
cast = e and
safeNarrowingCast(cast, upper.booleanNot()) and
boundedCastExpr(cast, b, delta, upper, fromBackEdge, origdelta, reason)
)
or
exists(
ConditionalExpr cond, int d1, int d2, boolean fbe1, boolean fbe2, int od1, int od2, Reason r1,
Reason r2
|
cond = e and
boundedConditionalExpr(cond, b, upper, true, d1, fbe1, od1, r1) and
boundedConditionalExpr(cond, b, upper, false, d2, fbe2, od2, r2) and
(
delta = d1 and fromBackEdge = fbe1 and origdelta = od1 and reason = r1
or
delta = d2 and fromBackEdge = fbe2 and origdelta = od2 and reason = r2
)
|
upper = true and delta = d1.maximum(d2)
or
upper = false and delta = d1.minimum(d2)
)
}
private predicate boundedConditionalExpr(
ConditionalExpr cond, Bound b, boolean upper, boolean branch, int delta, boolean fromBackEdge,
int origdelta, Reason reason
) {
bounded(cond.getBranchExpr(branch), b, delta, upper, fromBackEdge, origdelta, reason)
}

View File

@@ -0,0 +1,252 @@
/**
* Provides utility predicates for range analysis.
*/
import java
private import SSA
private import semmle.code.java.controlflow.internal.GuardsLogic
private import semmle.code.java.dataflow.internal.rangeanalysis.SsaReadPositionCommon
/**
* Holds if `v` is an input to `phi` that is not along a back edge, and the
* only other input to `phi` is a `null` value.
*
* Note that the declared type of `phi` is `SsaVariable` instead of
* `SsaPhiNode` in order for the reflexive case of `nonNullSsaFwdStep*(..)` to
* have non-`SsaPhiNode` results.
*/
private predicate nonNullSsaFwdStep(SsaVariable v, SsaVariable phi) {
exists(SsaExplicitUpdate vnull, SsaPhiNode phi0 | phi0 = phi |
2 = strictcount(phi0.getAPhiInput()) and
vnull = phi0.getAPhiInput() and
v = phi0.getAPhiInput() and
not backEdge(phi0, v, _) and
vnull != v and
vnull.getDefiningExpr().(VariableAssign).getSource() instanceof NullLiteral
)
}
private predicate nonNullDefStep(Expr e1, Expr e2) {
exists(ConditionalExpr cond, boolean branch | cond = e2 |
cond.getBranchExpr(branch) = e1 and
cond.getBranchExpr(branch.booleanNot()) instanceof NullLiteral
)
}
/**
* Gets the definition of `v` provided that `v` is a non-null array with an
* explicit `ArrayCreationExpr` definition and that the definition does not go
* through a back edge.
*/
ArrayCreationExpr getArrayDef(SsaVariable v) {
exists(Expr src |
v.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource() = src and
nonNullDefStep*(result, src)
)
or
exists(SsaVariable mid |
result = getArrayDef(mid) and
nonNullSsaFwdStep(mid, v)
)
}
/**
* Holds if `arrlen` is a read of an array `length` field on an array that, if
* it is non-null, is defined by `def` and that the definition can reach
* `arrlen` without going through a back edge.
*/
private predicate arrayLengthDef(FieldRead arrlen, ArrayCreationExpr def) {
exists(SsaVariable arr |
arrlen.getField() instanceof ArrayLengthField and
arrlen.getQualifier() = arr.getAUse() and
def = getArrayDef(arr)
)
}
/** An expression that always has the same integer value. */
pragma[nomagic]
private predicate constantIntegerExpr(Expr e, int val) {
e.(CompileTimeConstantExpr).getIntValue() = val
or
exists(SsaExplicitUpdate v, Expr src |
e = v.getAUse() and
src = v.getDefiningExpr().(VariableAssign).getSource() and
constantIntegerExpr(src, val)
)
or
exists(ArrayCreationExpr a |
arrayLengthDef(e, a) and
a.getFirstDimensionSize() = val
)
or
exists(Field a, FieldRead arrlen |
a.isFinal() and
a.getInitializer().(ArrayCreationExpr).getFirstDimensionSize() = val and
arrlen.getField() instanceof ArrayLengthField and
arrlen.getQualifier() = a.getAnAccess() and
e = arrlen
)
}
/** An expression that always has the same integer value. */
class ConstantIntegerExpr extends Expr {
ConstantIntegerExpr() { constantIntegerExpr(this, _) }
/** Gets the integer value of this expression. */
int getIntValue() { constantIntegerExpr(this, result) }
}
/**
* Gets an expression that equals `v - d`.
*/
Expr ssaRead(SsaVariable v, int delta) {
result = v.getAUse() and delta = 0
or
exists(int d1, ConstantIntegerExpr c |
result.(AddExpr).hasOperands(ssaRead(v, d1), c) and
delta = d1 - c.getIntValue()
)
or
exists(SubExpr sub, int d1, ConstantIntegerExpr c |
result = sub and
sub.getLeftOperand() = ssaRead(v, d1) and
sub.getRightOperand() = c and
delta = d1 + c.getIntValue()
)
or
v.(SsaExplicitUpdate).getDefiningExpr().(PreIncExpr) = result and delta = 0
or
v.(SsaExplicitUpdate).getDefiningExpr().(PreDecExpr) = result and delta = 0
or
v.(SsaExplicitUpdate).getDefiningExpr().(PostIncExpr) = result and delta = 1 // x++ === ++x - 1
or
v.(SsaExplicitUpdate).getDefiningExpr().(PostDecExpr) = result and delta = -1 // x-- === --x + 1
or
v.(SsaExplicitUpdate).getDefiningExpr().(Assignment) = result and delta = 0
or
result.(AssignExpr).getSource() = ssaRead(v, delta)
}
/**
* Holds if `inp` is an input to `phi` along a back edge.
*/
predicate backEdge(SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge) {
edge.phiInput(phi, inp) and
// Conservatively assume that every edge is a back edge if we don't have dominance information.
(
phi.getBasicBlock().bbDominates(edge.getOrigBlock()) or
not hasDominanceInformation(edge.getOrigBlock())
)
}
/**
* Holds if `guard` directly controls the position `controlled` with the
* value `testIsTrue`.
*/
predicate guardDirectlyControlsSsaRead(Guard guard, SsaReadPosition controlled, boolean testIsTrue) {
guard.directlyControls(controlled.(SsaReadPositionBlock).getBlock(), testIsTrue)
or
exists(SsaReadPositionPhiInputEdge controlledEdge | controlledEdge = controlled |
guard.directlyControls(controlledEdge.getOrigBlock(), testIsTrue) or
guard.hasBranchEdge(controlledEdge.getOrigBlock(), controlledEdge.getPhiBlock(), testIsTrue)
)
}
/**
* Holds if `guard` controls the position `controlled` with the value `testIsTrue`.
*/
predicate guardControlsSsaRead(Guard guard, SsaReadPosition controlled, boolean testIsTrue) {
guardDirectlyControlsSsaRead(guard, controlled, testIsTrue)
or
exists(Guard guard0, boolean testIsTrue0 |
implies_v2(guard0, testIsTrue0, guard, testIsTrue) and
guardControlsSsaRead(guard0, controlled, testIsTrue0)
)
}
/**
* Gets a condition that tests whether `v` equals `e + delta`.
*
* If the condition evaluates to `testIsTrue`:
* - `isEq = true` : `v == e + delta`
* - `isEq = false` : `v != e + delta`
*/
Guard eqFlowCond(SsaVariable v, Expr e, int delta, boolean isEq, boolean testIsTrue) {
exists(boolean eqpolarity |
result.isEquality(ssaRead(v, delta), e, eqpolarity) and
(testIsTrue = true or testIsTrue = false) and
eqpolarity.booleanXor(testIsTrue).booleanNot() = isEq
)
or
exists(boolean testIsTrue0 |
implies_v2(result, testIsTrue, eqFlowCond(v, e, delta, isEq, testIsTrue0), testIsTrue0)
)
}
/**
* Holds if `v` is an `SsaExplicitUpdate` that equals `e + delta`.
*/
predicate ssaUpdateStep(SsaExplicitUpdate v, Expr e, int delta) {
v.getDefiningExpr().(VariableAssign).getSource() = e and delta = 0
or
v.getDefiningExpr().(PostIncExpr).getExpr() = e and delta = 1
or
v.getDefiningExpr().(PreIncExpr).getExpr() = e and delta = 1
or
v.getDefiningExpr().(PostDecExpr).getExpr() = e and delta = -1
or
v.getDefiningExpr().(PreDecExpr).getExpr() = e and delta = -1
or
v.getDefiningExpr().(AssignOp) = e and delta = 0
}
/**
* Holds if `e1 + delta` equals `e2`.
*/
predicate valueFlowStep(Expr e2, Expr e1, int delta) {
e2.(AssignExpr).getSource() = e1 and delta = 0
or
e2.(PlusExpr).getExpr() = e1 and delta = 0
or
e2.(PostIncExpr).getExpr() = e1 and delta = 0
or
e2.(PostDecExpr).getExpr() = e1 and delta = 0
or
e2.(PreIncExpr).getExpr() = e1 and delta = 1
or
e2.(PreDecExpr).getExpr() = e1 and delta = -1
or
exists(ArrayCreationExpr a |
arrayLengthDef(e2, a) and
a.getDimension(0) = e1 and
delta = 0
)
or
exists(Expr x |
e2.(AddExpr).hasOperands(e1, x)
or
exists(AssignAddExpr add | add = e2 |
add.getDest() = e1 and add.getRhs() = x
or
add.getDest() = x and add.getRhs() = e1
)
|
x.(ConstantIntegerExpr).getIntValue() = delta
)
or
exists(Expr x |
exists(SubExpr sub |
e2 = sub and
sub.getLeftOperand() = e1 and
sub.getRightOperand() = x
)
or
exists(AssignSubExpr sub |
e2 = sub and
sub.getDest() = e1 and
sub.getRhs() = x
)
|
x.(ConstantIntegerExpr).getIntValue() = -delta
)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
/**
* Provides sign analysis to determine whether expression are always positive
* or negative.
*
* The analysis is implemented as an abstract interpretation over the
* three-valued domain `{negative, zero, positive}`.
*/
import semmle.code.java.dataflow.internal.rangeanalysis.SignAnalysisCommon

View File

@@ -0,0 +1,12 @@
/**
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) taint-tracking analyses.
*/
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.DataFlow2
import semmle.code.java.dataflow.internal.TaintTrackingUtil::StringBuilderVarModule
module TaintTracking {
import semmle.code.java.dataflow.internal.tainttracking1.TaintTrackingImpl
}

View File

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

View File

@@ -0,0 +1,469 @@
/**
* Provides predicates for giving improved type bounds on expressions.
*
* An inferred bound on the runtime type of an expression can be either exact
* or merely an upper bound. Bounds are only reported if they are likely to be
* better than the static bound, which can happen either if an inferred exact
* type has a subtype or if an inferred upper bound passed through at least one
* explicit or implicit cast that lost type information.
*/
import java
private import semmle.code.java.dispatch.VirtualDispatch
private import semmle.code.java.dataflow.internal.BaseSSA
private import semmle.code.java.controlflow.Guards
private newtype TTypeFlowNode =
TField(Field f) { not f.getType() instanceof PrimitiveType } or
TSsa(BaseSsaVariable ssa) { not ssa.getSourceVariable().getType() instanceof PrimitiveType } or
TExpr(Expr e) or
TMethod(Method m) { not m.getReturnType() instanceof PrimitiveType }
/**
* A `Field`, `BaseSsaVariable`, `Expr`, or `Method`.
*/
private class TypeFlowNode extends TTypeFlowNode {
string toString() {
result = asField().toString() or
result = asSsa().toString() or
result = asExpr().toString() or
result = asMethod().toString()
}
Location getLocation() {
result = asField().getLocation() or
result = asSsa().getLocation() or
result = asExpr().getLocation() or
result = asMethod().getLocation()
}
Field asField() { this = TField(result) }
BaseSsaVariable asSsa() { this = TSsa(result) }
Expr asExpr() { this = TExpr(result) }
Method asMethod() { this = TMethod(result) }
RefType getType() {
result = asField().getType() or
result = asSsa().getSourceVariable().getType() or
result = boxIfNeeded(asExpr().getType()) or
result = asMethod().getReturnType()
}
}
/** Gets `t` if it is a `RefType` or the boxed type if `t` is a primitive type. */
private RefType boxIfNeeded(Type t) {
t.(PrimitiveType).getBoxedType() = result or
result = t
}
/**
* Holds if `arg` is an argument for the parameter `p` in a private callable.
*/
private predicate privateParamArg(Parameter p, Argument arg) {
p.getAnArgument() = arg and
p.getCallable().isPrivate()
}
/**
* Holds if data can flow from `n1` to `n2` in one step, and `n1` is not
* necessarily functionally determined by `n2`.
*/
private predicate joinStep0(TypeFlowNode n1, TypeFlowNode n2) {
n2.asExpr().(ChooseExpr).getAResultExpr() = n1.asExpr()
or
exists(Field f, Expr e |
f = n2.asField() and
f.getAnAssignedValue() = e and
e = n1.asExpr() and
not e.(FieldAccess).getField() = f
)
or
n2.asSsa().(BaseSsaPhiNode).getAnUltimateLocalDefinition() = n1.asSsa()
or
exists(ReturnStmt ret |
n2.asMethod() = ret.getEnclosingCallable() and ret.getResult() = n1.asExpr()
)
or
viableImpl_v1(n2.asExpr()) = n1.asMethod()
or
exists(Argument arg, Parameter p |
privateParamArg(p, arg) and
n1.asExpr() = arg and
n2.asSsa().(BaseSsaImplicitInit).isParameterDefinition(p) and
// skip trivial recursion
not arg = n2.asSsa().getAUse()
)
}
/**
* Holds if data can flow from `n1` to `n2` in one step, and `n1` is
* functionally determined by `n2`.
*/
private predicate step(TypeFlowNode n1, TypeFlowNode n2) {
n2.asExpr() = n1.asField().getAnAccess()
or
n2.asExpr() = n1.asSsa().getAUse()
or
n2.asExpr().(CastExpr).getExpr() = n1.asExpr() and
not n2.asExpr().getType() instanceof PrimitiveType
or
n2.asExpr().(AssignExpr).getSource() = n1.asExpr() and
not n2.asExpr().getType() instanceof PrimitiveType
or
n2.asSsa().(BaseSsaUpdate).getDefiningExpr().(VariableAssign).getSource() = n1.asExpr()
or
n2.asSsa().(BaseSsaImplicitInit).captures(n1.asSsa())
}
/**
* Holds if `null` is the only value that flows to `n`.
*/
private predicate isNull(TypeFlowNode n) {
n.asExpr() instanceof NullLiteral
or
exists(LocalVariableDeclExpr decl |
n.asSsa().(BaseSsaUpdate).getDefiningExpr() = decl and
not decl.hasImplicitInit() and
not exists(decl.getInit())
)
or
exists(TypeFlowNode mid | isNull(mid) and step(mid, n))
or
forex(TypeFlowNode mid | joinStep0(mid, n) | isNull(mid)) and
// Fields that are never assigned a non-null value are probably set by
// reflection and are thus not always null.
not exists(n.asField())
}
/**
* Holds if data can flow from `n1` to `n2` in one step, `n1` is not necessarily
* functionally determined by `n2`, and `n1` might take a non-null value.
*/
private predicate joinStep(TypeFlowNode n1, TypeFlowNode n2) {
joinStep0(n1, n2) and not isNull(n1)
}
private predicate joinStepRank1(int r, TypeFlowNode n1, TypeFlowNode n2) {
n1 =
rank[r](TypeFlowNode n |
joinStep(n, n2)
|
n order by n.getLocation().getStartLine(), n.getLocation().getStartColumn()
)
}
private predicate joinStepRank2(int r2, int r1, TypeFlowNode n) {
r1 = rank[r2](int r | joinStepRank1(r, _, n) | r)
}
private predicate joinStepRank(int r, TypeFlowNode n1, TypeFlowNode n2) {
exists(int r1 |
joinStepRank1(r1, n1, n2) and
joinStepRank2(r, r1, n2)
)
}
private int lastRank(TypeFlowNode n) { result = max(int r | joinStepRank(r, _, n)) }
private predicate exactTypeBase(TypeFlowNode n, RefType t) {
exists(ClassInstanceExpr e |
n.asExpr() = e and
e.getType() = t and
not e instanceof FunctionalExpr and
exists(RefType sub | sub.getASourceSupertype() = t.getSourceDeclaration())
)
}
private predicate exactTypeRank(int r, TypeFlowNode n, RefType t) {
forall(TypeFlowNode mid | joinStepRank(r, mid, n) | exactType(mid, t)) and
joinStepRank(r, _, n)
}
private predicate exactTypeJoin(int r, TypeFlowNode n, RefType t) {
exactTypeRank(1, n, t) and r = 1
or
exactTypeJoin(r - 1, n, t) and exactTypeRank(r, n, t)
}
/**
* Holds if the runtime type of `n` is exactly `t` and if this bound is a
* non-trivial lower bound, that is, `t` has a subtype.
*/
private predicate exactType(TypeFlowNode n, RefType t) {
exactTypeBase(n, t)
or
exists(TypeFlowNode mid | exactType(mid, t) and step(mid, n))
or
// The following is an optimized version of
// `forex(TypeFlowNode mid | joinStep(mid, n) | exactType(mid, t))`
exactTypeJoin(lastRank(n), n, t)
}
/**
* Holds if `n` occurs in a position where type information might be discarded;
* `t` is the potentially boxed type of `n`, `t1` is the erasure of `t`, and
* `t2` is the erased type of the implicit or explicit cast.
*/
pragma[noinline]
private predicate upcastCand(TypeFlowNode n, RefType t, RefType t1, RefType t2) {
t = boxIfNeeded(n.getType()) and
t.getErasure() = t1 and
(
exists(Variable v | v.getAnAssignedValue() = n.asExpr() and t2 = v.getType().getErasure())
or
exists(CastExpr c | c.getExpr() = n.asExpr() and t2 = c.getType().getErasure())
or
exists(ReturnStmt ret |
ret.getResult() = n.asExpr() and t2 = ret.getEnclosingCallable().getReturnType().getErasure()
)
or
exists(MethodAccess ma | viableImpl_v1(ma) = n.asMethod() and t2 = ma.getType())
or
exists(Parameter p | privateParamArg(p, n.asExpr()) and t2 = p.getType().getErasure())
or
exists(ChooseExpr cond |
cond.getAResultExpr() = n.asExpr() and
t2 = cond.getType().getErasure()
)
)
}
/** Holds if `n` occurs in a position where type information is discarded. */
private predicate upcast(TypeFlowNode n, RefType t) {
exists(RefType t1, RefType t2 |
upcastCand(n, t, t1, t2) and
t1.getASourceSupertype+() = t2
)
}
/** Gets the element type of an array or subtype of `Iterable`. */
private Type elementType(RefType t) {
result = t.(Array).getComponentType()
or
exists(ParameterizedType it |
it.getSourceDeclaration().hasQualifiedName("java.lang", "Iterable") and
result = it.getATypeArgument() and
t.extendsOrImplements*(it)
)
}
private predicate upcastEnhancedForStmtAux(BaseSsaUpdate v, RefType t, RefType t1, RefType t2) {
exists(EnhancedForStmt for |
for.getVariable() = v.getDefiningExpr() and
v.getSourceVariable().getType().getErasure() = t2 and
t = boxIfNeeded(elementType(for.getExpr().getType())) and
t.getErasure() = t1
)
}
/**
* Holds if `v` is the iteration variable of an enhanced for statement, `t` is
* the type of the elements being iterated over, and this type is more precise
* than the type of `v`.
*/
private predicate upcastEnhancedForStmt(BaseSsaUpdate v, RefType t) {
exists(RefType t1, RefType t2 |
upcastEnhancedForStmtAux(v, t, t1, t2) and
t1.getASourceSupertype+() = t2
)
}
private predicate downcastSuccessorAux(
CastExpr cast, BaseSsaVariable v, RefType t, RefType t1, RefType t2
) {
cast.getExpr() = v.getAUse() and
t = cast.getType() and
t1 = t.getErasure() and
t2 = v.getSourceVariable().getType().getErasure()
}
/**
* Holds if `va` is an access to a value that has previously been downcast to `t`.
*/
private predicate downcastSuccessor(VarAccess va, RefType t) {
exists(CastExpr cast, BaseSsaVariable v, RefType t1, RefType t2 |
downcastSuccessorAux(cast, v, t, t1, t2) and
t1.getASourceSupertype+() = t2 and
va = v.getAUse() and
dominates(cast, va) and
dominates(cast.(ControlFlowNode).getANormalSuccessor(), va)
)
}
/**
* Holds if `va` is an access to a value that is guarded by `instanceof t`.
*/
private predicate instanceOfGuarded(VarAccess va, RefType t) {
exists(InstanceOfExpr ioe, BaseSsaVariable v |
ioe.getExpr() = v.getAUse() and
t = ioe.getCheckedType() and
va = v.getAUse() and
guardControls_v1(ioe, va.getBasicBlock(), true)
)
}
/**
* Holds if `aa` is an access to a value that is guarded by `instanceof t`.
*/
predicate arrayInstanceOfGuarded(ArrayAccess aa, RefType t) {
exists(InstanceOfExpr ioe, BaseSsaVariable v1, BaseSsaVariable v2, ArrayAccess aa1 |
ioe.getExpr() = aa1 and
t = ioe.getCheckedType() and
aa1.getArray() = v1.getAUse() and
aa1.getIndexExpr() = v2.getAUse() and
aa.getArray() = v1.getAUse() and
aa.getIndexExpr() = v2.getAUse() and
guardControls_v1(ioe, aa.getBasicBlock(), true)
)
}
/**
* Holds if `n` has type `t` and this information is discarded, such that `t`
* might be a better type bound for nodes where `n` flows to.
*/
private predicate typeFlowBase(TypeFlowNode n, RefType t) {
exists(RefType srctype |
upcast(n, srctype) or
upcastEnhancedForStmt(n.asSsa(), srctype) or
downcastSuccessor(n.asExpr(), srctype) or
instanceOfGuarded(n.asExpr(), srctype) or
arrayInstanceOfGuarded(n.asExpr(), srctype) or
n.asExpr().(FunctionalExpr).getConstructedType() = srctype
|
t = srctype.(BoundedType).getAnUltimateUpperBoundType()
or
t = srctype and not srctype instanceof BoundedType
)
}
/**
* Holds if `t` is a bound that holds on one of the incoming edges to `n` and
* thus is a candidate bound for `n`.
*/
pragma[noinline]
private predicate typeFlowJoinCand(TypeFlowNode n, RefType t) {
exists(TypeFlowNode mid | joinStep(mid, n) | typeFlow(mid, t))
}
/**
* Holds if `t` is a candidate bound for `n` that is also valid for data coming
* through the edges into `n` ranked from `1` to `r`.
*/
private predicate typeFlowJoin(int r, TypeFlowNode n, RefType t) {
(
r = 1 and typeFlowJoinCand(n, t)
or
typeFlowJoin(r - 1, n, t) and joinStepRank(r, _, n)
) and
forall(TypeFlowNode mid | joinStepRank(r, mid, n) |
exists(RefType midtyp | exactType(mid, midtyp) or typeFlow(mid, midtyp) |
midtyp.getASupertype*() = t
)
)
}
/**
* Holds if the runtime type of `n` is bounded by `t` and if this bound is
* likely to be better than the static type of `n`.
*/
private predicate typeFlow(TypeFlowNode n, RefType t) {
typeFlowBase(n, t)
or
exists(TypeFlowNode mid | typeFlow(mid, t) and step(mid, n))
or
typeFlowJoin(lastRank(n), n, t)
}
pragma[nomagic]
private predicate erasedTypeBound(RefType t) {
exists(RefType t0 | typeFlow(_, t0) and t = t0.getErasure())
}
pragma[nomagic]
private predicate typeBound(RefType t) { typeFlow(_, t) }
/**
* Holds if we have a bound for `n` that is better than `t`, taking only erased
* types into account.
*/
pragma[nomagic]
private predicate irrelevantErasedBound(TypeFlowNode n, RefType t) {
exists(RefType bound |
typeFlow(n, bound)
or
n.getType() = bound and typeFlow(n, _)
|
t = bound.getErasure().(RefType).getASourceSupertype+() and
erasedTypeBound(t)
)
}
/**
* Holds if we have a bound for `n` that is better than `t`.
*/
pragma[nomagic]
private predicate irrelevantBound(TypeFlowNode n, RefType t) {
exists(RefType bound |
typeFlow(n, bound) and
t = bound.getASupertype+() and
typeBound(t) and
typeFlow(n, t) and
not t.getASupertype*() = bound
or
n.getType() = bound and
typeFlow(n, t) and
t = bound.getASupertype*()
)
}
/**
* Holds if the runtime type of `n` is bounded by `t`, if this bound is likely
* to be better than the static type of `n`, and if this the best such bound.
*/
private predicate bestTypeFlow(TypeFlowNode n, RefType t) {
typeFlow(n, t) and
not irrelevantErasedBound(n, t.getErasure()) and
not irrelevantBound(n, t)
}
cached
private module TypeFlowBounds {
/**
* Holds if the runtime type of `f` is bounded by `t` and if this bound is
* likely to be better than the static type of `f`. The flag `exact` indicates
* whether `t` is an exact bound or merely an upper bound.
*/
cached
predicate fieldTypeFlow(Field f, RefType t, boolean exact) {
exists(TypeFlowNode n |
n.asField() = f and
(
exactType(n, t) and exact = true
or
not exactType(n, _) and bestTypeFlow(n, t) and exact = false
)
)
}
/**
* Holds if the runtime type of `e` is bounded by `t` and if this bound is
* likely to be better than the static type of `e`. The flag `exact` indicates
* whether `t` is an exact bound or merely an upper bound.
*/
cached
predicate exprTypeFlow(Expr e, RefType t, boolean exact) {
exists(TypeFlowNode n |
n.asExpr() = e and
(
exactType(n, t) and exact = true
or
not exactType(n, _) and bestTypeFlow(n, t) and exact = false
)
)
}
}
import TypeFlowBounds

View File

@@ -0,0 +1,578 @@
/**
* Provides classes and predicates for SSA representation (Static Single Assignment form)
* restricted to local variables.
*
* An SSA variable consists of the pair of a `BaseSsaSourceVariable` and a
* `ControlFlowNode` at which it is defined. Each SSA variable is defined
* either by a phi node, an implicit initial value (for parameters),
* or an explicit update.
*
* This is a restricted version of SSA.qll that only handles `LocalScopeVariable`s
* in order to not depend on virtual dispatch.
*/
import java
private newtype TBaseSsaSourceVariable =
TLocalVar(Callable c, LocalScopeVariable v) {
c = v.getCallable() or c = v.getAnAccess().getEnclosingCallable()
}
/**
* A local variable in the context of a `Callable` in which it is accessed.
*/
class BaseSsaSourceVariable extends TBaseSsaSourceVariable {
/** Gets the variable corresponding to this `BaseSsaSourceVariable`. */
LocalScopeVariable getVariable() { this = TLocalVar(_, result) }
/**
* Gets an access of this `BaseSsaSourceVariable`. This access is within `this.getEnclosingCallable()`.
*/
cached
VarAccess getAnAccess() {
exists(LocalScopeVariable v, Callable c |
this = TLocalVar(c, v) and result = v.getAnAccess() and result.getEnclosingCallable() = c
)
}
/** Gets the `Callable` in which this `BaseSsaSourceVariable` is defined. */
Callable getEnclosingCallable() { this = TLocalVar(result, _) }
string toString() {
exists(LocalScopeVariable v, Callable c | this = TLocalVar(c, v) |
if c = v.getCallable()
then result = v.getName()
else result = c.getName() + "(..)." + v.getName()
)
}
Location getLocation() {
exists(LocalScopeVariable v | this = TLocalVar(_, v) and result = v.getLocation())
}
/** Gets the type of this variable. */
Type getType() { result = this.getVariable().getType() }
}
cached
private module SsaImpl {
/** Gets the destination variable of an update of a tracked variable. */
cached
BaseSsaSourceVariable getDestVar(VariableUpdate upd) {
result.getAnAccess() = upd.(Assignment).getDest()
or
exists(LocalVariableDecl v | v = upd.(LocalVariableDeclExpr).getVariable() |
result = TLocalVar(v.getCallable(), v)
)
or
result.getAnAccess() = upd.(UnaryAssignExpr).getExpr()
}
/** Holds if `n` updates the local variable `v`. */
cached
predicate variableUpdate(BaseSsaSourceVariable v, ControlFlowNode n, BasicBlock b, int i) {
exists(VariableUpdate a | a = n | getDestVar(a) = v) and
b.getNode(i) = n and
hasDominanceInformation(b)
}
/** Gets the definition point of a nested class in the parent scope. */
private ControlFlowNode parentDef(NestedClass nc) {
nc.(AnonymousClass).getClassInstanceExpr() = result or
nc.(LocalClass).getLocalClassDeclStmt() = result
}
/**
* Gets the enclosing type of a nested class.
*
* Differs from `RefType.getEnclosingType()` by including anonymous classes defined by lambdas.
*/
private RefType desugaredGetEnclosingType(NestedClass inner) {
exists(ControlFlowNode node |
node = parentDef(inner) and
node.getEnclosingCallable().getDeclaringType() = result
)
}
/**
* Gets the control flow node at which the variable is read to get the value for
* a `VarAccess` inside a closure. `capturedvar` is the variable in its defining
* scope, and `closurevar` is the variable in the closure.
*/
private ControlFlowNode captureNode(
BaseSsaSourceVariable capturedvar, BaseSsaSourceVariable closurevar
) {
exists(
LocalScopeVariable v, Callable inner, Callable outer, NestedClass innerclass, VarAccess va
|
va.getVariable() = v and
inner = va.getEnclosingCallable() and
outer = v.getCallable() and
inner != outer and
inner.getDeclaringType() = innerclass and
result = parentDef(desugaredGetEnclosingType*(innerclass)) and
result.getEnclosingStmt().getEnclosingCallable() = outer and
capturedvar = TLocalVar(outer, v) and
closurevar = TLocalVar(inner, v)
)
}
/** Holds if `VarAccess` `use` of `v` occurs in `b` at index `i`. */
private predicate variableUse(BaseSsaSourceVariable v, RValue use, BasicBlock b, int i) {
v.getAnAccess() = use and b.getNode(i) = use
}
/** Holds if the value of `v` is captured in `b` at index `i`. */
private predicate variableCapture(
BaseSsaSourceVariable capturedvar, BaseSsaSourceVariable closurevar, BasicBlock b, int i
) {
b.getNode(i) = captureNode(capturedvar, closurevar)
}
/** Holds if the value of `v` is read in `b` at index `i`. */
private predicate variableUseOrCapture(BaseSsaSourceVariable v, BasicBlock b, int i) {
variableUse(v, _, b, i) or variableCapture(v, _, b, i)
}
/*
* Liveness analysis to restrict the size of the SSA representation.
*/
private predicate liveAtEntry(BaseSsaSourceVariable v, BasicBlock b) {
exists(int i | variableUseOrCapture(v, b, i) |
not exists(int j | variableUpdate(v, _, b, j) | j < i)
)
or
liveAtExit(v, b) and not variableUpdate(v, _, b, _)
}
private predicate liveAtExit(BaseSsaSourceVariable v, BasicBlock b) {
liveAtEntry(v, b.getABBSuccessor())
}
/** Holds if a phi node for `v` is needed at the beginning of basic block `b`. */
cached
predicate phiNode(BaseSsaSourceVariable v, BasicBlock b) {
liveAtEntry(v, b) and
exists(BasicBlock def | dominanceFrontier(def, b) |
variableUpdate(v, _, def, _) or phiNode(v, def)
)
}
/** Holds if `v` has an implicit definition at the entry, `b`, of the callable. */
cached
predicate hasEntryDef(BaseSsaSourceVariable v, BasicBlock b) {
exists(LocalScopeVariable l, Callable c | v = TLocalVar(c, l) and c.getBody() = b |
l instanceof Parameter or
l.getCallable() != c
)
}
/**
* The construction of SSA form ensures that each use of a variable is
* dominated by its definition. A definition of an SSA variable therefore
* reaches a `ControlFlowNode` if it is the _closest_ SSA variable definition
* that dominates the node. If two definitions dominate a node then one must
* dominate the other, so therefore the definition of _closest_ is given by the
* dominator tree. Thus, reaching definitions can be calculated in terms of
* dominance.
*/
cached
module SsaDefReaches {
/**
* Holds if `rankix` is the rank the index `i` at which there is an SSA definition or use of
* `v` in the basic block `b`.
*
* Basic block indices are translated to rank indices in order to skip
* irrelevant indices at which there is no definition or use when traversing
* basic blocks.
*/
private predicate defUseRank(BaseSsaSourceVariable v, BasicBlock b, int rankix, int i) {
i =
rank[rankix](int j |
any(TrackedSsaDef def).definesAt(v, b, j) or variableUseOrCapture(v, b, j)
)
}
/** Gets the maximum rank index for the given variable and basic block. */
private int lastRank(BaseSsaSourceVariable v, BasicBlock b) {
result = max(int rankix | defUseRank(v, b, rankix, _))
}
/** Holds if a definition of an SSA variable occurs at the specified rank index in basic block `b`. */
private predicate ssaDefRank(
BaseSsaSourceVariable v, TrackedSsaDef def, BasicBlock b, int rankix
) {
exists(int i |
def.definesAt(v, b, i) and
defUseRank(v, b, rankix, i)
)
}
/** Holds if the SSA definition reaches the rank index `rankix` in its own basic block `b`. */
private predicate ssaDefReachesRank(
BaseSsaSourceVariable v, TrackedSsaDef def, BasicBlock b, int rankix
) {
ssaDefRank(v, def, b, rankix)
or
ssaDefReachesRank(v, def, b, rankix - 1) and
rankix <= lastRank(v, b) and
not ssaDefRank(v, _, b, rankix)
}
/**
* Holds if the SSA definition of `v` at `def` reaches the end of a basic block `b`, at
* which point it is still live, without crossing another SSA definition of `v`.
*/
cached
predicate ssaDefReachesEndOfBlock(BaseSsaSourceVariable v, TrackedSsaDef def, BasicBlock b) {
liveAtExit(v, b) and
(
ssaDefReachesRank(v, def, b, lastRank(v, b))
or
exists(BasicBlock idom |
bbIDominates(idom, b) and // It is sufficient to traverse the dominator graph, cf. discussion above.
ssaDefReachesEndOfBlock(v, def, idom) and
not any(TrackedSsaDef other).definesAt(v, b, _)
)
)
}
/**
* Holds if the SSA definition of `v` at `def` reaches `use` in the same basic block
* without crossing another SSA definition of `v`.
*/
private predicate ssaDefReachesUseWithinBlock(
BaseSsaSourceVariable v, TrackedSsaDef def, RValue use
) {
exists(BasicBlock b, int rankix, int i |
ssaDefReachesRank(v, def, b, rankix) and
defUseRank(v, b, rankix, i) and
variableUse(v, use, b, i)
)
}
/**
* Holds if the SSA definition of `v` at `def` reaches `use` without crossing another
* SSA definition of `v`.
*/
cached
predicate ssaDefReachesUse(BaseSsaSourceVariable v, TrackedSsaDef def, RValue use) {
ssaDefReachesUseWithinBlock(v, def, use)
or
exists(BasicBlock b |
variableUse(v, use, b, _) and
ssaDefReachesEndOfBlock(v, def, b.getABBPredecessor()) and
not ssaDefReachesUseWithinBlock(v, _, use)
)
}
/**
* Holds if the SSA definition of `v` at `def` reaches the capture point of
* `closurevar` in the same basic block without crossing another SSA
* definition of `v`.
*/
private predicate ssaDefReachesCaptureWithinBlock(
BaseSsaSourceVariable v, TrackedSsaDef def, BaseSsaSourceVariable closurevar
) {
exists(BasicBlock b, int rankix, int i |
ssaDefReachesRank(v, def, b, rankix) and
defUseRank(v, b, rankix, i) and
variableCapture(v, closurevar, b, i)
)
}
/**
* Holds if the SSA definition of `v` at `def` reaches capture point of
* `closurevar` without crossing another SSA definition of `v`.
*/
cached
predicate ssaDefReachesCapture(
BaseSsaSourceVariable v, TrackedSsaDef def, BaseSsaSourceVariable closurevar
) {
ssaDefReachesCaptureWithinBlock(v, def, closurevar)
or
exists(BasicBlock b |
variableCapture(v, closurevar, b, _) and
ssaDefReachesEndOfBlock(v, def, b.getABBPredecessor()) and
not ssaDefReachesCaptureWithinBlock(v, _, closurevar)
)
}
}
private module AdjacentUsesImpl {
/**
* Holds if `rankix` is the rank the index `i` at which there is an SSA definition or explicit use of
* `v` in the basic block `b`.
*/
private predicate defUseRank(BaseSsaSourceVariable v, BasicBlock b, int rankix, int i) {
i = rank[rankix](int j | any(TrackedSsaDef def).definesAt(v, b, j) or variableUse(v, _, b, j))
}
/** Gets the maximum rank index for the given variable and basic block. */
private int lastRank(BaseSsaSourceVariable v, BasicBlock b) {
result = max(int rankix | defUseRank(v, b, rankix, _))
}
/** Holds if `v` is defined or used in `b`. */
private predicate varOccursInBlock(BaseSsaSourceVariable v, BasicBlock b) {
defUseRank(v, b, _, _)
}
/** Holds if `v` occurs in `b` or one of `b`'s transitive successors. */
private predicate blockPrecedesVar(BaseSsaSourceVariable v, BasicBlock b) {
varOccursInBlock(v, b)
or
ssaDefReachesEndOfBlock(v, _, b)
}
/**
* Holds if `b2` is a transitive successor of `b1` and `v` occurs in `b1` and
* in `b2` or one of its transitive successors but not in any block on the path
* between `b1` and `b2`.
*/
private predicate varBlockReaches(BaseSsaSourceVariable v, BasicBlock b1, BasicBlock b2) {
varOccursInBlock(v, b1) and
b2 = b1.getABBSuccessor() and
blockPrecedesVar(v, b2)
or
exists(BasicBlock mid |
varBlockReaches(v, b1, mid) and
b2 = mid.getABBSuccessor() and
not varOccursInBlock(v, mid) and
blockPrecedesVar(v, b2)
)
}
/**
* Holds if `b2` is a transitive successor of `b1` and `v` occurs in `b1` and
* `b2` but not in any block on the path between `b1` and `b2`.
*/
private predicate varBlockStep(BaseSsaSourceVariable v, BasicBlock b1, BasicBlock b2) {
varBlockReaches(v, b1, b2) and
varOccursInBlock(v, b2)
}
/**
* Holds if `v` occurs at index `i1` in `b1` and at index `i2` in `b2` and
* there is a path between them without any occurrence of `v`.
*/
pragma[nomagic]
predicate adjacentVarRefs(BaseSsaSourceVariable v, BasicBlock b1, int i1, BasicBlock b2, int i2) {
exists(int rankix |
b1 = b2 and
defUseRank(v, b1, rankix, i1) and
defUseRank(v, b2, rankix + 1, i2)
)
or
defUseRank(v, b1, lastRank(v, b1), i1) and
varBlockStep(v, b1, b2) and
defUseRank(v, b2, 1, i2)
}
}
private import AdjacentUsesImpl
/**
* Holds if the value defined at `def` can reach `use` without passing through
* any other uses, but possibly through phi nodes.
*/
cached
predicate firstUse(TrackedSsaDef def, RValue use) {
exists(BaseSsaSourceVariable v, BasicBlock b1, int i1, BasicBlock b2, int i2 |
adjacentVarRefs(v, b1, i1, b2, i2) and
def.definesAt(v, b1, i1) and
variableUse(v, use, b2, i2)
)
or
exists(
BaseSsaSourceVariable v, TrackedSsaDef redef, BasicBlock b1, int i1, BasicBlock b2, int i2
|
redef instanceof BaseSsaPhiNode
|
adjacentVarRefs(v, b1, i1, b2, i2) and
def.definesAt(v, b1, i1) and
redef.definesAt(v, b2, i2) and
firstUse(redef, use)
)
}
cached
module SsaPublic {
/**
* Holds if `use1` and `use2` form an adjacent use-use-pair of the same SSA
* variable, that is, the value read in `use1` can reach `use2` without passing
* through any other use or any SSA definition of the variable.
*/
cached
predicate baseSsaAdjacentUseUseSameVar(RValue use1, RValue use2) {
exists(BaseSsaSourceVariable v, BasicBlock b1, int i1, BasicBlock b2, int i2 |
adjacentVarRefs(v, b1, i1, b2, i2) and
variableUse(v, use1, b1, i1) and
variableUse(v, use2, b2, i2)
)
}
/**
* Holds if `use1` and `use2` form an adjacent use-use-pair of the same
* `SsaSourceVariable`, that is, the value read in `use1` can reach `use2`
* without passing through any other use or any SSA definition of the variable
* except for phi nodes.
*/
cached
predicate baseSsaAdjacentUseUse(RValue use1, RValue use2) {
baseSsaAdjacentUseUseSameVar(use1, use2)
or
exists(
BaseSsaSourceVariable v, TrackedSsaDef def, BasicBlock b1, int i1, BasicBlock b2, int i2
|
adjacentVarRefs(v, b1, i1, b2, i2) and
variableUse(v, use1, b1, i1) and
def.definesAt(v, b2, i2) and
firstUse(def, use2) and
def instanceof BaseSsaPhiNode
)
}
}
}
private import SsaImpl
private import SsaDefReaches
import SsaPublic
private newtype TBaseSsaVariable =
TSsaPhiNode(BaseSsaSourceVariable v, BasicBlock b) { phiNode(v, b) } or
TSsaUpdate(BaseSsaSourceVariable v, ControlFlowNode n, BasicBlock b, int i) {
variableUpdate(v, n, b, i)
} or
TSsaEntryDef(BaseSsaSourceVariable v, BasicBlock b) { hasEntryDef(v, b) }
/**
* An SSA definition excluding those variables that use a trivial SSA construction.
*/
private class TrackedSsaDef extends BaseSsaVariable {
/**
* Holds if this SSA definition occurs at the specified position.
* Phi nodes are placed at index -1.
*/
predicate definesAt(BaseSsaSourceVariable v, BasicBlock b, int i) {
this = TSsaPhiNode(v, b) and i = -1
or
this = TSsaUpdate(v, _, b, i)
or
this = TSsaEntryDef(v, b) and i = 0
}
}
/**
* An SSA variable.
*/
class BaseSsaVariable extends TBaseSsaVariable {
/** Gets the SSA source variable underlying this SSA variable. */
BaseSsaSourceVariable getSourceVariable() {
this = TSsaPhiNode(result, _) or
this = TSsaUpdate(result, _, _, _) or
this = TSsaEntryDef(result, _)
}
/** Gets the `ControlFlowNode` at which this SSA variable is defined. */
ControlFlowNode getCFGNode() {
this = TSsaPhiNode(_, result) or
this = TSsaUpdate(_, result, _, _) or
this = TSsaEntryDef(_, result)
}
string toString() { none() }
Location getLocation() { result = getCFGNode().getLocation() }
/** Gets the `BasicBlock` in which this SSA variable is defined. */
BasicBlock getBasicBlock() { result = getCFGNode().getBasicBlock() }
/** Gets an access of this SSA variable. */
RValue getAUse() { ssaDefReachesUse(_, this, result) }
/**
* Gets an access of the SSA source variable underlying this SSA variable
* that can be reached from this SSA variable without passing through any
* other uses, but potentially through phi nodes.
*
* Subsequent uses can be found by following the steps defined by
* `baseSsaAdjacentUseUse`.
*/
RValue getAFirstUse() { firstUse(this, result) }
/** Holds if this SSA variable is live at the end of `b`. */
predicate isLiveAtEndOfBlock(BasicBlock b) { ssaDefReachesEndOfBlock(_, this, b) }
/** Gets an input to the phi node defining the SSA variable. */
private BaseSsaVariable getAPhiInput() { result = this.(BaseSsaPhiNode).getAPhiInput() }
/** Gets a definition in the same callable that ultimately defines this variable and is not itself a phi node. */
BaseSsaVariable getAnUltimateLocalDefinition() {
result = this.getAPhiInput*() and not result instanceof BaseSsaPhiNode
}
/**
* Gets an SSA variable whose value can flow to this one in one step. This
* includes inputs to phi nodes and the captured ssa variable for a closure
* variable.
*/
private BaseSsaVariable getAPhiInputOrCapturedVar() {
result = this.(BaseSsaPhiNode).getAPhiInput() or
this.(BaseSsaImplicitInit).captures(result)
}
/** Gets a definition that ultimately defines this variable and is not itself a phi node. */
BaseSsaVariable getAnUltimateDefinition() {
result = this.getAPhiInputOrCapturedVar*() and not result instanceof BaseSsaPhiNode
}
}
/** An SSA variable that is defined by a `VariableUpdate`. */
class BaseSsaUpdate extends BaseSsaVariable, TSsaUpdate {
BaseSsaUpdate() {
exists(VariableUpdate upd | upd = this.getCFGNode() and getDestVar(upd) = getSourceVariable())
}
override string toString() { result = "SSA def(" + getSourceVariable() + ")" }
/** Gets the `VariableUpdate` defining the SSA variable. */
VariableUpdate getDefiningExpr() {
result = this.getCFGNode() and getDestVar(result) = getSourceVariable()
}
}
/**
* An SSA variable that is defined by its initial value in the callable. This
* includes initial values of parameters, fields, and closure variables.
*/
class BaseSsaImplicitInit extends BaseSsaVariable, TSsaEntryDef {
override string toString() { result = "SSA init(" + getSourceVariable() + ")" }
/** Holds if this is a closure variable that captures the value of `capturedvar`. */
predicate captures(BaseSsaVariable capturedvar) {
ssaDefReachesCapture(_, capturedvar, getSourceVariable())
}
/**
* Holds if the SSA variable is a parameter defined by its initial value in the callable.
*/
predicate isParameterDefinition(Parameter p) {
getSourceVariable() = TLocalVar(p.getCallable(), p) and p.getCallable().getBody() = getCFGNode()
}
}
/** An SSA phi node. */
class BaseSsaPhiNode extends BaseSsaVariable, TSsaPhiNode {
override string toString() { result = "SSA phi(" + getSourceVariable() + ")" }
/** Gets an input to the phi node defining the SSA variable. */
BaseSsaVariable getAPhiInput() {
exists(BasicBlock phiPred, BaseSsaSourceVariable v |
v = getSourceVariable() and
getCFGNode().(BasicBlock).getABBPredecessor() = phiPred and
ssaDefReachesEndOfBlock(v, result, phiPred)
)
}
}

View File

@@ -0,0 +1,805 @@
import java
import semmle.code.java.Collections
import semmle.code.java.Maps
private import semmle.code.java.dataflow.SSA
private import DataFlowUtil
private import semmle.code.java.dataflow.ExternalFlow
private class EntryType extends RefType {
EntryType() {
this.getSourceDeclaration().getASourceSupertype*().hasQualifiedName("java.util", "Map$Entry")
}
RefType getValueType() {
exists(GenericType t | t.hasQualifiedName("java.util", "Map$Entry") |
indirectlyInstantiates(this, t, 1, result)
)
}
}
private class IterableType extends RefType {
IterableType() {
this.getSourceDeclaration().getASourceSupertype*().hasQualifiedName("java.lang", "Iterable")
}
RefType getElementType() {
exists(GenericType t | t.hasQualifiedName("java.lang", "Iterable") |
indirectlyInstantiates(this, t, 0, result)
)
}
}
private class IteratorType extends RefType {
IteratorType() {
this.getSourceDeclaration().getASourceSupertype*().hasQualifiedName("java.util", "Iterator")
}
RefType getElementType() {
exists(GenericType t | t.hasQualifiedName("java.util", "Iterator") |
indirectlyInstantiates(this, t, 0, result)
)
}
}
private class EnumerationType extends RefType {
EnumerationType() {
this.getSourceDeclaration().getASourceSupertype*().hasQualifiedName("java.util", "Enumeration")
}
RefType getElementType() {
exists(GenericType t | t.hasQualifiedName("java.util", "Enumeration") |
indirectlyInstantiates(this, t, 0, result)
)
}
}
/**
* A type that acts as a container. This includes collections, maps, iterators,
* iterables, enumerations, and map entry pairs. For maps and map entry pairs
* only the value component is considered to act as a container.
*/
class ContainerType extends RefType {
ContainerType() {
this instanceof EntryType or
this instanceof IterableType or
this instanceof IteratorType or
this instanceof EnumerationType or
this instanceof MapType or
this instanceof CollectionType
}
/** Gets the type of the contained elements. */
RefType getElementType() {
result = this.(EntryType).getValueType() or
result = this.(IterableType).getElementType() or
result = this.(IteratorType).getElementType() or
result = this.(EnumerationType).getElementType() or
result = this.(MapType).getValueType() or
result = this.(CollectionType).getElementType()
}
/**
* Gets the type of the contained elements or its upper bound if the type is
* a type variable or wildcard.
*/
RefType getElementTypeBound() {
exists(RefType e | e = this.getElementType() |
result = e and not e instanceof BoundedType
or
result = e.(BoundedType).getAnUltimateUpperBoundType()
)
}
}
private class ContainerFlowSummaries extends SummaryModelCsv {
override predicate row(string row) {
row =
[
"java.util;Map$Entry;true;getKey;;;MapKey of Argument[-1];ReturnValue;value",
"java.util;Map$Entry;true;getValue;;;MapValue of Argument[-1];ReturnValue;value",
"java.util;Map$Entry;true;setValue;;;MapValue of Argument[-1];ReturnValue;value",
"java.util;Map$Entry;true;setValue;;;Argument[0];MapValue of Argument[-1];value",
"java.lang;Iterable;true;iterator;();;Element of Argument[-1];Element of ReturnValue;value",
"java.lang;Iterable;true;spliterator;();;Element of Argument[-1];Element of ReturnValue;value",
"java.util;Iterator;true;next;;;Element of Argument[-1];ReturnValue;value",
"java.util;ListIterator;true;previous;;;Element of Argument[-1];ReturnValue;value",
"java.util;ListIterator;true;add;(Object);;Argument[0];Element of Argument[-1];value",
"java.util;ListIterator;true;set;(Object);;Argument[0];Element of Argument[-1];value",
"java.util;Enumeration;true;asIterator;;;Element of Argument[-1];Element of ReturnValue;value",
"java.util;Enumeration;true;nextElement;;;Element of Argument[-1];ReturnValue;value",
"java.util;Map;true;computeIfAbsent;;;MapValue of Argument[-1];ReturnValue;value",
"java.util;Map;true;computeIfAbsent;;;ReturnValue of Argument[1];ReturnValue;value",
"java.util;Map;true;computeIfAbsent;;;ReturnValue of Argument[1];MapValue of Argument[-1];value",
"java.util;Map;true;entrySet;;;MapValue of Argument[-1];MapValue of Element of ReturnValue;value",
"java.util;Map;true;entrySet;;;MapKey of Argument[-1];MapKey of Element of ReturnValue;value",
"java.util;Map;true;get;;;MapValue of Argument[-1];ReturnValue;value",
"java.util;Map;true;getOrDefault;;;MapValue of Argument[-1];ReturnValue;value",
"java.util;Map;true;getOrDefault;;;Argument[1];ReturnValue;value",
"java.util;Map;true;put;(Object,Object);;MapValue of Argument[-1];ReturnValue;value",
"java.util;Map;true;put;(Object,Object);;Argument[0];MapKey of Argument[-1];value",
"java.util;Map;true;put;(Object,Object);;Argument[1];MapValue of Argument[-1];value",
"java.util;Map;true;putIfAbsent;;;MapValue of Argument[-1];ReturnValue;value",
"java.util;Map;true;putIfAbsent;;;Argument[0];MapKey of Argument[-1];value",
"java.util;Map;true;putIfAbsent;;;Argument[1];MapValue of Argument[-1];value",
"java.util;Map;true;remove;(Object);;MapValue of Argument[-1];ReturnValue;value",
"java.util;Map;true;replace;(Object,Object);;MapValue of Argument[-1];ReturnValue;value",
"java.util;Map;true;replace;(Object,Object);;Argument[0];MapKey of Argument[-1];value",
"java.util;Map;true;replace;(Object,Object);;Argument[1];MapValue of Argument[-1];value",
"java.util;Map;true;replace;(Object,Object,Object);;Argument[0];MapKey of Argument[-1];value",
"java.util;Map;true;replace;(Object,Object,Object);;Argument[2];MapValue of Argument[-1];value",
"java.util;Map;true;keySet;();;MapKey of Argument[-1];Element of ReturnValue;value",
"java.util;Map;true;values;();;MapValue of Argument[-1];Element of ReturnValue;value",
"java.util;Map;true;merge;(Object,Object,BiFunction);;Argument[1];MapValue of Argument[-1];value",
"java.util;Map;true;putAll;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;Map;true;putAll;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;Collection;true;parallelStream;();;Element of Argument[-1];Element of ReturnValue;value",
"java.util;Collection;true;stream;();;Element of Argument[-1];Element of ReturnValue;value",
"java.util;Collection;true;toArray;;;Element of Argument[-1];ArrayElement of ReturnValue;value",
"java.util;Collection;true;toArray;;;Element of Argument[-1];ArrayElement of Argument[0];value",
"java.util;Collection;true;add;;;Argument[0];Element of Argument[-1];value",
"java.util;Collection;true;addAll;;;Element of Argument[0];Element of Argument[-1];value",
"java.util;List;true;get;(int);;Element of Argument[-1];ReturnValue;value",
"java.util;List;true;listIterator;;;Element of Argument[-1];Element of ReturnValue;value",
"java.util;List;true;remove;(int);;Element of Argument[-1];ReturnValue;value",
"java.util;List;true;set;(int,Object);;Element of Argument[-1];ReturnValue;value",
"java.util;List;true;set;(int,Object);;Argument[1];Element of Argument[-1];value",
"java.util;List;true;subList;;;Element of Argument[-1];Element of ReturnValue;value",
"java.util;List;true;add;(int,Object);;Argument[1];Element of Argument[-1];value",
"java.util;List;true;addAll;(int,Collection);;Element of Argument[1];Element of Argument[-1];value",
"java.util;Vector;true;elementAt;(int);;Element of Argument[-1];ReturnValue;value",
"java.util;Vector;true;elements;();;Element of Argument[-1];Element of ReturnValue;value",
"java.util;Vector;true;firstElement;();;Element of Argument[-1];ReturnValue;value",
"java.util;Vector;true;lastElement;();;Element of Argument[-1];ReturnValue;value",
"java.util;Vector;true;addElement;(Object);;Argument[0];Element of Argument[-1];value",
"java.util;Vector;true;insertElementAt;(Object,int);;Argument[0];Element of Argument[-1];value",
"java.util;Vector;true;setElementAt;(Object,int);;Argument[0];Element of Argument[-1];value",
"java.util;Vector;true;copyInto;(Object[]);;Element of Argument[-1];ArrayElement of Argument[0];value",
"java.util;Stack;true;peek;();;Element of Argument[-1];ReturnValue;value",
"java.util;Stack;true;pop;();;Element of Argument[-1];ReturnValue;value",
"java.util;Stack;true;push;(Object);;Argument[0];Element of Argument[-1];value",
"java.util;Queue;true;element;();;Element of Argument[-1];ReturnValue;value",
"java.util;Queue;true;peek;();;Element of Argument[-1];ReturnValue;value",
"java.util;Queue;true;poll;();;Element of Argument[-1];ReturnValue;value",
"java.util;Queue;true;remove;();;Element of Argument[-1];ReturnValue;value",
"java.util;Queue;true;offer;(Object);;Argument[0];Element of Argument[-1];value",
"java.util;Deque;true;descendingIterator;();;Element of Argument[-1];Element of ReturnValue;value",
"java.util;Deque;true;getFirst;();;Element of Argument[-1];ReturnValue;value",
"java.util;Deque;true;getLast;();;Element of Argument[-1];ReturnValue;value",
"java.util;Deque;true;peekFirst;();;Element of Argument[-1];ReturnValue;value",
"java.util;Deque;true;peekLast;();;Element of Argument[-1];ReturnValue;value",
"java.util;Deque;true;pollFirst;();;Element of Argument[-1];ReturnValue;value",
"java.util;Deque;true;pollLast;();;Element of Argument[-1];ReturnValue;value",
"java.util;Deque;true;pop;();;Element of Argument[-1];ReturnValue;value",
"java.util;Deque;true;removeFirst;();;Element of Argument[-1];ReturnValue;value",
"java.util;Deque;true;removeLast;();;Element of Argument[-1];ReturnValue;value",
"java.util;Deque;true;push;(Object);;Argument[0];Element of Argument[-1];value",
"java.util;Deque;true;offerLast;(Object);;Argument[0];Element of Argument[-1];value",
"java.util;Deque;true;offerFirst;(Object);;Argument[0];Element of Argument[-1];value",
"java.util;Deque;true;addLast;(Object);;Argument[0];Element of Argument[-1];value",
"java.util;Deque;true;addFirst;(Object);;Argument[0];Element of Argument[-1];value",
"java.util.concurrent;BlockingDeque;true;pollFirst;(long,TimeUnit);;Element of Argument[-1];ReturnValue;value",
"java.util.concurrent;BlockingDeque;true;pollLast;(long,TimeUnit);;Element of Argument[-1];ReturnValue;value",
"java.util.concurrent;BlockingDeque;true;takeFirst;();;Element of Argument[-1];ReturnValue;value",
"java.util.concurrent;BlockingDeque;true;takeLast;();;Element of Argument[-1];ReturnValue;value",
"java.util.concurrent;BlockingQueue;true;poll;(long,TimeUnit);;Element of Argument[-1];ReturnValue;value",
"java.util.concurrent;BlockingQueue;true;take;();;Element of Argument[-1];ReturnValue;value",
"java.util.concurrent;BlockingQueue;true;offer;(Object,long,TimeUnit);;Argument[0];Element of Argument[-1];value",
"java.util.concurrent;BlockingQueue;true;put;(Object);;Argument[0];Element of Argument[-1];value",
"java.util.concurrent;BlockingDeque;true;offerLast;(Object,long,TimeUnit);;Argument[0];Element of Argument[-1];value",
"java.util.concurrent;BlockingDeque;true;offerFirst;(Object,long,TimeUnit);;Argument[0];Element of Argument[-1];value",
"java.util.concurrent;BlockingDeque;true;putLast;(Object);;Argument[0];Element of Argument[-1];value",
"java.util.concurrent;BlockingDeque;true;putFirst;(Object);;Argument[0];Element of Argument[-1];value",
"java.util.concurrent;BlockingQueue;true;drainTo;(Collection,int);;Element of Argument[-1];Element of Argument[0];value",
"java.util.concurrent;BlockingQueue;true;drainTo;(Collection);;Element of Argument[-1];Element of Argument[0];value",
"java.util.concurrent;ConcurrentHashMap;true;elements;();;MapValue of Argument[-1];Element of ReturnValue;value",
"java.util;Dictionary;true;elements;();;MapValue of Argument[-1];Element of ReturnValue;value",
"java.util;Dictionary;true;get;(Object);;MapValue of Argument[-1];ReturnValue;value",
"java.util;Dictionary;true;keys;();;MapKey of Argument[-1];Element of ReturnValue;value",
"java.util;Dictionary;true;put;(Object,Object);;MapValue of Argument[-1];ReturnValue;value",
"java.util;Dictionary;true;put;(Object,Object);;Argument[0];MapKey of Argument[-1];value",
"java.util;Dictionary;true;put;(Object,Object);;Argument[1];MapValue of Argument[-1];value",
"java.util;Dictionary;true;remove;(Object);;MapValue of Argument[-1];ReturnValue;value",
"java.util;NavigableMap;true;ceilingEntry;(Object);;MapKey of Argument[-1];MapKey of ReturnValue;value",
"java.util;NavigableMap;true;ceilingEntry;(Object);;MapValue of Argument[-1];MapValue of ReturnValue;value",
"java.util;NavigableMap;true;descendingMap;();;MapKey of Argument[-1];MapKey of ReturnValue;value",
"java.util;NavigableMap;true;descendingMap;();;MapValue of Argument[-1];MapValue of ReturnValue;value",
"java.util;NavigableMap;true;firstEntry;();;MapKey of Argument[-1];MapKey of ReturnValue;value",
"java.util;NavigableMap;true;firstEntry;();;MapValue of Argument[-1];MapValue of ReturnValue;value",
"java.util;NavigableMap;true;floorEntry;(Object);;MapKey of Argument[-1];MapKey of ReturnValue;value",
"java.util;NavigableMap;true;floorEntry;(Object);;MapValue of Argument[-1];MapValue of ReturnValue;value",
"java.util;NavigableMap;true;headMap;(Object,boolean);;MapKey of Argument[-1];MapKey of ReturnValue;value",
"java.util;NavigableMap;true;headMap;(Object,boolean);;MapValue of Argument[-1];MapValue of ReturnValue;value",
"java.util;NavigableMap;true;higherEntry;(Object);;MapKey of Argument[-1];MapKey of ReturnValue;value",
"java.util;NavigableMap;true;higherEntry;(Object);;MapValue of Argument[-1];MapValue of ReturnValue;value",
"java.util;NavigableMap;true;lastEntry;();;MapKey of Argument[-1];MapKey of ReturnValue;value",
"java.util;NavigableMap;true;lastEntry;();;MapValue of Argument[-1];MapValue of ReturnValue;value",
"java.util;NavigableMap;true;lowerEntry;(Object);;MapKey of Argument[-1];MapKey of ReturnValue;value",
"java.util;NavigableMap;true;lowerEntry;(Object);;MapValue of Argument[-1];MapValue of ReturnValue;value",
"java.util;NavigableMap;true;pollFirstEntry;();;MapKey of Argument[-1];MapKey of ReturnValue;value",
"java.util;NavigableMap;true;pollFirstEntry;();;MapValue of Argument[-1];MapValue of ReturnValue;value",
"java.util;NavigableMap;true;pollLastEntry;();;MapKey of Argument[-1];MapKey of ReturnValue;value",
"java.util;NavigableMap;true;pollLastEntry;();;MapValue of Argument[-1];MapValue of ReturnValue;value",
"java.util;NavigableMap;true;subMap;(Object,boolean,Object,boolean);;MapKey of Argument[-1];MapKey of ReturnValue;value",
"java.util;NavigableMap;true;subMap;(Object,boolean,Object,boolean);;MapValue of Argument[-1];MapValue of ReturnValue;value",
"java.util;NavigableMap;true;tailMap;(Object,boolean);;MapKey of Argument[-1];MapKey of ReturnValue;value",
"java.util;NavigableMap;true;tailMap;(Object,boolean);;MapValue of Argument[-1];MapValue of ReturnValue;value",
"java.util;NavigableSet;true;ceiling;(Object);;Element of Argument[-1];ReturnValue;value",
"java.util;NavigableSet;true;descendingIterator;();;Element of Argument[-1];Element of ReturnValue;value",
"java.util;NavigableSet;true;descendingSet;();;Element of Argument[-1];Element of ReturnValue;value",
"java.util;NavigableSet;true;floor;(Object);;Element of Argument[-1];ReturnValue;value",
"java.util;NavigableSet;true;headSet;(Object,boolean);;Element of Argument[-1];Element of ReturnValue;value",
"java.util;NavigableSet;true;higher;(Object);;Element of Argument[-1];ReturnValue;value",
"java.util;NavigableSet;true;lower;(Object);;Element of Argument[-1];ReturnValue;value",
"java.util;NavigableSet;true;pollFirst;();;Element of Argument[-1];ReturnValue;value",
"java.util;NavigableSet;true;pollLast;();;Element of Argument[-1];ReturnValue;value",
"java.util;NavigableSet;true;subSet;(Object,boolean,Object,boolean);;Element of Argument[-1];Element of ReturnValue;value",
"java.util;NavigableSet;true;tailSet;(Object,boolean);;Element of Argument[-1];Element of ReturnValue;value",
"java.util;Scanner;true;next;(Pattern);;Argument[-1];ReturnValue;taint",
"java.util;Scanner;true;next;(String);;Argument[-1];ReturnValue;taint",
"java.util;SortedMap;true;headMap;(Object);;MapKey of Argument[-1];MapKey of ReturnValue;value",
"java.util;SortedMap;true;headMap;(Object);;MapValue of Argument[-1];MapValue of ReturnValue;value",
"java.util;SortedMap;true;subMap;(Object,Object);;MapKey of Argument[-1];MapKey of ReturnValue;value",
"java.util;SortedMap;true;subMap;(Object,Object);;MapValue of Argument[-1];MapValue of ReturnValue;value",
"java.util;SortedMap;true;tailMap;(Object);;MapKey of Argument[-1];MapKey of ReturnValue;value",
"java.util;SortedMap;true;tailMap;(Object);;MapValue of Argument[-1];MapValue of ReturnValue;value",
"java.util;SortedSet;true;first;();;Element of Argument[-1];ReturnValue;value",
"java.util;SortedSet;true;headSet;(Object);;Element of Argument[-1];Element of ReturnValue;value",
"java.util;SortedSet;true;last;();;Element of Argument[-1];ReturnValue;value",
"java.util;SortedSet;true;subSet;(Object,Object);;Element of Argument[-1];Element of ReturnValue;value",
"java.util;SortedSet;true;tailSet;(Object);;Element of Argument[-1];Element of ReturnValue;value",
"java.util.concurrent;TransferQueue;true;tryTransfer;(Object,long,TimeUnit);;Argument[0];Element of Argument[-1];value",
"java.util.concurrent;TransferQueue;true;transfer;(Object);;Argument[0];Element of Argument[-1];value",
"java.util.concurrent;TransferQueue;true;tryTransfer;(Object);;Argument[0];Element of Argument[-1];value",
"java.util;List;false;copyOf;(Collection);;Element of Argument[0];Element of ReturnValue;value",
"java.util;List;false;of;(Object[]);;ArrayElement of Argument[0];Element of ReturnValue;value",
"java.util;List;false;of;(Object);;Argument[0];Element of ReturnValue;value",
"java.util;List;false;of;(Object,Object);;Argument[0..1];Element of ReturnValue;value",
"java.util;List;false;of;(Object,Object,Object);;Argument[0..2];Element of ReturnValue;value",
"java.util;List;false;of;(Object,Object,Object,Object);;Argument[0..3];Element of ReturnValue;value",
"java.util;List;false;of;(Object,Object,Object,Object,Object);;Argument[0..4];Element of ReturnValue;value",
"java.util;List;false;of;(Object,Object,Object,Object,Object,Object);;Argument[0..5];Element of ReturnValue;value",
"java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[0..6];Element of ReturnValue;value",
"java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0..7];Element of ReturnValue;value",
"java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0..8];Element of ReturnValue;value",
"java.util;List;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0..9];Element of ReturnValue;value",
"java.util;Map;false;copyOf;(Map);;MapKey of Argument[0];MapKey of ReturnValue;value",
"java.util;Map;false;copyOf;(Map);;MapValue of Argument[0];MapValue of ReturnValue;value",
"java.util;Map;false;entry;(Object,Object);;Argument[0];MapKey of ReturnValue;value",
"java.util;Map;false;entry;(Object,Object);;Argument[1];MapValue of ReturnValue;value",
"java.util;Map;false;of;;;Argument[0];MapKey of ReturnValue;value",
"java.util;Map;false;of;;;Argument[1];MapValue of ReturnValue;value",
"java.util;Map;false;of;;;Argument[2];MapKey of ReturnValue;value",
"java.util;Map;false;of;;;Argument[3];MapValue of ReturnValue;value",
"java.util;Map;false;of;;;Argument[4];MapKey of ReturnValue;value",
"java.util;Map;false;of;;;Argument[5];MapValue of ReturnValue;value",
"java.util;Map;false;of;;;Argument[6];MapKey of ReturnValue;value",
"java.util;Map;false;of;;;Argument[7];MapValue of ReturnValue;value",
"java.util;Map;false;of;;;Argument[8];MapKey of ReturnValue;value",
"java.util;Map;false;of;;;Argument[9];MapValue of ReturnValue;value",
"java.util;Map;false;of;;;Argument[10];MapKey of ReturnValue;value",
"java.util;Map;false;of;;;Argument[11];MapValue of ReturnValue;value",
"java.util;Map;false;of;;;Argument[12];MapKey of ReturnValue;value",
"java.util;Map;false;of;;;Argument[13];MapValue of ReturnValue;value",
"java.util;Map;false;of;;;Argument[14];MapKey of ReturnValue;value",
"java.util;Map;false;of;;;Argument[15];MapValue of ReturnValue;value",
"java.util;Map;false;of;;;Argument[16];MapKey of ReturnValue;value",
"java.util;Map;false;of;;;Argument[17];MapValue of ReturnValue;value",
"java.util;Map;false;of;;;Argument[18];MapKey of ReturnValue;value",
"java.util;Map;false;of;;;Argument[19];MapValue of ReturnValue;value",
"java.util;Map;false;ofEntries;;;MapKey of ArrayElement of Argument[0];MapKey of ReturnValue;value",
"java.util;Map;false;ofEntries;;;MapValue of ArrayElement of Argument[0];MapValue of ReturnValue;value",
"java.util;Set;false;copyOf;(Collection);;Element of Argument[0];Element of ReturnValue;value",
"java.util;Set;false;of;(Object[]);;ArrayElement of Argument[0];Element of ReturnValue;value",
"java.util;Set;false;of;(Object);;Argument[0];Element of ReturnValue;value",
"java.util;Set;false;of;(Object,Object);;Argument[0..1];Element of ReturnValue;value",
"java.util;Set;false;of;(Object,Object,Object);;Argument[0..2];Element of ReturnValue;value",
"java.util;Set;false;of;(Object,Object,Object,Object);;Argument[0..3];Element of ReturnValue;value",
"java.util;Set;false;of;(Object,Object,Object,Object,Object);;Argument[0..4];Element of ReturnValue;value",
"java.util;Set;false;of;(Object,Object,Object,Object,Object,Object);;Argument[0..5];Element of ReturnValue;value",
"java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object);;Argument[0..6];Element of ReturnValue;value",
"java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0..7];Element of ReturnValue;value",
"java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0..8];Element of ReturnValue;value",
"java.util;Set;false;of;(Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0..9];Element of ReturnValue;value",
"java.util;Arrays;false;stream;;;ArrayElement of Argument[0];Element of ReturnValue;value",
"java.util;Arrays;false;spliterator;;;ArrayElement of Argument[0];Element of ReturnValue;value",
"java.util;Arrays;false;copyOfRange;;;ArrayElement of Argument[0];ArrayElement of ReturnValue;value",
"java.util;Arrays;false;copyOf;;;ArrayElement of Argument[0];ArrayElement of ReturnValue;value",
"java.util;Collections;false;list;(Enumeration);;Element of Argument[0];Element of ReturnValue;value",
"java.util;Collections;false;enumeration;(Collection);;Element of Argument[0];Element of ReturnValue;value",
"java.util;Collections;false;nCopies;(int,Object);;Argument[1];Element of ReturnValue;value",
"java.util;Collections;false;singletonMap;(Object,Object);;Argument[0];MapKey of ReturnValue;value",
"java.util;Collections;false;singletonMap;(Object,Object);;Argument[1];MapValue of ReturnValue;value",
"java.util;Collections;false;singletonList;(Object);;Argument[0];Element of ReturnValue;value",
"java.util;Collections;false;singleton;(Object);;Argument[0];Element of ReturnValue;value",
"java.util;Collections;false;checkedNavigableMap;(NavigableMap,Class,Class);;MapKey of Argument[0];MapKey of ReturnValue;value",
"java.util;Collections;false;checkedNavigableMap;(NavigableMap,Class,Class);;MapValue of Argument[0];MapValue of ReturnValue;value",
"java.util;Collections;false;checkedSortedMap;(SortedMap,Class,Class);;MapKey of Argument[0];MapKey of ReturnValue;value",
"java.util;Collections;false;checkedSortedMap;(SortedMap,Class,Class);;MapValue of Argument[0];MapValue of ReturnValue;value",
"java.util;Collections;false;checkedMap;(Map,Class,Class);;MapKey of Argument[0];MapKey of ReturnValue;value",
"java.util;Collections;false;checkedMap;(Map,Class,Class);;MapValue of Argument[0];MapValue of ReturnValue;value",
"java.util;Collections;false;checkedList;(List,Class);;Element of Argument[0];Element of ReturnValue;value",
"java.util;Collections;false;checkedNavigableSet;(NavigableSet,Class);;Element of Argument[0];Element of ReturnValue;value",
"java.util;Collections;false;checkedSortedSet;(SortedSet,Class);;Element of Argument[0];Element of ReturnValue;value",
"java.util;Collections;false;checkedSet;(Set,Class);;Element of Argument[0];Element of ReturnValue;value",
"java.util;Collections;false;checkedCollection;(Collection,Class);;Element of Argument[0];Element of ReturnValue;value",
"java.util;Collections;false;synchronizedNavigableMap;(NavigableMap);;MapKey of Argument[0];MapKey of ReturnValue;value",
"java.util;Collections;false;synchronizedNavigableMap;(NavigableMap);;MapValue of Argument[0];MapValue of ReturnValue;value",
"java.util;Collections;false;synchronizedSortedMap;(SortedMap);;MapKey of Argument[0];MapKey of ReturnValue;value",
"java.util;Collections;false;synchronizedSortedMap;(SortedMap);;MapValue of Argument[0];MapValue of ReturnValue;value",
"java.util;Collections;false;synchronizedMap;(Map);;MapKey of Argument[0];MapKey of ReturnValue;value",
"java.util;Collections;false;synchronizedMap;(Map);;MapValue of Argument[0];MapValue of ReturnValue;value",
"java.util;Collections;false;synchronizedList;(List);;Element of Argument[0];Element of ReturnValue;value",
"java.util;Collections;false;synchronizedNavigableSet;(NavigableSet);;Element of Argument[0];Element of ReturnValue;value",
"java.util;Collections;false;synchronizedSortedSet;(SortedSet);;Element of Argument[0];Element of ReturnValue;value",
"java.util;Collections;false;synchronizedSet;(Set);;Element of Argument[0];Element of ReturnValue;value",
"java.util;Collections;false;synchronizedCollection;(Collection);;Element of Argument[0];Element of ReturnValue;value",
"java.util;Collections;false;unmodifiableNavigableMap;(NavigableMap);;MapKey of Argument[0];MapKey of ReturnValue;value",
"java.util;Collections;false;unmodifiableNavigableMap;(NavigableMap);;MapValue of Argument[0];MapValue of ReturnValue;value",
"java.util;Collections;false;unmodifiableSortedMap;(SortedMap);;MapKey of Argument[0];MapKey of ReturnValue;value",
"java.util;Collections;false;unmodifiableSortedMap;(SortedMap);;MapValue of Argument[0];MapValue of ReturnValue;value",
"java.util;Collections;false;unmodifiableMap;(Map);;MapKey of Argument[0];MapKey of ReturnValue;value",
"java.util;Collections;false;unmodifiableMap;(Map);;MapValue of Argument[0];MapValue of ReturnValue;value",
"java.util;Collections;false;unmodifiableList;(List);;Element of Argument[0];Element of ReturnValue;value",
"java.util;Collections;false;unmodifiableNavigableSet;(NavigableSet);;Element of Argument[0];Element of ReturnValue;value",
"java.util;Collections;false;unmodifiableSortedSet;(SortedSet);;Element of Argument[0];Element of ReturnValue;value",
"java.util;Collections;false;unmodifiableSet;(Set);;Element of Argument[0];Element of ReturnValue;value",
"java.util;Collections;false;unmodifiableCollection;(Collection);;Element of Argument[0];Element of ReturnValue;value",
"java.util;Collections;false;max;;;Element of Argument[0];ReturnValue;value",
"java.util;Collections;false;min;;;Element of Argument[0];ReturnValue;value",
"java.util;Arrays;false;fill;(Object[],int,int,Object);;Argument[3];ArrayElement of Argument[0];value",
"java.util;Arrays;false;fill;(Object[],Object);;Argument[1];ArrayElement of Argument[0];value",
"java.util;Arrays;false;fill;(float[],int,int,float);;Argument[3];ArrayElement of Argument[0];value",
"java.util;Arrays;false;fill;(float[],float);;Argument[1];ArrayElement of Argument[0];value",
"java.util;Arrays;false;fill;(double[],int,int,double);;Argument[3];ArrayElement of Argument[0];value",
"java.util;Arrays;false;fill;(double[],double);;Argument[1];ArrayElement of Argument[0];value",
"java.util;Arrays;false;fill;(boolean[],int,int,boolean);;Argument[3];ArrayElement of Argument[0];value",
"java.util;Arrays;false;fill;(boolean[],boolean);;Argument[1];ArrayElement of Argument[0];value",
"java.util;Arrays;false;fill;(byte[],int,int,byte);;Argument[3];ArrayElement of Argument[0];value",
"java.util;Arrays;false;fill;(byte[],byte);;Argument[1];ArrayElement of Argument[0];value",
"java.util;Arrays;false;fill;(char[],int,int,char);;Argument[3];ArrayElement of Argument[0];value",
"java.util;Arrays;false;fill;(char[],char);;Argument[1];ArrayElement of Argument[0];value",
"java.util;Arrays;false;fill;(short[],int,int,short);;Argument[3];ArrayElement of Argument[0];value",
"java.util;Arrays;false;fill;(short[],short);;Argument[1];ArrayElement of Argument[0];value",
"java.util;Arrays;false;fill;(int[],int,int,int);;Argument[3];ArrayElement of Argument[0];value",
"java.util;Arrays;false;fill;(int[],int);;Argument[1];ArrayElement of Argument[0];value",
"java.util;Arrays;false;fill;(long[],int,int,long);;Argument[3];ArrayElement of Argument[0];value",
"java.util;Arrays;false;fill;(long[],long);;Argument[1];ArrayElement of Argument[0];value",
"java.util;Collections;false;replaceAll;(List,Object,Object);;Argument[2];Element of Argument[0];value",
"java.util;Collections;false;copy;(List,List);;Element of Argument[1];Element of Argument[0];value",
"java.util;Collections;false;fill;(List,Object);;Argument[1];Element of Argument[0];value",
"java.util;Arrays;false;asList;;;ArrayElement of Argument[0];Element of ReturnValue;value",
"java.util;Collections;false;addAll;(Collection,Object[]);;ArrayElement of Argument[1];Element of Argument[0];value",
"java.util;AbstractMap$SimpleEntry;false;SimpleEntry;(Object,Object);;Argument[0];MapKey of Argument[-1];value",
"java.util;AbstractMap$SimpleEntry;false;SimpleEntry;(Object,Object);;Argument[1];MapValue of Argument[-1];value",
"java.util;AbstractMap$SimpleEntry;false;SimpleEntry;(Entry);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;AbstractMap$SimpleEntry;false;SimpleEntry;(Entry);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;AbstractMap$SimpleImmutableEntry;false;SimpleImmutableEntry;(Object,Object);;Argument[0];MapKey of Argument[-1];value",
"java.util;AbstractMap$SimpleImmutableEntry;false;SimpleImmutableEntry;(Object,Object);;Argument[1];MapValue of Argument[-1];value",
"java.util;AbstractMap$SimpleImmutableEntry;false;SimpleImmutableEntry;(Entry);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;AbstractMap$SimpleImmutableEntry;false;SimpleImmutableEntry;(Entry);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;ArrayDeque;false;ArrayDeque;(Collection);;Element of Argument[0];Element of Argument[-1];value",
"java.util;ArrayList;false;ArrayList;(Collection);;Element of Argument[0];Element of Argument[-1];value",
"java.util;EnumMap;false;EnumMap;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;EnumMap;false;EnumMap;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;EnumMap;false;EnumMap;(EnumMap);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;EnumMap;false;EnumMap;(EnumMap);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;HashMap;false;HashMap;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;HashMap;false;HashMap;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;HashSet;false;HashSet;(Collection);;Element of Argument[0];Element of Argument[-1];value",
"java.util;Hashtable;false;Hashtable;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;Hashtable;false;Hashtable;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;IdentityHashMap;false;IdentityHashMap;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;IdentityHashMap;false;IdentityHashMap;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;LinkedHashMap;false;LinkedHashMap;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;LinkedHashMap;false;LinkedHashMap;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;LinkedHashSet;false;LinkedHashSet;(Collection);;Element of Argument[0];Element of Argument[-1];value",
"java.util;LinkedList;false;LinkedList;(Collection);;Element of Argument[0];Element of Argument[-1];value",
"java.util;PriorityQueue;false;PriorityQueue;(Collection);;Element of Argument[0];Element of Argument[-1];value",
"java.util;PriorityQueue;false;PriorityQueue;(PriorityQueue);;Element of Argument[0];Element of Argument[-1];value",
"java.util;PriorityQueue;false;PriorityQueue;(SortedSet);;Element of Argument[0];Element of Argument[-1];value",
"java.util;TreeMap;false;TreeMap;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;TreeMap;false;TreeMap;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;TreeMap;false;TreeMap;(SortedMap);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;TreeMap;false;TreeMap;(SortedMap);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;TreeSet;false;TreeSet;(Collection);;Element of Argument[0];Element of Argument[-1];value",
"java.util;TreeSet;false;TreeSet;(SortedSet);;Element of Argument[0];Element of Argument[-1];value",
"java.util;Vector;false;Vector;(Collection);;Element of Argument[0];Element of Argument[-1];value",
"java.util;WeakHashMap;false;WeakHashMap;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;WeakHashMap;false;WeakHashMap;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value"
]
}
}
private predicate taintPreservingQualifierToMethod(Method m) {
// java.util.Map.Entry
m.getDeclaringType() instanceof EntryType and
m.hasName(["getValue", "setValue"])
or
// java.util.Iterable
m.getDeclaringType() instanceof IterableType and
m.hasName(["iterator", "spliterator"])
or
// java.util.Iterator
m.getDeclaringType() instanceof IteratorType and
m.hasName("next")
or
// java.util.ListIterator
m.getDeclaringType() instanceof IteratorType and
m.hasName("previous")
or
// java.util.Enumeration
m.getDeclaringType() instanceof EnumerationType and
m.hasName(["asIterator", "nextElement"])
or
// java.util.Map
m.(MapMethod)
.hasName([
"computeIfAbsent", "entrySet", "get", "getOrDefault", "put", "putIfAbsent", "remove",
"replace", "values"
])
or
// java.util.Collection
m.(CollectionMethod).hasName(["parallelStream", "stream", "toArray"])
or
// java.util.List
m.(CollectionMethod).hasName(["get", "listIterator", "set", "subList"])
or
m.(CollectionMethod).hasName("remove") and m.getParameterType(0).(PrimitiveType).hasName("int")
or
// java.util.Vector
m.(CollectionMethod).hasName(["elementAt", "elements", "firstElement", "lastElement"])
or
// java.util.Stack
m.(CollectionMethod).hasName(["peek", "pop"])
or
// java.util.Queue
// covered by Stack: peek()
m.(CollectionMethod).hasName(["element", "poll"])
or
m.(CollectionMethod).hasName("remove") and m.getNumberOfParameters() = 0
or
// java.util.Deque
m.(CollectionMethod)
.hasName([
"getFirst", "getLast", "peekFirst", "peekLast", "pollFirst", "pollLast", "removeFirst",
"removeLast"
])
or
// java.util.concurrent.BlockingQueue
// covered by Queue: poll(long, TimeUnit)
m.(CollectionMethod).hasName("take")
or
// java.util.concurrent.BlockingDeque
// covered by Deque: pollFirst(long, TimeUnit), pollLast(long, TimeUnit)
m.(CollectionMethod).hasName(["takeFirst", "takeLast"])
or
// java.util.SortedSet
m.(CollectionMethod).hasName(["first", "headSet", "last", "subSet", "tailSet"])
or
// java.util.NavigableSet
// covered by Deque: pollFirst(), pollLast()
// covered by SortedSet: headSet(E, boolean), subSet(E, boolean, E, boolean) and tailSet(E, boolean)
m.(CollectionMethod)
.hasName(["ceiling", "descendingIterator", "descendingSet", "floor", "higher", "lower"])
or
// java.util.SortedMap
m.(MapMethod).hasName(["headMap", "subMap", "tailMap"])
or
// java.util.NavigableMap
// covered by SortedMap: headMap(K, boolean), subMap(K, boolean, K, boolean), tailMap(K, boolean)
m.(MapMethod)
.hasName([
"ceilingEntry", "descendingMap", "firstEntry", "floorEntry", "higherEntry", "lastEntry",
"lowerEntry", "pollFirstEntry", "pollLastEntry"
])
or
// java.util.Dictionary
m.getDeclaringType()
.getSourceDeclaration()
.getASourceSupertype*()
.hasQualifiedName("java.util", "Dictionary") and
m.hasName(["elements", "get", "put", "remove"])
or
// java.util.concurrent.ConcurrentHashMap
m.(MapMethod).hasName(["elements", "search", "searchEntries", "searchValues"])
}
private predicate qualifierToMethodStep(Expr tracked, MethodAccess sink) {
taintPreservingQualifierToMethod(sink.getMethod()) and
tracked = sink.getQualifier()
}
private predicate qualifierToArgumentStep(Expr tracked, Expr sink) {
exists(MethodAccess ma, CollectionMethod method |
method = ma.getMethod() and
(
// java.util.Vector
method.hasName("copyInto")
or
// java.util.concurrent.BlockingQueue
method.hasName("drainTo")
or
// java.util.Collection
method.hasName("toArray") and method.getParameter(0).getType() instanceof Array
) and
tracked = ma.getQualifier() and
sink = ma.getArgument(0)
)
}
private predicate taintPreservingArgumentToQualifier(Method method, int arg) {
// java.util.Map.Entry
method.getDeclaringType() instanceof EntryType and
method.hasName("setValue") and
arg = 0
or
// java.util.Map
method.(MapMethod).hasName(["merge", "put", "putIfAbsent"]) and arg = 1
or
method.(MapMethod).hasName("replace") and arg = method.getNumberOfParameters() - 1
or
method.(MapMethod).hasName("putAll") and arg = 0
or
// java.util.ListIterator
method.getDeclaringType() instanceof IteratorType and
method.hasName(["add", "set"]) and
arg = 0
or
// java.util.Collection
method.(CollectionMethod).hasName(["add", "addAll"]) and
// Refer to the last parameter to also cover List::add(int, E) and List::addAll(int, Collection)
arg = method.getNumberOfParameters() - 1
or
// java.util.List
// covered by Collection: add(int, E), addAll(int, Collection<? extends E>)
method.(CollectionMethod).hasName("set") and arg = 1
or
// java.util.Vector
method.(CollectionMethod).hasName(["addElement", "insertElementAt", "setElementAt"]) and arg = 0
or
// java.util.Stack
method.(CollectionMethod).hasName("push") and arg = 0
or
// java.util.Queue
method.(CollectionMethod).hasName("offer") and arg = 0
or
// java.util.Deque
// covered by Stack: push(E)
method.(CollectionMethod).hasName(["addFirst", "addLast", "offerFirst", "offerLast"]) and arg = 0
or
// java.util.concurrent.BlockingQueue
// covered by Queue: offer(E, long, TimeUnit)
method.(CollectionMethod).hasName("put") and arg = 0
or
// java.util.concurrent.TransferQueue
method.(CollectionMethod).hasName(["transfer", "tryTransfer"]) and arg = 0
or
// java.util.concurrent.BlockingDeque
// covered by Deque: offerFirst(E, long, TimeUnit), offerLast(E, long, TimeUnit)
method.(CollectionMethod).hasName(["putFirst", "putLast"]) and arg = 0
or
// java.util.Dictionary
method
.getDeclaringType()
.getSourceDeclaration()
.getASourceSupertype*()
.hasQualifiedName("java.util", "Dictionary") and
method.hasName("put") and
arg = 1
}
/**
* Holds if `method` is a library method that returns tainted data if its
* `arg`th argument is tainted.
*/
private predicate taintPreservingArgumentToMethod(Method method, int arg) {
method.getDeclaringType().hasQualifiedName("java.util", "Collections") and
(
method
.hasName([
"checkedCollection", "checkedList", "checkedMap", "checkedNavigableMap",
"checkedNavigableSet", "checkedSet", "checkedSortedMap", "checkedSortedSet",
"enumeration", "list", "max", "min", "singleton", "singletonList",
"synchronizedCollection", "synchronizedList", "synchronizedMap",
"synchronizedNavigableMap", "synchronizedNavigableSet", "synchronizedSet",
"synchronizedSortedMap", "synchronizedSortedSet", "unmodifiableCollection",
"unmodifiableList", "unmodifiableMap", "unmodifiableNavigableMap",
"unmodifiableNavigableSet", "unmodifiableSet", "unmodifiableSortedMap",
"unmodifiableSortedSet"
]) and
arg = 0
or
method.hasName(["nCopies", "singletonMap"]) and arg = 1
)
or
method
.getDeclaringType()
.getSourceDeclaration()
.hasQualifiedName("java.util", ["List", "Map", "Set"]) and
method.hasName("copyOf") and
arg = 0
or
method.getDeclaringType().getSourceDeclaration().hasQualifiedName("java.util", "Map") and
(
method.hasName("of") and
arg = any(int i | i in [1 .. 10] | 2 * i - 1)
or
method.hasName("entry") and
arg = 1
)
or
method.getDeclaringType().hasQualifiedName("java.util", "Arrays") and
(
method.hasName(["copyOf", "copyOfRange", "spliterator", "stream"]) and
arg = 0
)
}
/**
* Holds if `method` is a library method that returns tainted data if any
* of its arguments are tainted.
*/
private predicate taintPreservingArgumentToMethod(Method method) {
method.getDeclaringType().getSourceDeclaration().hasQualifiedName("java.util", ["Set", "List"]) and
method.hasName("of")
or
method.getDeclaringType().getSourceDeclaration().hasQualifiedName("java.util", "Map") and
method.hasName("ofEntries")
}
/**
* Holds if `method` is a library method that writes tainted data to the
* `output`th argument if the `input`th argument is tainted.
*/
private predicate taintPreservingArgToArg(Method method, int input, int output) {
method.getDeclaringType().hasQualifiedName("java.util", "Collections") and
(
method.hasName(["copy", "fill"]) and
input = 1 and
output = 0
or
method.hasName("replaceAll") and input = 2 and output = 0
)
or
method.getDeclaringType().hasQualifiedName("java.util", "Arrays") and
(
method.hasName("fill") and
output = 0 and
input = method.getNumberOfParameters() - 1
)
}
private predicate argToQualifierStep(Expr tracked, Expr sink) {
exists(Method m, int i, MethodAccess ma |
taintPreservingArgumentToQualifier(m, i) and
ma.getMethod() = m and
tracked = ma.getArgument(i) and
sink = ma.getQualifier()
)
}
/** Access to a method that passes taint from an argument. */
private predicate argToMethodStep(Expr tracked, MethodAccess sink) {
exists(Method m |
m = sink.getMethod() and
(
exists(int i |
taintPreservingArgumentToMethod(m, i) and
tracked = sink.getArgument(i)
)
or
m.getDeclaringType().hasQualifiedName("java.util", "Arrays") and
m.hasName("asList") and
tracked = sink.getAnArgument()
)
)
or
taintPreservingArgumentToMethod(sink.getMethod()) and
tracked = sink.getAnArgument()
}
/**
* Holds if `tracked` and `sink` are arguments to a method that transfers taint
* between arguments.
*/
private predicate argToArgStep(Expr tracked, Expr sink) {
exists(MethodAccess ma, Method method, int input, int output |
ma.getMethod() = method and
ma.getArgument(input) = tracked and
ma.getArgument(output) = sink and
(
taintPreservingArgToArg(method, input, output)
or
method.getDeclaringType().hasQualifiedName("java.util", "Collections") and
method.hasName("addAll") and
input >= 1 and
output = 0
)
)
}
/**
* Holds if the step from `n1` to `n2` is either extracting a value from a
* container, inserting a value into a container, or transforming one container
* to another. This is restricted to cases where `n2` is the returned value of
* a call.
*/
predicate containerReturnValueStep(Expr n1, Expr n2) {
qualifierToMethodStep(n1, n2) or argToMethodStep(n1, n2)
}
/**
* Holds if the step from `n1` to `n2` is either extracting a value from a
* container, inserting a value into a container, or transforming one container
* to another. This is restricted to cases where the value of `n2` is being modified.
*/
predicate containerUpdateStep(Expr n1, Expr n2) {
qualifierToArgumentStep(n1, n2) or
argToQualifierStep(n1, n2) or
argToArgStep(n1, n2)
}
/**
* Holds if the step from `n1` to `n2` is either extracting a value from a
* container, inserting a value into a container, or transforming one container
* to another.
*/
predicate containerStep(Expr n1, Expr n2) {
containerReturnValueStep(n1, n2) or
containerUpdateStep(n1, n2)
}
/**
* Holds if the step from `node1` to `node2` stores a value in an array.
* This covers array assignments and initializers as well as implicit array
* creations for varargs.
*/
predicate arrayStoreStep(Node node1, Node node2) {
exists(Argument arg |
node1.asExpr() = arg and
arg.isVararg() and
node2.(ImplicitVarargsArray).getCall() = arg.getCall()
)
or
node2.asExpr().(ArrayInit).getAnInit() = node1.asExpr()
or
exists(Assignment assign | assign.getSource() = node1.asExpr() |
node2.(PostUpdateNode).getPreUpdateNode().asExpr() = assign.getDest().(ArrayAccess).getArray()
)
}
private predicate enhancedForStmtStep(Node node1, Node node2, Type containerType) {
exists(EnhancedForStmt for, Expr e, SsaExplicitUpdate v |
for.getExpr() = e and
node1.asExpr() = e and
containerType = e.getType() and
v.getDefiningExpr() = for.getVariable() and
v.getAFirstUse() = node2.asExpr()
)
}
/**
* Holds if the step from `node1` to `node2` reads a value from an array.
* This covers ordinary array reads as well as array iteration through enhanced
* `for` statements.
*/
predicate arrayReadStep(Node node1, Node node2, Type elemType) {
exists(ArrayAccess aa |
aa.getArray() = node1.asExpr() and
aa.getType() = elemType and
node2.asExpr() = aa
)
or
exists(Array arr |
enhancedForStmtStep(node1, node2, arr) and
arr.getComponentType() = elemType
)
}
/**
* Holds if the step from `node1` to `node2` reads a value from a collection.
* This only covers iteration through enhanced `for` statements.
*/
predicate collectionReadStep(Node node1, Node node2) {
enhancedForStmtStep(node1, node2, any(Type t | not t instanceof Array))
}

View File

@@ -0,0 +1,186 @@
private import java
private import DataFlowPrivate
private import DataFlowUtil
private import semmle.code.java.dataflow.InstanceAccess
private import semmle.code.java.dataflow.FlowSummary
private import semmle.code.java.dispatch.VirtualDispatch as VirtualDispatch
private module DispatchImpl {
/** Gets a viable implementation of the target of the given `Call`. */
Callable viableCallable(Call c) {
result = VirtualDispatch::viableCallable(c)
or
result.(SummarizedCallable) = c.getCallee().getSourceDeclaration()
}
/**
* Holds if the set of viable implementations that can be called by `ma`
* might be improved by knowing the call context. This is the case if the
* qualifier is the `i`th parameter of the enclosing callable `c`.
*/
private predicate mayBenefitFromCallContext(MethodAccess ma, Callable c, int i) {
exists(Parameter p |
2 <= strictcount(VirtualDispatch::viableImpl(ma)) and
ma.getQualifier().(VarAccess).getVariable() = p and
p.getPosition() = i and
c.getAParameter() = p and
not p.isVarargs() and
c = ma.getEnclosingCallable()
)
or
exists(OwnInstanceAccess ia |
2 <= strictcount(VirtualDispatch::viableImpl(ma)) and
(ia.isExplicit(ma.getQualifier()) or ia.isImplicitMethodQualifier(ma)) and
i = -1 and
c = ma.getEnclosingCallable()
)
}
/**
* Holds if the call `ctx` might act as a context that improves the set of
* dispatch targets of a `MethodAccess` that occurs in a viable target of
* `ctx`.
*/
pragma[nomagic]
private predicate relevantContext(Call ctx, int i) {
exists(Callable c |
mayBenefitFromCallContext(_, c, i) and
c = viableCallable(ctx)
)
}
private RefType getPreciseType(Expr e) {
result = e.(FunctionalExpr).getConstructedType()
or
not e instanceof FunctionalExpr and result = e.getType()
}
/**
* Holds if the `i`th argument of `ctx` has type `t` and `ctx` is a
* relevant call context.
*/
private predicate contextArgHasType(Call ctx, int i, RefType t, boolean exact) {
relevantContext(ctx, i) and
exists(RefType srctype |
exists(Expr arg, Expr src |
i = -1 and
ctx.getQualifier() = arg
or
ctx.getArgument(i) = arg
|
src = VirtualDispatch::variableTrack(arg) and
srctype = getPreciseType(src) and
if src instanceof ClassInstanceExpr then exact = true else exact = false
)
or
exists(Node arg |
i = -1 and
not exists(ctx.getQualifier()) and
getInstanceArgument(ctx) = arg and
arg.getTypeBound() = srctype and
if ctx instanceof ClassInstanceExpr then exact = true else exact = false
)
|
t = srctype.(BoundedType).getAnUltimateUpperBoundType()
or
t = srctype and not srctype instanceof BoundedType
)
}
/**
* Holds if the set of viable implementations that can be called by `ma`
* might be improved by knowing the call context. This is the case if the
* qualifier is a parameter of the enclosing callable `c`.
*/
predicate mayBenefitFromCallContext(MethodAccess ma, Callable c) {
mayBenefitFromCallContext(ma, c, _)
}
/**
* Gets a viable dispatch target of `ma` in the context `ctx`. This is
* restricted to those `ma`s for which a context might make a difference.
*/
Method viableImplInCallContext(MethodAccess ma, Call ctx) {
result = viableCallable(ma) and
exists(int i, Callable c, Method def, RefType t, boolean exact |
mayBenefitFromCallContext(ma, c, i) and
c = viableCallable(ctx) and
contextArgHasType(ctx, i, t, exact) and
ma.getMethod().getSourceDeclaration() = def
|
exact = true and result = VirtualDispatch::exactMethodImpl(def, t.getSourceDeclaration())
or
exact = false and
exists(RefType t2 |
result = VirtualDispatch::viableMethodImpl(def, t.getSourceDeclaration(), t2) and
not failsUnification(t, t2)
)
or
result = def and def instanceof SummarizedCallable
)
}
pragma[noinline]
private predicate unificationTargetLeft(ParameterizedType t1, GenericType g) {
contextArgHasType(_, _, t1, _) and t1.getGenericType() = g
}
pragma[noinline]
private predicate unificationTargetRight(ParameterizedType t2, GenericType g) {
exists(VirtualDispatch::viableMethodImpl(_, _, t2)) and t2.getGenericType() = g
}
private predicate unificationTargets(Type t1, Type t2) {
exists(GenericType g | unificationTargetLeft(t1, g) and unificationTargetRight(t2, g))
or
exists(Array a1, Array a2 |
unificationTargets(a1, a2) and
t1 = a1.getComponentType() and
t2 = a2.getComponentType()
)
or
exists(ParameterizedType pt1, ParameterizedType pt2, int pos |
unificationTargets(pt1, pt2) and
not pt1.getSourceDeclaration() != pt2.getSourceDeclaration() and
t1 = pt1.getTypeArgument(pos) and
t2 = pt2.getTypeArgument(pos)
)
}
pragma[noinline]
private predicate typeArgsOfUnificationTargets(
ParameterizedType t1, ParameterizedType t2, int pos, RefType arg1, RefType arg2
) {
unificationTargets(t1, t2) and
arg1 = t1.getTypeArgument(pos) and
arg2 = t2.getTypeArgument(pos)
}
private predicate failsUnification(Type t1, Type t2) {
unificationTargets(t1, t2) and
(
exists(RefType arg1, RefType arg2 |
typeArgsOfUnificationTargets(t1, t2, _, arg1, arg2) and
failsUnification(arg1, arg2)
)
or
failsUnification(t1.(Array).getComponentType(), t2.(Array).getComponentType())
or
not (
t1 instanceof Array and t2 instanceof Array
or
t1.(PrimitiveType) = t2.(PrimitiveType)
or
t1.(Class).getSourceDeclaration() = t2.(Class).getSourceDeclaration()
or
t1.(Interface).getSourceDeclaration() = t2.(Interface).getSourceDeclaration()
or
t1 instanceof BoundedType and t2 instanceof RefType
or
t1 instanceof RefType and t2 instanceof BoundedType
)
)
}
}
import DispatchImpl

View File

@@ -0,0 +1,19 @@
/**
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) data flow analyses (for internal use only).
*
* This copy of the library is exclusively for use by `Serializability.qll` and
* related libraries. Configurations computed using this instance of the library
* are in scope whenever `java.qll` is imported, and are used to compute among
* other things `AdditionalTaintStep`.
*/
import java
/**
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) data flow analyses (for internal use only).
*/
module DataFlowForSerializability {
import semmle.code.java.dataflow.internal.DataFlowImplForSerializability
}

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

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,181 @@
/**
* Provides consistency queries for checking invariants in the language-specific
* data-flow classes and predicates.
*/
private import DataFlowImplSpecific::Private
private import DataFlowImplSpecific::Public
private import tainttracking1.TaintTrackingParameter::Private
private import tainttracking1.TaintTrackingParameter::Public
module Consistency {
private class RelevantNode extends Node {
RelevantNode() {
this instanceof ArgumentNode or
this instanceof ParameterNode or
this instanceof ReturnNode or
this = getAnOutNode(_, _) or
simpleLocalFlowStep(this, _) or
simpleLocalFlowStep(_, this) or
jumpStep(this, _) or
jumpStep(_, this) or
storeStep(this, _, _) or
storeStep(_, _, this) or
readStep(this, _, _) or
readStep(_, _, this) or
defaultAdditionalTaintStep(this, _) or
defaultAdditionalTaintStep(_, this)
}
}
query predicate uniqueEnclosingCallable(Node n, string msg) {
exists(int c |
n instanceof RelevantNode and
c = count(n.getEnclosingCallable()) and
c != 1 and
msg = "Node should have one enclosing callable but has " + c + "."
)
}
query predicate uniqueType(Node n, string msg) {
exists(int c |
n instanceof RelevantNode and
c = count(getNodeType(n)) and
c != 1 and
msg = "Node should have one type but has " + c + "."
)
}
query predicate uniqueNodeLocation(Node n, string msg) {
exists(int c |
c =
count(string filepath, int startline, int startcolumn, int endline, int endcolumn |
n.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
) and
c != 1 and
msg = "Node should have one location but has " + c + "."
)
}
query predicate missingLocation(string msg) {
exists(int c |
c =
strictcount(Node n |
not exists(string filepath, int startline, int startcolumn, int endline, int endcolumn |
n.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
)
) and
msg = "Nodes without location: " + c
)
}
query predicate uniqueNodeToString(Node n, string msg) {
exists(int c |
c = count(n.toString()) and
c != 1 and
msg = "Node should have one toString but has " + c + "."
)
}
query predicate missingToString(string msg) {
exists(int c |
c = strictcount(Node n | not exists(n.toString())) and
msg = "Nodes without toString: " + c
)
}
query predicate parameterCallable(ParameterNode p, string msg) {
exists(DataFlowCallable c | p.isParameterOf(c, _) and c != p.getEnclosingCallable()) and
msg = "Callable mismatch for parameter."
}
query predicate localFlowIsLocal(Node n1, Node n2, string msg) {
simpleLocalFlowStep(n1, n2) and
n1.getEnclosingCallable() != n2.getEnclosingCallable() and
msg = "Local flow step does not preserve enclosing callable."
}
private DataFlowType typeRepr() { result = getNodeType(_) }
query predicate compatibleTypesReflexive(DataFlowType t, string msg) {
t = typeRepr() and
not compatibleTypes(t, t) and
msg = "Type compatibility predicate is not reflexive."
}
query predicate unreachableNodeCCtx(Node n, DataFlowCall call, string msg) {
isUnreachableInCall(n, call) and
exists(DataFlowCallable c |
c = n.getEnclosingCallable() and
not viableCallable(call) = c
) and
msg = "Call context for isUnreachableInCall is inconsistent with call graph."
}
query predicate localCallNodes(DataFlowCall call, Node n, string msg) {
(
n = getAnOutNode(call, _) and
msg = "OutNode and call does not share enclosing callable."
or
n.(ArgumentNode).argumentOf(call, _) and
msg = "ArgumentNode and call does not share enclosing callable."
) and
n.getEnclosingCallable() != call.getEnclosingCallable()
}
// This predicate helps the compiler forget that in some languages
// it is impossible for a result of `getPreUpdateNode` to be an
// instance of `PostUpdateNode`.
private Node getPre(PostUpdateNode n) {
result = n.getPreUpdateNode()
or
none()
}
query predicate postIsNotPre(PostUpdateNode n, string msg) {
getPre(n) = n and
msg = "PostUpdateNode should not equal its pre-update node."
}
query predicate postHasUniquePre(PostUpdateNode n, string msg) {
exists(int c |
c = count(n.getPreUpdateNode()) and
c != 1 and
msg = "PostUpdateNode should have one pre-update node but has " + c + "."
)
}
query predicate uniquePostUpdate(Node n, string msg) {
1 < strictcount(PostUpdateNode post | post.getPreUpdateNode() = n) and
msg = "Node has multiple PostUpdateNodes."
}
query predicate postIsInSameCallable(PostUpdateNode n, string msg) {
n.getEnclosingCallable() != n.getPreUpdateNode().getEnclosingCallable() and
msg = "PostUpdateNode does not share callable with its pre-update node."
}
private predicate hasPost(Node n) { exists(PostUpdateNode post | post.getPreUpdateNode() = n) }
query predicate reverseRead(Node n, string msg) {
exists(Node n2 | readStep(n, _, n2) and hasPost(n2) and not hasPost(n)) and
msg = "Origin of readStep is missing a PostUpdateNode."
}
query predicate argHasPostUpdate(ArgumentNode n, string msg) {
not hasPost(n) and
not isImmutableOrUnobservable(n) and
msg = "ArgumentNode is missing PostUpdateNode."
}
// This predicate helps the compiler forget that in some languages
// it is impossible for a `PostUpdateNode` to be the target of
// `simpleLocalFlowStep`.
private predicate isPostUpdateNode(Node n) { n instanceof PostUpdateNode or none() }
query predicate postWithInFlow(Node n, string msg) {
isPostUpdateNode(n) and
simpleLocalFlowStep(_, n) and
msg = "PostUpdateNode should not be the target of local flow."
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
/**
* Provides Java-specific definitions for use in the data flow library.
*/
module Private {
import DataFlowPrivate
import DataFlowDispatch
}
module Public {
import DataFlowUtil
}

View File

@@ -0,0 +1,424 @@
private import java
private import semmle.code.java.dataflow.InstanceAccess
private import semmle.code.java.dataflow.FlowSummary
private import semmle.code.java.dataflow.TypeFlow
private import DataFlowPrivate
private import FlowSummaryImpl as FlowSummaryImpl
private import DataFlowImplCommon as DataFlowImplCommon
cached
newtype TNode =
TExprNode(Expr e) {
DataFlowImplCommon::forceCachingInSameStage() and
not e.getType() instanceof VoidType and
not e.getParent*() instanceof Annotation
} or
TExplicitParameterNode(Parameter p) {
exists(p.getCallable().getBody()) or p.getCallable() instanceof SummarizedCallable
} or
TImplicitVarargsArray(Call c) {
c.getCallee().isVarargs() and
not exists(Argument arg | arg.getCall() = c and arg.isExplicitVarargsArray())
} or
TInstanceParameterNode(Callable c) {
(exists(c.getBody()) or c instanceof SummarizedCallable) and
not c.isStatic()
} or
TImplicitInstanceAccess(InstanceAccessExt ia) { not ia.isExplicit(_) } or
TMallocNode(ClassInstanceExpr cie) or
TExplicitExprPostUpdate(Expr e) {
explicitInstanceArgument(_, e)
or
e instanceof Argument and not e.getType() instanceof ImmutableType
or
exists(FieldAccess fa | fa.getField() instanceof InstanceField and e = fa.getQualifier())
or
exists(ArrayAccess aa | e = aa.getArray())
} or
TImplicitExprPostUpdate(InstanceAccessExt ia) {
implicitInstanceArgument(_, ia)
or
exists(FieldAccess fa |
fa.getField() instanceof InstanceField and ia.isImplicitFieldQualifier(fa)
)
} or
TSummaryInternalNode(SummarizedCallable c, FlowSummaryImpl::Private::SummaryNodeState state) {
FlowSummaryImpl::Private::summaryNodeRange(c, state)
}
private predicate explicitInstanceArgument(Call call, Expr instarg) {
call instanceof MethodAccess and
instarg = call.getQualifier() and
not call.getCallee().isStatic()
}
private predicate implicitInstanceArgument(Call call, InstanceAccessExt ia) {
ia.isImplicitMethodQualifier(call) or
ia.isImplicitThisConstructorArgument(call)
}
module Public {
/**
* An element, viewed as a node in a data flow graph. Either an expression,
* a parameter, or an implicit varargs array creation.
*/
class Node extends TNode {
/** Gets a textual representation of this element. */
string toString() { none() }
/** Gets the source location for this element. */
Location getLocation() { none() }
/** Gets the expression corresponding to this node, if any. */
Expr asExpr() { result = this.(ExprNode).getExpr() }
/** Gets the parameter corresponding to this node, if any. */
Parameter asParameter() { result = this.(ExplicitParameterNode).getParameter() }
/** Gets the type of this node. */
Type getType() {
result = this.asExpr().getType()
or
result = this.asParameter().getType()
or
exists(Parameter p |
result = p.getType() and
p.isVarargs() and
p = this.(ImplicitVarargsArray).getCall().getCallee().getAParameter()
)
or
result = this.(InstanceParameterNode).getCallable().getDeclaringType()
or
result = this.(ImplicitInstanceAccess).getInstanceAccess().getType()
or
result = this.(MallocNode).getClassInstanceExpr().getType()
or
result = this.(ImplicitPostUpdateNode).getPreUpdateNode().getType()
}
/** Gets the callable in which this node occurs. */
Callable getEnclosingCallable() {
result = this.asExpr().getEnclosingCallable() or
result = this.asParameter().getCallable() or
result = this.(ImplicitVarargsArray).getCall().getEnclosingCallable() or
result = this.(InstanceParameterNode).getCallable() or
result = this.(ImplicitInstanceAccess).getInstanceAccess().getEnclosingCallable() or
result = this.(MallocNode).getClassInstanceExpr().getEnclosingCallable() or
result = this.(ImplicitPostUpdateNode).getPreUpdateNode().getEnclosingCallable() or
this = TSummaryInternalNode(result, _)
}
private Type getImprovedTypeBound() {
exprTypeFlow(this.asExpr(), result, _) or
result = this.(ImplicitPostUpdateNode).getPreUpdateNode().getImprovedTypeBound()
}
/**
* Gets an upper bound on the type of this node.
*/
Type getTypeBound() {
result = getImprovedTypeBound()
or
result = getType() and not exists(getImprovedTypeBound())
}
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
}
/**
* An expression, viewed as a node in a data flow graph.
*/
class ExprNode extends Node, TExprNode {
Expr expr;
ExprNode() { this = TExprNode(expr) }
override string toString() { result = expr.toString() }
override Location getLocation() { result = expr.getLocation() }
/** Gets the expression corresponding to this node. */
Expr getExpr() { result = expr }
}
/** Gets the node corresponding to `e`. */
ExprNode exprNode(Expr e) { result.getExpr() = e }
/** An explicit or implicit parameter. */
abstract class ParameterNode extends Node {
/**
* Holds if this node is the parameter of `c` at the specified (zero-based)
* position. The implicit `this` parameter is considered to have index `-1`.
*/
abstract predicate isParameterOf(Callable c, int pos);
}
/**
* A parameter, viewed as a node in a data flow graph.
*/
class ExplicitParameterNode extends ParameterNode, TExplicitParameterNode {
Parameter param;
ExplicitParameterNode() { this = TExplicitParameterNode(param) }
override string toString() { result = param.toString() }
override Location getLocation() { result = param.getLocation() }
/** Gets the parameter corresponding to this node. */
Parameter getParameter() { result = param }
override predicate isParameterOf(Callable c, int pos) { c.getParameter(pos) = param }
}
/** Gets the node corresponding to `p`. */
ExplicitParameterNode parameterNode(Parameter p) { result.getParameter() = p }
/**
* An implicit varargs array creation expression.
*
* A call `f(x1, x2)` to a method `f(A... xs)` desugars to `f(new A[]{x1, x2})`,
* and this node corresponds to such an implicit array creation.
*/
class ImplicitVarargsArray extends Node, TImplicitVarargsArray {
Call call;
ImplicitVarargsArray() { this = TImplicitVarargsArray(call) }
override string toString() { result = "new ..[] { .. }" }
override Location getLocation() { result = call.getLocation() }
/** Gets the call containing this varargs array creation argument. */
Call getCall() { result = call }
}
/**
* An instance parameter for an instance method or constructor.
*/
class InstanceParameterNode extends ParameterNode, TInstanceParameterNode {
Callable callable;
InstanceParameterNode() { this = TInstanceParameterNode(callable) }
override string toString() { result = "parameter this" }
override Location getLocation() { result = callable.getLocation() }
/** Gets the callable containing this `this` parameter. */
Callable getCallable() { result = callable }
override predicate isParameterOf(Callable c, int pos) { callable = c and pos = -1 }
}
/**
* An implicit read of `this` or `A.this`.
*/
class ImplicitInstanceAccess extends Node, TImplicitInstanceAccess {
InstanceAccessExt ia;
ImplicitInstanceAccess() { this = TImplicitInstanceAccess(ia) }
override string toString() { result = ia.toString() }
override Location getLocation() { result = ia.getLocation() }
/** Gets the instance access corresponding to this node. */
InstanceAccessExt getInstanceAccess() { result = ia }
}
/**
* A node associated with an object after an operation that might have
* changed its state.
*
* This can be either the argument to a callable after the callable returns
* (which might have mutated the argument), or the qualifier of a field after
* an update to the field.
*
* Nodes corresponding to AST elements, for example `ExprNode`, usually refer
* to the value before the update with the exception of `ClassInstanceExpr`,
* which represents the value after the constructor has run.
*/
abstract class PostUpdateNode extends Node {
/**
* Gets the node before the state update.
*/
abstract Node getPreUpdateNode();
}
/**
* Gets the node that occurs as the qualifier of `fa`.
*/
Node getFieldQualifier(FieldAccess fa) {
fa.getField() instanceof InstanceField and
(
result.asExpr() = fa.getQualifier() or
result.(ImplicitInstanceAccess).getInstanceAccess().isImplicitFieldQualifier(fa)
)
}
/** Gets the instance argument of a non-static call. */
Node getInstanceArgument(Call call) {
result.(MallocNode).getClassInstanceExpr() = call or
explicitInstanceArgument(call, result.asExpr()) or
implicitInstanceArgument(call, result.(ImplicitInstanceAccess).getInstanceAccess())
}
}
private import Public
private class NewExpr extends PostUpdateNode, TExprNode {
NewExpr() { exists(ClassInstanceExpr cie | this = TExprNode(cie)) }
override Node getPreUpdateNode() { this = TExprNode(result.(MallocNode).getClassInstanceExpr()) }
}
/**
* A `PostUpdateNode` that is not a `ClassInstanceExpr`.
*/
abstract private class ImplicitPostUpdateNode extends PostUpdateNode {
override Location getLocation() { result = getPreUpdateNode().getLocation() }
override string toString() { result = getPreUpdateNode().toString() + " [post update]" }
}
private class ExplicitExprPostUpdate extends ImplicitPostUpdateNode, TExplicitExprPostUpdate {
override Node getPreUpdateNode() { this = TExplicitExprPostUpdate(result.asExpr()) }
}
private class ImplicitExprPostUpdate extends ImplicitPostUpdateNode, TImplicitExprPostUpdate {
override Node getPreUpdateNode() {
this = TImplicitExprPostUpdate(result.(ImplicitInstanceAccess).getInstanceAccess())
}
}
module Private {
/**
* A data flow node that occurs as the argument of a call and is passed as-is
* to the callable. Arguments that are wrapped in an implicit varargs array
* creation are not included, but the implicitly created array is.
* Instance arguments are also included.
*/
class ArgumentNode extends Node {
ArgumentNode() {
exists(Argument arg | this.asExpr() = arg | not arg.isVararg())
or
this instanceof ImplicitVarargsArray
or
this = getInstanceArgument(_)
or
this.(SummaryNode).isArgumentOf(_, _)
}
/**
* Holds if this argument occurs at the given position in the given call.
* The instance argument is considered to have index `-1`.
*/
predicate argumentOf(DataFlowCall call, int pos) {
exists(Argument arg | this.asExpr() = arg | call = arg.getCall() and pos = arg.getPosition())
or
call = this.(ImplicitVarargsArray).getCall() and
pos = call.getCallee().getNumberOfParameters() - 1
or
pos = -1 and this = getInstanceArgument(call)
or
this.(SummaryNode).isArgumentOf(call, pos)
}
/** Gets the call in which this node is an argument. */
DataFlowCall getCall() { this.argumentOf(result, _) }
}
/** A data flow node that occurs as the result of a `ReturnStmt`. */
class ReturnNode extends Node {
ReturnNode() {
exists(ReturnStmt ret | this.asExpr() = ret.getResult()) or
this.(SummaryNode).isReturn()
}
/** Gets the kind of this returned value. */
ReturnKind getKind() { any() }
}
/** A data flow node that represents the output of a call. */
class OutNode extends Node {
OutNode() {
this.asExpr() instanceof MethodAccess
or
this.(SummaryNode).isOut(_)
}
/** Gets the underlying call. */
DataFlowCall getCall() {
result = this.asExpr()
or
this.(SummaryNode).isOut(result)
}
}
/**
* A data-flow node used to model flow summaries.
*/
class SummaryNode extends Node, TSummaryInternalNode {
private SummarizedCallable c;
private FlowSummaryImpl::Private::SummaryNodeState state;
SummaryNode() { this = TSummaryInternalNode(c, state) }
override Location getLocation() { result = c.getLocation() }
override string toString() { result = "[summary] " + state + " in " + c }
/** Holds if this summary node is the `i`th argument of `call`. */
predicate isArgumentOf(DataFlowCall call, int i) {
FlowSummaryImpl::Private::summaryArgumentNode(call, this, i)
}
/** Holds if this summary node is a return node. */
predicate isReturn() { FlowSummaryImpl::Private::summaryReturnNode(this, _) }
/** Holds if this summary node is an out node for `call`. */
predicate isOut(DataFlowCall call) { FlowSummaryImpl::Private::summaryOutNode(call, this, _) }
}
SummaryNode getSummaryNode(SummarizedCallable c, FlowSummaryImpl::Private::SummaryNodeState state) {
result = TSummaryInternalNode(c, state)
}
}
private import Private
/**
* A node that corresponds to the value of a `ClassInstanceExpr` before the
* constructor has run.
*/
private class MallocNode extends Node, TMallocNode {
ClassInstanceExpr cie;
MallocNode() { this = TMallocNode(cie) }
override string toString() { result = cie.toString() + " [pre constructor]" }
override Location getLocation() { result = cie.getLocation() }
ClassInstanceExpr getClassInstanceExpr() { result = cie }
}
private class SummaryPostUpdateNode extends SummaryNode, PostUpdateNode {
private Node pre;
SummaryPostUpdateNode() { FlowSummaryImpl::Private::summaryPostUpdateNode(this, pre) }
override Node getPreUpdateNode() { result = pre }
}

View File

@@ -0,0 +1,287 @@
private import java
private import DataFlowUtil
private import DataFlowImplCommon
private import DataFlowDispatch
private import semmle.code.java.controlflow.Guards
private import semmle.code.java.dataflow.SSA
private import ContainerFlow
private import FlowSummaryImpl as FlowSummaryImpl
import DataFlowNodes::Private
private newtype TReturnKind = TNormalReturnKind()
/**
* A return kind. A return kind describes how a value can be returned
* from a callable. For Java, this is simply a method return.
*/
class ReturnKind extends TReturnKind {
/** Gets a textual representation of this return kind. */
string toString() { result = "return" }
}
/**
* Gets a node that can read the value returned from `call` with return kind
* `kind`.
*/
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) {
result = call.getNode() and
kind = TNormalReturnKind()
}
/**
* Holds if data can flow from `node1` to `node2` through a static field.
*/
private predicate staticFieldStep(ExprNode node1, ExprNode node2) {
exists(Field f, FieldRead fr |
f.isStatic() and
f.getAnAssignedValue() = node1.getExpr() and
fr.getField() = f and
fr = node2.getExpr() and
hasNonlocalValue(fr)
)
}
/**
* Holds if data can flow from `node1` to `node2` through variable capture.
*/
private predicate variableCaptureStep(Node node1, ExprNode node2) {
exists(SsaImplicitInit closure, SsaVariable captured |
closure.captures(captured) and
node2.getExpr() = closure.getAFirstUse()
|
node1.asExpr() = captured.getAUse()
or
not exists(captured.getAUse()) and
exists(SsaVariable capturedDef | capturedDef = captured.getAnUltimateDefinition() |
capturedDef.(SsaImplicitInit).isParameterDefinition(node1.asParameter()) or
capturedDef.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource() =
node1.asExpr() or
capturedDef.(SsaExplicitUpdate).getDefiningExpr().(AssignOp) = node1.asExpr()
)
)
}
/**
* Holds if data can flow from `node1` to `node2` through a static field or
* variable capture.
*/
predicate jumpStep(Node node1, Node node2) {
staticFieldStep(node1, node2) or
variableCaptureStep(node1, node2) or
variableCaptureStep(node1.(PostUpdateNode).getPreUpdateNode(), node2)
}
/**
* Holds if `fa` is an access to an instance field that occurs as the
* destination of an assignment of the value `src`.
*/
private predicate instanceFieldAssign(Expr src, FieldAccess fa) {
exists(AssignExpr a |
a.getSource() = src and
a.getDest() = fa and
fa.getField() instanceof InstanceField
)
}
/**
* Holds if data can flow from `node1` to `node2` via an assignment to `f`.
* Thus, `node2` references an object with a field `f` that contains the
* value of `node1`.
*/
predicate storeStep(Node node1, Content f, Node node2) {
exists(FieldAccess fa |
instanceFieldAssign(node1.asExpr(), fa) and
node2.(PostUpdateNode).getPreUpdateNode() = getFieldQualifier(fa) and
f.(FieldContent).getField() = fa.getField()
)
or
f instanceof ArrayContent and arrayStoreStep(node1, node2)
or
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1, f, node2)
}
/**
* 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) {
exists(FieldRead fr |
node1 = getFieldQualifier(fr) and
fr.getField() = f.(FieldContent).getField() and
fr = node2.asExpr()
)
or
exists(Record r, Method getter, Field recf, MethodAccess get |
getter.getDeclaringType() = r and
recf.getDeclaringType() = r and
getter.getNumberOfParameters() = 0 and
getter.getName() = recf.getName() and
not exists(getter.getBody()) and
recf = f.(FieldContent).getField() and
get.getMethod() = getter and
node1.asExpr() = get.getQualifier() and
node2.asExpr() = get
)
or
f instanceof ArrayContent and arrayReadStep(node1, node2, _)
or
f instanceof CollectionContent and collectionReadStep(node1, node2)
or
FlowSummaryImpl::Private::Steps::summaryReadStep(node1, f, node2)
}
/**
* Holds if values stored inside content `c` are cleared at node `n`. For example,
* any value stored inside `f` is cleared at the pre-update node associated with `x`
* in `x.f = newValue`.
*/
predicate clearsContent(Node n, Content c) {
c instanceof FieldContent and
(
n = any(PostUpdateNode pun | storeStep(_, c, pun)).getPreUpdateNode()
or
FlowSummaryImpl::Private::Steps::summaryStoresIntoArg(c, n)
)
or
FlowSummaryImpl::Private::Steps::summaryClearsContent(n, c)
}
/**
* Gets a representative (boxed) type for `t` for the purpose of pruning
* possible flow. A single type is used for all numeric types to account for
* numeric conversions, and otherwise the erasure is used.
*/
DataFlowType getErasedRepr(Type t) {
exists(Type e | e = t.getErasure() |
if e instanceof NumericOrCharType
then result.(BoxedType).getPrimitiveType().getName() = "double"
else
if e instanceof BooleanType
then result.(BoxedType).getPrimitiveType().getName() = "boolean"
else result = e
)
or
t instanceof NullType and result instanceof TypeObject
}
pragma[noinline]
DataFlowType getNodeType(Node n) {
result = getErasedRepr(n.getTypeBound())
or
result = FlowSummaryImpl::Private::summaryNodeType(n)
}
/** Gets a string representation of a type returned by `getErasedRepr`. */
string ppReprType(Type t) {
if t.(BoxedType).getPrimitiveType().getName() = "double"
then result = "Number"
else result = t.toString()
}
private predicate canContainBool(Type t) {
t instanceof BooleanType or
any(BooleanType b).(RefType).getASourceSupertype+() = t
}
/**
* 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) {
exists(Type e1, Type e2 |
e1 = getErasedRepr(t1) and
e2 = getErasedRepr(t2)
|
// Because of `getErasedRepr`, `erasedHaveIntersection` is a sufficient
// compatibility check, but `conContainBool` is kept as a dummy disjunct
// to get the proper join-order.
erasedHaveIntersection(e1, e2)
or
canContainBool(e1) and canContainBool(e2)
)
}
/** A node that performs a type cast. */
class CastNode extends ExprNode {
CastNode() { this.getExpr() instanceof CastExpr }
}
class DataFlowCallable = Callable;
class DataFlowExpr = Expr;
class DataFlowType = RefType;
class DataFlowCall extends Call {
/** Gets the data flow node corresponding to this call. */
ExprNode getNode() { result.getExpr() = this }
}
/** Holds if `e` is an expression that always has the same Boolean value `val`. */
private predicate constantBooleanExpr(Expr e, boolean val) {
e.(CompileTimeConstantExpr).getBooleanValue() = val
or
exists(SsaExplicitUpdate v, Expr src |
e = v.getAUse() and
src = v.getDefiningExpr().(VariableAssign).getSource() 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) }
}
/**
* Holds if the node `n` is unreachable when the call context is `call`.
*/
predicate isUnreachableInCall(Node n, DataFlowCall call) {
exists(
ExplicitParameterNode paramNode, ConstantBooleanArgumentNode arg, SsaImplicitInit param,
Guard guard
|
// get constant bool argument and parameter for this call
viableParamArg(call, paramNode, arg) and
// get the ssa variable definition for this parameter
param.isParameterDefinition(paramNode.getParameter()) and
// which is used in a guard
param.getAUse() = guard and
// which controls `n` with the opposite value of `arg`
guard
.controls(n.asExpr().getBasicBlock(),
pragma[only_bind_out](arg.getBooleanValue()).booleanNot())
)
}
int accessPathLimit() { result = 5 }
/**
* Holds if `n` does not require a `PostUpdateNode` as it either cannot be
* modified or its modification cannot be observed, for example if it is a
* freshly created object that is not saved in a variable.
*
* This predicate is only used for consistency checks.
*/
predicate isImmutableOrUnobservable(Node n) {
n.getType() instanceof ImmutableType or n instanceof ImplicitVarargsArray
}
/** Holds if `n` should be hidden from path explanations. */
predicate nodeIsHidden(Node n) { n instanceof SummaryNode }
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() }

View File

@@ -0,0 +1,272 @@
/**
* Basic definitions for use in the data flow library.
*/
private import java
private import DataFlowPrivate
private import semmle.code.java.dataflow.SSA
private import semmle.code.java.controlflow.Guards
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.FlowSteps
private import semmle.code.java.dataflow.FlowSummary
private import semmle.code.java.dataflow.InstanceAccess
private import FlowSummaryImpl as FlowSummaryImpl
private import TaintTrackingUtil as TaintTrackingUtil
import DataFlowNodes::Public
import semmle.code.Unit
/** Holds if `n` is an access to an unqualified `this` at `cfgnode`. */
private predicate thisAccess(Node n, ControlFlowNode cfgnode) {
n.(InstanceParameterNode).getCallable().getBody() = cfgnode
or
exists(InstanceAccess ia | ia = n.asExpr() and ia = cfgnode and ia.isOwnInstanceAccess())
or
n.(ImplicitInstanceAccess).getInstanceAccess().(OwnInstanceAccess).getCfgNode() = cfgnode
}
/** Calculation of the relative order in which `this` references are read. */
private module ThisFlow {
private predicate thisAccess(Node n, BasicBlock b, int i) { thisAccess(n, b.getNode(i)) }
private predicate thisRank(Node n, BasicBlock b, int rankix) {
exists(int i |
i = rank[rankix](int j | thisAccess(_, b, j)) and
thisAccess(n, b, i)
)
}
private int lastRank(BasicBlock b) { result = max(int rankix | thisRank(_, b, rankix)) }
private predicate blockPrecedesThisAccess(BasicBlock b) { thisAccess(_, b.getABBSuccessor*(), _) }
private predicate thisAccessBlockReaches(BasicBlock b1, BasicBlock b2) {
thisAccess(_, b1, _) and b2 = b1.getABBSuccessor()
or
exists(BasicBlock mid |
thisAccessBlockReaches(b1, mid) and
b2 = mid.getABBSuccessor() and
not thisAccess(_, mid, _) and
blockPrecedesThisAccess(b2)
)
}
private predicate thisAccessBlockStep(BasicBlock b1, BasicBlock b2) {
thisAccessBlockReaches(b1, b2) and
thisAccess(_, b2, _)
}
/** Holds if `n1` and `n2` are control-flow adjacent references to `this`. */
predicate adjacentThisRefs(Node n1, Node n2) {
exists(int rankix, BasicBlock b |
thisRank(n1, b, rankix) and
thisRank(n2, b, rankix + 1)
)
or
exists(BasicBlock b1, BasicBlock b2 |
thisRank(n1, b1, lastRank(b1)) and
thisAccessBlockStep(b1, b2) and
thisRank(n2, b2, 1)
)
}
}
/**
* Holds if data can flow from `node1` to `node2` in zero or more
* local (intra-procedural) steps.
*/
predicate localFlow(Node node1, Node node2) { localFlowStep*(node1, node2) }
/**
* Holds if data can flow from `e1` to `e2` in zero or more
* local (intra-procedural) steps.
*/
predicate localExprFlow(Expr e1, Expr e2) { localFlow(exprNode(e1), exprNode(e2)) }
/**
* Holds if the `FieldRead` is not completely determined by explicit SSA
* updates.
*/
predicate hasNonlocalValue(FieldRead fr) {
not exists(SsaVariable v | v.getAUse() = fr)
or
exists(SsaVariable v, SsaVariable def | v.getAUse() = fr and def = v.getAnUltimateDefinition() |
def instanceof SsaImplicitInit or
def instanceof SsaImplicitUpdate
)
}
/**
* Holds if data can flow from `node1` to `node2` in one local step.
*/
predicate localFlowStep(Node node1, Node node2) {
simpleLocalFlowStep(node1, node2)
or
// Simple flow through library code is included in the exposed local
// step relation, even though flow is technically inter-procedural
FlowSummaryImpl::Private::Steps::summaryThroughStep(node1, node2, true)
}
/**
* INTERNAL: do not use.
*
* This is the local flow predicate that's used as a building block in global
* data flow. It may have less flow than the `localFlowStep` predicate.
*/
cached
predicate simpleLocalFlowStep(Node node1, Node node2) {
TaintTrackingUtil::forceCachingInSameStage() and
// Variable flow steps through adjacent def-use and use-use pairs.
exists(SsaExplicitUpdate upd |
upd.getDefiningExpr().(VariableAssign).getSource() = node1.asExpr() or
upd.getDefiningExpr().(AssignOp) = node1.asExpr()
|
node2.asExpr() = upd.getAFirstUse()
)
or
exists(SsaImplicitInit init |
init.isParameterDefinition(node1.asParameter()) and
node2.asExpr() = init.getAFirstUse()
)
or
adjacentUseUse(node1.asExpr(), node2.asExpr()) and
not exists(FieldRead fr |
hasNonlocalValue(fr) and fr.getField().isStatic() and fr = node1.asExpr()
)
or
ThisFlow::adjacentThisRefs(node1, node2)
or
adjacentUseUse(node1.(PostUpdateNode).getPreUpdateNode().asExpr(), node2.asExpr())
or
ThisFlow::adjacentThisRefs(node1.(PostUpdateNode).getPreUpdateNode(), node2)
or
node2.asExpr().(CastExpr).getExpr() = node1.asExpr()
or
node2.asExpr().(ChooseExpr).getAResultExpr() = node1.asExpr()
or
node2.asExpr().(AssignExpr).getSource() = node1.asExpr()
or
node2.asExpr().(ArrayCreationExpr).getInit() = node1.asExpr()
or
exists(MethodAccess ma, ValuePreservingMethod m, int argNo |
ma.getCallee().getSourceDeclaration() = m and m.returnsValue(argNo)
|
node2.asExpr() = ma and
node1.(ArgumentNode).argumentOf(ma, argNo)
)
or
FlowSummaryImpl::Private::Steps::summaryLocalStep(node1, node2, true)
}
private newtype TContent =
TFieldContent(InstanceField f) or
TArrayContent() or
TCollectionContent() or
TMapKeyContent() or
TMapValueContent() or
TSyntheticFieldContent(SyntheticField s)
/**
* A description of the way data may be stored inside an object. Examples
* include instance fields, the contents of a collection object, or the contents
* of an array.
*/
class Content extends TContent {
/** Gets the type of the contained data for the purpose of type pruning. */
abstract DataFlowType getType();
/** Gets a textual representation of this element. */
abstract string 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://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
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
}
}
/** A reference through an instance field. */
class FieldContent extends Content, TFieldContent {
InstanceField f;
FieldContent() { this = TFieldContent(f) }
InstanceField getField() { result = f }
override DataFlowType getType() { result = getErasedRepr(f.getType()) }
override string toString() { result = f.toString() }
override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
f.getLocation().hasLocationInfo(path, sl, sc, el, ec)
}
}
/** A reference through an array. */
class ArrayContent extends Content, TArrayContent {
override DataFlowType getType() { result instanceof TypeObject }
override string toString() { result = "[]" }
}
/** A reference through the contents of some collection-like container. */
class CollectionContent extends Content, TCollectionContent {
override DataFlowType getType() { result instanceof TypeObject }
override string toString() { result = "<element>" }
}
/** A reference through a map key. */
class MapKeyContent extends Content, TMapKeyContent {
override DataFlowType getType() { result instanceof TypeObject }
override string toString() { result = "<map.key>" }
}
/** A reference through a map value. */
class MapValueContent extends Content, TMapValueContent {
override DataFlowType getType() { result instanceof TypeObject }
override string toString() { result = "<map.value>" }
}
/** A reference through a synthetic instance field. */
class SyntheticFieldContent extends Content, TSyntheticFieldContent {
SyntheticField s;
SyntheticFieldContent() { this = TSyntheticFieldContent(s) }
SyntheticField getField() { result = s }
override DataFlowType getType() { result = getErasedRepr(s.getType()) }
override string toString() { result = s.toString() }
}
/**
* A guard that validates some expression.
*
* To use this in a configuration, extend the class and provide a
* characteristic predicate precisely specifying the guard, and override
* `checks` to specify what is being validated and in which branch.
*
* It is important that all extending classes in scope are disjoint.
*/
class BarrierGuard extends Guard {
/** Holds if this guard validates `e` upon evaluating to `branch`. */
abstract predicate checks(Expr e, boolean branch);
/** Gets a node guarded by this guard. */
final Node getAGuardedNode() {
exists(SsaVariable v, boolean branch, RValue use |
this.checks(v.getAUse(), branch) and
use = v.getAUse() and
this.controls(use.getBasicBlock(), branch) and
result.asExpr() = use
)
}
}

View File

@@ -0,0 +1,827 @@
/**
* Provides classes and predicates for defining flow summaries.
*
* The definitions in this file are language-independent, and language-specific
* definitions are passed in via the `DataFlowImplSpecific` and
* `FlowSummaryImplSpecific` modules.
*/
private import FlowSummaryImplSpecific
private import DataFlowImplSpecific::Private
private import DataFlowImplSpecific::Public
private import DataFlowImplCommon
/** Provides classes and predicates for defining flow summaries. */
module Public {
private import Private
/**
* A component used in a flow summary.
*
* Either a parameter or an argument at a given position, a specific
* content type, or a return kind.
*/
class SummaryComponent extends TSummaryComponent {
/** Gets a textual representation of this summary component. */
string toString() {
exists(Content c | this = TContentSummaryComponent(c) and result = c.toString())
or
exists(int i | this = TParameterSummaryComponent(i) and result = "parameter " + i)
or
exists(int i | this = TArgumentSummaryComponent(i) and result = "argument " + i)
or
exists(ReturnKind rk | this = TReturnSummaryComponent(rk) and result = "return (" + rk + ")")
}
}
/** Provides predicates for constructing summary components. */
module SummaryComponent {
/** Gets a summary component for content `c`. */
SummaryComponent content(Content c) { result = TContentSummaryComponent(c) }
/** Gets a summary component for parameter `i`. */
SummaryComponent parameter(int i) { result = TParameterSummaryComponent(i) }
/** Gets a summary component for argument `i`. */
SummaryComponent argument(int i) { result = TArgumentSummaryComponent(i) }
/** Gets a summary component for a return of kind `rk`. */
SummaryComponent return(ReturnKind rk) { result = TReturnSummaryComponent(rk) }
}
/**
* A (non-empty) stack of summary components.
*
* A stack is used to represent where data is read from (input) or where it
* is written to (output). For example, an input stack `[Field f, Argument 0]`
* means that data is read from field `f` from the `0`th argument, while an
* output stack `[Field g, Return]` means that data is written to the field
* `g` of the returned object.
*/
class SummaryComponentStack extends TSummaryComponentStack {
/** Gets the head of this stack. */
SummaryComponent head() {
this = TSingletonSummaryComponentStack(result) or
this = TConsSummaryComponentStack(result, _)
}
/** Gets the tail of this stack, if any. */
SummaryComponentStack tail() { this = TConsSummaryComponentStack(_, result) }
/** Gets the length of this stack. */
int length() {
this = TSingletonSummaryComponentStack(_) and result = 1
or
result = 1 + this.tail().length()
}
/** Gets the stack obtained by dropping the first `i` elements, if any. */
SummaryComponentStack drop(int i) {
i = 0 and result = this
or
result = this.tail().drop(i - 1)
}
/** Holds if this stack contains summary component `c`. */
predicate contains(SummaryComponent c) { c = this.drop(_).head() }
/** Gets a textual representation of this stack. */
string toString() {
exists(SummaryComponent head, SummaryComponentStack tail |
head = this.head() and
tail = this.tail() and
result = head + " of " + tail
)
or
exists(SummaryComponent c |
this = TSingletonSummaryComponentStack(c) and
result = c.toString()
)
}
}
/** Provides predicates for constructing stacks of summary components. */
module SummaryComponentStack {
/** Gets a singleton stack containing `c`. */
SummaryComponentStack singleton(SummaryComponent c) {
result = TSingletonSummaryComponentStack(c)
}
/**
* Gets the stack obtained by pushing `head` onto `tail`.
*
* Make sure to override `RequiredSummaryComponentStack::required()` in order
* to ensure that the constructed stack exists.
*/
SummaryComponentStack push(SummaryComponent head, SummaryComponentStack tail) {
result = TConsSummaryComponentStack(head, tail)
}
/** Gets a singleton stack for argument `i`. */
SummaryComponentStack argument(int i) { result = singleton(SummaryComponent::argument(i)) }
/** Gets a singleton stack representing a return of kind `rk`. */
SummaryComponentStack return(ReturnKind rk) { result = singleton(SummaryComponent::return(rk)) }
}
/**
* A class that exists for QL technical reasons only (the IPA type used
* to represent component stacks needs to be bounded).
*/
abstract class RequiredSummaryComponentStack extends SummaryComponentStack {
/**
* Holds if the stack obtained by pushing `head` onto `tail` is required.
*/
abstract predicate required(SummaryComponent c);
}
/** A callable with a flow summary. */
abstract class SummarizedCallable extends DataFlowCallable {
/**
* Holds if data may flow from `input` to `output` through this callable.
*
* `preservesValue` indicates whether this is a value-preserving step
* or a taint-step.
*
* Input specifications are restricted to stacks that end with
* `SummaryComponent::argument(_)`, preceded by zero or more
* `SummaryComponent::return(_)` or `SummaryComponent::content(_)` components.
*
* Output specifications are restricted to stacks that end with
* `SummaryComponent::return(_)` or `SummaryComponent::argument(_)`.
*
* Output stacks ending with `SummaryComponent::return(_)` can be preceded by zero
* or more `SummaryComponent::content(_)` components.
*
* Output stacks ending with `SummaryComponent::argument(_)` can be preceded by an
* optional `SummaryComponent::parameter(_)` component, which in turn can be preceded
* by zero or more `SummaryComponent::content(_)` components.
*/
pragma[nomagic]
predicate propagatesFlow(
SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
) {
none()
}
/**
* Holds if values stored inside `content` are cleared on objects passed as
* the `i`th argument to this callable.
*/
pragma[nomagic]
predicate clearsContent(int i, Content content) { none() }
}
}
/**
* Provides predicates for compiling flow summaries down to atomic local steps,
* read steps, and store steps.
*/
module Private {
private import Public
newtype TSummaryComponent =
TContentSummaryComponent(Content c) or
TParameterSummaryComponent(int i) { parameterPosition(i) } or
TArgumentSummaryComponent(int i) { parameterPosition(i) } or
TReturnSummaryComponent(ReturnKind rk)
newtype TSummaryComponentStack =
TSingletonSummaryComponentStack(SummaryComponent c) or
TConsSummaryComponentStack(SummaryComponent head, SummaryComponentStack tail) {
tail.(RequiredSummaryComponentStack).required(head)
}
pragma[nomagic]
private predicate summary(
SummarizedCallable c, SummaryComponentStack input, SummaryComponentStack output,
boolean preservesValue
) {
c.propagatesFlow(input, output, preservesValue)
}
private newtype TSummaryNodeState =
TSummaryNodeInputState(SummaryComponentStack s) {
exists(SummaryComponentStack input |
summary(_, input, _, _) and
s = input.drop(_)
)
} or
TSummaryNodeOutputState(SummaryComponentStack s) {
exists(SummaryComponentStack output |
summary(_, _, output, _) and
s = output.drop(_)
)
}
/**
* A state used to break up (complex) flow summaries into atomic flow steps.
* For a flow summary
*
* ```ql
* propagatesFlow(
* SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
* )
* ```
*
* the following states are used:
*
* - `TSummaryNodeInputState(SummaryComponentStack s)`:
* this state represents that the components in `s` _have been read_ from the
* input.
* - `TSummaryNodeOutputState(SummaryComponentStack s)`:
* this state represents that the components in `s` _remain to be written_ to
* the output.
*/
class SummaryNodeState extends TSummaryNodeState {
/** Holds if this state is a valid input state for `c`. */
pragma[nomagic]
predicate isInputState(SummarizedCallable c, SummaryComponentStack s) {
this = TSummaryNodeInputState(s) and
exists(SummaryComponentStack input |
summary(c, input, _, _) and
s = input.drop(_)
)
}
/** Holds if this state is a valid output state for `c`. */
pragma[nomagic]
predicate isOutputState(SummarizedCallable c, SummaryComponentStack s) {
this = TSummaryNodeOutputState(s) and
exists(SummaryComponentStack output |
summary(c, _, output, _) and
s = output.drop(_)
)
}
/** Gets a textual representation of this state. */
string toString() {
exists(SummaryComponentStack s |
this = TSummaryNodeInputState(s) and
result = "read: " + s
)
or
exists(SummaryComponentStack s |
this = TSummaryNodeOutputState(s) and
result = "to write: " + s
)
}
}
/**
* Holds if `state` represents having read the `i`th argument for `c`. In this case
* we are not synthesizing a data-flow node, but instead assume that a relevant
* parameter node already exists.
*/
private predicate parameterReadState(SummarizedCallable c, SummaryNodeState state, int i) {
state.isInputState(c, SummaryComponentStack::argument(i))
}
/**
* Holds if a synthesized summary node is needed for the state `state` in summarized
* callable `c`.
*/
predicate summaryNodeRange(SummarizedCallable c, SummaryNodeState state) {
state.isInputState(c, _) and
not parameterReadState(c, state, _)
or
state.isOutputState(c, _)
}
pragma[noinline]
private Node summaryNodeInputState(SummarizedCallable c, SummaryComponentStack s) {
exists(SummaryNodeState state | state.isInputState(c, s) |
result = summaryNode(c, state)
or
exists(int i |
parameterReadState(c, state, i) and
result.(ParamNode).isParameterOf(c, i)
)
)
}
pragma[noinline]
private Node summaryNodeOutputState(SummarizedCallable c, SummaryComponentStack s) {
exists(SummaryNodeState state |
state.isOutputState(c, s) and
result = summaryNode(c, state)
)
}
/**
* Holds if a write targets `post`, which is a post-update node for the `i`th
* parameter of `c`.
*/
private predicate isParameterPostUpdate(Node post, SummarizedCallable c, int i) {
post = summaryNodeOutputState(c, SummaryComponentStack::argument(i))
}
/** Holds if a parameter node is required for the `i`th parameter of `c`. */
predicate summaryParameterNodeRange(SummarizedCallable c, int i) {
parameterReadState(c, _, i)
or
isParameterPostUpdate(_, c, i)
}
private predicate callbackOutput(
SummarizedCallable c, SummaryComponentStack s, Node receiver, ReturnKind rk
) {
any(SummaryNodeState state).isInputState(c, s) and
s.head() = TReturnSummaryComponent(rk) and
receiver = summaryNodeInputState(c, s.drop(1))
}
private Node pre(Node post) {
summaryPostUpdateNode(post, result)
or
not summaryPostUpdateNode(post, _) and
result = post
}
private predicate callbackInput(
SummarizedCallable c, SummaryComponentStack s, Node receiver, int i
) {
any(SummaryNodeState state).isOutputState(c, s) and
s.head() = TParameterSummaryComponent(i) and
receiver = pre(summaryNodeOutputState(c, s.drop(1)))
}
/** Holds if a call targeting `receiver` should be synthesized inside `c`. */
predicate summaryCallbackRange(SummarizedCallable c, Node receiver) {
callbackOutput(c, _, receiver, _)
or
callbackInput(c, _, receiver, _)
}
/**
* Gets the type of synthesized summary node `n`.
*
* The type is computed based on the language-specific predicates
* `getContentType()`, `getReturnType()`, `getCallbackParameterType()`, and
* `getCallbackReturnType()`.
*/
DataFlowType summaryNodeType(Node n) {
exists(Node pre |
summaryPostUpdateNode(n, pre) and
result = getNodeType(pre)
)
or
exists(SummarizedCallable c, SummaryComponentStack s, SummaryComponent head | head = s.head() |
n = summaryNodeInputState(c, s) and
(
exists(Content cont |
head = TContentSummaryComponent(cont) and result = getContentType(cont)
)
or
exists(ReturnKind rk |
head = TReturnSummaryComponent(rk) and
result =
getCallbackReturnType(getNodeType(summaryNodeInputState(pragma[only_bind_out](c),
s.drop(1))), rk)
)
)
or
n = summaryNodeOutputState(c, s) and
(
exists(Content cont |
head = TContentSummaryComponent(cont) and result = getContentType(cont)
)
or
s.length() = 1 and
exists(ReturnKind rk |
head = TReturnSummaryComponent(rk) and
result = getReturnType(c, rk)
)
or
exists(int i | head = TParameterSummaryComponent(i) |
result =
getCallbackParameterType(getNodeType(summaryNodeOutputState(pragma[only_bind_out](c),
s.drop(1))), i)
)
)
)
}
/** Holds if summary node `out` contains output of kind `rk` from call `c`. */
predicate summaryOutNode(DataFlowCall c, Node out, ReturnKind rk) {
exists(SummarizedCallable callable, SummaryComponentStack s, Node receiver |
callbackOutput(callable, s, receiver, rk) and
out = summaryNodeInputState(callable, s) and
c = summaryDataFlowCall(receiver)
)
}
/** Holds if summary node `arg` is the `i`th argument of call `c`. */
predicate summaryArgumentNode(DataFlowCall c, Node arg, int i) {
exists(SummarizedCallable callable, SummaryComponentStack s, Node receiver |
callbackInput(callable, s, receiver, i) and
arg = summaryNodeOutputState(callable, s) and
c = summaryDataFlowCall(receiver)
)
}
/** Holds if summary node `post` is a post-update node with pre-update node `pre`. */
predicate summaryPostUpdateNode(Node post, ParamNode pre) {
exists(SummarizedCallable c, int i |
isParameterPostUpdate(post, c, i) and
pre.isParameterOf(c, i)
)
}
/** Holds if summary node `ret` is a return node of kind `rk`. */
predicate summaryReturnNode(Node ret, ReturnKind rk) {
exists(SummarizedCallable callable, SummaryComponentStack s |
ret = summaryNodeOutputState(callable, s) and
s = TSingletonSummaryComponentStack(TReturnSummaryComponent(rk))
)
}
/** Provides a compilation of flow summaries to atomic data-flow steps. */
module Steps {
/**
* Holds if there is a local step from `pred` to `succ`, which is synthesized
* from a flow summary.
*/
predicate summaryLocalStep(Node pred, Node succ, boolean preservesValue) {
exists(
SummarizedCallable c, SummaryComponentStack inputContents,
SummaryComponentStack outputContents
|
summary(c, inputContents, outputContents, preservesValue) and
pred = summaryNodeInputState(c, inputContents) and
succ = summaryNodeOutputState(c, outputContents)
|
preservesValue = true
or
preservesValue = false and not summary(c, inputContents, outputContents, true)
)
or
// If flow through a method updates a parameter from some input A, and that
// parameter also is returned through B, then we'd like a combined flow from A
// to B as well. As an example, this simplifies modeling of fluent methods:
// for `StringBuilder.append(x)` with a specified value flow from qualifier to
// return value and taint flow from argument 0 to the qualifier, then this
// allows us to infer taint flow from argument 0 to the return value.
summaryPostUpdateNode(pred, succ) and preservesValue = true
}
/**
* Holds if there is a read step of content `c` from `pred` to `succ`, which
* is synthesized from a flow summary.
*/
predicate summaryReadStep(Node pred, Content c, Node succ) {
exists(SummarizedCallable sc, SummaryComponentStack s |
pred = summaryNodeInputState(sc, s.drop(1)) and
succ = summaryNodeInputState(sc, s) and
SummaryComponent::content(c) = s.head()
)
}
/**
* Holds if there is a store step of content `c` from `pred` to `succ`, which
* is synthesized from a flow summary.
*/
predicate summaryStoreStep(Node pred, Content c, Node succ) {
exists(SummarizedCallable sc, SummaryComponentStack s |
pred = summaryNodeOutputState(sc, s) and
succ = summaryNodeOutputState(sc, s.drop(1)) and
SummaryComponent::content(c) = s.head()
)
}
/**
* Holds if values stored inside content `c` are cleared when passed as
* input of type `input` in `call`.
*/
predicate summaryClearsContent(ArgNode arg, Content c) {
exists(DataFlowCall call, int i |
viableCallable(call).(SummarizedCallable).clearsContent(i, c) and
arg.argumentOf(call, i)
)
}
pragma[nomagic]
private ParamNode summaryArgParam(ArgNode arg, ReturnKindExt rk, OutNodeExt out) {
exists(DataFlowCall call, int pos, SummarizedCallable callable |
arg.argumentOf(call, pos) and
viableCallable(call) = callable and
result.isParameterOf(callable, pos) and
out = rk.getAnOutNode(call)
)
}
/**
* Holds if `arg` flows to `out` using a simple flow summary, that is, a flow
* summary without reads and stores.
*
* NOTE: This step should not be used in global data-flow/taint-tracking, but may
* be useful to include in the exposed local data-flow/taint-tracking relations.
*/
predicate summaryThroughStep(ArgNode arg, Node out, boolean preservesValue) {
exists(ReturnKindExt rk, ReturnNodeExt ret |
summaryLocalStep(summaryArgParam(arg, rk, out), ret, preservesValue) and
ret.getKind() = rk
)
}
/**
* Holds if there is a read(+taint) of `c` from `arg` to `out` using a
* flow summary.
*
* NOTE: This step should not be used in global data-flow/taint-tracking, but may
* be useful to include in the exposed local data-flow/taint-tracking relations.
*/
predicate summaryGetterStep(ArgNode arg, Content c, Node out) {
exists(ReturnKindExt rk, Node mid, ReturnNodeExt ret |
summaryReadStep(summaryArgParam(arg, rk, out), c, mid) and
summaryLocalStep(mid, ret, _) and
ret.getKind() = rk
)
}
/**
* Holds if there is a (taint+)store of `arg` into content `c` of `out` using a
* flow summary.
*
* NOTE: This step should not be used in global data-flow/taint-tracking, but may
* be useful to include in the exposed local data-flow/taint-tracking relations.
*/
predicate summarySetterStep(ArgNode arg, Content c, Node out) {
exists(ReturnKindExt rk, Node mid, ReturnNodeExt ret |
summaryLocalStep(summaryArgParam(arg, rk, out), mid, _) and
summaryStoreStep(mid, c, ret) and
ret.getKind() = rk
)
}
/**
* Holds if data is written into content `c` of argument `arg` using a flow summary.
*
* Depending on the type of `c`, this predicate may be relevant to include in the
* definition of `clearsContent()`.
*/
predicate summaryStoresIntoArg(Content c, Node arg) {
exists(ParamUpdateReturnKind rk, ReturnNodeExt ret, PostUpdateNode out |
exists(DataFlowCall call, SummarizedCallable callable |
getNodeEnclosingCallable(ret) = callable and
viableCallable(call) = callable and
summaryStoreStep(_, c, ret) and
ret.getKind() = pragma[only_bind_into](rk) and
out = rk.getAnOutNode(call) and
arg = out.getPreUpdateNode()
)
)
}
}
/**
* Provides a means of translating externally (e.g., CSV) defined flow
* summaries into a `SummarizedCallable`s.
*/
module External {
/** Holds if `spec` is a relevant external specification. */
private predicate relevantSpec(string spec) {
summaryElement(_, spec, _, _) or
summaryElement(_, _, spec, _) or
sourceElement(_, spec, _) or
sinkElement(_, spec, _)
}
/** Holds if the `n`th component of specification `s` is `c`. */
predicate specSplit(string s, string c, int n) { relevantSpec(s) and s.splitAt(" of ", n) = c }
/** Holds if specification `s` has length `len`. */
predicate specLength(string s, int len) { len = 1 + max(int n | specSplit(s, _, n)) }
/** Gets the last component of specification `s`. */
string specLast(string s) {
exists(int len |
specLength(s, len) and
specSplit(s, result, len - 1)
)
}
/** Holds if specification component `c` parses as parameter `n`. */
predicate parseParam(string c, int n) {
specSplit(_, c, _) and
(
c.regexpCapture("Parameter\\[([-0-9]+)\\]", 1).toInt() = n
or
exists(int n1, int n2 |
c.regexpCapture("Parameter\\[([-0-9]+)\\.\\.([0-9]+)\\]", 1).toInt() = n1 and
c.regexpCapture("Parameter\\[([-0-9]+)\\.\\.([0-9]+)\\]", 2).toInt() = n2 and
n = [n1 .. n2]
)
)
}
/** Holds if specification component `c` parses as argument `n`. */
predicate parseArg(string c, int n) {
specSplit(_, c, _) and
(
c.regexpCapture("Argument\\[([-0-9]+)\\]", 1).toInt() = n
or
exists(int n1, int n2 |
c.regexpCapture("Argument\\[([-0-9]+)\\.\\.([0-9]+)\\]", 1).toInt() = n1 and
c.regexpCapture("Argument\\[([-0-9]+)\\.\\.([0-9]+)\\]", 2).toInt() = n2 and
n = [n1 .. n2]
)
)
}
private SummaryComponent interpretComponent(string c) {
specSplit(_, c, _) and
(
exists(int pos | parseArg(c, pos) and result = SummaryComponent::argument(pos))
or
exists(int pos | parseParam(c, pos) and result = SummaryComponent::parameter(pos))
or
c = "ReturnValue" and result = SummaryComponent::return(getReturnValueKind())
or
result = interpretComponentSpecific(c)
)
}
/**
* Holds if `spec` specifies summary component stack `stack`.
*/
predicate interpretSpec(string spec, SummaryComponentStack stack) {
interpretSpec(spec, 0, stack)
}
private predicate interpretSpec(string spec, int idx, SummaryComponentStack stack) {
exists(string c |
relevantSpec(spec) and
specLength(spec, idx + 1) and
specSplit(spec, c, idx) and
stack = SummaryComponentStack::singleton(interpretComponent(c))
)
or
exists(SummaryComponent head, SummaryComponentStack tail |
interpretSpec(spec, idx, head, tail) and
stack = SummaryComponentStack::push(head, tail)
)
}
private predicate interpretSpec(
string output, int idx, SummaryComponent head, SummaryComponentStack tail
) {
exists(string c |
interpretSpec(output, idx + 1, tail) and
specSplit(output, c, idx) and
head = interpretComponent(c)
)
}
private class MkStack extends RequiredSummaryComponentStack {
MkStack() { interpretSpec(_, _, _, this) }
override predicate required(SummaryComponent c) { interpretSpec(_, _, c, this) }
}
private class SummarizedCallableExternal extends SummarizedCallable {
SummarizedCallableExternal() { summaryElement(this, _, _, _) }
override predicate propagatesFlow(
SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
) {
exists(string inSpec, string outSpec, string kind |
summaryElement(this, inSpec, outSpec, kind) and
interpretSpec(inSpec, input) and
interpretSpec(outSpec, output)
|
kind = "value" and preservesValue = true
or
kind = "taint" and preservesValue = false
)
}
}
/** Holds if component `c` of specification `spec` cannot be parsed. */
predicate invalidSpecComponent(string spec, string c) {
specSplit(spec, c, _) and
not exists(interpretComponent(c))
}
private predicate inputNeedsReference(string c) {
c = "Argument" or
parseArg(c, _)
}
private predicate outputNeedsReference(string c) {
c = "Argument" or
parseArg(c, _) or
c = "ReturnValue"
}
private predicate sourceElementRef(InterpretNode ref, string output, string kind) {
exists(SourceOrSinkElement e |
sourceElement(e, output, kind) and
if outputNeedsReference(specLast(output))
then e = ref.getCallTarget()
else e = ref.asElement()
)
}
private predicate sinkElementRef(InterpretNode ref, string input, string kind) {
exists(SourceOrSinkElement e |
sinkElement(e, input, kind) and
if inputNeedsReference(specLast(input))
then e = ref.getCallTarget()
else e = ref.asElement()
)
}
private predicate interpretOutput(string output, int idx, InterpretNode ref, InterpretNode node) {
sourceElementRef(ref, output, _) and
specLength(output, idx) and
node = ref
or
exists(InterpretNode mid, string c |
interpretOutput(output, idx + 1, ref, mid) and
specSplit(output, c, idx)
|
exists(int pos |
node.asNode().(PostUpdateNode).getPreUpdateNode().(ArgNode).argumentOf(mid.asCall(), pos)
|
c = "Argument" or parseArg(c, pos)
)
or
exists(int pos | node.asNode().(ParamNode).isParameterOf(mid.asCallable(), pos) |
c = "Parameter" or parseParam(c, pos)
)
or
c = "ReturnValue" and
node.asNode() = getAnOutNodeExt(mid.asCall(), TValueReturn(getReturnValueKind()))
or
interpretOutputSpecific(c, mid, node)
)
}
private predicate interpretInput(string input, int idx, InterpretNode ref, InterpretNode node) {
sinkElementRef(ref, input, _) and
specLength(input, idx) and
node = ref
or
exists(InterpretNode mid, string c |
interpretInput(input, idx + 1, ref, mid) and
specSplit(input, c, idx)
|
exists(int pos | node.asNode().(ArgNode).argumentOf(mid.asCall(), pos) |
c = "Argument" or parseArg(c, pos)
)
or
exists(ReturnNodeExt ret |
c = "ReturnValue" and
ret = node.asNode() and
ret.getKind().(ValueReturnKind).getKind() = getReturnValueKind() and
mid.asCallable() = getNodeEnclosingCallable(ret)
)
or
interpretInputSpecific(c, mid, node)
)
}
/**
* Holds if `node` is specified as a source with the given kind in a CSV flow
* model.
*/
predicate isSourceNode(InterpretNode node, string kind) {
exists(InterpretNode ref, string output |
sourceElementRef(ref, output, kind) and
interpretOutput(output, 0, ref, node)
)
}
/**
* Holds if `node` is specified as a sink with the given kind in a CSV flow
* model.
*/
predicate isSinkNode(InterpretNode node, string kind) {
exists(InterpretNode ref, string input |
sinkElementRef(ref, input, kind) and
interpretInput(input, 0, ref, node)
)
}
}
/** Provides a query predicate for outputting a set of relevant flow summaries. */
module TestOutput {
/** A flow summary to include in the `summary/3` query predicate. */
abstract class RelevantSummarizedCallable extends SummarizedCallable {
/** Gets the string representation of this callable used by `summary/3`. */
string getFullString() { result = this.toString() }
}
/** A query predicate for outputting flow summaries in QL tests. */
query predicate summary(string callable, string flow, boolean preservesValue) {
exists(
RelevantSummarizedCallable c, SummaryComponentStack input, SummaryComponentStack output
|
callable = c.getFullString() and
c.propagatesFlow(input, output, preservesValue) and
flow = input + " -> " + output
)
}
}
}

View File

@@ -0,0 +1,155 @@
/**
* Provides Java specific classes and predicates for definining flow summaries.
*/
private import java
private import DataFlowPrivate
private import DataFlowUtil
private import FlowSummaryImpl::Private
private import FlowSummaryImpl::Public
private import semmle.code.java.dataflow.ExternalFlow
private module FlowSummaries {
private import semmle.code.java.dataflow.FlowSummary as F
}
/** Holds is `i` is a valid parameter position. */
predicate parameterPosition(int i) { i in [-1 .. any(Parameter p).getPosition()] }
/** Gets the synthesized summary data-flow node for the given values. */
Node summaryNode(SummarizedCallable c, SummaryNodeState state) { result = getSummaryNode(c, state) }
/** Gets the synthesized data-flow call for `receiver`. */
DataFlowCall summaryDataFlowCall(Node receiver) { none() }
/** Gets the type of content `c`. */
DataFlowType getContentType(Content c) { result = c.getType() }
/** Gets the return type of kind `rk` for callable `c`. */
DataFlowType getReturnType(SummarizedCallable c, ReturnKind rk) {
result = getErasedRepr(c.getReturnType()) and
exists(rk)
}
/**
* Gets the type of the `i`th parameter in a synthesized call that targets a
* callback of type `t`.
*/
DataFlowType getCallbackParameterType(DataFlowType t, int i) { none() }
/**
* Gets the return type of kind `rk` in a synthesized call that targets a
* callback of type `t`.
*/
DataFlowType getCallbackReturnType(DataFlowType t, ReturnKind rk) { none() }
/**
* Holds if an external flow summary exists for `c` with input specification
* `input`, output specification `output`, and kind `kind`.
*/
predicate summaryElement(DataFlowCallable c, string input, string output, string kind) {
exists(
string namespace, string type, boolean subtypes, string name, string signature, string ext
|
summaryModel(namespace, type, subtypes, name, signature, ext, input, output, kind) and
c = interpretElement(namespace, type, subtypes, name, signature, ext)
)
}
/** Gets the summary component for specification component `c`, if any. */
bindingset[c]
SummaryComponent interpretComponentSpecific(string c) {
exists(Content content | parseContent(c, content) and result = SummaryComponent::content(content))
}
class SourceOrSinkElement = Top;
/**
* Holds if an external source specification exists for `e` with output specification
* `output` and kind `kind`.
*/
predicate sourceElement(SourceOrSinkElement e, string output, string kind) {
exists(
string namespace, string type, boolean subtypes, string name, string signature, string ext
|
sourceModel(namespace, type, subtypes, name, signature, ext, output, kind) and
e = interpretElement(namespace, type, subtypes, name, signature, ext)
)
}
/**
* Holds if an external sink specification exists for `e` with input specification
* `input` and kind `kind`.
*/
predicate sinkElement(SourceOrSinkElement e, string input, string kind) {
exists(
string namespace, string type, boolean subtypes, string name, string signature, string ext
|
sinkModel(namespace, type, subtypes, name, signature, ext, input, kind) and
e = interpretElement(namespace, type, subtypes, name, signature, ext)
)
}
/** Gets the return kind corresponding to specification `"ReturnValue"`. */
ReturnKind getReturnValueKind() { any() }
private newtype TInterpretNode =
TElement(SourceOrSinkElement n) or
TNode(Node n)
/** An entity used to interpret a source/sink specification. */
class InterpretNode extends TInterpretNode {
/** Gets the element that this node corresponds to, if any. */
SourceOrSinkElement asElement() { this = TElement(result) }
/** Gets the data-flow node that this node corresponds to, if any. */
Node asNode() { this = TNode(result) }
/** Gets the call that this node corresponds to, if any. */
DataFlowCall asCall() { result = this.asElement() }
/** Gets the callable that this node corresponds to, if any. */
DataFlowCallable asCallable() { result = this.asElement() }
/** Gets the target of this call, if any. */
Callable getCallTarget() { result = this.asCall().getCallee().getSourceDeclaration() }
/** Gets a textual representation of this node. */
string toString() {
result = this.asElement().toString()
or
result = this.asNode().toString()
}
/** Gets the location of this node. */
Location getLocation() {
result = this.asElement().getLocation()
or
result = this.asNode().getLocation()
}
}
/** Provides additional sink specification logic required for annotations. */
pragma[inline]
predicate interpretOutputSpecific(string c, InterpretNode mid, InterpretNode node) {
exists(Node n, Top ast |
n = node.asNode() and
ast = mid.asElement()
|
(c = "Parameter" or c = "") and
node.asNode().asParameter() = mid.asElement()
or
c = "" and
n.asExpr().(FieldRead).getField() = ast
)
}
/** Provides additional source specification logic required for annotations. */
pragma[inline]
predicate interpretInputSpecific(string c, InterpretNode mid, InterpretNode n) {
exists(FieldWrite fw |
c = "" and
fw.getField() = mid.asElement() and
n.asNode().asExpr() = fw.getRHS()
)
}

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