This fix issue: https://github.com/llvm/llvm-project/issues/63453
constexpr int foo(unsigned char c) { switch (f) { case 0: return 7; default: break; } return 0; } static_assert(foo('d'));
Differential D153296
[AST] Stop evaluate constant expression if the condition expression which in switch statement contains errors yronglin on Jun 19 2023, 9:43 AM. Authored by
Details This fix issue: https://github.com/llvm/llvm-project/issues/63453 constexpr int foo(unsigned char c) { switch (f) { case 0: return 7; default: break; } return 0; } static_assert(foo('d'));
Diff Detail
Event TimelineComment Actions Oops, compiler explorer website seems crashed, Once it's recovery, I'll append a link. <source>:2:13: error: use of undeclared identifier 'f' 2 | switch (f) { | ^ clang++: /root/llvm-project/llvm/lib/Support/APInt.cpp:282: int llvm::APInt::compareSigned(const llvm::APInt&) const: Assertion `BitWidth == RHS.BitWidth && "Bit widths must be same for comparison"' failed. PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace, preprocessed source, and associated run script. Stack dump: 0. Program arguments: /opt/compiler-explorer/clang-assertions-trunk/bin/clang++ -gdwarf-4 -g -o /app/output.s -mllvm --x86-asm-syntax=intel -S --gcc-toolchain=/opt/compiler-explorer/gcc-snapshot -fcolor-diagnostics -fno-crash-diagnostics -std=c++20 <source> 1. <source>:11:1: current parser token 'static_assert' 2. <source>:1:36: parsing function body 'foo' #0 0x000055cfeaf267ff llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x3c3a7ff) #1 0x000055cfeaf2456c llvm::sys::CleanupOnSignal(unsigned long) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x3c3856c) #2 0x000055cfeae6e528 CrashRecoverySignalHandler(int) CrashRecoveryContext.cpp:0:0 #3 0x00007f6bd7059420 __restore_rt (/lib/x86_64-linux-gnu/libpthread.so.0+0x14420) #4 0x00007f6bd6b2600b raise (/lib/x86_64-linux-gnu/libc.so.6+0x4300b) #5 0x00007f6bd6b05859 abort (/lib/x86_64-linux-gnu/libc.so.6+0x22859) #6 0x00007f6bd6b05729 (/lib/x86_64-linux-gnu/libc.so.6+0x22729) #7 0x00007f6bd6b16fd6 (/lib/x86_64-linux-gnu/libc.so.6+0x33fd6) #8 0x000055cfeae461cd llvm::APInt::compareSigned(llvm::APInt const&) const (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x3b5a1cd) #9 0x000055cfee84a705 EvaluateStmt((anonymous namespace)::StmtResult&, (anonymous namespace)::EvalInfo&, clang::Stmt const*, clang::SwitchCase const*) (.part.0) ExprConstant.cpp:0:0 #10 0x000055cfee84a25f EvaluateStmt((anonymous namespace)::StmtResult&, (anonymous namespace)::EvalInfo&, clang::Stmt const*, clang::SwitchCase const*) (.part.0) ExprConstant.cpp:0:0 #11 0x000055cfee84e961 HandleFunctionCall(clang::SourceLocation, clang::FunctionDecl const*, (anonymous namespace)::LValue const*, llvm::ArrayRef<clang::Expr const*>, (anonymous namespace)::CallRef, clang::Stmt const*, (anonymous namespace)::EvalInfo&, clang::APValue&, (anonymous namespace)::LValue const*) ExprConstant.cpp:0:0 #12 0x000055cfee8918d8 clang::Expr::isPotentialConstantExpr(clang::FunctionDecl const*, llvm::SmallVectorImpl<std::pair<clang::SourceLocation, clang::PartialDiagnostic>>&) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x75a58d8) #13 0x000055cfedaae4de clang::Sema::CheckConstexprFunctionDefinition(clang::FunctionDecl const*, clang::Sema::CheckConstexprKind) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x67c24de) #14 0x000055cfeda05350 clang::Sema::ActOnFinishFunctionBody(clang::Decl*, clang::Stmt*, bool) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x6719350) #15 0x000055cfed77b2dd clang::Parser::ParseFunctionStatementBody(clang::Decl*, clang::Parser::ParseScope&) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x648f2dd) #16 0x000055cfed6a5dc1 clang::Parser::ParseFunctionDefinition(clang::ParsingDeclarator&, clang::Parser::ParsedTemplateInfo const&, clang::Parser::LateParsedAttrList*) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x63b9dc1) #17 0x000055cfed6cccd0 clang::Parser::ParseDeclGroup(clang::ParsingDeclSpec&, clang::DeclaratorContext, clang::ParsedAttributes&, clang::SourceLocation*, clang::Parser::ForRangeInit*) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x63e0cd0) #18 0x000055cfed699661 clang::Parser::ParseDeclOrFunctionDefInternal(clang::ParsedAttributes&, clang::ParsedAttributes&, clang::ParsingDeclSpec&, clang::AccessSpecifier) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x63ad661) #19 0x000055cfed699f1f clang::Parser::ParseDeclarationOrFunctionDefinition(clang::ParsedAttributes&, clang::ParsedAttributes&, clang::ParsingDeclSpec*, clang::AccessSpecifier) (.part.0) Parser.cpp:0:0 #20 0x000055cfed6a08c1 clang::Parser::ParseExternalDeclaration(clang::ParsedAttributes&, clang::ParsedAttributes&, clang::ParsingDeclSpec*) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x63b48c1) #21 0x000055cfed6a1236 clang::Parser::ParseTopLevelDecl(clang::OpaquePtr<clang::DeclGroupRef>&, clang::Sema::ModuleImportState&) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x63b5236) #22 0x000055cfed6a16d4 clang::Parser::ParseFirstTopLevelDecl(clang::OpaquePtr<clang::DeclGroupRef>&, clang::Sema::ModuleImportState&) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x63b56d4) #23 0x000055cfed694fca clang::ParseAST(clang::Sema&, bool, bool) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x63a8fca) #24 0x000055cfec1997d8 clang::CodeGenAction::ExecuteAction() (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x4ead7d8) #25 0x000055cfeb9e6a49 clang::FrontendAction::Execute() (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x46faa49) #26 0x000055cfeb96b996 clang::CompilerInstance::ExecuteAction(clang::FrontendAction&) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x467f996) #27 0x000055cfebacac96 clang::ExecuteCompilerInvocation(clang::CompilerInstance*) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x47dec96) #28 0x000055cfe83d4fed cc1_main(llvm::ArrayRef<char const*>, char const*, void*) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x10e8fed) #29 0x000055cfe83d0cea ExecuteCC1Tool(llvm::SmallVectorImpl<char const*>&, llvm::ToolContext const&) driver.cpp:0:0 #30 0x000055cfeb7ccd5d void llvm::function_ref<void ()>::callback_fn<clang::driver::CC1Command::Execute(llvm::ArrayRef<std::optional<llvm::StringRef>>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>*, bool*) const::'lambda'()>(long) Job.cpp:0:0 #31 0x000055cfeae6ea30 llvm::CrashRecoveryContext::RunSafely(llvm::function_ref<void ()>) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x3b82a30) #32 0x000055cfeb7cd37f clang::driver::CC1Command::Execute(llvm::ArrayRef<std::optional<llvm::StringRef>>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>*, bool*) const (.part.0) Job.cpp:0:0 #33 0x000055cfeb79472c clang::driver::Compilation::ExecuteCommand(clang::driver::Command const&, clang::driver::Command const*&, bool) const (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x44a872c) #34 0x000055cfeb7951bd clang::driver::Compilation::ExecuteJobs(clang::driver::JobList const&, llvm::SmallVectorImpl<std::pair<int, clang::driver::Command const*>>&, bool) const (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x44a91bd) #35 0x000055cfeb79d29d clang::driver::Driver::ExecuteCompilation(clang::driver::Compilation&, llvm::SmallVectorImpl<std::pair<int, clang::driver::Command const*>>&) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x44b129d) #36 0x000055cfe83d324a clang_main(int, char**, llvm::ToolContext const&) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x10e724a) #37 0x000055cfe82d95c5 main (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0xfed5c5) #38 0x00007f6bd6b07083 __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24083) #39 0x000055cfe83cbace _start (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+++0x10dface) clang++: error: clang frontend command failed with exit code 134 (use -v to see invocation) Compiler returned: 134
Comment Actions Also needs a release note.
Comment Actions Just a rewording of the message, else LGTM.
Comment Actions So I think I'm pretty confident that the only time we would call EvaluateDependentExpr is when we are in an error condition, so I'm convinced the fix 'as is' is incorrect. The check for noteSideEffect records that we HAVE a side effect, then returns if we are OK ignoring them right now. So since we are in a state where ignoring this error-case is acceptable, I think returning early there is incorrect as well, at least from a 'code correctness' (even if there isn't a reproducer that would matter?). I think we're in a case where we want to continue in order to ensure we go through the entire flow, so I THINK we should treat this as 'we have a value we don't know, so its just not found', and should fall into the check on 5019 (unless of course, there is a 'default' option!). So I think that we should be checking if Value is valid right after the default check, which lets us fall into the 'default' branch and get additional diagnostics/continued evaluation. WDYT @shafik / @yronglin ? Comment Actions I do see what you are saying but I think that we have similar cases where without a value the next step is impossible to do for example in EvaluateStmt the case Stmt::ReturnStmtClass: case: case Stmt::ReturnStmtClass: { const Expr *RetExpr = cast<ReturnStmt>(S)->getRetValue(); FullExpressionRAII Scope(Info); if (RetExpr && RetExpr->isValueDependent()) { EvaluateDependentExpr(RetExpr, Info); // We know we returned, but we don't know what the value is. return ESR_Failed; } We can't return a value we can't calculate right? and here as well for the Stmt::DoStmtClass case if (DS->getCond()->isValueDependent()) { EvaluateDependentExpr(DS->getCond(), Info); // Bailout as we don't know whether to keep going or terminate the loop. return ESR_Failed; } This case feels the same as the two above, we can't calculate the jump for the switch if we can't calculate the value. CC @rsmith Comment Actions Erich, do you mean we do a modification like this?If I'm not misunderstand, I think this looks good to me, we can get more diagnostics. diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index f1f89122d4cc..967695c61df5 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -4984,8 +4984,7 @@ static EvalStmtResult EvaluateSwitch(StmtResult &Result, EvalInfo &Info, return ESR_Failed; if (SS->getCond()->isValueDependent()) { // Stop evaluate if condition expression contains errors. - if (SS->getCond()->containsErrors() || - !EvaluateDependentExpr(SS->getCond(), Info)) + if (!EvaluateDependentExpr(SS->getCond(), Info)) return ESR_Failed; } else { if (!EvaluateInteger(SS->getCond(), Value, Info)) @@ -4995,6 +4994,8 @@ static EvalStmtResult EvaluateSwitch(StmtResult &Result, EvalInfo &Info, return ESR_Failed; } + bool CondHasSideEffects = SS->getCond()->HasSideEffects(Info.getCtx()); + // Find the switch case corresponding to the value of the condition. // FIXME: Cache this lookup. const SwitchCase *Found = nullptr; @@ -5009,7 +5010,7 @@ static EvalStmtResult EvaluateSwitch(StmtResult &Result, EvalInfo &Info, APSInt LHS = CS->getLHS()->EvaluateKnownConstInt(Info.Ctx); APSInt RHS = CS->getRHS() ? CS->getRHS()->EvaluateKnownConstInt(Info.Ctx) : LHS; - if (LHS <= Value && Value <= RHS) { + if (!CondHasSideEffects && LHS <= Value && Value <= RHS) { Found = SC; break; } Comment Actions In the return-case I think it makes sense to skip out right away, I'm less convinced in the 'doStmt' case. Either way, I think we can get a somewhat 'better' diagnostic by continuing here, which is my point. We COULD start essentially compling with error-limit=1, but there is value to continuing when we can. And in this case, I think it is logical to continue. Instead of the CondHasSideEffects, just check the validity of Value (which is a 1 bit zero value... WHICH is actually a valid value here unfortunately thanks to Bitint, so we'd have to check if the LHS/RHS bit size matches the Value here I think).. We shouldn't be using the HasSideEffects, because that is overly dependent on the implementation of EvaluateDependentExpr. Comment Actions Can we both check SS->getCond()->containsErrors() ? Maybe it can avoid bitint's effect. WDYT? @erichkeane @shafik Comment Actions I'm a TOUCH uncomfortable in that this only fixes the 'current' issue, and is a little fragile perhaps? The problem we have is that the 'Value' is the incorrect size, which I presume we can get to a couple of ways? I'd like to make sure we fix as many of those problems as possible. Comment Actions Seems the diagnostic message <source>:5:9: note: constexpr evaluation hit maximum step limit; possible infinite loop? was redundant, also gcc dose not emit this message. Comment Actions Please help me, I have no better idea on this issue, do you have any better ideas? @erichkeane @shafik Comment Actions I think what's being suggested is to change EvaluateDependentExpr() somewhat along these lines: static bool EvaluateDependentExpr(const Expr *E, EvalInfo &Info) { assert(E->isValueDependent()); // Note that we have a side effect that matters for constant evaluation. bool SideEffects = Info.noteSideEffect(); // If the reason we're here is because of a recovery expression, we don't // want to continue to evaluate further as we will never know what the actual // value is. if (isa<RecoveryExpr>(E)) return false; // Otherwise, return whether we want to continue after noting the side // effects, which should only happen if the expression has errors but isn't // a recovery expression on its own. assert(E->containsErrors() && "valid value-dependent expression should never " "reach invalid code path."); return SideEffects; } This way, code paths that get down to a RecoveryExpr will not continue to evaluate further (as there's really no point -- there's no way to get a reasonable value from from the recovery expression anyway), but the fix isn't specific to just switch statements. After making these changes, you should look for places where EvaluateDependentExpr() is being called to try to come up with a test case where that expression is a recovery expression so that we can fill out test coverage beyond just the situation with switch from the original report. Does that make sense? (Marking as requesting changes so it's clear this review isn't yet accepted.) Comment Actions Thanks a lot! @aaron.ballman , I try to address comments and add more test, this case (https://godbolt.org/z/ExPoEKcrf) looks strange, why the do-statement missing in the printed AST? Comment Actions Address comment.
Comment Actions Thanks for the fix. Sorry for being late (I was looking through the emails and found this patch).
Comment Actions Thanks a lot for your comments! @hokein
Comment Actions Many thanks for all of your comments, I learned a lot from the discussions, your incredible depth of knowledge have helped fundamentally shaped Clang into a great compiler!
Comment Actions Making the return ESR_Failed; unconditional looks to be the correct change here. We can't continue evaluation past that point because we don't know what would be executed next. Unconditionally returning ESR_Failed in that situation is what the other similar paths through EvaluateStmt do.
Comment Actions Thanks for your review!
Comment Actions Thanks for your review! I don't know why the reversion status still Needs Review, and the libcxx ci often fails to start. Comment Actions I guess the Needs Review is probably caused by the previous "Requested Changes" by Aaron. Comment Actions LGTM. thank you! Correct.
Agreed, the libcxx failure is unrelated. |
I don't think the changes to this function are appropriate, because:
So I think we should undo all the changes in this function, and only fix SwitchStmt to properly handle a value-dependent condition.