Skip to content

Commit 1166645

Browse files
authored
Merge pull request github#16125 from MathiasVP/destructors-for-unconditional-unnamed
C++: Generate IR for destruction of unconditionally constructed temporaries
2 parents 3614d3d + 736d59c commit 1166645

23 files changed

+4075
-2176
lines changed

cpp/ql/lib/semmle/code/cpp/PrintAST.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,8 @@ class ConversionNode extends ExprNode {
364364
childIndex = 0 and
365365
result.getAst() = conv.getExpr() and
366366
conv.getExpr() instanceof Conversion
367+
or
368+
result.getAst() = expr.getImplicitDestructorCall(childIndex - 1)
367369
}
368370
}
369371

cpp/ql/lib/semmle/code/cpp/exprs/Expr.qll

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ class Expr extends StmtParent, @expr {
6363
* order of destruction.
6464
*/
6565
DestructorCall getImplicitDestructorCall(int n) {
66+
exists(Expr e |
67+
e = this.(TemporaryObjectExpr).getExpr() and
68+
synthetic_destructor_call(e, max(int i | synthetic_destructor_call(e, i, _)) - n, result)
69+
)
70+
or
71+
not this = any(TemporaryObjectExpr temp).getExpr() and
6672
synthetic_destructor_call(this, max(int i | synthetic_destructor_call(this, i, _)) - n, result)
6773
}
6874

cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedElement.qll

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,10 @@ private predicate ignoreExprAndDescendants(Expr expr) {
128128
vaStartExpr.getLastNamedParameter().getFullyConverted() = expr
129129
)
130130
or
131-
// suppress destructors of temporary variables until proper support is added for them.
132-
exists(Expr parent | parent.getAnImplicitDestructorCall() = expr)
131+
// Do not translate implicit destructor calls for unnamed temporary variables that are
132+
// conditionally constructed (until we have a mechanism for calling these only when the
133+
// temporary's constructor was run)
134+
isConditionalTemporaryDestructorCall(expr)
133135
}
134136

135137
/**
@@ -255,6 +257,42 @@ private predicate usedAsCondition(Expr expr) {
255257
)
256258
}
257259

260+
private predicate hasThrowingChild(Expr e) {
261+
e = any(ThrowExpr throw).getFullyConverted()
262+
or
263+
exists(Expr child |
264+
e = getRealParent(child) and
265+
hasThrowingChild(child)
266+
)
267+
}
268+
269+
private predicate isInConditionalEvaluation(Expr e) {
270+
exists(ConditionalExpr cond |
271+
e = cond.getThen().getFullyConverted() and not cond.isTwoOperand()
272+
or
273+
e = cond.getElse().getFullyConverted()
274+
or
275+
// If one of the operands throws then the temporaries constructed in either
276+
// branch will also be attached to the ternary expression. We suppress
277+
// those destructor calls as well.
278+
hasThrowingChild([cond.getThen(), cond.getElse()]) and
279+
e = cond.getFullyConverted()
280+
)
281+
or
282+
e = any(LogicalAndExpr lae).getRightOperand().getFullyConverted()
283+
or
284+
e = any(LogicalOrExpr loe).getRightOperand().getFullyConverted()
285+
or
286+
isInConditionalEvaluation(getRealParent(e))
287+
}
288+
289+
private predicate isConditionalTemporaryDestructorCall(DestructorCall dc) {
290+
exists(TemporaryObjectExpr temp |
291+
temp = dc.getQualifier().(ReuseExpr).getReusedExpr() and
292+
isInConditionalEvaluation(temp)
293+
)
294+
}
295+
258296
/**
259297
* Holds if `conv` is an `InheritanceConversion` that requires a `TranslatedLoad`, despite not being
260298
* marked as having an lvalue-to-rvalue conversion.
@@ -782,6 +820,16 @@ newtype TTranslatedElement =
782820
not ignoreSideEffects(expr) and
783821
opcode = getCallSideEffectOpcode(expr)
784822
} or
823+
// The set of destructors to invoke after a `throw`. These need to be special
824+
// cased because the edge kind following a throw is an `ExceptionEdge`, and
825+
// we need to make sure that the edge kind is still an `ExceptionEdge` after
826+
// all the destructors have run.
827+
TTranslatedDestructorsAfterThrow(ThrowExpr throw) {
828+
exists(DestructorCall dc |
829+
dc = throw.getAnImplicitDestructorCall() and
830+
not ignoreExpr(dc)
831+
)
832+
} or
785833
// A precise side effect of an argument to a `Call`
786834
TTranslatedArgumentExprSideEffect(Call call, Expr expr, int n, SideEffectOpcode opcode) {
787835
not ignoreExpr(expr) and

cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedExpr.qll

Lines changed: 128 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,26 @@ abstract class TranslatedExpr extends TranslatedElement {
9090
final override TranslatedElement getChild(int id) {
9191
result = this.getChildInternal(id)
9292
or
93-
exists(int maxChildId, int destructorIndex |
94-
maxChildId = max(int childId | exists(this.getChildInternal(childId))) and
95-
result.(TranslatedExpr).getExpr() = expr.getImplicitDestructorCall(destructorIndex) and
96-
id = maxChildId + 1 + destructorIndex
93+
exists(int destructorIndex |
94+
result = this.getImplicitDestructorCall(destructorIndex) and
95+
id = this.getFirstDestructorCallIndex() + destructorIndex
96+
)
97+
}
98+
99+
final private TranslatedExpr getImplicitDestructorCall(int index) {
100+
result.getExpr() = expr.getImplicitDestructorCall(index)
101+
}
102+
103+
final override predicate hasAnImplicitDestructorCall() {
104+
exists(this.getImplicitDestructorCall(_))
105+
}
106+
107+
final override int getFirstDestructorCallIndex() {
108+
not this.handlesDestructorsExplicitly() and
109+
(
110+
result = max(int childId | exists(this.getChildInternal(childId))) + 1
111+
or
112+
not exists(this.getChildInternal(_)) and result = 0
97113
)
98114
}
99115

@@ -395,6 +411,14 @@ class TranslatedLoad extends TranslatedValueCategoryAdjustment, TTranslatedLoad
395411
result = this.getOperand().getResult()
396412
)
397413
}
414+
415+
override predicate handlesDestructorsExplicitly() {
416+
// The class that generates IR for `e` will (implicitly or explicitly)
417+
// handle the generation of destructor calls for `e`. Without disabling
418+
// destructor call generation here the destructor will get multiple
419+
// parents.
420+
any()
421+
}
398422
}
399423

400424
/**
@@ -1999,6 +2023,13 @@ abstract class TranslatedAllocationSize extends TranslatedExpr, TTranslatedAlloc
19992023
final override predicate producesExprResult() { none() }
20002024

20012025
final override Instruction getResult() { result = this.getInstruction(AllocationSizeTag()) }
2026+
2027+
final override predicate handlesDestructorsExplicitly() {
2028+
// Since the enclosing `TranslatedNewOrNewArrayExpr` (implicitly) handles the destructors
2029+
// we need to disable the implicit handling here as otherwise the destructors will have
2030+
// multiple parents
2031+
any()
2032+
}
20022033
}
20032034

20042035
TranslatedAllocationSize getTranslatedAllocationSize(NewOrNewArrayExpr newExpr) {
@@ -2156,6 +2187,13 @@ class TranslatedAllocatorCall extends TTranslatedAllocatorCall, TranslatedDirect
21562187

21572188
final override predicate producesExprResult() { none() }
21582189

2190+
final override predicate handlesDestructorsExplicitly() {
2191+
// Since the enclosing `TranslatedNewOrNewArrayExpr` (implicitly) handles the destructors
2192+
// we need to disable the implicit handling here as otherwise the destructors will have
2193+
// multiple parents
2194+
any()
2195+
}
2196+
21592197
override Function getInstructionFunction(InstructionTag tag) {
21602198
tag = CallTargetTag() and result = expr.getAllocator()
21612199
}
@@ -2817,6 +2855,68 @@ class TranslatedReuseExpr extends TranslatedNonConstantExpr {
28172855
}
28182856
}
28192857

2858+
/**
2859+
* The IR translation of the destructor calls of the parent `TranslatedThrow`.
2860+
*
2861+
* This object does not itself generate the destructor calls. Instead, its
2862+
* children provide the actual calls, and this object ensures that we correctly
2863+
* exit with an `ExceptionEdge` after executing all the destructor calls.
2864+
*/
2865+
class TranslatedDestructorsAfterThrow extends TranslatedElement, TTranslatedDestructorsAfterThrow {
2866+
ThrowExpr throw;
2867+
2868+
TranslatedDestructorsAfterThrow() { this = TTranslatedDestructorsAfterThrow(throw) }
2869+
2870+
override string toString() { result = "Destructor calls after throw: " + throw }
2871+
2872+
private TranslatedCall getTranslatedImplicitDestructorCall(int id) {
2873+
result.getExpr() = throw.getImplicitDestructorCall(id)
2874+
}
2875+
2876+
override Instruction getFirstInstruction(EdgeKind kind) {
2877+
result = this.getChild(0).getFirstInstruction(kind)
2878+
}
2879+
2880+
override ThrowExpr getAst() { result = throw }
2881+
2882+
override Instruction getInstructionSuccessorInternal(InstructionTag tag, EdgeKind kind) { none() }
2883+
2884+
override TranslatedElement getChild(int id) {
2885+
result = this.getTranslatedImplicitDestructorCall(id)
2886+
}
2887+
2888+
override predicate handlesDestructorsExplicitly() { any() }
2889+
2890+
override Declaration getFunction() { result = throw.getEnclosingFunction() }
2891+
2892+
override Instruction getChildSuccessorInternal(TranslatedElement child, EdgeKind kind) {
2893+
exists(int id | child = this.getChild(id) |
2894+
// Transition to the next child, if any.
2895+
result = this.getChild(id + 1).getFirstInstruction(kind)
2896+
or
2897+
// And otherwise, exit this element with an exceptional edge
2898+
not exists(this.getChild(id + 1)) and
2899+
kind instanceof ExceptionEdge and
2900+
result = this.getParent().getExceptionSuccessorInstruction(any(GotoEdge edge))
2901+
)
2902+
}
2903+
2904+
override TranslatedElement getLastChild() {
2905+
result =
2906+
this.getTranslatedImplicitDestructorCall(max(int id |
2907+
exists(throw.getImplicitDestructorCall(id))
2908+
))
2909+
}
2910+
2911+
override Instruction getALastInstructionInternal() {
2912+
result = this.getLastChild().getALastInstruction()
2913+
}
2914+
2915+
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
2916+
none()
2917+
}
2918+
}
2919+
28202920
/**
28212921
* IR translation of a `throw` expression.
28222922
*/
@@ -2831,13 +2931,22 @@ abstract class TranslatedThrowExpr extends TranslatedNonConstantExpr {
28312931

28322932
override Instruction getInstructionSuccessorInternal(InstructionTag tag, EdgeKind kind) {
28332933
tag = ThrowTag() and
2834-
kind instanceof ExceptionEdge and
2835-
result = this.getParent().getExceptionSuccessorInstruction(any(GotoEdge edge))
2934+
(
2935+
result = this.getDestructors().getFirstInstruction(kind)
2936+
or
2937+
not exists(this.getDestructors()) and
2938+
kind instanceof ExceptionEdge and
2939+
result = this.getParent().getExceptionSuccessorInstruction(any(GotoEdge edge))
2940+
)
28362941
}
28372942

28382943
override Instruction getResult() { none() }
28392944

28402945
abstract Opcode getThrowOpcode();
2946+
2947+
override predicate handlesDestructorsExplicitly() { any() }
2948+
2949+
TranslatedDestructorsAfterThrow getDestructors() { result.getAst() = expr }
28412950
}
28422951

28432952
/**
@@ -2849,6 +2958,9 @@ class TranslatedThrowValueExpr extends TranslatedThrowExpr, TranslatedVariableIn
28492958

28502959
final override TranslatedElement getChildInternal(int id) {
28512960
result = TranslatedVariableInitialization.super.getChildInternal(id)
2961+
or
2962+
id = max(int i | exists(TranslatedVariableInitialization.super.getChildInternal(i))) + 1 and
2963+
result = this.getDestructors()
28522964
}
28532965

28542966
final override Instruction getChildSuccessorInternal(TranslatedElement elem, EdgeKind kind) {
@@ -2914,14 +3026,22 @@ class TranslatedThrowValueExpr extends TranslatedThrowExpr, TranslatedVariableIn
29143026
class TranslatedReThrowExpr extends TranslatedThrowExpr {
29153027
override ReThrowExpr expr;
29163028

2917-
override TranslatedElement getChildInternal(int id) { none() }
3029+
override TranslatedElement getChildInternal(int id) {
3030+
id = 0 and
3031+
result = this.getDestructors()
3032+
}
29183033

29193034
override Instruction getFirstInstruction(EdgeKind kind) {
29203035
result = this.getInstruction(ThrowTag()) and
29213036
kind instanceof GotoEdge
29223037
}
29233038

2924-
override Instruction getALastInstructionInternal() { result = this.getInstruction(ThrowTag()) }
3039+
override Instruction getALastInstructionInternal() {
3040+
result = this.getDestructors().getALastInstruction()
3041+
or
3042+
not this.hasAnImplicitDestructorCall() and
3043+
result = this.getInstruction(ThrowTag())
3044+
}
29253045

29263046
override Instruction getChildSuccessorInternal(TranslatedElement child, EdgeKind kind) { none() }
29273047

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
| test.cpp:680:30:680:30 | call to operator[] | This object is destroyed before $@ is called. | test.cpp:680:17:680:17 | call to begin | call to begin |
2+
| test.cpp:680:30:680:30 | call to operator[] | This object is destroyed before $@ is called. | test.cpp:680:17:680:17 | call to end | call to end |
3+
| test.cpp:683:31:683:32 | call to at | This object is destroyed before $@ is called. | test.cpp:683:17:683:17 | call to begin | call to begin |
4+
| test.cpp:683:31:683:32 | call to at | This object is destroyed before $@ is called. | test.cpp:683:17:683:17 | call to end | call to end |
5+
| test.cpp:689:17:689:29 | temporary object | This object is destroyed before $@ is called. | test.cpp:689:31:689:35 | call to begin | call to begin |
6+
| test.cpp:689:46:689:58 | temporary object | This object is destroyed before $@ is called. | test.cpp:689:60:689:62 | call to end | call to end |
7+
| test.cpp:702:27:702:27 | call to operator[] | This object is destroyed before $@ is called. | test.cpp:703:19:703:23 | call to begin | call to begin |
8+
| test.cpp:702:27:702:27 | call to operator[] | This object is destroyed before $@ is called. | test.cpp:703:36:703:38 | call to end | call to end |
9+
| test.cpp:716:36:716:48 | temporary object | This object is destroyed before $@ is called. | test.cpp:716:17:716:17 | call to begin | call to begin |
10+
| test.cpp:716:36:716:48 | temporary object | This object is destroyed before $@ is called. | test.cpp:716:17:716:17 | call to end | call to end |
11+
| test.cpp:727:23:727:23 | call to operator[] | This object is destroyed before $@ is called. | test.cpp:750:17:750:17 | call to begin | call to begin |
12+
| test.cpp:727:23:727:23 | call to operator[] | This object is destroyed before $@ is called. | test.cpp:750:17:750:17 | call to end | call to end |
13+
| test.cpp:735:23:735:23 | call to operator[] | This object is destroyed before $@ is called. | test.cpp:759:17:759:17 | call to begin | call to begin |
14+
| test.cpp:735:23:735:23 | call to operator[] | This object is destroyed before $@ is called. | test.cpp:759:17:759:17 | call to end | call to end |

cpp/ql/test/experimental/query-tests/Security/CWE/CWE-416/test.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -677,10 +677,10 @@ std::vector<std::vector<int>> return_self_by_value(const std::vector<std::vector
677677

678678
void test() {
679679
for (auto x : returnValue()) {} // GOOD
680-
for (auto x : returnValue()[0]) {} // BAD [NOT DETECTED] (see *)
680+
for (auto x : returnValue()[0]) {} // BAD
681681
for (auto x : external_by_value(returnValue())) {} // GOOD
682682
for (auto x : external_by_const_ref(returnValue())) {} // GOOD
683-
for (auto x : returnValue().at(0)) {} // BAD [NOT DETECTED] (see *)
683+
for (auto x : returnValue().at(0)) {} // BAD
684684

685685
for (auto x : returnRef()) {} // GOOD
686686
for (auto x : returnRef()[0]) {} // GOOD
@@ -700,7 +700,7 @@ void test() {
700700

701701
{
702702
auto&& v = returnValue()[0];
703-
for(auto it = v.begin(); it != v.end(); ++it) {} // BAD [NOT DETECTED] (see *)
703+
for(auto it = v.begin(); it != v.end(); ++it) {} // BAD
704704
}
705705

706706
{
@@ -713,7 +713,7 @@ void test() {
713713
for(auto it = v.begin(); it != v.end(); ++it) {} // GOOD
714714
}
715715

716-
for (auto x : return_self_by_ref(returnValue())) {} // BAD [NOT DETECTED] (see *)
716+
for (auto x : return_self_by_ref(returnValue())) {} // BAD
717717

718718
for (auto x : return_self_by_value(returnValue())) {} // GOOD
719719
}
@@ -724,15 +724,15 @@ void iterate(const std::vector<T>& v) {
724724
}
725725

726726
std::vector<int>& ref_to_first_in_returnValue_1() {
727-
return returnValue()[0]; // BAD [NOT DETECTED] (see *)
727+
return returnValue()[0]; // BAD
728728
}
729729

730730
std::vector<int>& ref_to_first_in_returnValue_2() {
731731
return returnValue()[0]; // BAD [NOT DETECTED]
732732
}
733733

734734
std::vector<int>& ref_to_first_in_returnValue_3() {
735-
return returnValue()[0]; // BAD [NOT DETECTED] (see *)
735+
return returnValue()[0]; // BAD
736736
}
737737

738738
std::vector<int> first_in_returnValue_1() {

0 commit comments

Comments
 (0)