Skip to content

Commit 23cf997

Browse files
committed
C++: Add a new experimental query ' cpp/iterator-to-expired-container'.
1 parent f7c29e6 commit 23cf997

File tree

1 file changed

+89
-0
lines changed

1 file changed

+89
-0
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* @name Iterator to expired container
3+
* @description Using an iterator owned by a container whose lifetimes has expired may lead to unexpected behavior.
4+
* @kind problem
5+
* @precision high
6+
* @id cpp/iterator-to-expired-container
7+
* @problem.severity warning
8+
* @tags reliability
9+
* security
10+
* external/cwe/cwe-416
11+
* external/cwe/cwe-664
12+
*/
13+
14+
// IMPORTANT: This query does not currently find anything since it relies on extractor and analysis improvements that hasn't yet been released
15+
import cpp
16+
import semmle.code.cpp.ir.IR
17+
import semmle.code.cpp.dataflow.new.DataFlow
18+
import semmle.code.cpp.models.implementations.StdContainer
19+
import semmle.code.cpp.models.implementations.StdMap
20+
import semmle.code.cpp.models.implementations.Iterator
21+
22+
/**
23+
* A configuration to track flow from a temporary variable to the qualifier of
24+
* a destructor call
25+
*/
26+
module TempToDestructorConfig implements DataFlow::ConfigSig {
27+
predicate isSource(DataFlow::Node source) {
28+
source.asInstruction().(VariableAddressInstruction).getIRVariable() instanceof IRTempVariable
29+
}
30+
31+
predicate isSink(DataFlow::Node sink) {
32+
sink.asOperand().(ThisArgumentOperand).getCall().getStaticCallTarget() instanceof Destructor
33+
}
34+
}
35+
36+
module TempToDestructorFlow = DataFlow::Global<TempToDestructorConfig>;
37+
38+
/**
39+
* Gets a `DataFlow::Node` that represents a temporary that will be destroyed
40+
* by a call to a destructor, or a `DataFlow::Node` that will transitively be
41+
* destroyed by a call to a destructor.
42+
*
43+
* For the latter case, consider something like:
44+
* ```
45+
* std::vector<std::vector<int>> get_2d_vector();
46+
* auto& v = get_2d_vector()[0];
47+
* ```
48+
* Given the above, this predicate returns the node corresponding
49+
* to `get_2d_vector()[0]` since the temporary `get_2d_vector()` gets
50+
* destroyed by a call to `std::vector<std::vector<int>>::~vector`,
51+
* and thus the result of `get_2d_vector()[0]` is also an invalid reference.
52+
*/
53+
DataFlow::Node getADestroyedNode() {
54+
exists(TempToDestructorFlow::PathNode destroyedTemp | destroyedTemp.isSource() |
55+
result = destroyedTemp.getNode()
56+
or
57+
exists(CallInstruction call |
58+
result.asInstruction() = call and
59+
DataFlow::localFlow(destroyedTemp.getNode(),
60+
DataFlow::operandNode(call.getThisArgumentOperand()))
61+
|
62+
call.getStaticCallTarget() instanceof StdSequenceContainerAt or
63+
call.getStaticCallTarget() instanceof StdMapAt
64+
)
65+
)
66+
}
67+
68+
predicate isSinkImpl(DataFlow::Node sink, FunctionCall fc) {
69+
exists(CallInstruction call |
70+
call = sink.asOperand().(ThisArgumentOperand).getCall() and
71+
fc = call.getUnconvertedResultExpression() and
72+
call.getStaticCallTarget() instanceof BeginOrEndFunction
73+
)
74+
}
75+
76+
/**
77+
* Flow from any destroyed object to the qualifier of a `begin` call
78+
*/
79+
module DestroyedToBeginConfig implements DataFlow::ConfigSig {
80+
predicate isSource(DataFlow::Node source) { source = getADestroyedNode() }
81+
82+
predicate isSink(DataFlow::Node sink) { isSinkImpl(sink, _) }
83+
}
84+
85+
module DestroyedToBeginFlow = DataFlow::Global<DestroyedToBeginConfig>;
86+
87+
from DataFlow::Node source, DataFlow::Node sink, FunctionCall beginOrEnd
88+
where DestroyedToBeginFlow::flow(source, sink) and isSinkImpl(sink, beginOrEnd)
89+
select source, "This object is destroyed before $@ is called.", beginOrEnd, beginOrEnd.toString()

0 commit comments

Comments
 (0)