Skip to content

Commit

Permalink
C++: Support if consteval and if ! consteval
Browse files Browse the repository at this point in the history
  • Loading branch information
jketema committed Jan 15, 2025
1 parent 84c674b commit b16283e
Show file tree
Hide file tree
Showing 14 changed files with 788 additions and 0 deletions.
4 changes: 4 additions & 0 deletions cpp/ql/lib/semmle/code/cpp/PrintAST.qll
Original file line number Diff line number Diff line change
Expand Up @@ -912,6 +912,10 @@ private predicate namedStmtChildPredicates(Locatable s, Element e, string pred)
or
s.(ConstexprIfStmt).getElse() = e and pred = "getElse()"
or
s.(ConstevalOrNotConstevalIfStmt).getThen() = e and pred = "getThen()"
or
s.(ConstevalOrNotConstevalIfStmt).getElse() = e and pred = "getElse()"
or
s.(Handler).getParameter() = e and pred = "getParameter()"
or
s.(IfStmt).getInitialization() = e and pred = "getInitialization()"
Expand Down
22 changes: 22 additions & 0 deletions cpp/ql/lib/semmle/code/cpp/controlflow/internal/CFG.qll
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,28 @@ private predicate subEdge(Pos p1, Node n1, Node n2, Pos p2) {
p2.nodeAfter(n2, s)
)
or
// ConstevalOrNotConstevalIfStmt -> { then, else } ->
exists(ConstevalOrNotConstevalIfStmt s |
p1.nodeAt(n1, s) and
p2.nodeBefore(n2, s.getThen())
or
p1.nodeAt(n1, s) and
p2.nodeBefore(n2, s.getElse())
or
p1.nodeAt(n1, s) and
not exists(s.getElse()) and
p2.nodeBeforeDestructors(n2, s)
or
p1.nodeAfter(n1, s.getThen()) and
p2.nodeBeforeDestructors(n2, s)
or
p1.nodeAfter(n1, s.getElse()) and
p2.nodeBeforeDestructors(n2, s)
or
p1.nodeAfterDestructors(n1, s) and
p2.nodeAfter(n2, s)
)
or
// WhileStmt -> condition ; body -> condition ; after dtors -> after
exists(WhileStmt s |
p1.nodeAt(n1, s) and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1098,6 +1098,61 @@ class TranslatedConstExprIfStmt extends TranslatedIfLikeStmt {
override predicate hasElse() { exists(stmt.getElse()) }
}

class TranslatedConstevalOrNotConstevalIfStmt extends TranslatedStmt {
override ConstevalOrNotConstevalIfStmt stmt;

override Instruction getFirstInstruction(EdgeKind kind) {
if not this.hasEvaluatedBranch()
then
kind instanceof GotoEdge and
result = this.getInstruction(OnlyInstructionTag())
else result = this.getEvaluatedBranch().getFirstInstruction(kind)
}

override TranslatedElement getChildInternal(int id) {
id = 0 and
result = this.getThen()
or
id = 1 and
result = this.getElse()
}

override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
not this.hasEvaluatedBranch() and
opcode instanceof Opcode::NoOp and
tag = OnlyInstructionTag() and
resultType = getVoidType()
}

override Instruction getALastInstructionInternal() {
if not this.hasEvaluatedBranch()
then result = this.getInstruction(OnlyInstructionTag())
else result = this.getEvaluatedBranch().getALastInstruction()
}

override TranslatedElement getLastChild() { result = this.getEvaluatedBranch() }

override Instruction getInstructionSuccessorInternal(InstructionTag tag, EdgeKind kind) {
tag = OnlyInstructionTag() and
result = this.getParent().getChildSuccessor(this, kind)
}

override Instruction getChildSuccessorInternal(TranslatedElement child, EdgeKind kind) {
(child = this.getThen() or child = this.getElse()) and
result = this.getParent().getChildSuccessor(this, kind)
}

TranslatedStmt getEvaluatedBranch() {
result = getTranslatedStmt(stmt.getRuntimeEvaluatedBranch())
}

predicate hasEvaluatedBranch() { stmt.hasRuntimeEvaluatedBranch() }

TranslatedStmt getThen() { result = getTranslatedStmt(stmt.getThen()) }

TranslatedStmt getElse() { result = getTranslatedStmt(stmt.getElse()) }
}

abstract class TranslatedLoop extends TranslatedStmt, ConditionContext {
override Loop stmt;

Expand Down
161 changes: 161 additions & 0 deletions cpp/ql/lib/semmle/code/cpp/stmts/Stmt.qll
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,167 @@ class ConstexprIfStmt extends ConditionalStmt, @stmt_constexpr_if {
}
}

/**
* A C/C++ '(not) consteval if'. For example, the `if consteval` statement
* in the following code:
* ```cpp
* if consteval {
* ...
* }
* ```
*/
class ConstevalOrNotConstevalIfStmt extends Stmt, @stmt_consteval_or_not_consteval_if {
/**
* Gets the 'then' statement of this '(not) consteval if' statement.
*
* For example, for
* ```cpp
* if consteval { return true; }
* ```
* the result is the `BlockStmt` `{ return true; }`.
*/
Stmt getThen() { consteval_if_then(underlyingElement(this), unresolveElement(result)) }

/**
* Gets the 'else' statement of this '(not) constexpr if' statement, if any.
*
* For example, for
* ```cpp
* if consteval { return true; } else { return false; }
* ```
* the result is the `BlockStmt` `{ return false; }`, and for
* ```cpp
* if consteval { return true; }
* ```
* there is no result.
*/
Stmt getElse() { consteval_if_else(underlyingElement(this), unresolveElement(result)) }

/**
* Holds if this '(not) constexpr if' statement has an 'else' statement.
*
* For example, this holds for
* ```cpp
* if consteval { return true; } else { return false; }
* ```
* but not for
* ```cpp
* if consteval { return true; }
* ```
*/
predicate hasElse() { exists(this.getElse()) }

override predicate mayBeImpure() {
this.getThen().mayBeImpure() or
this.getElse().mayBeImpure()
}

override predicate mayBeGloballyImpure() {
this.getThen().mayBeGloballyImpure() or
this.getElse().mayBeGloballyImpure()
}

override MacroInvocation getGeneratingMacro() {
this.getThen().getGeneratingMacro() = result and
(this.hasElse() implies this.getElse().getGeneratingMacro() = result)
}

/**
* Gets the statement of this '(not) consteval if' statement evaluated during compile time, if any.
*
* For example, for
* ```cpp
* if ! consteval { return true; } else { return false; }
* ```
* the result is the `BlockStmt` `{ return false; }`, and for
* ```cpp
* if ! consteval { return true; }
* ```
* there is no result.
*/
Stmt getCompileTimeEvaluatedBranch() { none() }

/**
* Holds if this '(not) constexpr if' statement has a compile time evaluated statement.
*
* For example, this holds for
* ```cpp
* if ! consteval { return true; } else { return false; }
* ```
* but not for
* ```cpp
* if ! consteval { return true; }
* ```
*/
predicate hasCompileTimeEvaluatedBranch() { exists(this.getCompileTimeEvaluatedBranch()) }

/**
* Gets the statement of this '(not) consteval if' statement evaluated during runtime, if any.
*
* For example, for
* ```cpp
* if consteval { return true; } else { return false; }
* ```
* the result is the `BlockStmt` `{ return false; }`, and for
* ```cpp
* if consteval { return true; }
* ```
* there is no result.
*/
Stmt getRuntimeEvaluatedBranch() { none() }

/**
* Holds if this '(not) constexpr if' statement has a runtime evaluated statement.
*
* For example, this holds for
* ```cpp
* if consteval { return true; } else { return false; }
* ```
* but not for
* ```cpp
* if consteval { return true; }
* ```
*/
predicate hasRuntimeEvaluatedBranch() { exists(this.getRuntimeEvaluatedBranch()) }
}

/**
* A C/C++ 'consteval if'. For example, the `if consteval` statement
* in the following code:
* ```cpp
* if consteval {
* ...
* }
* ```
*/
class ConstevalIfStmt extends ConstevalOrNotConstevalIfStmt, @stmt_consteval_if {
override string getAPrimaryQlClass() { result = "ConstevallIfStmt" }

override string toString() { result = "if consteval ..." }

override Stmt getCompileTimeEvaluatedBranch() { result = this.getThen() }

override Stmt getRuntimeEvaluatedBranch() { result = this.getElse() }
}

/**
* A C/C++ 'not consteval if'. For example, the `if ! consteval` statement
* in the following code:
* ```cpp
* if ! consteval {
* ...
* }
* ```
*/
class NotConstevalIfStmt extends ConstevalOrNotConstevalIfStmt, @stmt_not_consteval_if {
override string getAPrimaryQlClass() { result = "NotConstevallIfStmt" }
override string toString() { result = "if ! consteval ..." }

override Stmt getCompileTimeEvaluatedBranch() { result = this.getElse() }

override Stmt getRuntimeEvaluatedBranch() { result = this.getThen() }
}

private class TLoop = @stmt_while or @stmt_end_test_while or @stmt_range_based_for or @stmt_for;

/**
Expand Down
14 changes: 14 additions & 0 deletions cpp/ql/lib/semmlecode.cpp.dbscheme
Original file line number Diff line number Diff line change
Expand Up @@ -2152,6 +2152,8 @@ case @stmt.kind of
// ... 34 @stmt_finally_end deprecated
| 35 = @stmt_constexpr_if
| 37 = @stmt_co_return
| 38 = @stmt_consteval_if
| 39 = @stmt_not_consteval_if
;

type_vla(
Expand Down Expand Up @@ -2194,6 +2196,18 @@ constexpr_if_else(
int else_id: @stmt ref
);

@stmt_consteval_or_not_consteval_if = @stmt_consteval_if | @stmt_not_consteval_if;

consteval_if_then(
unique int constexpr_if_stmt: @stmt_consteval_or_not_consteval_if ref,
int then_id: @stmt ref
);

consteval_if_else(
unique int constexpr_if_stmt: @stmt_consteval_or_not_consteval_if ref,
int else_id: @stmt ref
);

while_body(
unique int while_stmt: @stmt_while ref,
int body_id: @stmt ref
Expand Down
48 changes: 48 additions & 0 deletions cpp/ql/test/library-tests/consteval_if/cfg.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
| __va_list_tag::operator= | false | 66 | 66 | operator= |
| __va_list_tag::operator= | false | 72 | 72 | operator= |
| test | false | 142 | 142 | test |
| test | false | 147 | 147 | declaration |
| test | false | 152 | 152 | if consteval ... |
| test | false | 154 | 154 | ExprStmt |
| test | false | 156 | 156 | x |
| test | false | 160 | 160 | 1 |
| test | false | 161 | 161 | ... = ... |
| test | false | 163 | 163 | { ... } |
| test | false | 165 | 165 | ExprStmt |
| test | false | 167 | 167 | x |
| test | false | 171 | 171 | 2 |
| test | false | 172 | 172 | ... = ... |
| test | false | 174 | 174 | { ... } |
| test | false | 176 | 176 | if consteval ... |
| test | false | 178 | 178 | ExprStmt |
| test | false | 180 | 180 | x |
| test | false | 184 | 184 | 3 |
| test | false | 185 | 185 | ... = ... |
| test | false | 187 | 187 | { ... } |
| test | false | 189 | 189 | return ... |
| test | false | 191 | 191 | x |
| test | false | 193 | 193 | (bool)... |
| test | false | 194 | 194 | { ... } |
| test | true | 147 | 152 | |
| test | true | 152 | 163 | |
| test | true | 152 | 174 | |
| test | true | 154 | 160 | |
| test | true | 156 | 161 | |
| test | true | 160 | 156 | |
| test | true | 161 | 176 | |
| test | true | 163 | 154 | |
| test | true | 165 | 171 | |
| test | true | 167 | 172 | |
| test | true | 171 | 167 | |
| test | true | 172 | 176 | |
| test | true | 174 | 165 | |
| test | true | 176 | 187 | |
| test | true | 176 | 189 | |
| test | true | 178 | 184 | |
| test | true | 180 | 185 | |
| test | true | 184 | 180 | |
| test | true | 185 | 189 | |
| test | true | 187 | 178 | |
| test | true | 189 | 191 | |
| test | true | 191 | 142 | |
| test | true | 194 | 147 | |
42 changes: 42 additions & 0 deletions cpp/ql/test/library-tests/consteval_if/cfg.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* query-type: graph
*
* @kind graph-equivalence-test
*/

import cpp

class DestructorCallEnhanced extends DestructorCall {
override string toString() {
if exists(this.getQualifier().(VariableAccess).getTarget().getName())
then
result =
"call to " + this.getQualifier().(VariableAccess).getTarget().getName() + "." +
this.getTarget().getName()
else result = super.toString()
}
}

string scope(ControlFlowNode x) {
if exists(x.getControlFlowScope().getQualifiedName())
then result = x.getControlFlowScope().getQualifiedName()
else result = "<no scope>"
}

predicate isNode(boolean isEdge, ControlFlowNode x, ControlFlowNode y, string label) {
isEdge = false and x = y and label = x.toString()
}

predicate isSuccessor(boolean isEdge, ControlFlowNode x, ControlFlowNode y, string label) {
exists(string truelabel, string falselabel |
isEdge = true and
x.getASuccessor() = y and
(if x.getATrueSuccessor() = y then truelabel = "T" else truelabel = "") and
(if x.getAFalseSuccessor() = y then falselabel = "F" else falselabel = "") and
label = truelabel + falselabel
)
}

from boolean isEdge, ControlFlowNode x, ControlFlowNode y, string label
where isNode(isEdge, x, y, label) or isSuccessor(isEdge, x, y, label)
select scope(x), isEdge, x, y, label
Loading

0 comments on commit b16283e

Please sign in to comment.