Timm =?utf-8?q?Bäder?= <[email protected]>, Timm =?utf-8?q?Bäder?= <[email protected]> Message-ID: In-Reply-To: <llvm.org/llvm/llvm-project/pull/[email protected]>
https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/204781 >From f7e738898d940b538eff47a7467f807047507eb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]> Date: Thu, 18 Jun 2026 17:57:09 +0200 Subject: [PATCH 1/3] puh --- clang/lib/AST/ByteCode/ByteCodeEmitter.h | 4 + clang/lib/AST/ByteCode/Compiler.cpp | 117 +++++++++- clang/lib/AST/ByteCode/Compiler.h | 3 + clang/lib/AST/ByteCode/Context.cpp | 27 +++ clang/lib/AST/ByteCode/Context.h | 6 + clang/lib/AST/ByteCode/EvalEmitter.cpp | 17 ++ clang/lib/AST/ByteCode/EvalEmitter.h | 9 + clang/lib/AST/ExprConstant.cpp | 11 + .../test/AST/ByteCode/builtin-constant-p.cpp | 5 +- clang/test/AST/ByteCode/enable_if.c | 202 ++++++++++++++++++ ...-nested-call-with-valuedependent-param.cpp | 1 + clang/test/SemaCXX/enable_if.cpp | 2 + 12 files changed, 399 insertions(+), 5 deletions(-) create mode 100644 clang/test/AST/ByteCode/enable_if.c diff --git a/clang/lib/AST/ByteCode/ByteCodeEmitter.h b/clang/lib/AST/ByteCode/ByteCodeEmitter.h index 4b42b7eb4063b..34342e53837b9 100644 --- a/clang/lib/AST/ByteCode/ByteCodeEmitter.h +++ b/clang/lib/AST/ByteCode/ByteCodeEmitter.h @@ -50,6 +50,10 @@ class ByteCodeEmitter { virtual bool visitDeclAndReturn(const VarDecl *VD, const Expr *Init, bool ConstantContext) = 0; virtual bool visitDtorCall(const VarDecl *VD, const APValue &) = 0; + virtual bool visitWithSubstitutions(const FunctionDecl *Callee, + ArrayRef<const Expr *> Args, + const Expr *This, + const Expr *Condition) = 0; virtual bool visit(const Expr *E) = 0; virtual bool emitBool(bool V, const Expr *E) = 0; diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index e8572afe8f69c..fa2a68e6edd19 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -5416,6 +5416,115 @@ bool Compiler<Emitter>::visitDtorCall(const VarDecl *VD, const APValue &Value) { return this->emitDestructionPop(D, VD); } +class ParamFinder : public DynamicRecursiveASTVisitorBase<true> { +public: + llvm::SmallPtrSet<const ParmVarDecl *, 1> FoundParams; + explicit ParamFinder() {} + + bool VisitDeclRefExpr(const DeclRefExpr *E) override { + if (const auto *P = dyn_cast<ParmVarDecl>(E->getDecl())) + FoundParams.insert(P); + return true; + } +}; + +/// Evaluate the \p Condition as if it was in the body of \p Callee. +/// Specifically, all the parameters of the callee are available to use +/// for the condition, and their values are given by \p Args (and \p This). +/// +// Since this is a somewhat niche feature, we're abusing a few other mechanisms +// to implement this. +// +// We don't create an actual function frame but instead register the parameters +// as local variables. +// +// So we evaluate something like: +// +// bool thisfunc() { +// auto Arg0 = Args[0]; +// ... +// return Condition; +// } +// +template <class Emitter> +bool Compiler<Emitter>::visitWithSubstitutions(const FunctionDecl *Callee, + ArrayRef<const Expr *> Args, + const Expr *This, + const Expr *Condition) { + // Instead of evaluating all parameters and trying to ignore failure, + // we collect all the parameters used in the condition and only evaluate + // those. Note that we still ignore failure in the loop below because the + // failure might be inconsequential in the end, + // e.g. in the case of `true || x`. + ParamFinder PF; + PF.TraverseStmt(Condition); + + LocalScope<Emitter> ArgScope(this); + for (const ParmVarDecl *PVD : PF.FoundParams) { + unsigned ParamIndex = 0; + for (const ParmVarDecl *P : Callee->parameters()) { + if (P == PVD) + break; + ++ParamIndex; + } + + const Expr *Arg = Args[ParamIndex]; + const ParmVarDecl *Param = Callee->getParamDecl(ParamIndex); + if (OptPrimType ParamT = classify(Param->getType())) { + unsigned ArgOffset = + allocateLocalPrimitive(Param, *ParamT, /*IsConst=*/true); + if (!this->visit(Arg)) + continue; + if (!this->emitSetLocal(*ParamT, ArgOffset, Arg)) + return false; + } else { + UnsignedOrNone ArgOffset = this->allocateLocal(Param, Param->getType()); + if (!ArgOffset) + return false; + if (!this->emitGetPtrLocal(*ArgOffset, Arg)) + return false; + if (!this->visitInitializerPop(Arg)) + continue; + } + } + + if (This) { + // We abuse the init stack for this and tell it to use + // either a local variable or another decl for the This pointer. + this->InitStackActive = true; + + if (This->getType()->isPointerType()) { + // Nothing to do here, the evaluation will fail if the instance + // pointer is used. + } else if (const auto *DRE = dyn_cast<DeclRefExpr>(This)) { + InitStack.push_back(InitLink::Decl(DRE->getDecl())); + } else { + assert(!canClassify(This->getType())); + UnsignedOrNone ArgOffset = this->allocateLocal(This, This->getType()); + if (!ArgOffset) + return false; + if (!this->emitGetPtrLocal(*ArgOffset, This)) + return false; + if (!this->visitInitializerPop(This)) + return false; + this->InitStack.push_back(InitLink::Temp(*ArgOffset)); + } + } + + // Destruction of the argument values is part of the callee frame, + // so we simply ignore them here. + this->VarScope = nullptr; + + LocalScope<Emitter> RetScope(this); + if (!this->visit(Condition)) + return false; + if (!RetScope.destroyLocals()) + return false; + + // Result of the condition should be on the stack. + return this->emitRet(PT_Bool, Condition); +} + template <class Emitter> bool Compiler<Emitter>::visitAPValue(const APValue &Val, PrimType ValType, const Expr *E) { @@ -6042,7 +6151,10 @@ bool Compiler<Emitter>::VisitCXXThisExpr(const CXXThisExpr *E) { if (StartIndex == 0 && EndIndex == 0) EndIndex = InitStack.size() - 1; - assert(StartIndex < EndIndex); + // NOTE: This could be StartIndex < EndIndex, but we're also abusing the + // InitStack mechanism in visitWithSubstitutions to have the This pointer + // _just_ be a local variable. + assert(StartIndex <= EndIndex); // Emit the instructions. for (unsigned I = StartIndex; I != (EndIndex + 1); ++I) { @@ -7702,10 +7814,11 @@ bool Compiler<Emitter>::visitDeclRef(const ValueDecl *D, const Expr *E) { return this->emitGetPtrParam(It->second.Index, E); } - if (!Ctx.getLangOpts().CPlusPlus23 && IsReference) + if (!Ctx.getLangOpts().CPlusPlus23 && IsReference && !Locals.contains(D)) return this->emitInvalidDeclRef(cast<DeclRefExpr>(E), /*InitializerFailed=*/false, E); } + // Local variables. if (auto It = Locals.find(D); It != Locals.end()) { const unsigned Offset = It->second.Offset; diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h index e0008e4eeebc4..741e619230e16 100644 --- a/clang/lib/AST/ByteCode/Compiler.h +++ b/clang/lib/AST/ByteCode/Compiler.h @@ -260,6 +260,9 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>, bool visitDeclAndReturn(const VarDecl *VD, const Expr *Init, bool ConstantContext) override; bool visitDtorCall(const VarDecl *VD, const APValue &Value) override; + bool visitWithSubstitutions(const FunctionDecl *Callee, + ArrayRef<const Expr *> Args, const Expr *This, + const Expr *Condition) override; protected: /// Emits scope cleanup instructions. diff --git a/clang/lib/AST/ByteCode/Context.cpp b/clang/lib/AST/ByteCode/Context.cpp index 4beb35a9a7b43..10cafd4b47934 100644 --- a/clang/lib/AST/ByteCode/Context.cpp +++ b/clang/lib/AST/ByteCode/Context.cpp @@ -387,6 +387,33 @@ Context::tryEvaluateObjectSize(State &Parent, const Expr *E, unsigned Kind) { return Result; } +std::optional<bool> +Context::evaluateWithSubstitution(State &Parent, const FunctionDecl *Callee, + ArrayRef<const Expr *> Args, const Expr *This, + const Expr *Condition) { + if (OptPrimType ConditionT = classify(Condition); + !ConditionT || ConditionT != PT_Bool) { + return std::nullopt; + } + + assert(Stk.empty()); + Compiler<EvalEmitter> C(*this, *P, Parent, Stk); + std::optional<bool> Result = + C.interpretWithSubstitutions(Callee, Args, This, Condition); + + // This is somewhat of a special case here. We don't allow + // evaluateWithSubstitution to recurse (see the Stk.empty() assertion above), + // BUT we allow the args to fail evaluation, which means they can leave some + // garbage on the stack. So we always clear() here, not only if the evaluation + // failed. + Stk.clear(); + if (!Result) { + C.cleanup(); + return std::nullopt; + } + return Result; +} + const LangOptions &Context::getLangOpts() const { return Ctx.getLangOpts(); } static PrimType integralTypeToPrimTypeS(unsigned BitWidth) { diff --git a/clang/lib/AST/ByteCode/Context.h b/clang/lib/AST/ByteCode/Context.h index 789f72ae34f73..47821a3e3a7f3 100644 --- a/clang/lib/AST/ByteCode/Context.h +++ b/clang/lib/AST/ByteCode/Context.h @@ -97,6 +97,12 @@ class Context final { std::optional<uint64_t> tryEvaluateObjectSize(State &Parent, const Expr *E, unsigned Kind); + std::optional<bool> evaluateWithSubstitution(State &Parent, + const FunctionDecl *Callee, + ArrayRef<const Expr *> Args, + const Expr *This, + const Expr *Condition); + /// Returns the AST context. ASTContext &getASTContext() const { return Ctx; } /// Returns the language options. diff --git a/clang/lib/AST/ByteCode/EvalEmitter.cpp b/clang/lib/AST/ByteCode/EvalEmitter.cpp index 7aeac16b9639e..3f0cbf04ee232 100644 --- a/clang/lib/AST/ByteCode/EvalEmitter.cpp +++ b/clang/lib/AST/ByteCode/EvalEmitter.cpp @@ -126,6 +126,23 @@ bool EvalEmitter::interpretCall(const FunctionDecl *FD, const Expr *E) { return this->visitExpr(E, /*DestroyToplevelScope=*/false); } +std::optional<bool> EvalEmitter::interpretWithSubstitutions( + const FunctionDecl *Callee, ArrayRef<const Expr *> Args, const Expr *This, + const Expr *Condition) { + + if (!this->visitWithSubstitutions(Callee, Args, This, Condition)) + return std::nullopt; + + if (EvalResult.empty() || EvalResult.isInvalid()) + return false; + + assert(!EvalResult.empty()); + APValue Result = EvalResult.stealAPValue(); + + assert(Result.isInt()); + return Result.getInt().getBoolValue(); +} + void EvalEmitter::emitLabel(LabelTy Label) { CurrentLabel = Label; } EvalEmitter::LabelTy EvalEmitter::getLabel() { return NextLabel++; } diff --git a/clang/lib/AST/ByteCode/EvalEmitter.h b/clang/lib/AST/ByteCode/EvalEmitter.h index 6fd50da8cad76..b1080c993ab8c 100644 --- a/clang/lib/AST/ByteCode/EvalEmitter.h +++ b/clang/lib/AST/ByteCode/EvalEmitter.h @@ -48,6 +48,11 @@ class EvalEmitter : public SourceMapper { /// function, i.e. the parameters of the function are available for use. bool interpretCall(const FunctionDecl *FD, const Expr *E); + std::optional<bool> interpretWithSubstitutions(const FunctionDecl *Callee, + ArrayRef<const Expr *> Args, + const Expr *This, + const Expr *Condition); + /// Clean up all resources. void cleanup(); @@ -67,6 +72,10 @@ class EvalEmitter : public SourceMapper { virtual bool visitDeclAndReturn(const VarDecl *VD, const Expr *Init, bool ConstantContext) = 0; virtual bool visitDtorCall(const VarDecl *VD, const APValue &Value) = 0; + virtual bool visitWithSubstitutions(const FunctionDecl *Callee, + ArrayRef<const Expr *> Args, + const Expr *This, + const Expr *Condition) = 0; virtual bool visitFunc(const FunctionDecl *F) = 0; virtual bool visit(const Expr *E) = 0; virtual bool emitBool(bool V, const Expr *E) = 0; diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 6ac16c2b831d2..347a527855c9c 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -16303,6 +16303,7 @@ static bool determineEndOffset(EvalInfo &Info, SourceLocation ExprLoc, static std::optional<uint64_t> tryEvaluateBuiltinObjectSize(const Expr *E, unsigned Type, EvalInfo &Info, bool IsDynamic = false) { + // Determine the denoted object. LValue LVal; { @@ -22323,6 +22324,16 @@ bool Expr::EvaluateWithSubstitution(APValue &Value, ASTContext &Ctx, EvalInfo Info(Ctx, Status, EvaluationMode::ConstantExpressionUnevaluated); Info.InConstantContext = true; + if (Info.EnableNewConstInterp) { + if (std::optional<bool> BoolResult = + Info.Ctx.getInterpContext().evaluateWithSubstitution( + Info, Callee, Args, This, this)) { + Value = APValue(APSInt(APInt(1, static_cast<uint64_t>(*BoolResult)))); + return true; + } + return false; + } + LValue ThisVal; const LValue *ThisPtr = nullptr; if (This) { diff --git a/clang/test/AST/ByteCode/builtin-constant-p.cpp b/clang/test/AST/ByteCode/builtin-constant-p.cpp index c6b074e403bed..1738ed1e9328d 100644 --- a/clang/test/AST/ByteCode/builtin-constant-p.cpp +++ b/clang/test/AST/ByteCode/builtin-constant-p.cpp @@ -141,12 +141,11 @@ void test17(void) { // both-note {{use array indexing}} } -/// FIXME -static void foo(int i) __attribute__((__diagnose_if__(!__builtin_constant_p(i), "not constant", "error"))) // expected-note {{from}} +static void foo(int i) __attribute__((__diagnose_if__(!__builtin_constant_p(i), "not constant", "error"))) { } static void bar(int i) { - foo(15); // expected-error {{not constant}} + foo(15); } namespace Inactive { diff --git a/clang/test/AST/ByteCode/enable_if.c b/clang/test/AST/ByteCode/enable_if.c new file mode 100644 index 0000000000000..8148db2719449 --- /dev/null +++ b/clang/test/AST/ByteCode/enable_if.c @@ -0,0 +1,202 @@ +// RUN: %clang_cc1 -verify=ref,both %s +// RUN: %clang_cc1 -verify=expected,both %s -fexperimental-new-constant-interpreter + +// %clang_cc1 %s -DCODEGEN -emit-llvm -o - | FileCheck %s +// %clang_cc1 %s -DCODEGEN -emit-llvm -o - -fexperimental-new-constant-interpreter | FileCheck %s + +/// This is the same file we have in test/Sema/, but there is one test that doesn't yet pass with the bytecode interpreter. +/// TODO: Delete this file and add an appropriate RUN line to the file in test/Sema/ instead. +/// +/// The problem is related to a wrongly computed value in the __builtin_object_size implementation. + +#define O_CREAT 0x100 +typedef int mode_t; +typedef unsigned long size_t; + +enum { TRUE = 1 }; + +int open(const char *pathname, int flags) __attribute__((enable_if(!(flags & O_CREAT), "must specify mode when using O_CREAT"))) __attribute__((overloadable)); // both-note{{candidate disabled: must specify mode when using O_CREAT}} +int open(const char *pathname, int flags, mode_t mode) __attribute__((overloadable)); // both-note{{candidate function not viable: requires 3 arguments, but 2 were provided}} + +void test1(void) { +#ifndef CODEGEN + open("path", O_CREAT); // both-error{{no matching function for call to 'open'}} +#endif + open("path", O_CREAT, 0660); + open("path", 0); + open("path", 0, 0); +} + +size_t __strnlen_chk(const char *s, size_t requested_amount, size_t s_len); + +size_t strnlen(const char *s, size_t maxlen) + __attribute__((overloadable)) + __asm__("strnlen_real1"); + +__attribute__((always_inline)) +inline size_t strnlen(const char *s, size_t maxlen) + __attribute__((overloadable)) + __attribute__((enable_if(__builtin_object_size(s, 0) != -1, + "chosen when target buffer size is known"))) +{ + return __strnlen_chk(s, maxlen, __builtin_object_size(s, 0)); +} + +size_t strnlen(const char *s, size_t maxlen) + __attribute__((overloadable)) + __attribute__((enable_if(__builtin_object_size(s, 0) != -1, + "chosen when target buffer size is known"))) + __attribute__((enable_if(maxlen <= __builtin_object_size(s, 0), + "chosen when 'maxlen' is known to be less than or equal to the buffer size"))) + __asm__("strnlen_real2"); + +size_t strnlen(const char *s, size_t maxlen) // ref-note {{'strnlen' has been explicitly marked unavailable here}} + __attribute__((overloadable)) + __attribute__((enable_if(__builtin_object_size(s, 0) != -1, + "chosen when target buffer size is known"))) + __attribute__((enable_if(maxlen > __builtin_object_size(s, 0), + "chosen when 'maxlen' is larger than the buffer size"))) + __attribute__((unavailable("'maxlen' is larger than the buffer size"))); + +void test2(const char *s, int i) { +// CHECK: define {{.*}}void @test2 + const char c[123] = { 0 }; + strnlen(s, i); +// CHECK: call {{.*}}strnlen_real1 + strnlen(s, 999); +// CHECK: call {{.*}}strnlen_real1 + strnlen(c, 1); +// CHECK: call {{.*}}strnlen_real2 + strnlen(c, i); +// CHECK: call {{.*}}strnlen_chk +#ifndef CODEGEN + strnlen(c, 999); // ref-error{{'strnlen' is unavailable: 'maxlen' is larger than the buffer size}} +#endif +} + +int isdigit(int c) __attribute__((overloadable)); +int isdigit(int c) __attribute__((overloadable)) // both-note {{'isdigit' has been explicitly marked unavailable here}} + __attribute__((enable_if(c <= -1 || c > 255, "'c' must have the value of an unsigned char or EOF"))) + __attribute__((unavailable("'c' must have the value of an unsigned char or EOF"))); + +void test3(int c) { + isdigit(c); // both-warning{{ignoring return value of function declared with pure attribute}} + isdigit(10); // both-warning{{ignoring return value of function declared with pure attribute}} +#ifndef CODEGEN + isdigit(-10); // both-error{{'isdigit' is unavailable: 'c' must have the value of an unsigned char or EOF}} +#endif +} + +// Verify that the alternate spelling __enable_if__ works as well. +int isdigit2(int c) __attribute__((overloadable)); +int isdigit2(int c) __attribute__((overloadable)) // both-note {{'isdigit2' has been explicitly marked unavailable here}} + __attribute__((__enable_if__(c <= -1 || c > 255, "'c' must have the value of an unsigned char or EOF"))) + __attribute__((unavailable("'c' must have the value of an unsigned char or EOF"))); + +void test4(int c) { + isdigit2(c); + isdigit2(10); +#ifndef CODEGEN + isdigit2(-10); // both-error{{'isdigit2' is unavailable: 'c' must have the value of an unsigned char or EOF}} +#endif +} + +void test5(void) { + int (*p1)(int) = &isdigit2; + int (*p2)(int) = isdigit2; + void *p3 = (void *)&isdigit2; + void *p4 = (void *)isdigit2; +} + +#ifndef CODEGEN +__attribute__((enable_if(n == 0, "chosen when 'n' is zero"))) void f1(int n); // both-error{{use of undeclared identifier 'n'}} + +int n __attribute__((enable_if(1, "always chosen"))); // both-warning{{'enable_if' attribute only applies to functions}} + +void f(int n) __attribute__((enable_if("chosen when 'n' is zero", n == 0))); // both-error{{expected string literal as argument of 'enable_if' attribute}} + +void f(int n) __attribute__((enable_if())); // both-error{{'enable_if' attribute requires exactly 2 arguments}} + +void f(int n) __attribute__((enable_if(unresolvedid, "chosen when 'unresolvedid' is non-zero"))); // both-error{{use of undeclared identifier 'unresolvedid'}} + +int global; +void f(int n) __attribute__((enable_if(global == 0, "chosen when 'global' is zero"))); // both-error{{'enable_if' attribute expression never produces a constant expression}} \ + // both-note{{subexpression not valid in a constant expression}} + +enum { cst = 7 }; +void return_cst(void) __attribute__((overloadable)) __attribute__((enable_if(cst == 7, "chosen when 'cst' is 7"))); +void test_return_cst(void) { return_cst(); } + +void f2(void) __attribute__((overloadable)) __attribute__((enable_if(1, "always chosen"))); // #f2_1 +void f2(void) __attribute__((overloadable)) __attribute__((enable_if(0, "never chosen"))); // #f2_2 +void f2(void) __attribute__((overloadable)) __attribute__((enable_if(TRUE, "always chosen #2"))); // #f2_3 +void test6(void) { + void (*p1)(void) = &f2; // both-error {{initializing 'void (*)(void)' with an expression of incompatible type '<overloaded function type>'}} \ + // both-note@#f2_1 {{candidate function}} \ + // both-note@#f2_2 {{candidate function made ineligible by enable_if}} \ + // both-note@#f2_3 {{candidate function}} + void (*p2)(void) = f2; // both-error {{initializing 'void (*)(void)' with an expression of incompatible type '<overloaded function type>'}} \ + // both-note@#f2_1 {{candidate function}} \ + // both-note@#f2_2 {{candidate function made ineligible by enable_if}} \ + // both-note@#f2_3 {{candidate function}} + void *p3 = (void*)&f2; // both-error {{address of overloaded function 'f2' is ambiguous}} \ + // both-note@#f2_1 {{candidate function}} \ + // both-note@#f2_2 {{candidate function made ineligible by enable_if}} \ + // both-note@#f2_3 {{candidate function}} + void *p4 = (void*)f2; // both-error {{address of overloaded function 'f2' is ambiguous}} \ + // both-note@#f2_1 {{candidate function}} \ + // both-note@#f2_2 {{candidate function made ineligible by enable_if}} \ + // both-note@#f2_3 {{candidate function}} +} + +void f3(int m) __attribute__((overloadable)) __attribute__((enable_if(m >= 0, "positive"))); // #f3_1 +void f3(int m) __attribute__((overloadable)) __attribute__((enable_if(m < 0, "negative"))); // #f3_2 +void test7(void) { + void (*p1)(int) = &f3; // both-error {{initializing 'void (*)(int)' with an expression of incompatible type '<overloaded function type>'}} \ + // both-note@#f3_1 {{candidate function made ineligible by enable_if}} \ + // both-note@#f3_2 {{candidate function made ineligible by enable_if}} + void (*p2)(int) = f3; // both-error {{initializing 'void (*)(int)' with an expression of incompatible type '<overloaded function type>'}} \ + // both-note@#f3_1 {{candidate function made ineligible by enable_if}} \ + // both-note@#f3_2 {{candidate function made ineligible by enable_if}} + void *p3 = (void*)&f3; // both-error {{address of overloaded function 'f3' does not match required type 'void'}} \ + // both-note@#f3_1 {{candidate function made ineligible by enable_if}} \ + // both-note@#f3_2 {{candidate function made ineligible by enable_if}} + void *p4 = (void*)f3; // both-error {{address of overloaded function 'f3' does not match required type 'void'}} \ + // both-note@#f3_1 {{candidate function made ineligible by enable_if}} \ + // both-note@#f3_2 {{candidate function made ineligible by enable_if}} +} + +void f4(int m) __attribute__((enable_if(0, ""))); +void test8(void) { + void (*p1)(int) = &f4; // both-error{{cannot take address of function 'f4' because it has one or more non-tautological enable_if conditions}} + void (*p2)(int) = f4; // both-error{{cannot take address of function 'f4' because it has one or more non-tautological enable_if conditions}} +} + +void regular_enable_if(int a) __attribute__((enable_if(a, ""))); // both-note 3{{declared here}} +void PR27122_ext(void) { + regular_enable_if(0, 2); // both-error{{too many arguments}} + regular_enable_if(1, 2); // both-error{{too many arguments}} + regular_enable_if(); // both-error{{too few arguments}} +} + +// We had a bug where we'd crash upon trying to evaluate varargs. +void variadic_enable_if(int a, ...) __attribute__((enable_if(a, ""))); // both-note 6 {{disabled}} +void variadic_test(void) { + variadic_enable_if(1); + variadic_enable_if(1, 2); + variadic_enable_if(1, "c", 3); + + variadic_enable_if(0); // both-error{{no matching}} + variadic_enable_if(0, 2); // both-error{{no matching}} + variadic_enable_if(0, "c", 3); // both-error{{no matching}} + + int m; + variadic_enable_if(1); + variadic_enable_if(1, m); + variadic_enable_if(1, m, "c"); + + variadic_enable_if(0); // both-error{{no matching}} + variadic_enable_if(0, m); // both-error{{no matching}} + variadic_enable_if(0, m, 3); // both-error{{no matching}} +} +#endif diff --git a/clang/test/SemaCXX/enable_if-nested-call-with-valuedependent-param.cpp b/clang/test/SemaCXX/enable_if-nested-call-with-valuedependent-param.cpp index 998f2ccf92534..31a49e9ae3d91 100644 --- a/clang/test/SemaCXX/enable_if-nested-call-with-valuedependent-param.cpp +++ b/clang/test/SemaCXX/enable_if-nested-call-with-valuedependent-param.cpp @@ -1,4 +1,5 @@ // RUN: %clang_cc1 -fsyntax-only %s -std=c++14 +// RUN: %clang_cc1 -fsyntax-only %s -std=c++14 -fexperimental-new-constant-interpreter // Checks that Clang doesn't crash/assert on the nested call to "kaboom" // in "bar()". diff --git a/clang/test/SemaCXX/enable_if.cpp b/clang/test/SemaCXX/enable_if.cpp index 9b35bf2ac0c8d..a34b87064b49d 100644 --- a/clang/test/SemaCXX/enable_if.cpp +++ b/clang/test/SemaCXX/enable_if.cpp @@ -1,5 +1,7 @@ // RUN: %clang_cc1 -std=c++11 -verify %s +// RUN: %clang_cc1 -std=c++11 -verify %s -fexperimental-new-constant-interpreter // RUN: %clang_cc1 -std=c++2a -verify %s +// RUN: %clang_cc1 -std=c++2a -verify %s -fexperimental-new-constant-interpreter typedef int (*fp)(int); int surrogate(int); >From d255d07325af417af12315bb2584471c9abf8f2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]> Date: Fri, 19 Jun 2026 10:50:43 +0200 Subject: [PATCH 2/3] ConstDynamicRecursiveASTVisitor --- clang/lib/AST/ByteCode/Compiler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index fa2a68e6edd19..e354f882e1376 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -5416,7 +5416,7 @@ bool Compiler<Emitter>::visitDtorCall(const VarDecl *VD, const APValue &Value) { return this->emitDestructionPop(D, VD); } -class ParamFinder : public DynamicRecursiveASTVisitorBase<true> { +class ParamFinder : public ConstDynamicRecursiveASTVisitor { public: llvm::SmallPtrSet<const ParmVarDecl *, 1> FoundParams; explicit ParamFinder() {} >From f41ad4078734fa01d8d6a17fd3381c1bad3b75f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]> Date: Fri, 19 Jun 2026 12:32:27 +0200 Subject: [PATCH 3/3] Missing include? --- clang/lib/AST/ByteCode/Compiler.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index e354f882e1376..353362d9d51ec 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -17,6 +17,7 @@ #include "Program.h" #include "clang/AST/Attr.h" #include "llvm/Support/SaveAndRestore.h" +#include "clang/AST/DynamicRecursiveASTVisitor.h" using namespace clang; using namespace clang::interp; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
