Skip to content

[DRAFT] [clang] translate if constexpr and constant expressions by their compile-time value - #2166

Draft
VladimirMakaev wants to merge 1 commit into
facebook:mainfrom
VladimirMakaev:clang-constexpr-if
Draft

VladimirMakaev wants to merge 1 commit into
facebook:mainfrom
VladimirMakaev:clang-constexpr-if

Conversation

@VladimirMakaev

@VladimirMakaev VladimirMakaev commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

The frontend translated if constexpr as a run-time if, and constant expressions (case labels,
enumerators, consteval calls) as run-time code. When a condition reads untranslated library
code, Pulse explores the branch that clang discarded.

#include <limits>

template <typename T>
int* if_signed(int* p) {
  if constexpr (std::numeric_limits<T>::is_signed) {
    return p;
  } else {
    return nullptr;
  }
}

void deref_unsigned_bad(int* p) {
  *if_signed<unsigned>(p) = 1; // Infer missed: null dereference
}

The clang plugin now exports IfStmt::isConstexpr() and the integer value of a ConstantExpr,
which the frontend translates. It drops the discarded branch rather than pruning it, so checkers
that are not path-sensitive ignore it too (RacerD no longer reports lock consistency violations
for locks in disabled branches), and locals read only there are not dead stores. Enumerator
initializers that call functions no longer leave a dangling identifier. Code using magic_enum is
analyzed 3x to 30x faster when its headers are inside --project-root.

Test plan

New tests in cpp/{frontend,pulse}-17, cpp/{frontend,pulse,liveness}-20 and the plugin's
constant_expr.cpp. Enumerators become literals in c/frontend/enumeration;
c/bufferoverrun/global.c loses a false positive and gains CONDITION_ALWAYS_TRUE/FALSE
expectations (disabled by default).

…ile-time value

The frontend translated `if constexpr` as a run-time `if`, and constant expressions (case labels,
enumerators, `consteval` calls) as run-time code. When a condition reads untranslated library
code, Pulse explores the branch that clang discarded.

```cpp
#include <limits>

template <typename T>
int* if_signed(int* p) {
  if constexpr (std::numeric_limits<T>::is_signed) {
    return p;
  } else {
    return nullptr;
  }
}

void deref_unsigned_bad(int* p) {
  *if_signed<unsigned>(p) = 1; // Infer missed: null dereference
}
```

The clang plugin now exports `IfStmt::isConstexpr()` and the integer value of a `ConstantExpr`,
which the frontend translates. It drops the discarded branch rather than pruning it, so checkers
that are not path-sensitive ignore it too (RacerD no longer reports lock consistency violations
for locks in disabled branches), and locals read only there are not dead stores. Enumerator
initializers that call functions no longer leave a dangling identifier. Code using magic_enum is
analyzed 3x to 30x faster when its headers are inside `--project-root`.

## Test plan

New tests in `cpp/{frontend,pulse}-17`, `cpp/{frontend,pulse,liveness}-20` and the plugin's
`constant_expr.cpp`. Enumerators become literals in `c/frontend/enumeration`;
`c/bufferoverrun/global.c` loses a false positive and gains CONDITION_ALWAYS_TRUE/FALSE
expectations (disabled by default).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant