Skip to content
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

C++: Support if consteval and if ! consteval #18502

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
2,429 changes: 2,429 additions & 0 deletions cpp/downgrades/1aa71a4a687fc93f807d4dfeeef70feceeced242/old.dbscheme

Large diffs are not rendered by default.

Large diffs are not rendered by default.

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 statement in it
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
if isConstevalIf(stmt) then kind_new = 7 else kind_new = kind // Turns consteval if into a block with two block statement in it
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
---
* New classes `ConstevalIfStmt` and `NotConstEvalIfStmt` were introduced, which represent 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.(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
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
// 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())
Comment on lines +881 to +885
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here we allow both branches of the consteval if to be reached.

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 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())
}
Comment on lines +1145 to +1147
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We pick a single branch that can be reached. For now this is the branch that is taken at runtime, we can change this later if we see that using the compile time branch improves analysis quality.


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
162 changes: 162 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,168 @@ 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 = "ConstevalIfStmt" }

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 = "NotConstevalIfStmt" }

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
Loading