-
Notifications
You must be signed in to change notification settings - Fork 1.8k
C++: Post-update flow through &, *, +, ... #3389
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
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
4ddf121
C++: Don't suppress consistency checks for calls
jbj 5f74c24
C++: Test definitions through &, *, ...
jbj 1b1095e
C++: Post-update flow through &, *, +, ...
jbj 32e04b4
C++: Support std::addressof
jbj 5e8bd0a
C++: Fix variable name in comment
jbj 88eeca3
Merge commit '52d8acc1a198c5ea29c1dddceda1d6c0fb75de14' into dataflow…
jbj 4b9a3f1
Merge remote-tracking branch 'upstream/master' into dataflow-defbyref…
jbj 71c21e6
C++: Accept test changes forgotten in 32e04b403
jbj File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
247 changes: 247 additions & 0 deletions
247
cpp/ql/src/semmle/code/cpp/dataflow/internal/AddressFlow.qll
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,247 @@ | ||
/** | ||
* Provides a local analysis for identifying where a variable address | ||
* is effectively taken. Array-like offsets are allowed to pass through but | ||
* not field-like offsets. | ||
* | ||
* This library is specialized to meet the needs of `FlowVar.qll`. | ||
*/ | ||
|
||
/* | ||
* Maintainer note: this file is one of several files that are similar but not | ||
* identical. Many changes to this file will also apply to the others: | ||
* - AddressConstantExpression.qll | ||
* - AddressFlow.qll | ||
* - EscapesTree.qll | ||
*/ | ||
|
||
private import cpp | ||
|
||
/** | ||
* Holds if `f` is an instantiation of the `std::move` or `std::forward` | ||
* template functions, these functions are essentially casts, so we treat them | ||
* as such. | ||
*/ | ||
private predicate stdIdentityFunction(Function f) { f.hasQualifiedName("std", ["move", "forward"]) } | ||
|
||
/** | ||
* Holds if `f` is an instantiation of `std::addressof`, which effectively | ||
* converts a reference to a pointer. | ||
*/ | ||
private predicate stdAddressOf(Function f) { f.hasQualifiedName("std", "addressof") } | ||
|
||
private predicate lvalueToLvalueStep(Expr lvalueIn, Expr lvalueOut) { | ||
lvalueIn.getConversion() = lvalueOut.(ParenthesisExpr) | ||
or | ||
// When an object is implicitly converted to a reference to one of its base | ||
// classes, it gets two `Conversion`s: there is first an implicit | ||
// `CStyleCast` to its base class followed by a `ReferenceToExpr` to a | ||
// reference to its base class. Whereas an explicit cast to the base class | ||
// would produce an rvalue, which would not be convertible to an lvalue | ||
// reference, this implicit cast instead produces an lvalue. The following | ||
// case ensures that we propagate the property of being an lvalue through | ||
// such casts. | ||
lvalueIn.getConversion() = lvalueOut and | ||
lvalueOut.(CStyleCast).isImplicit() | ||
or | ||
// C++ only | ||
lvalueIn = lvalueOut.(PrefixCrementOperation).getOperand().getFullyConverted() | ||
or | ||
// C++ only | ||
lvalueIn = lvalueOut.(Assignment).getLValue().getFullyConverted() | ||
} | ||
|
||
private predicate pointerToLvalueStep(Expr pointerIn, Expr lvalueOut) { | ||
pointerIn = lvalueOut.(ArrayExpr).getArrayBase().getFullyConverted() | ||
or | ||
pointerIn = lvalueOut.(PointerDereferenceExpr).getOperand().getFullyConverted() | ||
} | ||
|
||
private predicate lvalueToPointerStep(Expr lvalueIn, Expr pointerOut) { | ||
lvalueIn.getConversion() = pointerOut.(ArrayToPointerConversion) | ||
or | ||
lvalueIn = pointerOut.(AddressOfExpr).getOperand().getFullyConverted() | ||
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. Would it make sense to add 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. Done. It turned out to be a bit more involved than adding it to this list. |
||
} | ||
|
||
private predicate pointerToPointerStep(Expr pointerIn, Expr pointerOut) { | ||
( | ||
pointerOut instanceof PointerAddExpr | ||
or | ||
pointerOut instanceof PointerSubExpr | ||
) and | ||
pointerIn = pointerOut.getAChild().getFullyConverted() and | ||
pointerIn.getUnspecifiedType() instanceof PointerType | ||
or | ||
pointerIn = pointerOut.(UnaryPlusExpr).getOperand().getFullyConverted() | ||
or | ||
pointerIn.getConversion() = pointerOut.(Cast) | ||
or | ||
pointerIn.getConversion() = pointerOut.(ParenthesisExpr) | ||
or | ||
pointerIn = pointerOut.(ConditionalExpr).getThen().getFullyConverted() | ||
or | ||
pointerIn = pointerOut.(ConditionalExpr).getElse().getFullyConverted() | ||
or | ||
pointerIn = pointerOut.(CommaExpr).getRightOperand().getFullyConverted() | ||
or | ||
pointerIn = pointerOut.(StmtExpr).getResultExpr().getFullyConverted() | ||
} | ||
|
||
private predicate lvalueToReferenceStep(Expr lvalueIn, Expr referenceOut) { | ||
lvalueIn.getConversion() = referenceOut.(ReferenceToExpr) | ||
} | ||
|
||
private predicate referenceToLvalueStep(Expr referenceIn, Expr lvalueOut) { | ||
referenceIn.getConversion() = lvalueOut.(ReferenceDereferenceExpr) | ||
} | ||
|
||
private predicate referenceToPointerStep(Expr referenceIn, Expr pointerOut) { | ||
pointerOut = | ||
any(FunctionCall call | | ||
stdAddressOf(call.getTarget()) and | ||
referenceIn = call.getArgument(0).getFullyConverted() | ||
) | ||
} | ||
|
||
private predicate referenceToReferenceStep(Expr referenceIn, Expr referenceOut) { | ||
referenceOut = | ||
any(FunctionCall call | | ||
stdIdentityFunction(call.getTarget()) and | ||
referenceIn = call.getArgument(0).getFullyConverted() | ||
) | ||
or | ||
referenceIn.getConversion() = referenceOut.(Cast) | ||
or | ||
referenceIn.getConversion() = referenceOut.(ParenthesisExpr) | ||
} | ||
|
||
private predicate assignmentTo(Expr updated, ControlFlowNode node) { | ||
updated = node.(Assignment).getLValue().getFullyConverted() | ||
or | ||
updated = node.(CrementOperation).getOperand().getFullyConverted() | ||
} | ||
|
||
private predicate lvalueToUpdate(Expr lvalue, Expr outer, ControlFlowNode node) { | ||
( | ||
exists(Call call | node = call | | ||
outer = call.getQualifier().getFullyConverted() and | ||
outer.getUnspecifiedType() instanceof Class and | ||
not call.getTarget().hasSpecifier("const") | ||
) | ||
or | ||
assignmentTo(outer, node) | ||
or | ||
exists(DotFieldAccess fa | | ||
// `fa.otherField = ...` or `f(&fa)` or similar | ||
outer = fa.getQualifier().getFullyConverted() and | ||
valueToUpdate(fa, _, node) | ||
) | ||
) and | ||
lvalue = outer | ||
or | ||
exists(Expr lvalueMid | | ||
lvalueToLvalueStep(lvalue, lvalueMid) and | ||
lvalueToUpdate(lvalueMid, outer, node) | ||
) | ||
or | ||
exists(Expr pointerMid | | ||
lvalueToPointerStep(lvalue, pointerMid) and | ||
pointerToUpdate(pointerMid, outer, node) | ||
) | ||
or | ||
exists(Expr referenceMid | | ||
lvalueToReferenceStep(lvalue, referenceMid) and | ||
referenceToUpdate(referenceMid, outer, node) | ||
) | ||
} | ||
|
||
private predicate pointerToUpdate(Expr pointer, Expr outer, ControlFlowNode node) { | ||
( | ||
exists(Call call | node = call | | ||
outer = call.getAnArgument().getFullyConverted() and | ||
exists(PointerType pt | pt = outer.getType().stripTopLevelSpecifiers() | | ||
not pt.getBaseType().isConst() | ||
) | ||
or | ||
outer = call.getQualifier().getFullyConverted() and | ||
outer.getUnspecifiedType() instanceof PointerType and | ||
not call.getTarget().hasSpecifier("const") | ||
) | ||
or | ||
exists(PointerFieldAccess fa | | ||
// `fa.otherField = ...` or `f(&fa)` or similar | ||
outer = fa.getQualifier().getFullyConverted() and | ||
valueToUpdate(fa, _, node) | ||
) | ||
) and | ||
pointer = outer | ||
or | ||
exists(Expr lvalueMid | | ||
pointerToLvalueStep(pointer, lvalueMid) and | ||
lvalueToUpdate(lvalueMid, outer, node) | ||
) | ||
or | ||
exists(Expr pointerMid | | ||
pointerToPointerStep(pointer, pointerMid) and | ||
pointerToUpdate(pointerMid, outer, node) | ||
) | ||
} | ||
|
||
private predicate referenceToUpdate(Expr reference, Expr outer, ControlFlowNode node) { | ||
exists(Call call | | ||
node = call and | ||
outer = call.getAnArgument().getFullyConverted() and | ||
not stdIdentityFunction(call.getTarget()) and | ||
not stdAddressOf(call.getTarget()) and | ||
exists(ReferenceType rt | rt = outer.getType().stripTopLevelSpecifiers() | | ||
not rt.getBaseType().isConst() | ||
) | ||
) and | ||
reference = outer | ||
or | ||
exists(Expr lvalueMid | | ||
referenceToLvalueStep(reference, lvalueMid) and | ||
lvalueToUpdate(lvalueMid, outer, node) | ||
) | ||
or | ||
exists(Expr pointerMid | | ||
referenceToPointerStep(reference, pointerMid) and | ||
pointerToUpdate(pointerMid, outer, node) | ||
) | ||
or | ||
exists(Expr referenceMid | | ||
referenceToReferenceStep(reference, referenceMid) and | ||
referenceToUpdate(referenceMid, outer, node) | ||
) | ||
} | ||
|
||
/** | ||
* Holds if `node` is a control-flow node that may modify `inner` (or what it | ||
* points to) through `outer`. The two expressions may be `Conversion`s. Plain | ||
* assignments to variables are not included in this predicate since they are | ||
* assumed to be analyzed by SSA or similar means. | ||
* | ||
* For example, in `f(& (*a).x)`, there are two results: | ||
* - `inner` = `... .x`, `outer` = `&...`, `node` = `f(...)`. | ||
* - `inner` = `a`, `outer` = `(...)`, `node` = `f(...)`. | ||
*/ | ||
cached | ||
predicate valueToUpdate(Expr inner, Expr outer, ControlFlowNode node) { | ||
( | ||
lvalueToUpdate(inner, outer, node) | ||
or | ||
pointerToUpdate(inner, outer, node) | ||
or | ||
referenceToUpdate(inner, outer, node) | ||
) and | ||
( | ||
inner instanceof VariableAccess and | ||
// Don't track non-field assignments | ||
(assignmentTo(outer, _) implies inner instanceof FieldAccess) | ||
or | ||
inner instanceof ThisExpr | ||
or | ||
inner instanceof Call | ||
// `inner` could also be `*` or `ReferenceDereferenceExpr`, but we | ||
// can't do anything useful with those at the moment. | ||
) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
I think it's possible for these three files to share code, but I don't think it would make this PR any easier to review.