Skip to content

C++: Fix IR edge case where there are no function calls taking an argument #19493

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 2 commits into from
May 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion cpp/ql/lib/semmle/code/cpp/ir/internal/IRCppLanguage.qll
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,13 @@ class Class = Cpp::Class; // Used for inheritance conversions
predicate hasCaseEdge(string minValue, string maxValue) { hasCaseEdge(_, minValue, maxValue) }

predicate hasPositionalArgIndex(int argIndex) {
exists(Cpp::FunctionCall call | exists(call.getArgument(argIndex))) or
exists(Cpp::FunctionCall call | exists(call.getArgument(argIndex)))
or
exists(Cpp::BuiltInOperation op | exists(op.getChild(argIndex)))
or
// Ensure we are always able to output the argument of a call to the delete operator.
exists(Cpp::DeleteExpr d) and
argIndex = 0
Comment on lines +75 to +76
Copy link
Preview

Copilot AI May 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The clause makes hasPositionalArgIndex(0) true for any delete expression in the module rather than when the delete’s argument is at the target index. Consider scoping this to the specific delete node, e.g., exists(Cpp::DeleteExpr d | exists(d.getChild(argIndex))) to ensure correctness.

Suggested change
exists(Cpp::DeleteExpr d) and
argIndex = 0
exists(Cpp::DeleteExpr d | exists(d.getChild(argIndex)))

Copilot uses AI. Check for mistakes.

}

predicate hasAsmOperandIndex(int operandIndex) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#-----| [CopyAssignmentOperator] __va_list_tag& __va_list_tag::operator=(__va_list_tag const&)
#-----| <params>:
#-----| getParameter(0): [Parameter] (unnamed parameter 0)
#-----| Type = [LValueReferenceType] const __va_list_tag &
#-----| [MoveAssignmentOperator] __va_list_tag& __va_list_tag::operator=(__va_list_tag&&)
#-----| <params>:
#-----| getParameter(0): [Parameter] (unnamed parameter 0)
#-----| Type = [RValueReferenceType] __va_list_tag &&
#-----| [Operator,TopLevelFunction] void operator delete(void*)
#-----| <params>:
#-----| getParameter(0): [Parameter] (unnamed parameter 0)
#-----| Type = [VoidPointerType] void *
test.cpp:
# 5| [TopLevelFunction] void foo(int*)
# 5| <params>:
# 5| getParameter(0): [Parameter] x
# 5| Type = [IntPointerType] int *
# 5| getEntryPoint(): [BlockStmt] { ... }
# 6| getStmt(0): [ExprStmt] ExprStmt
# 6| getExpr(): [DeleteExpr] delete
# 6| Type = [VoidType] void
# 6| ValueCategory = prvalue
# 6| getExprWithReuse(): [VariableAccess] x
# 6| Type = [IntPointerType] int *
# 6| ValueCategory = prvalue(load)
# 7| getStmt(1): [ReturnStmt] return ...
# 9| [TopLevelFunction] void bar()
# 9| <params>:
# 11| [TopLevelFunction] void jazz()
# 11| <params>:
# 11| getEntryPoint(): [BlockStmt] { ... }
# 12| getStmt(0): [ExprStmt] ExprStmt
# 12| getExpr(): [FunctionCall] call to bar
# 12| Type = [VoidType] void
# 12| ValueCategory = prvalue
# 13| getStmt(1): [ReturnStmt] return ...
11 changes: 11 additions & 0 deletions cpp/ql/test/library-tests/ir/no-function-calls/PrintAST.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* @kind graph
*/

private import cpp
private import semmle.code.cpp.PrintAST
private import PrintConfig

private class PrintConfig extends PrintAstConfiguration {
override predicate shouldPrintDeclaration(Declaration decl) { shouldDumpDeclaration(decl) }
}
24 changes: 24 additions & 0 deletions cpp/ql/test/library-tests/ir/no-function-calls/PrintConfig.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
private import cpp

/**
* Holds if the specified location is in standard headers.
*/
predicate locationIsInStandardHeaders(Location loc) {
loc.getFile().getAbsolutePath().regexpMatch(".*/include/[^/]+")
}

/**
* Holds if the AST or IR for the specified declaration should be printed in the test output.
*
* This predicate excludes declarations defined in standard headers.
*/
predicate shouldDumpDeclaration(Declaration decl) {
not locationIsInStandardHeaders(decl.getLocation()) and
(
decl instanceof Function
or
decl.(GlobalOrNamespaceVariable).hasInitializer()
or
decl.(StaticLocalVariable).hasInitializer()
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
test.cpp:
# 5| void foo(int*)
# 5| Block 0
# 5| v5_1(void) = EnterFunction :
# 5| m5_2(unknown) = AliasedDefinition :
# 5| m5_3(unknown) = InitializeNonLocal :
# 5| m5_4(unknown) = Chi : total:m5_2, partial:m5_3
# 5| r5_5(glval<int *>) = VariableAddress[x] :
# 5| m5_6(int *) = InitializeParameter[x] : &:r5_5
# 5| r5_7(int *) = Load[x] : &:r5_5, m5_6
# 5| m5_8(unknown) = InitializeIndirection[x] : &:r5_7
# 5| m5_9(unknown) = Chi : total:m5_4, partial:m5_8
# 6| r6_1(glval<unknown>) = FunctionAddress[operator delete] :
# 6| r6_2(glval<int *>) = VariableAddress[x] :
# 6| r6_3(int *) = Load[x] : &:r6_2, m5_6
# 6| v6_4(void) = Call[operator delete] : func:r6_1, 0:r6_3
# 6| m6_5(unknown) = ^CallSideEffect : ~m5_9
# 6| m6_6(unknown) = Chi : total:m5_9, partial:m6_5
# 7| v7_1(void) = NoOp :
# 5| v5_10(void) = ReturnIndirection[x] : &:r5_7, ~m6_6
# 5| v5_11(void) = ReturnVoid :
# 5| v5_12(void) = AliasedUse : ~m6_6
# 5| v5_13(void) = ExitFunction :

# 11| void jazz()
# 11| Block 0
# 11| v11_1(void) = EnterFunction :
# 11| m11_2(unknown) = AliasedDefinition :
# 11| m11_3(unknown) = InitializeNonLocal :
# 11| m11_4(unknown) = Chi : total:m11_2, partial:m11_3
# 12| r12_1(glval<unknown>) = FunctionAddress[bar] :
# 12| v12_2(void) = Call[bar] : func:r12_1
# 12| m12_3(unknown) = ^CallSideEffect : ~m11_4
# 12| m12_4(unknown) = Chi : total:m11_4, partial:m12_3
# 13| v13_1(void) = NoOp :
# 11| v11_5(void) = ReturnVoid :
# 11| v11_6(void) = AliasedUse : ~m12_4
# 11| v11_7(void) = ExitFunction :
11 changes: 11 additions & 0 deletions cpp/ql/test/library-tests/ir/no-function-calls/aliased_ir.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* @kind graph
*/

private import cpp
private import semmle.code.cpp.ir.implementation.aliased_ssa.PrintIR
private import PrintConfig

private class PrintConfig extends PrintIRConfiguration {
override predicate shouldPrintDeclaration(Declaration decl) { shouldDumpDeclaration(decl) }
}
13 changes: 13 additions & 0 deletions cpp/ql/test/library-tests/ir/no-function-calls/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Test for edge case, where we have a database without any function calls or
// where none of the function calls have any arguments, but where we do have
// a delete expression.

void foo(int* x) {
delete x;
}

void bar();

void jazz() {
bar();
}
Loading