From c64bccda8e9a66fccaa24cb23e633021ad62239b Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Mon, 5 Oct 2026 20:33:47 +0100 Subject: [PATCH 1/2] C++: Add a testcase that would generate invalid IR when running on the next version of our C/C++ frontend. --- .../library-tests/ir/ir/PrintAST.expected | 83 +++++++++++++++++++ .../library-tests/ir/ir/aliased_ir.expected | 51 ++++++++++++ .../ir/ir/destructors_for_temps.cpp | 7 ++ .../test/library-tests/ir/ir/raw_ir.expected | 49 +++++++++++ 4 files changed, 190 insertions(+) diff --git a/cpp/ql/test/library-tests/ir/ir/PrintAST.expected b/cpp/ql/test/library-tests/ir/ir/PrintAST.expected index 67e3c03d8f1f..0f84e8e3506a 100644 --- a/cpp/ql/test/library-tests/ir/ir/PrintAST.expected +++ b/cpp/ql/test/library-tests/ir/ir/PrintAST.expected @@ -4304,6 +4304,89 @@ destructors_for_temps.cpp: # 103| Type = [IntType] int # 103| ValueCategory = prvalue # 104| getStmt(1): [ReturnStmt] return ... +# 106| [CopyAssignmentOperator] ClassWithArrayAndDestructor& ClassWithArrayAndDestructor::operator=(ClassWithArrayAndDestructor const&) +# 106| : +#-----| getParameter(0): [Parameter] (unnamed parameter 0) +#-----| Type = [LValueReferenceType] const ClassWithArrayAndDestructor & +# 106| [Constructor] void ClassWithArrayAndDestructor::ClassWithArrayAndDestructor() +# 106| : +# 106| [Destructor] void ClassWithArrayAndDestructor::~ClassWithArrayAndDestructor() +# 106| : +# 108| [TopLevelFunction] void temp_test13(bool) +# 108| : +# 108| getParameter(0): [Parameter] b +# 108| Type = [BoolType] bool +# 108| getEntryPoint(): [BlockStmt] { ... } +# 109| getStmt(0): [DeclStmt] declaration +# 109| getDeclarationEntry(0): [VariableDeclarationEntry] definition of t +# 109| Type = [LValueReferenceType] const int & +# 109| getVariable().getInitializer(): [Initializer] initializer for t +# 109| getExpr(): [ConditionalExpr] ... ? ... : ... +# 109| Type = [IntType] int +# 109| ValueCategory = lvalue +# 109| getCondition(): [VariableAccess] b +# 109| Type = [BoolType] bool +# 109| ValueCategory = prvalue(load) +# 109| getThen(): [ArrayExpr] access to array +# 109| Type = [IntType] int +# 109| ValueCategory = lvalue +# 109| getArrayBase(): [ValueFieldAccess] a +# 109| Type = [ArrayType] int[2] +# 109| ValueCategory = prvalue +# 109| getQualifier(): [Literal] 0 +# 109| Type = [Struct] ClassWithArrayAndDestructor +# 109| Value = [Literal] 0 +# 109| ValueCategory = prvalue +# 109| getQualifier().getFullyConverted(): [TemporaryObjectExpr] temporary object +# 109| Type = [Struct] ClassWithArrayAndDestructor +# 109| ValueCategory = prvalue(load) +# 109| getArrayOffset(): [Literal] 0 +# 109| Type = [IntType] int +# 109| Value = [Literal] 0 +# 109| ValueCategory = prvalue +# 109| getArrayBase().getFullyConverted(): [ArrayToPointerConversion] array to pointer conversion +# 109| Type = [IntPointerType] int * +# 109| ValueCategory = prvalue +# 109| getElse(): [ArrayExpr] access to array +# 109| Type = [IntType] int +# 109| ValueCategory = lvalue +# 109| getArrayBase(): [ValueFieldAccess] a +# 109| Type = [ArrayType] int[2] +# 109| ValueCategory = prvalue +# 109| getQualifier(): [Literal] 0 +# 109| Type = [Struct] ClassWithArrayAndDestructor +# 109| Value = [Literal] 0 +# 109| ValueCategory = prvalue +# 109| getQualifier().getFullyConverted(): [TemporaryObjectExpr] temporary object +# 109| Type = [Struct] ClassWithArrayAndDestructor +# 109| ValueCategory = prvalue(load) +# 109| getArrayOffset(): [Literal] 1 +# 109| Type = [IntType] int +# 109| Value = [Literal] 1 +# 109| ValueCategory = prvalue +# 109| getArrayBase().getFullyConverted(): [ArrayToPointerConversion] array to pointer conversion +# 109| Type = [IntPointerType] int * +# 109| ValueCategory = prvalue +# 109| getImplicitDestructorCall(0): [DestructorCall] call to ~ClassWithArrayAndDestructor +# 109| Type = [VoidType] void +# 109| ValueCategory = prvalue +# 109| getQualifier(): [ReuseExpr] reuse of temporary object +# 109| Type = [Struct] ClassWithArrayAndDestructor +# 109| ValueCategory = xvalue +# 109| getImplicitDestructorCall(1): [DestructorCall] call to ~ClassWithArrayAndDestructor +# 109| Type = [VoidType] void +# 109| ValueCategory = prvalue +# 109| getQualifier(): [ReuseExpr] reuse of temporary object +# 109| Type = [Struct] ClassWithArrayAndDestructor +# 109| ValueCategory = xvalue +# 109| getExpr().getFullyConverted(): [ReferenceToExpr] (reference to) +# 109| Type = [LValueReferenceType] const int & +# 109| ValueCategory = prvalue +# 109| getExpr(): [CStyleCast] (const int)... +# 109| Conversion = [GlvalueConversion] glvalue conversion +# 109| Type = [SpecifiedType] const int +# 109| ValueCategory = lvalue +# 110| getStmt(1): [ReturnStmt] return ... generic.c: # 1| [TopLevelFunction] void c11_generic_test_with_load(unsigned int, int) # 1| : diff --git a/cpp/ql/test/library-tests/ir/ir/aliased_ir.expected b/cpp/ql/test/library-tests/ir/ir/aliased_ir.expected index 6efd5067acd1..3e96dd007636 100644 --- a/cpp/ql/test/library-tests/ir/ir/aliased_ir.expected +++ b/cpp/ql/test/library-tests/ir/ir/aliased_ir.expected @@ -3280,6 +3280,57 @@ destructors_for_temps.cpp: # 102| v102_9(void) = AliasedUse : ~m103_26 # 102| v102_10(void) = ExitFunction : +# 108| void temp_test13(bool) +# 108| Block 0 +# 108| v108_1(void) = EnterFunction : +# 108| m108_2(unknown) = AliasedDefinition : +# 108| m108_3(unknown) = InitializeNonLocal : +# 108| m108_4(unknown) = Chi : total:m108_2, partial:m108_3 +# 108| r108_5(glval) = VariableAddress[b] : +# 108| m108_6(bool) = InitializeParameter[b] : &:r108_5 +# 109| r109_1(glval) = VariableAddress[t] : +# 109| r109_2(glval) = VariableAddress[b] : +# 109| r109_3(bool) = Load[b] : &:r109_2, m108_6 +# 109| v109_4(void) = ConditionalBranch : r109_3 +#-----| False -> Block 3 +#-----| True -> Block 2 + +# 109| Block 1 +# 109| m109_5(glval) = Phi : from 2:m109_19, from 3:m109_28 +# 109| r109_6(glval) = VariableAddress[#temp109:18] : +# 109| r109_7(glval) = Load[#temp109:18] : &:r109_6, m109_5 +# 109| r109_8(glval) = Convert : r109_7 +# 109| r109_9(int &) = CopyValue : r109_8 +# 109| m109_10(int &) = Store[t] : &:r109_1, r109_9 +# 110| v110_1(void) = NoOp : +# 108| v108_7(void) = ReturnVoid : +# 108| v108_8(void) = AliasedUse : m108_3 +# 108| v108_9(void) = ExitFunction : + +# 109| Block 2 +# 109| r109_11(glval) = VariableAddress[#temp109:22] : +# 109| r109_12(ClassWithArrayAndDestructor) = Constant[0] : +# 109| m109_13(ClassWithArrayAndDestructor) = Store[#temp109:22] : &:r109_11, r109_12 +# 109| r109_14(glval) = FieldAddress[a] : r109_11 +# 109| r109_15(int *) = Convert : r109_14 +# 109| r109_16(int) = Constant[0] : +# 109| r109_17(glval) = PointerAdd[4] : r109_15, r109_16 +# 109| r109_18(glval) = VariableAddress[#temp109:18] : +# 109| m109_19(glval) = Store[#temp109:18] : &:r109_18, r109_17 +#-----| Goto -> Block 1 + +# 109| Block 3 +# 109| r109_20(glval) = VariableAddress[#temp109:59] : +# 109| r109_21(ClassWithArrayAndDestructor) = Constant[0] : +# 109| m109_22(ClassWithArrayAndDestructor) = Store[#temp109:59] : &:r109_20, r109_21 +# 109| r109_23(glval) = FieldAddress[a] : r109_20 +# 109| r109_24(int *) = Convert : r109_23 +# 109| r109_25(int) = Constant[1] : +# 109| r109_26(glval) = PointerAdd[4] : r109_24, r109_25 +# 109| r109_27(glval) = VariableAddress[#temp109:18] : +# 109| m109_28(glval) = Store[#temp109:18] : &:r109_27, r109_26 +#-----| Goto -> Block 1 + generic.c: # 1| void c11_generic_test_with_load(unsigned int, int) # 1| Block 0 diff --git a/cpp/ql/test/library-tests/ir/ir/destructors_for_temps.cpp b/cpp/ql/test/library-tests/ir/ir/destructors_for_temps.cpp index aefbc27173ac..eaeb222121f0 100644 --- a/cpp/ql/test/library-tests/ir/ir/destructors_for_temps.cpp +++ b/cpp/ql/test/library-tests/ir/ir/destructors_for_temps.cpp @@ -101,4 +101,11 @@ void temp_test11() { void temp_test12(ClassWithDestructor3 x) { x.getClassWithDestructor2().get_x() + 5; +} + +struct ClassWithArrayAndDestructor { int a[2]; ~ClassWithArrayAndDestructor(); }; + +void temp_test13(bool b) { + const int &t = b ? ClassWithArrayAndDestructor().a[0] : ClassWithArrayAndDestructor().a[1]; + return; } \ No newline at end of file diff --git a/cpp/ql/test/library-tests/ir/ir/raw_ir.expected b/cpp/ql/test/library-tests/ir/ir/raw_ir.expected index be7d41ad09a4..bcb9606658fd 100644 --- a/cpp/ql/test/library-tests/ir/ir/raw_ir.expected +++ b/cpp/ql/test/library-tests/ir/ir/raw_ir.expected @@ -2901,6 +2901,55 @@ destructors_for_temps.cpp: # 102| v102_7(void) = AliasedUse : ~m? # 102| v102_8(void) = ExitFunction : +# 108| void temp_test13(bool) +# 108| Block 0 +# 108| v108_1(void) = EnterFunction : +# 108| mu108_2(unknown) = AliasedDefinition : +# 108| mu108_3(unknown) = InitializeNonLocal : +# 108| r108_4(glval) = VariableAddress[b] : +# 108| mu108_5(bool) = InitializeParameter[b] : &:r108_4 +# 109| r109_1(glval) = VariableAddress[t] : +# 109| r109_2(glval) = VariableAddress[b] : +# 109| r109_3(bool) = Load[b] : &:r109_2, ~m? +# 109| v109_4(void) = ConditionalBranch : r109_3 +#-----| False -> Block 3 +#-----| True -> Block 2 + +# 109| Block 1 +# 109| r109_5(glval) = VariableAddress[#temp109:18] : +# 109| r109_6(glval) = Load[#temp109:18] : &:r109_5, ~m? +# 109| r109_7(glval) = Convert : r109_6 +# 109| r109_8(int &) = CopyValue : r109_7 +# 109| mu109_9(int &) = Store[t] : &:r109_1, r109_8 +# 110| v110_1(void) = NoOp : +# 108| v108_6(void) = ReturnVoid : +# 108| v108_7(void) = AliasedUse : ~m? +# 108| v108_8(void) = ExitFunction : + +# 109| Block 2 +# 109| r109_10(glval) = VariableAddress[#temp109:22] : +# 109| r109_11(ClassWithArrayAndDestructor) = Constant[0] : +# 109| mu109_12(ClassWithArrayAndDestructor) = Store[#temp109:22] : &:r109_10, r109_11 +# 109| r109_13(glval) = FieldAddress[a] : r109_10 +# 109| r109_14(int *) = Convert : r109_13 +# 109| r109_15(int) = Constant[0] : +# 109| r109_16(glval) = PointerAdd[4] : r109_14, r109_15 +# 109| r109_17(glval) = VariableAddress[#temp109:18] : +# 109| mu109_18(glval) = Store[#temp109:18] : &:r109_17, r109_16 +#-----| Goto -> Block 1 + +# 109| Block 3 +# 109| r109_19(glval) = VariableAddress[#temp109:59] : +# 109| r109_20(ClassWithArrayAndDestructor) = Constant[0] : +# 109| mu109_21(ClassWithArrayAndDestructor) = Store[#temp109:59] : &:r109_19, r109_20 +# 109| r109_22(glval) = FieldAddress[a] : r109_19 +# 109| r109_23(int *) = Convert : r109_22 +# 109| r109_24(int) = Constant[1] : +# 109| r109_25(glval) = PointerAdd[4] : r109_23, r109_24 +# 109| r109_26(glval) = VariableAddress[#temp109:18] : +# 109| mu109_27(glval) = Store[#temp109:18] : &:r109_26, r109_25 +#-----| Goto -> Block 1 + generic.c: # 1| void c11_generic_test_with_load(unsigned int, int) # 1| Block 0 From 44536bd072241d8a88329b0d9b973c6b926e97d3 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Mon, 5 Oct 2026 20:44:02 +0100 Subject: [PATCH 2/2] C++: Filter out destructor calls of temporaries in the translation classes. --- .../raw/internal/TranslatedElement.qll | 2 +- .../raw/internal/TranslatedExpr.qll | 7 ++++++- .../raw/internal/TranslatedStmt.qll | 21 +++++++++++++------ 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedElement.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedElement.qll index 7e9f7760b5a8..8103f64a5144 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedElement.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedElement.qll @@ -303,7 +303,7 @@ private predicate isInConditionalEvaluation(Expr e) { isInConditionalEvaluation(getRealParent(e)) } -private predicate isConditionalTemporaryDestructorCall(DestructorCall dc) { +predicate isConditionalTemporaryDestructorCall(DestructorCall dc) { exists(TemporaryObjectExpr temp | temp = dc.getQualifier().(ReuseExpr).getReusedExpr() and isInConditionalEvaluation(temp) diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedExpr.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedExpr.qll index 9a437b905381..5605e4857f7b 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedExpr.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedExpr.qll @@ -107,7 +107,12 @@ abstract class TranslatedExpr extends TranslatedElement { } final private TranslatedExpr getImplicitDestructorCall(int index) { - result.getExpr() = expr.getImplicitDestructorCall(index) + result.getExpr() = + rank[index + 1](DestructorCall dc, int i | + dc = expr.getImplicitDestructorCall(i) and not isConditionalTemporaryDestructorCall(dc) + | + dc order by i + ) } final override predicate hasAnImplicitDestructorCall() { diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedStmt.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedStmt.qll index 7dfebc56fe18..9ffa132c7eab 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedStmt.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedStmt.qll @@ -297,11 +297,20 @@ abstract class TranslatedStmt extends TranslatedElement, TTranslatedStmt { abstract TranslatedElement getChildInternal(int id); + DestructorCall getImplicitDestructorCall(int index) { + result = + rank[index + 1](DestructorCall dc, int i | + dc = stmt.getImplicitDestructorCall(i) and not isConditionalTemporaryDestructorCall(dc) + | + dc order by i + ) + } + final override TranslatedElement getChild(int id) { result = this.getChildInternal(id) or exists(int destructorIndex | - result.(TranslatedExpr).getExpr() = stmt.getImplicitDestructorCall(destructorIndex) and + result.(TranslatedExpr).getExpr() = this.getImplicitDestructorCall(destructorIndex) and id = this.getFirstDestructorCallIndex() + destructorIndex ) } @@ -313,7 +322,7 @@ abstract class TranslatedStmt extends TranslatedElement, TTranslatedStmt { } final override predicate hasAnImplicitDestructorCall() { - exists(stmt.getAnImplicitDestructorCall()) + exists(this.getImplicitDestructorCall(_)) } final override string toString() { result = stmt.toString() } @@ -1227,7 +1236,7 @@ class TranslatedWhileStmt extends TranslatedLoop { id = 1 and result = this.getBody() or exists(int n | - result.getAst() = stmt.getImplicitDestructorCall(n) and + result.getAst() = this.getImplicitDestructorCall(n) and id = 2 + n ) } @@ -1289,7 +1298,7 @@ class TranslatedForStmt extends TranslatedLoop { id = 3 and result = this.getBody() or exists(int n | - result.getAst() = stmt.getImplicitDestructorCall(n) and + result.getAst() = this.getImplicitDestructorCall(n) and id = 4 + n ) } @@ -1492,13 +1501,13 @@ class TranslatedJumpStmt extends TranslatedStmt { } private TranslatedCall getTranslatedImplicitDestructorCall(int id) { - result.getExpr() = stmt.getImplicitDestructorCall(id) + result.getExpr() = this.getImplicitDestructorCall(id) } override TranslatedElement getLastChild() { result = this.getTranslatedImplicitDestructorCall(max(int id | - exists(stmt.getImplicitDestructorCall(id)) + exists(this.getImplicitDestructorCall(id)) )) }