Skip to content

Cherry-pick [Clang] SemaFunctionEffects: When verifying a function, ignore any trailing requires clause. (#114266) #9512

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
Nov 12, 2024
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
31 changes: 31 additions & 0 deletions clang/lib/Sema/SemaFunctionEffects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,8 @@ class Analyzer {
PendingFunctionAnalysis &CurrentFunction;
CallableInfo &CurrentCaller;
ViolationSite VSite;
const Expr *TrailingRequiresClause = nullptr;
const Expr *NoexceptExpr = nullptr;

FunctionBodyASTVisitor(Analyzer &Outer,
PendingFunctionAnalysis &CurrentFunction,
Expand All @@ -985,6 +987,22 @@ class Analyzer {
if (auto *Dtor = dyn_cast<CXXDestructorDecl>(CurrentCaller.CDecl))
followDestructor(dyn_cast<CXXRecordDecl>(Dtor->getParent()), Dtor);

if (auto *FD = dyn_cast<FunctionDecl>(CurrentCaller.CDecl)) {
TrailingRequiresClause = FD->getTrailingRequiresClause();

// Note that FD->getType->getAs<FunctionProtoType>() can yield a
// noexcept Expr which has been boiled down to a constant expression.
// Going through the TypeSourceInfo obtains the actual expression which
// will be traversed as part of the function -- unless we capture it
// here and have TraverseStmt skip it.
if (TypeSourceInfo *TSI = FD->getTypeSourceInfo()) {
if (FunctionProtoTypeLoc TL =
TSI->getTypeLoc().getAs<FunctionProtoTypeLoc>())
if (const FunctionProtoType *FPT = TL.getTypePtr())
NoexceptExpr = FPT->getNoexceptExpr();
}
}

// Do an AST traversal of the function/block body
TraverseDecl(const_cast<Decl *>(CurrentCaller.CDecl));
}
Expand Down Expand Up @@ -1259,6 +1277,18 @@ class Analyzer {
return true;
}

bool TraverseStmt(Stmt *Statement) {
// If this statement is a `requires` clause from the top-level function
// being traversed, ignore it, since it's not generating runtime code.
// We skip the traversal of lambdas (beyond their captures, see
// TraverseLambdaExpr below), so just caching this from our constructor
// should suffice.
// The exact same is true for a conditional `noexcept()` clause.
if (Statement != TrailingRequiresClause && Statement != NoexceptExpr)
return Base::TraverseStmt(Statement);
return true;
}

bool TraverseConstructorInitializer(CXXCtorInitializer *Init) {
ViolationSite PrevVS = VSite;
if (Init->isAnyMemberInitializer())
Expand Down Expand Up @@ -1297,6 +1327,7 @@ class Analyzer {
}

bool TraverseBlockExpr(BlockExpr * /*unused*/) {
// As with lambdas, don't traverse the block's body.
// TODO: are the capture expressions (ctor call?) safe?
return true;
}
Expand Down
55 changes: 55 additions & 0 deletions clang/test/Sema/attr-nonblocking-constraints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,61 @@ void nb26() [[clang::nonblocking]] {
abort_wrapper(); // no diagnostic
}

// --- Make sure we don't traverse requires and noexcept clauses. ---

// Apparently some requires clauses are able to be collapsed into a constant before the nonblocking
// analysis sees any function calls. This example (extracted from a real-world case where
// `operator&&` in <valarray>, preceding the inclusion of <expected>) is sufficiently complex
// to look like it contains function calls. There may be simpler examples.

namespace ExpectedTest {

template <class _Tp>
inline constexpr bool is_copy_constructible_v = __is_constructible(_Tp, _Tp&);

template <bool, class _Tp = void>
struct enable_if {};
template <class _Tp>
struct enable_if<true, _Tp> {
typedef _Tp type;
};

template <bool _Bp, class _Tp = void>
using enable_if_t = typename enable_if<_Bp, _Tp>::type;

// Doesn't seem to matter whether the enable_if is true or false.
template <class E1, class E2, enable_if_t<is_copy_constructible_v<E1>> = 0>
inline bool operator&&(const E1& x, const E2& y);

template <class _Tp, class _Err>
class expected {
public:
constexpr expected()
{}

// This is a deliberate corruption of the real implementation for simplicity.
constexpr expected(const expected&)
requires(is_copy_constructible_v<_Tp> && is_copy_constructible_v<_Err>)
= default;
};

void test() [[clang::nonblocking]]
{
expected<int, int> a;
auto b = a; // Copy constructor.
}

} // namespace ExpectedTest

// Make sure a function call in a noexcept() clause is ignored.
constexpr bool foo() [[clang::nonblocking(false)]] { return true; }
void nb27() noexcept(foo()) [[clang::nonblocking]] {}

// Make sure that simple type traits don't cause violations.
void nb28() [[clang::nonblocking]] {
bool x = __is_constructible(int, const int&);
}

// --- nonblocking implies noexcept ---
#pragma clang diagnostic warning "-Wperf-constraint-implies-noexcept"

Expand Down