Repository navigation
Unified: captured variables #22726
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
Unified: captured variables #22726
Changes from all commits
593e17a
d08e0b9
2f46c31
6d4e275
d76cdb3
607010a
f736b03
ca6a6a8
058d767
8e5b2c6
0ea037b
0e84147
bd1c743
c26ebf9
f3be2ce
c8ae4d3
b0c2d54
7e6bd55
d7130c2
6e0a3b7
7762b17
343fe76
68d7e3e
3321c32
7b462e4
f30f6c9
6533df1
4afec0c
d0a38cb
7f9ebee
f3afa24
d82fc83
f50c74c
1b94078
1ab8fcd
cd6386a
31488c0
4be4ff5
48e3e11
10853e6
f666d7e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| private import unified | ||
| private import codeql.unified.internal.dataflow.CaptureSsa | ||
| import CaptureSsaOutput::ConsistencyChecks |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,180 @@ | ||
| /** | ||
| * SSA for captured variables. | ||
| */ | ||
|
|
||
| private import unified | ||
| private import unified as U | ||
| private import AllDataFlow | ||
| private import codeql.dataflow.VariableCapture | ||
| private import codeql.unified.internal.ControlFlowGraph | ||
| private import codeql.unified.internal.ExprPositions | ||
|
|
||
| module CaptureSsaInput implements InputSig<Location, BasicBlock> { | ||
| final private class FinalCallable = U::Callable; | ||
|
|
||
| class Callable extends FinalCallable { | ||
| Callable() { this.fromSource() } | ||
|
|
||
| predicate isConstructor() { none() } // TODO | ||
| } | ||
|
|
||
| Callable basicBlockGetEnclosingCallable(BasicBlock bb) { result = bb.getEnclosingCallable() } | ||
|
|
||
| class CapturedVariable extends LocalVariable { | ||
| CapturedVariable() { this.isCaptured() } | ||
|
|
||
| Callable getCallable() { result = super.getDeclaringCallable() } | ||
| } | ||
|
|
||
| class CapturedParameter extends CapturedVariable { | ||
| // This class should be empty, as the capture library never gets to work with a node that corresponds to a `DataFlowInput::ParameterNode`. | ||
| // | ||
| // When a variable is mentioned in a pattern in the parameter list, the pattern itself (TValueNode) becomes the ParameterNode | ||
| // and the assignment to the variable is modeled as an ordinary variable-assignment at the point where the variable mention appears in the CFG. | ||
| // Likewise, the receiver is represented by TImplicitParameterNode and its assignment to 'self/this' is modeled as an ordinary assignment. | ||
| CapturedParameter() { none() } | ||
| } | ||
|
|
||
| private class TExpr = TCallableNode or TLocalVariableRefNode; | ||
|
|
||
| private predicate isTopLevelCallable(BuilderNode callable) { | ||
| callable.isCallable(any(TopLevel t)) | ||
| } | ||
|
|
||
| class Expr extends TExpr { | ||
| Expr() { not isTopLevelCallable(this) } | ||
|
|
||
| string toString() { result = this.(BuilderNode).toString() } | ||
|
|
||
| Location getLocation() { result = this.(BuilderNode).getLocation() } | ||
|
|
||
| predicate hasCfgNode(BasicBlock bb, int i) { | ||
| this.(LocalVariableRefNode).hasCfgNode(bb, i) | ||
| or | ||
| exists(DataFlowCallable callable | | ||
| this = TCallableNode(callable) and | ||
| bb.getNode(i).injects(callable.asSourceCallable()) | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| final private class FinalLocalVariableRefNode = LocalVariableRefNode; | ||
|
|
||
| class VariableWrite extends FinalLocalVariableRefNode { | ||
| VariableWrite() { | ||
| this.getRefKind().isWrite() and super.getVariable() instanceof CapturedVariable | ||
| } | ||
|
|
||
| CapturedVariable getVariable() { result = super.getVariable() } | ||
| } | ||
|
|
||
| class VariableRead extends Expr instanceof LocalVariableRefNode { | ||
| VariableRead() { | ||
| super.getVariable() instanceof CapturedVariable and | ||
| super.getRefKind().isRead() | ||
| } | ||
|
|
||
| CapturedVariable getVariable() { result = super.getVariable() } | ||
| } | ||
|
|
||
| /** | ||
| * Holds if `node` is a possible alias for `callable`. | ||
| */ | ||
| private predicate callableHasLocalAlias(Callable callable, Stage2Node node) { | ||
| node.(BuilderNode).isCallable(callable) | ||
| or | ||
| exists(Stage2Node prev | callableHasLocalAlias(callable, prev) | | ||
| step(prev, any(Step s | s.value()), node) | ||
| or | ||
| localSsaStep(prev, node, _) | ||
| ) | ||
| or | ||
| exists(CapturedVariable var | | ||
| callableHasLocalAliasVar(callable, var) and | ||
| node.(BuilderNode).isLocalVariableRead(_, var) | ||
| ) | ||
| } | ||
|
|
||
| pragma[nomagic] | ||
| private predicate callableHasLocalAliasVar(Callable callable, CapturedVariable var) { | ||
| exists(BuilderNode ref | | ||
| callableHasLocalAlias(callable, ref) and | ||
| ref.isLocalVariableWrite(_, var) | ||
| ) | ||
| } | ||
|
|
||
| class ClosureExpr extends Expr instanceof TCallableNode { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This equates closure expressions and callables. I'm not sure if this happens in Swift, but that won't work in general. Some languages allow the construction of closure objects with multiple methods, e.g. anonymous class instances in Java (of which a Java lambda is but one example).
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A local class definition is another example where capture might happen in an object with multiple methods (again, I'm not sure which languages this applies to, but I know it's valid Java).
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As discussed offline, I'll add a TODO. Swift local classes can't capture variables so we can't exercise this feature with Swift. |
||
| // TODO: In order to support classes with captures, this needs to cover constructor calls to such classes as well. | ||
| private Callable callable; | ||
|
|
||
| ClosureExpr() { this.(BuilderNode).isCallable(callable) } | ||
|
|
||
| predicate hasBody(Callable body) { callable = body } | ||
|
|
||
| predicate hasAliasedAccess(Expr f) { | ||
| // TODO: Restrict to places where the function is called or escapes into a call where it might get called. | ||
| callableHasLocalAlias(callable, f) and not f.(LocalVariableRefNode).getRefKind().isWrite() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This relation was not meant to be reflexive. Also, it was not meant to include all the aliases - just the ones that might escape or represent an invocation.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll leave a TODO for now. It's easy to accidentally make something too under-approximate and not notice because FNs are hard to detect. I want to over-approximate this first and defer escape analysis until we are in a better position to measure its correctness at scale. |
||
| } | ||
| } | ||
| } | ||
|
|
||
| module CaptureSsaOutput = Flow<Location, Cfg, CaptureSsaInput>; | ||
|
|
||
| Node getNodeFromCaptureSsaNode(CaptureSsaOutput::ClosureNode n) { | ||
| result = TCaptureSsaNode(n) | ||
| or | ||
| result = n.(CaptureSsaOutput::ExprNode).getExpr() | ||
| or | ||
| result = n.(CaptureSsaOutput::ExprPostUpdateNode).getExpr().(BuilderNode).getPostUpdateNode() | ||
| or | ||
| result = n.(CaptureSsaOutput::VariableWriteSourceNode).getVariableWrite() | ||
| or | ||
| // NOTE: This only supports lambdas at the moment. Local classes in Swift cannot capture variables. | ||
| result = n.(CaptureSsaOutput::MallocNode).getClosureExpr() | ||
| or | ||
| exists(CaptureSsaOutput::ThisParameterNode thisParam, Callable callable | | ||
| n = thisParam and | ||
| callable = thisParam.getCallable() and | ||
| result | ||
| .(BuilderNode) | ||
| .isImplicitParameter(toDataFlowCallable(callable), any(ParameterPosition p | p.isCallee())) | ||
| ) | ||
| } | ||
|
|
||
| CaptureSsaOutput::ClosureNode getCaptureSsaNodeFromNode(Node n) { | ||
| n = getNodeFromCaptureSsaNode(result) | ||
| } | ||
|
|
||
| predicate captureSsaLocalFlowStep(Node node1, Node node2) { | ||
| CaptureSsaOutput::localFlowStep(getCaptureSsaNodeFromNode(node1), getCaptureSsaNodeFromNode(node2)) | ||
| } | ||
|
|
||
| predicate captureSsaStoreStep(Node node1, ContentSet contents, Node node2) { | ||
| CaptureSsaOutput::storeStep(getCaptureSsaNodeFromNode(node1), | ||
| contents.asSingleton().asCapturedVariable(), getCaptureSsaNodeFromNode(node2)) | ||
| } | ||
|
|
||
| predicate captureSsaReadStep(Node node1, ContentSet contents, Node node2) { | ||
| CaptureSsaOutput::readStep(getCaptureSsaNodeFromNode(node1), | ||
| contents.asSingleton().asCapturedVariable(), getCaptureSsaNodeFromNode(node2)) | ||
| } | ||
|
|
||
| predicate captureSsaClearsContent(Node node, ContentSet contents) { | ||
| CaptureSsaOutput::clearsContent(getCaptureSsaNodeFromNode(node), | ||
| contents.asSingleton().asCapturedVariable()) | ||
| } | ||
|
|
||
| Node getCaptureSsaPostUpdate(Node pre) { | ||
| CaptureSsaOutput::capturePostUpdateNode(getCaptureSsaNodeFromNode(result), | ||
| getCaptureSsaNodeFromNode(pre)) | ||
| } | ||
|
|
||
| predicate captureSsaAllowParameterReturnInSelf(Node param) { | ||
| exists(Callable callable | | ||
| CaptureSsaOutput::heuristicAllowInstanceParameterReturnInSelf(callable) and | ||
| param = | ||
| getNodeFromCaptureSsaNode(any(CaptureSsaOutput::ThisParameterNode n | | ||
| n.getCallable() = callable | ||
| )) | ||
| ) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could use unwrap_or_else.