Merge pull request #68 from esben-semmle/determinate-1-cfa-type-inference

Approved by xiemaisi
This commit is contained in:
semmle-qlci
2018-08-22 08:02:27 +01:00
committed by GitHub
29 changed files with 678 additions and 103 deletions

View File

@@ -170,6 +170,27 @@ string getTypeDescription(string message1, string message2, int complexity1, int
result = message1
}
/**
* Holds if `e` directly uses a parameter's initial value as passed in from the caller.
*/
predicate isInitialParameterUse(Expr e) {
// unlike `SimpleParameter.getAnInitialUse` this will not include uses we have refinement information for
exists (SimpleParameter p, SsaExplicitDefinition ssa |
ssa.getDef() = p and
ssa.getVariable().getAUse() = e and
not p.isRestParameter()
)
}
/**
* Holds if `e` is an expression that should not be considered in a heterogeneous comparison.
*
* We currently whitelist expressions that rely on inter-procedural parameter information.
*/
predicate whitelist(Expr e) {
isInitialParameterUse(e)
}
from ASTNode cmp,
DataFlow::AnalyzedNode left, DataFlow::AnalyzedNode right,
string leftTypes, string rightTypes,
@@ -177,6 +198,8 @@ from ASTNode cmp,
int leftTypeCount, int rightTypeCount ,
string leftTypeDescription, string rightTypeDescription
where isHeterogeneousComparison(cmp, left, right, leftTypes, rightTypes) and
not whitelist(left.asExpr()) and
not whitelist(right.asExpr()) and
leftExprDescription = capitalize(getDescription(left.asExpr(), "this expression")) and
rightExprDescription = getDescription(right.asExpr(), "an expression") and
leftTypeCount = strictcount(left.getAType()) and

View File

@@ -14,6 +14,7 @@
import javascript
import semmle.javascript.RestrictedLocations
import semmle.javascript.dataflow.Refinements
/**
* Holds if `va` is a defensive truthiness check that may be worth keeping, even if it
@@ -62,19 +63,55 @@ predicate isConstant(Expr e) {
isSymbolicConstant(e.(VarAccess).getVariable())
}
/**
* Holds if `e` directly uses a parameter's negated or initial value as passed in from the caller.
*/
predicate isInitialParameterUse(Expr e) {
// unlike `SimpleParameter.getAnInitialUse` this will not include uses we have refinement information for
exists (SimpleParameter p, SsaExplicitDefinition ssa |
ssa.getDef() = p and
ssa.getVariable().getAUse() = e and
not p.isRestParameter()
)
or
isInitialParameterUse(e.(LogNotExpr).getOperand())
}
/**
* Holds if `e` directly uses the returned value from a function call that returns a constant boolean value.
*/
predicate isConstantBooleanReturnValue(Expr e) {
// unlike `SourceNode.flowsTo` this will not include uses we have refinement information for
exists (DataFlow::CallNode call |
exists (call.analyze().getTheBooleanValue()) |
e = call.asExpr() or
// also support return values that are assigned to variables
exists (SsaExplicitDefinition ssa |
ssa.getDef().getSource() = call.asExpr() and
ssa.getVariable().getAUse() = e
)
)
or
isConstantBooleanReturnValue(e.(LogNotExpr).getOperand())
}
/**
* Holds if `e` is an expression that should not be flagged as a useless condition.
*
* We currently whitelist three kinds of expressions:
* We currently whitelist these kinds of expressions:
*
* - constants (including references to literal constants);
* - negations of constants;
* - defensive checks.
* - defensive checks;
* - expressions that rely on inter-procedural parameter information;
* - constant boolean returned values.
*/
predicate whitelist(Expr e) {
isConstant(e) or
isConstant(e.(LogNotExpr).getOperand()) or
isDefensiveInit(e)
isDefensiveInit(e) or
isInitialParameterUse(e) or
isConstantBooleanReturnValue(e)
}
/**
@@ -107,4 +144,4 @@ where isConditional(cond, op.asExpr()) and
msg = "This expression always evaluates to " + cv + "."
)
select sel, msg
select sel, msg

View File

@@ -239,3 +239,52 @@ class AnalyzedModule extends TopLevel {
)
}
}
/**
* Flow analysis for functions.
*/
class AnalyzedFunction extends DataFlow::AnalyzedValueNode {
override Function astNode;
override AbstractValue getALocalValue() { result = TAbstractFunction(astNode) }
/**
* Gets a return value for a call to this function.
*/
AbstractValue getAReturnValue() {
if astNode.isGenerator() or astNode.isAsync() then
result = TAbstractOtherObject()
else (
// explicit return value
result = astNode.getAReturnedExpr().analyze().getALocalValue()
or
// implicit return value
(
// either because execution of the function may terminate normally
mayReturnImplicitly()
or
// or because there is a bare `return;` statement
exists (ReturnStmt ret | ret = astNode.getAReturnStmt() | not exists(ret.getExpr()))
) and
result = TAbstractUndefined()
)
}
/**
* Holds if the execution of this function may complete normally without
* encountering a `return` or `throw` statement.
*
* Note that this is an overapproximation, that is, the predicate may hold
* of functions that cannot actually complete normally, since it does not
* account for `finally` blocks and does not check reachability.
*/
private predicate mayReturnImplicitly() {
exists (ConcreteControlFlowNode final |
final.getContainer() = astNode and
final.isAFinalNode() and
not final instanceof ReturnStmt and
not final instanceof ThrowStmt
)
}
}

View File

@@ -89,15 +89,6 @@ private class AnalyzedArrayComprehensionExpr extends DataFlow::AnalyzedValueNode
override AbstractValue getALocalValue() { result = TAbstractOtherObject() }
}
/**
* Flow analysis for functions.
*/
private class AnalyzedFunction extends DataFlow::AnalyzedValueNode {
override Function astNode;
override AbstractValue getALocalValue() { result = TAbstractFunction(astNode) }
}
/**
* Flow analysis for class declarations.
*/

View File

@@ -7,69 +7,6 @@
import javascript
import AbstractValuesImpl
/**
* Flow analysis for immediately-invoked function expressions (IIFEs).
*/
private class IifeReturnFlow extends DataFlow::AnalyzedValueNode {
ImmediatelyInvokedFunctionExpr iife;
IifeReturnFlow() {
astNode = (CallExpr)iife.getInvocation()
}
override AbstractValue getALocalValue() {
result = getAReturnValue(iife)
}
}
/**
* Gets a return value for the immediately-invoked function expression `f`.
*/
private AbstractValue getAReturnValue(ImmediatelyInvokedFunctionExpr f) {
// explicit return value
result = f.getAReturnedExpr().analyze().getALocalValue()
or
// implicit return value
(
// either because execution of the function may terminate normally
mayReturnImplicitly(f)
or
// or because there is a bare `return;` statement
exists (ReturnStmt ret | ret = f.getAReturnStmt() | not exists(ret.getExpr()))
) and
result = getDefaultReturnValue(f)
}
/**
* Holds if the execution of function `f` may complete normally without
* encountering a `return` or `throw` statement.
*
* Note that this is an overapproximation, that is, the predicate may hold
* of functions that cannot actually complete normally, since it does not
* account for `finally` blocks and does not check reachability.
*/
private predicate mayReturnImplicitly(Function f) {
exists (ConcreteControlFlowNode final |
final.getContainer() = f and
final.isAFinalNode() and
not final instanceof ReturnStmt and
not final instanceof ThrowStmt
)
}
/**
* Gets the default return value for immediately-invoked function expression `f`,
* that is, the value that `f` returns if its execution terminates without
* encountering an explicit `return` statement.
*/
private AbstractValue getDefaultReturnValue(ImmediatelyInvokedFunctionExpr f) {
if f.isGenerator() or f.isAsync() then
result = TAbstractOtherObject()
else
result = TAbstractUndefined()
}
/**
* Flow analysis for `this` expressions inside functions.
*/
@@ -190,3 +127,79 @@ private class AnalyzedThisInPropertyFunction extends AnalyzedThisExpr {
}
}
/**
* A call with inter-procedural type inference for the return value.
*/
abstract class CallWithAnalyzedReturnFlow extends DataFlow::AnalyzedValueNode {
/**
* Gets a called function.
*/
abstract AnalyzedFunction getACallee();
override AbstractValue getALocalValue() {
result = getACallee().getAReturnValue() and
not this instanceof DataFlow::NewNode
}
}
/**
* Flow analysis for the return value of IIFEs.
*/
private class IIFEWithAnalyzedReturnFlow extends CallWithAnalyzedReturnFlow {
ImmediatelyInvokedFunctionExpr iife;
IIFEWithAnalyzedReturnFlow() {
astNode = iife.getInvocation()
}
override AnalyzedFunction getACallee() {
result = iife.analyze()
}
}
/** A function that only is used locally, making it amenable to type inference. */
class LocalFunction extends Function {
DataFlow::Impl::ExplicitInvokeNode invk;
LocalFunction() {
this instanceof FunctionDeclStmt and
exists (LocalVariable v, Expr callee |
callee = invk.getCalleeNode().asExpr() and
v = getVariable() and
v.getAnAccess() = callee and
forall(VarAccess o | o = v.getAnAccess() | o = callee) and
not exists(v.getAnAssignedExpr()) and
not exists(ExportDeclaration export | export.exportsAs(v, _))
) and
// if the function is non-strict and its `arguments` object is accessed, we
// also assume that there may be other calls (through `arguments.callee`)
(isStrict() or not usesArgumentsObject())
}
/** Gets an invocation of this function. */
DataFlow::InvokeNode getAnInvocation() {
result = invk
}
}
/**
* Enables inter-procedural type inference for a call to a `LocalFunction`.
*/
private class LocalFunctionCallWithAnalyzedReturnFlow extends CallWithAnalyzedReturnFlow {
LocalFunction f;
LocalFunctionCallWithAnalyzedReturnFlow() {
this = f.getAnInvocation()
}
override AnalyzedFunction getACallee() {
result = f.analyze()
}
}

View File

@@ -112,47 +112,43 @@ class AnalyzedVarDef extends VarDef {
}
/**
* Flow analysis for simple IIFE parameters.
* Flow analysis for simple parameters of selected functions.
*/
private class AnalyzedIIFEParameter extends AnalyzedVarDef, @vardecl {
AnalyzedIIFEParameter() {
exists (ImmediatelyInvokedFunctionExpr iife, int parmIdx |
this = iife.getParameter(parmIdx) |
// we cannot track flow into rest parameters...
not this.(Parameter).isRestParameter() and
// ...nor flow out of spread arguments
exists (int argIdx | argIdx = parmIdx + iife.getArgumentOffset() |
not iife.isSpreadArgument([0..argIdx])
)
private class AnalyzedParameter extends AnalyzedVarDef, @vardecl {
AnalyzedParameter() {
exists (FunctionWithAnalyzedParameters f, int parmIdx |
this = f.getParameter(parmIdx) |
// we cannot track flow into rest parameters
not this.(Parameter).isRestParameter()
)
}
/** Gets the IIFE this is a parameter of. */
ImmediatelyInvokedFunctionExpr getIIFE() {
/** Gets the function this is a parameter of. */
FunctionWithAnalyzedParameters getFunction() {
this = result.getAParameter()
}
override DataFlow::AnalyzedNode getRhs() {
getIIFE().argumentPassing(this, result.asExpr()) or
getFunction().argumentPassing(this, result.asExpr()) or
result = this.(Parameter).getDefault().analyze()
}
override AbstractValue getAnRhsValue() {
result = AnalyzedVarDef.super.getAnRhsValue() or
not getIIFE().argumentPassing(this, _) and result = TAbstractUndefined()
result = AnalyzedVarDef.super.getAnRhsValue()
or
not getFunction().mayReceiveArgument(this) and
result = TAbstractUndefined()
}
override predicate isIncomplete(DataFlow::Incompleteness cause) {
exists (ImmediatelyInvokedFunctionExpr iife | iife = getIIFE() |
// if the IIFE has a name and that name is referenced, we conservatively
// assume that there may be other calls than the direct one
exists (iife.getVariable().getAnAccess()) and cause = "call" or
// if the IIFE is non-strict and its `arguments` object is accessed, we
// also assume that there may be other calls (through `arguments.callee`)
not iife.isStrict() and
exists (iife.getArgumentsVariable().getAnAccess()) and cause = "call"
getFunction().isIncomplete(cause) or
(
not getFunction().argumentPassing(this, _) and
getFunction().mayReceiveArgument(this) and
cause = "call"
)
}
}
/**
@@ -650,3 +646,103 @@ private class NamespaceExportVarFlow extends DataFlow::AnalyzedValueNode {
override AbstractValue getALocalValue() { result = TIndefiniteAbstractValue("namespace") }
}
/**
* A function with inter-procedural type inference for its parameters.
*/
abstract class FunctionWithAnalyzedParameters extends Function {
/**
* Holds if `p` is a parameter of this function and `arg` is
* the corresponding argument.
*/
abstract predicate argumentPassing(SimpleParameter p, Expr arg);
/**
* Holds if `p` is a parameter of this function that may receive a value from an argument.
*/
abstract predicate mayReceiveArgument(Parameter p);
/**
* Holds if flow analysis results for the parameters may be incomplete
* due to the given `cause`.
*/
abstract predicate isIncomplete(DataFlow::Incompleteness cause);
}
private abstract class CallWithAnalyzedParameters extends FunctionWithAnalyzedParameters {
/**
* Gets an invocation of this function.
*/
abstract DataFlow::InvokeNode getAnInvocation();
override predicate argumentPassing(SimpleParameter p, Expr arg) {
exists (DataFlow::InvokeNode invk, int argIdx |
invk = getAnInvocation() |
p = getParameter(argIdx) and not p.isRestParameter() and
arg = invk.getArgument(argIdx).asExpr()
)
}
override predicate mayReceiveArgument(Parameter p) {
exists (DataFlow::InvokeNode invk, int argIdx |
invk = getAnInvocation() and
p = getParameter(argIdx) |
exists (invk.getArgument(argIdx))
or
invk.asExpr().(InvokeExpr).isSpreadArgument([0..argIdx])
)
}
}
/**
* Flow analysis for simple parameters of IIFEs.
*/
private class IIFEWithAnalyzedParameters extends CallWithAnalyzedParameters {
ImmediatelyInvokedFunctionExpr iife;
IIFEWithAnalyzedParameters() {
this = iife and
iife.getInvocationKind() = "direct"
}
override DataFlow::InvokeNode getAnInvocation() {
result = iife.getInvocation().flow()
}
override predicate isIncomplete(DataFlow::Incompleteness cause) {
// if the IIFE has a name and that name is referenced, we conservatively
// assume that there may be other calls than the direct one
exists (iife.getVariable().getAnAccess()) and cause = "call" or
// if the IIFE is non-strict and its `arguments` object is accessed, we
// also assume that there may be other calls (through `arguments.callee`)
not iife.isStrict() and
exists (iife.getArgumentsVariable().getAnAccess()) and cause = "call"
}
}
/**
* Enables inter-procedural type inference for `LocalFunction`.
*/
private class LocalFunctionWithAnalyzedParameters extends CallWithAnalyzedParameters {
LocalFunction local;
LocalFunctionWithAnalyzedParameters() {
this = local
}
override DataFlow::InvokeNode getAnInvocation() {
result = local.getAnInvocation()
}
override predicate isIncomplete(DataFlow::Incompleteness cause) {
none()
}
}