Skip to content

Java: Add proper support for variable capture flow. #13478

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions java/ql/lib/change-notes/2023-06-16-initial-version.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: majorAnalysis
---
* Improved support for flow through captured variables that properly adheres to inter-procedural control flow.
1 change: 0 additions & 1 deletion java/ql/lib/ext/java.lang.model.yml
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,6 @@ extensions:
- ["java.lang", "Object", "getClass", "()", "summary", "manual"]
- ["java.lang", "Object", "hashCode", "()", "summary", "manual"]
- ["java.lang", "Object", "toString", "()", "summary", "manual"]
- ["java.lang", "Runnable", "run", "()", "summary", "manual"]
- ["java.lang", "Runtime", "getRuntime", "()", "summary", "manual"]
- ["java.lang", "String", "compareTo", "(String)", "summary", "manual"]
- ["java.lang", "String", "contains", "(CharSequence)", "summary", "manual"]
Expand Down
3 changes: 3 additions & 0 deletions java/ql/lib/semmle/code/java/dataflow/FlowSummary.qll
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ abstract class SyntheticCallable extends string {
* A module for importing frameworks that define synthetic callables.
*/
private module SyntheticCallables {
private import semmle.code.java.dispatch.WrappedInvocation
private import semmle.code.java.frameworks.android.Intent
private import semmle.code.java.frameworks.Stream
}
Expand Down Expand Up @@ -170,6 +171,8 @@ class SummarizedCallableBase extends TSummarizedCallableBase {
}
}

class Provenance = Impl::Public::Provenance;

class SummarizedCallable = Impl::Public::SummarizedCallable;

class NeutralCallable = Impl::Public::NeutralCallable;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ private module Cached {
)
} or
TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) or
TFieldValueNode(Field f)
TFieldValueNode(Field f) or
TCaptureNode(CaptureFlow::SynthesizedCaptureNode cn)

cached
newtype TContent =
Expand All @@ -64,6 +65,7 @@ private module Cached {
TCollectionContent() or
TMapKeyContent() or
TMapValueContent() or
TCapturedVariableContent(CapturedVariable v) or
TSyntheticFieldContent(SyntheticField s)

cached
Expand All @@ -73,6 +75,7 @@ private module Cached {
TCollectionContentApprox() or
TMapKeyContentApprox() or
TMapValueContentApprox() or
TCapturedVariableContentApprox(CapturedVariable v) or
TSyntheticFieldApproxContent()
}

Expand Down Expand Up @@ -127,6 +130,8 @@ module Public {
or
result = this.(ImplicitPostUpdateNode).getPreUpdateNode().getType()
or
result = this.(CaptureNode).getTypeImpl()
or
result = this.(FieldValueNode).getField().getType()
}

Expand Down Expand Up @@ -372,6 +377,7 @@ module Private {
result.asCallable() = n.(MallocNode).getClassInstanceExpr().getEnclosingCallable() or
result = nodeGetEnclosingCallable(n.(ImplicitPostUpdateNode).getPreUpdateNode()) or
result.asSummarizedCallable() = n.(FlowSummaryNode).getSummarizedCallable() or
result.asCallable() = n.(CaptureNode).getSynthesizedCaptureNode().getEnclosingCallable() or
result.asFieldScope() = n.(FieldValueNode).getField()
}

Expand Down Expand Up @@ -491,6 +497,28 @@ module Private {
c.asSummarizedCallable() = this.getSummarizedCallable() and pos = this.getPosition()
}
}

/**
* A synthesized data flow node representing a closure object that tracks
* captured variables.
*/
class CaptureNode extends Node, TCaptureNode {
private CaptureFlow::SynthesizedCaptureNode cn;

CaptureNode() { this = TCaptureNode(cn) }

CaptureFlow::SynthesizedCaptureNode getSynthesizedCaptureNode() { result = cn }

override Location getLocation() { result = cn.getLocation() }

override string toString() { result = cn.toString() }

Type getTypeImpl() {
exists(Variable v | cn.isVariableAccess(v) and result = v.getType())
or
cn.isInstanceAccess() and result = cn.getEnclosingCallable().getDeclaringType()
}
}
}

private import Private
Expand Down Expand Up @@ -520,3 +548,14 @@ private class SummaryPostUpdateNode extends FlowSummaryNode, PostUpdateNode {

override Node getPreUpdateNode() { result = pre }
}

private class CapturePostUpdateNode extends PostUpdateNode, CaptureNode {
private CaptureNode pre;

CapturePostUpdateNode() {
CaptureFlow::capturePostUpdateNode(this.getSynthesizedCaptureNode(),
pre.getSynthesizedCaptureNode())
}

override Node getPreUpdateNode() { result = pre }
}
164 changes: 143 additions & 21 deletions java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ private import semmle.code.java.dataflow.FlowSummary
private import FlowSummaryImpl as FlowSummaryImpl
private import DataFlowImplConsistency
private import DataFlowNodes
private import codeql.dataflow.VariableCapture as VariableCapture
import DataFlowNodes::Private

private newtype TReturnKind = TNormalReturnKind()
Expand Down Expand Up @@ -51,37 +52,138 @@ private predicate fieldStep(Node node1, Node node2) {
)
}

/**
* 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()
)
private predicate closureFlowStep(Expr e1, Expr e2) {
simpleAstFlowStep(e1, e2)
or
exists(SsaVariable v |
v.getAUse() = e2 and
v.getAnUltimateDefinition().(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource() =
e1
)
}

private module CaptureInput implements VariableCapture::InputSig {
private import java as J

class Location = J::Location;

class BasicBlock instanceof J::BasicBlock {
string toString() { result = super.toString() }

Callable getEnclosingCallable() { result = super.getEnclosingCallable() }

Location getLocation() { result = super.getLocation() }
}

BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { bbIDominates(result, bb) }

BasicBlock getABasicBlockSuccessor(BasicBlock bb) {
result = bb.(J::BasicBlock).getABBSuccessor()
}

//TODO: support capture of `this` in lambdas
class CapturedVariable instanceof LocalScopeVariable {
CapturedVariable() {
2 <=
strictcount(J::Callable c |
c = this.getCallable() or c = this.getAnAccess().getEnclosingCallable()
)
}

string toString() { result = super.toString() }

Callable getCallable() { result = super.getCallable() }

Location getLocation() { result = super.getLocation() }
}

class CapturedParameter extends CapturedVariable instanceof Parameter { }

class Expr instanceof J::Expr {
string toString() { result = super.toString() }

Location getLocation() { result = super.getLocation() }

predicate hasCfgNode(BasicBlock bb, int i) { this = bb.(J::BasicBlock).getNode(i) }
}

class VariableWrite extends Expr instanceof VariableUpdate {
CapturedVariable v;

VariableWrite() { super.getDestVar() = v }

CapturedVariable getVariable() { result = v }

Expr getSource() {
result = this.(VariableAssign).getSource() or
result = this.(AssignOp)
}
}

class VariableRead extends Expr instanceof RValue {
CapturedVariable v;

VariableRead() { super.getVariable() = v }

CapturedVariable getVariable() { result = v }
}

class ClosureExpr extends Expr instanceof ClassInstanceExpr {
NestedClass nc;

ClosureExpr() {
nc.(AnonymousClass).getClassInstanceExpr() = this
or
nc instanceof LocalClass and
super.getConstructedType().getASourceSupertype*().getSourceDeclaration() = nc
}

predicate hasBody(Callable body) { nc.getACallable() = body }

predicate hasAliasedAccess(Expr f) { closureFlowStep+(this, f) and not closureFlowStep(f, _) }
}

class Callable extends J::Callable {
predicate isConstructor() { this instanceof Constructor }
}
}

class CapturedVariable = CaptureInput::CapturedVariable;

class CapturedParameter = CaptureInput::CapturedParameter;

module CaptureFlow = VariableCapture::Flow<CaptureInput>;

private CaptureFlow::ClosureNode asClosureNode(Node n) {
result = n.(CaptureNode).getSynthesizedCaptureNode() or
result.(CaptureFlow::ExprNode).getExpr() = n.asExpr() or
result.(CaptureFlow::ExprPostUpdateNode).getExpr() =
n.(PostUpdateNode).getPreUpdateNode().asExpr() or
result.(CaptureFlow::ParameterNode).getParameter() = n.asParameter() or
result.(CaptureFlow::ThisParameterNode).getCallable() = n.(InstanceParameterNode).getCallable() or
exprNode(result.(CaptureFlow::MallocNode).getClosureExpr()).(PostUpdateNode).getPreUpdateNode() =
n
}

private predicate captureStoreStep(Node node1, CapturedVariableContent c, Node node2) {
CaptureFlow::storeStep(asClosureNode(node1), c.getVariable(), asClosureNode(node2))
}

private predicate captureReadStep(Node node1, CapturedVariableContent c, Node node2) {
CaptureFlow::readStep(asClosureNode(node1), c.getVariable(), asClosureNode(node2))
}

predicate captureValueStep(Node node1, Node node2) {
CaptureFlow::localFlowStep(asClosureNode(node1), asClosureNode(node2))
}

/**
* Holds if data can flow from `node1` to `node2` through a field or
* variable capture.
*/
predicate jumpStep(Node node1, Node node2) {
fieldStep(node1, node2)
or
variableCaptureStep(node1, node2)
or
variableCaptureStep(node1.(PostUpdateNode).getPreUpdateNode(), node2)
or
any(AdditionalValueStep a).step(node1, node2) and
node1.getEnclosingCallable() != node2.getEnclosingCallable()
or
Expand Down Expand Up @@ -117,6 +219,8 @@ predicate storeStep(Node node1, ContentSet f, Node node2) {
or
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1.(FlowSummaryNode).getSummaryNode(), f,
node2.(FlowSummaryNode).getSummaryNode())
or
captureStoreStep(node1, f, node2)
}

/**
Expand Down Expand Up @@ -149,6 +253,8 @@ predicate readStep(Node node1, ContentSet f, Node node2) {
or
FlowSummaryImpl::Private::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(), f,
node2.(FlowSummaryNode).getSummaryNode())
or
captureReadStep(node1, f, node2)
}

/**
Expand Down Expand Up @@ -231,19 +337,29 @@ private newtype TDataFlowCallable =
TSummarizedCallable(SummarizedCallable c) or
TFieldScope(Field f)

/**
* A callable or scope enclosing some number of data flow nodes. This can either
* be a source callable, a synthesized callable for which we have a summary
* model, or a synthetic scope for a field value node.
*/
class DataFlowCallable extends TDataFlowCallable {
/** Gets the source callable corresponding to this callable, if any. */
Callable asCallable() { this = TSrcCallable(result) }

/** Gets the summary model callable corresponding to this callable, if any. */
SummarizedCallable asSummarizedCallable() { this = TSummarizedCallable(result) }

/** Gets the field corresponding to this callable, if it is a field value scope. */
Field asFieldScope() { this = TFieldScope(result) }

/** Gets a textual representation of this callable. */
string toString() {
result = this.asCallable().toString() or
result = "Synthetic: " + this.asSummarizedCallable().toString() or
result = "Field scope: " + this.asFieldScope().toString()
}

/** Gets the location of this callable. */
Location getLocation() {
result = this.asCallable().getLocation() or
result = this.asSummarizedCallable().getLocation() or
Expand Down Expand Up @@ -406,6 +522,8 @@ predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preserves
*/
predicate allowParameterReturnInSelf(ParameterNode p) {
FlowSummaryImpl::Private::summaryAllowParameterReturnInSelf(p)
or
CaptureFlow::heuristicAllowInstanceParameterReturnInSelf(p.(InstanceParameterNode).getCallable())
}

/** An approximated `Content`. */
Expand Down Expand Up @@ -447,6 +565,10 @@ ContentApprox getContentApprox(Content c) {
or
c instanceof MapValueContent and result = TMapValueContentApprox()
or
exists(CapturedVariable v |
c = TCapturedVariableContent(v) and result = TCapturedVariableContentApprox(v)
)
or
c instanceof SyntheticFieldContent and result = TSyntheticFieldApproxContent()
}

Expand Down
Loading