Skip to content

Commit

Permalink
Merge pull request #18502 from jketema/consteval
Browse files Browse the repository at this point in the history
C++: Support `if consteval` and `if ! consteval`
  • Loading branch information
jketema authored Jan 20, 2025
2 parents ef034bc + a9e0f20 commit 4b85ea3
Show file tree
Hide file tree
Showing 25 changed files with 11,929 additions and 1,216 deletions.
2,429 changes: 2,429 additions & 0 deletions cpp/downgrades/1aa71a4a687fc93f807d4dfeeef70feceeced242/old.dbscheme

Large diffs are not rendered by default.

2,415 changes: 2,415 additions & 0 deletions cpp/downgrades/1aa71a4a687fc93f807d4dfeeef70feceeced242/semmlecode.cpp.dbscheme

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions cpp/downgrades/1aa71a4a687fc93f807d4dfeeef70feceeced242/stmts.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class Stmt extends @stmt {
string toString() { none() }
}

class Location extends @location_stmt {
string toString() { none() }
}

predicate isConstevalIf(Stmt stmt) {
exists(int kind | stmts(stmt, kind, _) | kind = 38 or kind = 39)
}

from Stmt stmt, int kind, int kind_new, Location location
where
stmts(stmt, kind, location) and
if isConstevalIf(stmt) then kind_new = 7 else kind_new = kind // Turns consteval if into a block with two block statements in it
select stmt, kind_new, location
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
description: Support (not) consteval if
compatibility: full
consteval_if_then.rel: delete
consteval_if_else.rel: delete
stmts.rel: run stmts.qlo
4 changes: 4 additions & 0 deletions cpp/ql/lib/change-notes/2024-01-16-consteval-if.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: feature
---
* A new class `ConstevalIfStmt` was introduced, which represents the C++23 `if consteval` and `if ! consteval` statements.
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.(ConstevalIfStmt).getThen() = e and pred = "getThen()"
or
s.(ConstevalIfStmt).getElse() = e and pred = "getElse()"
or
s.(Handler).getParameter() = e and pred = "getParameter()"
or
s.(IfStmt).getInitialization() = e and pred = "getInitialization()"
Expand Down
19 changes: 19 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,25 @@ private predicate subEdge(Pos p1, Node n1, Node n2, Pos p2) {
p2.nodeAfter(n2, s)
)
or
// NotConstevalIfStmt -> { then, else } ->
exists(ConstevalIfStmt 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.nodeAfter(n2, s)
or
p1.nodeAfter(n1, s.getThen()) and
p2.nodeAfter(n2, s)
or
p1.nodeAfter(n1, s.getElse()) 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 @@ -47,7 +47,7 @@ abstract class TranslatedFlexibleCondition extends TranslatedCondition, Conditio
{
TranslatedFlexibleCondition() { this = TTranslatedFlexibleCondition(expr) }

final override predicate handlesDestructorsExplicitly() { none() } // TODO: this needs to be revisted when we get unnamed destructors
final override predicate handlesDestructorsExplicitly() { none() } // TODO: this needs to be revisited when we get unnamed destructors

final override TranslatedElement getChild(int id) { id = 0 and result = this.getOperand() }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ private predicate ignoreExpr(Expr expr) {
}

/**
* Holds if the side effects of `expr` should be ignoredf for the purposes of IR generation.
* Holds if the side effects of `expr` should be ignored for the purposes of IR generation.
*
* In cases involving `constexpr`, a call can wind up as a constant expression. `ignoreExpr()` will
* not hold for such a call, since we do need to translate the call (as a constant), but we need to
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 TranslatedConstevalIfStmt extends TranslatedStmt {
override ConstevalIfStmt 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
148 changes: 148 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,154 @@ 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 ConstevalIfStmt extends Stmt, @stmt_consteval_or_not_consteval_if {
override string getAPrimaryQlClass() { result = "ConstevalIfStmt" }

override string toString() {
if this.isNot() then result = "if ! consteval ..." else result = "if consteval ..."
}

/**
* Holds if this is a 'not consteval if' statement.
*
* For example, this holds for
* ```cpp
* if ! consteval { return true; }
* ```
* but not for
* ```cpp
* if consteval { return true; }
* ```
*/
predicate isNot() { this instanceof @stmt_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() {
if this.isNot() then result = this.getElse() else result = this.getThen()
}

/**
* 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() {
if this.isNot() then result = this.getThen() else result = this.getElse()
}

/**
* 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()) }
}

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
Loading

0 comments on commit 4b85ea3

Please sign in to comment.