Skip to content

[clang][Sema] Diagnose exceptions only in non-dependent context in discarded try/catch/throw blocks #139859

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 1 commit into from
May 28, 2025

Conversation

Rajveer100
Copy link
Member

Resolves #138939

When enabling --fno-exceptions flag, discarded statements containing try/catch/throw in an independent context can be avoided from being rejected.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels May 14, 2025
@llvmbot
Copy link
Member

llvmbot commented May 14, 2025

@llvm/pr-subscribers-clang

Author: Rajveer Singh Bharadwaj (Rajveer100)

Changes

Resolves #138939

When enabling --fno-exceptions flag, discarded statements containing try/catch/throw in an independent context can be avoided from being rejected.


Full diff: https://github.com/llvm/llvm-project/pull/139859.diff

3 Files Affected:

  • (modified) clang/lib/Sema/SemaExprCXX.cpp (+2-1)
  • (modified) clang/lib/Sema/SemaStmt.cpp (+2-1)
  • (modified) clang/test/SemaCXX/no-exceptions.cpp (+26-1)
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index b2a982e953012..16a4da40eec17 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -854,7 +854,8 @@ ExprResult Sema::BuildCXXThrow(SourceLocation OpLoc, Expr *Ex,
   // Don't report an error if 'throw' is used in system headers or in an OpenMP
   // target region compiled for a GPU architecture.
   if (!IsOpenMPGPUTarget && !getLangOpts().CXXExceptions &&
-      !getSourceManager().isInSystemHeader(OpLoc) && !getLangOpts().CUDA) {
+      !getSourceManager().isInSystemHeader(OpLoc) && !getLangOpts().CUDA &&
+      !CurContext->isDependentContext()) {
     // Delay error emission for the OpenMP device code.
     targetDiag(OpLoc, diag::err_exceptions_disabled) << "throw";
   }
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index e8c1f8490342a..2a0c8b017e2e0 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -4305,7 +4305,8 @@ StmtResult Sema::ActOnCXXTryBlock(SourceLocation TryLoc, Stmt *TryBlock,
   // Don't report an error if 'try' is used in system headers or in an OpenMP
   // target region compiled for a GPU architecture.
   if (!IsOpenMPGPUTarget && !getLangOpts().CXXExceptions &&
-      !getSourceManager().isInSystemHeader(TryLoc) && !getLangOpts().CUDA) {
+      !getSourceManager().isInSystemHeader(TryLoc) && !getLangOpts().CUDA &&
+      !CurContext->isDependentContext()) {
     // Delay error emission for the OpenMP device code.
     targetDiag(TryLoc, diag::err_exceptions_disabled) << "try";
   }
diff --git a/clang/test/SemaCXX/no-exceptions.cpp b/clang/test/SemaCXX/no-exceptions.cpp
index 097123d3fe523..665d43e260601 100644
--- a/clang/test/SemaCXX/no-exceptions.cpp
+++ b/clang/test/SemaCXX/no-exceptions.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 %s
 
 // Various tests for -fno-exceptions
 
@@ -32,3 +32,28 @@ void g() {
 }
 
 }
+
+namespace test2 {
+template <auto enable> void foo(auto &&Fnc) {
+  if constexpr (enable)
+    try {
+      Fnc();
+    } catch (...) {
+    }
+  else
+    Fnc();
+}
+
+void bar1() {
+  foo<false>([] {});
+}
+
+template <typename T> void foo() {
+  try {
+  } catch (...) {
+  }
+  throw 1;
+}
+void bar2() { foo<int>(); }
+
+}

@Rajveer100
Copy link
Member Author

@erichkeane
As expected the following case is being accepted, which shouldn't happen, let me know what you recommend to diagnose this:

template <typename T> void foo() {
  try {
  } catch (...) {
  }
  throw 1;
}
void bar2() { foo<int>(); }

@cor3ntin
Copy link
Contributor

@Rajveer100 I think instead of looking at the context, we should check if the expression (Ex) is instantiation dependent

@erichkeane
Copy link
Collaborator

@Rajveer100 I think instead of looking at the context, we should check if the expression (Ex) is instantiation dependent

I don't think we can just check the expression, the 'throw' itself could be completely fine. Consider:

template<typename T> void foo() {
  throw 1;

The expression is NOT dependent, but we don't wanna diagnose anyway unless it is instantiated.

@erichkeane As expected the following case is being accepted, which shouldn't happen, let me know what you recommend to diagnose this:

template <typename T> void foo() {
  try {
  } catch (...) {
  }
  throw 1;
}
void bar2() { foo<int>(); }

What we will have to do likely is move the checking for this to some piece of common code that is used by TreeTransform for these two. I haven't looked at a good place, but you'll probably see TransformCXXThrowExpr (and an equiv for try) that will call a rebuild/etc type function, which should then call the build (or some variety of a check function).

Also note, even in THOSE cases we have to properly check the decl context, since it could be a partial specialization/instantiation, so please make sure those are tested as well!

@Rajveer100
Copy link
Member Author

Rajveer100 commented May 14, 2025

@Rajveer100 I think instead of looking at the context, we should check if the expression (Ex) is instantiation dependent

Checking the expression causes a crash:

Details

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: /Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang -cc1 -internal-isystem /Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/lib/clang/21/include -nostdsysteminc -fsyntax-only -verify -std=c++20 /Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/clang/test/SemaCXX/no-exceptions.cpp
1.	/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/clang/test/SemaCXX/no-exceptions.cpp:24:8: current parser token ';'
2.	/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/clang/test/SemaCXX/no-exceptions.cpp:22:1: parsing namespace 'test1'
3.	/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/clang/test/SemaCXX/no-exceptions.cpp:23:10: parsing function body 'test1::f'
4.	/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/clang/test/SemaCXX/no-exceptions.cpp:23:10: in compound statement ('{}')
 #0 0x000000010954d198 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x10538d198)
 #1 0x000000010954d71c PrintStackTraceSignalHandler(void*) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x10538d71c)
 #2 0x000000010954b584 llvm::sys::RunSignalHandlers() (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x10538b584)
 #3 0x000000010954dfe8 SignalHandler(int, __siginfo*, void*) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x10538dfe8)
 #4 0x00000001876af624 (/usr/lib/system/libsystem_platform.dylib+0x1804ab624)
 #5 0x000000010a1ef9cc clang::Expr::isInstantiationDependent() const (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x10602f9cc)
 #6 0x000000010d507f54 clang::Sema::BuildCXXThrow(clang::SourceLocation, clang::Expr*, bool) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x109347f54)
 #7 0x000000010d507df0 clang::Sema::ActOnCXXThrow(clang::Scope*, clang::SourceLocation, clang::Expr*) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x109347df0)
 #8 0x000000010cd0e954 clang::Parser::ParseThrowExpression() (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x108b4e954)
 #9 0x000000010ccec734 clang::Parser::ParseAssignmentExpression(clang::TypeCastState) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x108b2c734)
#10 0x000000010ccec620 clang::Parser::ParseExpression(clang::TypeCastState) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x108b2c620)
#11 0x000000010cd88a2c clang::Parser::ParseExprStatement(clang::Parser::ParsedStmtContext) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x108bc8a2c)
#12 0x000000010cd86d74 clang::Parser::ParseStatementOrDeclarationAfterAttributes(llvm::SmallVector<clang::Stmt*, 24u>&, clang::Parser::ParsedStmtContext, clang::SourceLocation*, clang::ParsedAttributes&, clang::ParsedAttributes&) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x108bc6d74)
#13 0x000000010cd865e0 clang::Parser::ParseStatementOrDeclaration(llvm::SmallVector<clang::Stmt*, 24u>&, clang::Parser::ParsedStmtContext, clang::SourceLocation*) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x108bc65e0)
#14 0x000000010cd8f1f8 clang::Parser::ParseCompoundStatementBody(bool) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x108bcf1f8)
#15 0x000000010cd90914 clang::Parser::ParseFunctionStatementBody(clang::Decl*, clang::Parser::ParseScope&) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x108bd0914)
#16 0x000000010cdb078c clang::Parser::ParseFunctionDefinition(clang::ParsingDeclarator&, clang::Parser::ParsedTemplateInfo const&, clang::Parser::LateParsedAttrList*) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x108bf078c)
#17 0x000000010cc8d06c clang::Parser::ParseDeclGroup(clang::ParsingDeclSpec&, clang::DeclaratorContext, clang::ParsedAttributes&, clang::Parser::ParsedTemplateInfo&, clang::SourceLocation*, clang::Parser::ForRangeInit*) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x108acd06c)
#18 0x000000010cdaf884 clang::Parser::ParseDeclOrFunctionDefInternal(clang::ParsedAttributes&, clang::ParsedAttributes&, clang::ParsingDeclSpec&, clang::AccessSpecifier) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x108bef884)
#19 0x000000010cdaee10 clang::Parser::ParseDeclarationOrFunctionDefinition(clang::ParsedAttributes&, clang::ParsedAttributes&, clang::ParsingDeclSpec*, clang::AccessSpecifier) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x108beee10)
#20 0x000000010cdae00c clang::Parser::ParseExternalDeclaration(clang::ParsedAttributes&, clang::ParsedAttributes&, clang::ParsingDeclSpec*) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x108bee00c)
#21 0x000000010ccbee98 clang::Parser::ParseInnerNamespace(llvm::SmallVector<clang::Parser::InnerNamespaceInfo, 4u> const&, unsigned int, clang::SourceLocation&, clang::ParsedAttributes&, clang::BalancedDelimiterTracker&) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x108afee98)
#22 0x000000010ccbe458 clang::Parser::ParseNamespace(clang::DeclaratorContext, clang::SourceLocation&, clang::SourceLocation) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x108afe458)
#23 0x000000010cc8baf0 clang::Parser::ParseDeclaration(clang::DeclaratorContext, clang::SourceLocation&, clang::ParsedAttributes&, clang::ParsedAttributes&, clang::SourceLocation*) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x108acbaf0)
#24 0x000000010cdadaf8 clang::Parser::ParseExternalDeclaration(clang::ParsedAttributes&, clang::ParsedAttributes&, clang::ParsingDeclSpec*) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x108bedaf8)
#25 0x000000010cdac010 clang::Parser::ParseTopLevelDecl(clang::OpaquePtr<clang::DeclGroupRef>&, clang::Sema::ModuleImportState&) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x108bec010)
#26 0x000000010cc70f38 clang::ParseAST(clang::Sema&, bool, bool) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x108ab0f38)
#27 0x000000010b0dd6a4 clang::ASTFrontendAction::ExecuteAction() (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x106f1d6a4)
#28 0x000000010b0dcf38 clang::FrontendAction::Execute() (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x106f1cf38)
#29 0x000000010aff394c clang::CompilerInstance::ExecuteAction(clang::FrontendAction&) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x106e3394c)
#30 0x000000010b21832c clang::ExecuteCompilerInvocation(clang::CompilerInstance*) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x10705832c)
#31 0x00000001041d1b34 cc1_main(llvm::ArrayRef<char const*>, char const*, void*) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x100011b34)
#32 0x00000001041c3700 ExecuteCC1Tool(llvm::SmallVectorImpl<char const*>&, llvm::ToolContext const&) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x100003700)
#33 0x00000001041c2424 clang_main(int, char**, llvm::ToolContext const&) (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x100002424)
#34 0x00000001041fcf0c main (/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang-21+0x10003cf0c)
#35 0x00000001872d6b4c 
/Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/tools/clang/test/SemaCXX/Output/no-exceptions.cpp.script: line 1: 31304 Segmentation fault: 11  /Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/bin/clang -cc1 -internal-isystem /Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/build/lib/clang/21/include -nostdsysteminc -fsyntax-only -verify -std=c++20 /Users/rajveersingh/GitHub-OpenSource/llvm-project/llvm-project/clang/test/SemaCXX/no-exceptions.cpp

--

********************
********************
Failed Tests (1):
  Clang :: SemaCXX/no-exceptions.cpp


Testing Time: 7.40s

Total Discovered Tests: 1
  Failed: 1 (100.00%)

What we will have to do likely is move the checking for this to some piece of common code that is used by TreeTransform for these two. I haven't looked at a good place, but you'll probably see TransformCXXThrowExpr (and an equiv for try) that will call a rebuild/etc type function, which should then call the build (or some variety of a check function).

Also note, even in THOSE cases we have to properly check the decl context, since it could be a partial specialization/instantiation, so please make sure those are tested as well!

I will have a look.

@Rajveer100 Rajveer100 force-pushed the try-throw-discarded-no-exceptions branch from 552e394 to 46a9b01 Compare May 16, 2025 12:03
@Rajveer100
Copy link
Member Author

Rajveer100 commented May 16, 2025

Moving the checks does work for these limited cases, but now that we don't have them there, one of the old tests now doesn't show up the diagnosis:

void f() {
  throw;
}

void g() {
  try {
    f();
  } catch (...) {
  }
}

Since there is a separate call:

return Actions.ActOnCXXTryBlock(TryLoc, TryBlock.get(), Handlers);

and debugging under lldb, breakpoints don't reach (i.e exit early) the transform calls.

@erichkeane
Copy link
Collaborator

Moving the checks does work for these limited cases, but now that we don't have them there, one of the old tests now doesn't show up the diagnosis:

void f() {
  throw;
}

void g() {
  try {
    f();
  } catch (...) {
  }
}

Since there is a separate call:

return Actions.ActOnCXXTryBlock(TryLoc, TryBlock.get(), Handlers);

and debugging under lldb, breakpoints don't reach (i.e exit early) the transform calls.

Right, 'transform' only happens during template instantiation. So we need to find 'some place' (that is a seperate function) to do these diagnostics. I might suggest doing a new one, since there doesn't seem to be a sensible place to do so otherwise... perhaps something like:

void Sema::DiagnoseExceptionUse(bool isTry) {
// do the checking + diagnostic here, but have it check the decl-context for dependence
}

NOTE I had that return void instead of bool. (And is Diagnose instead of Check). I wonder if there is value to continuing (@AaronBallman??) and putting these in the AST anyway? The continued checking is perhaps valuable, and tooling might appreciate it in the AST anyway.

@AaronBallman
Copy link
Collaborator

Moving the checks does work for these limited cases, but now that we don't have them there, one of the old tests now doesn't show up the diagnosis:

void f() {
  throw;
}

void g() {
  try {
    f();
  } catch (...) {
  }
}

Since there is a separate call:

return Actions.ActOnCXXTryBlock(TryLoc, TryBlock.get(), Handlers);

and debugging under lldb, breakpoints don't reach (i.e exit early) the transform calls.

Right, 'transform' only happens during template instantiation. So we need to find 'some place' (that is a seperate function) to do these diagnostics. I might suggest doing a new one, since there doesn't seem to be a sensible place to do so otherwise... perhaps something like:

void Sema::DiagnoseExceptionUse(bool isTry) {
// do the checking + diagnostic here, but have it check the decl-context for dependence
}

NOTE I had that return void instead of bool. (And is Diagnose instead of Check). I wonder if there is value to continuing (@AaronBallman??) and putting these in the AST anyway? The continued checking is perhaps valuable, and tooling might appreciate it in the AST anyway.

Naively, I think -fno-cxx-exceptions would mean that try, catch, and throw are not even keywords; you're opting into a language dialect where exceptions simply don't exist at all. (noexcept is a bit of an outlier, I think the keyword should still exist, but I think noexcept(false) should be a semantic diagnostic.) However, that's not how Clang or GCC have traditionally behaved with this option.

GCC's behavior seems to be "if the code is outside of a discarded statement, then diagnose exception constructs, otherwise do not diagnose exception constructs but still validate that they're otherwise correct" which... is a choice: https://godbolt.org/z/36x5qM4dG GCC documents this as a codegen option so it kind of makes sense from that perspective: https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html#Code-Gen-Options but then they diagnose even when codegen is not enabled, which is confusing: https://godbolt.org/z/fWfzb94e8

Clang's behavior is: "yell, yell loudly, yell as often as you can, don't ever stop yelling", which is... a worse choice: https://godbolt.org/z/75Y3dMr87 Clang documents this as "enabling C++ exceptions" which is completely vague and unhelpful: https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fcxx-exceptions Directly below, we use the same phrasing for -fcxx-modules/-fno-cxx-modules, which behaves how I would expect by disabling the keywords: https://godbolt.org/z/eq4K8E7Mr So our docs are inconsistent as well as unhelpful. :-/

What are the use cases for disabling exceptions but still allowing the constructs in source? e.g., do we want to start disabling the keywords entirely, and thus they won't appear in the AST? Or do we want them to be a codegen-only option, at which point they do appear in the AST and we only diagnose at CodeGen time (which is also inconsistent with GCC)? Or do we want to do whatever GCC does? (Note, Clang has -fcxx-exceptions and GCC does not, so we're already somewhat inconsistent between the two compilers.)

@erichkeane
Copy link
Collaborator

What are the use cases for disabling exceptions but still allowing the constructs in source? e.g., do we want to start disabling the keywords entirely, and thus they won't appear in the AST? Or do we want them to be a codegen-only option, at which point they do appear in the AST and we only diagnose at CodeGen time (which is also inconsistent with GCC)? Or do we want to do whatever GCC does? (Note, Clang has -fcxx-exceptions and GCC does not, so we're already somewhat inconsistent between the two compilers.)

Yep, thats the question :) I think the 1st one (ignoring the construct entirely/stop trying to parse it at all/etc) is a worse experience, and inconsistent with history. AND I wouldn't want people to have a C++ code dialect where they can use 'try' as a variable name.

I don't think we should diagnose at CODEGEN time. I'm suggesting diagnosing them at 'non-dependent/non-discarded' time, but putting them in the AST anyway. This will inhibit codegen, but would mean tooling would be able to see them anyway. WHICH they'll have to do for dependent contexts anyway with the change.

@cor3ntin
Copy link
Contributor

cor3ntin commented May 19, 2025

In C++26, exceptions are allowed in a constant evaluated context, which is used extensively by reflection and maybe P2300. I also think allowing try not to be a keyword in some modes would harm the ecosystem.

Here is an interesting scenario

constexpr void g() {
   throw 42;
} 

constexpr int f() {
   try {g(); } catch {}
   return 0;
}
static_assert(f() == 0); // the call to g() from here should be allowed

int main() {
  return f(); //  but not here if we want to disallow exceptions
}

So, now that I think about it, doing it at code gen is the only solution post C++26

@AaronBallman
Copy link
Collaborator

In C++26, exceptions are allowed in a constant evaluated context, which is used extensively by reflection and maybe P2300.

Good point. :-/

I also think allowing try not to be a keyword in some modes would harm the ecosystem.

[citation needed]

You already cannot safely link code which disables exceptions into code which enables exceptions due to ABI issues, so I'm really not certain what harm would come from letting users use these identifiers themselves if they elect to ignore the language feature.

Here is an interesting scenario

constexpr void g() {
   throw 42;
} 

constexpr int f() {
   try {g(); } catch {}
   return 0;
}
static_assert(f() == 0); // the call to g() from here should be allowed

int main() {
  return f(); //  but not here if we want to disallow exceptions
}

So, now that I think about it, doing it at code gen is the only solution post C++26

I disagree. If the user disabled exceptions, why would they expect them to work in constant expressions?

@Rajveer100
Copy link
Member Author

Rajveer100 commented May 19, 2025

I feel like this issue is a little too niche to be adding such complexity, maybe just covering those specific use cases before codegen might be the way to go?

@AaronBallman
Copy link
Collaborator

I feel like this issue is a little too niche to be adding such complexity, maybe just covering those specific uses cases before codegen might be the way to go?

I think disabling the keywords entirely is a large ask (requires an RFC at a minimum) and so we don't need to go down that route right now.

I think constexpr exceptions are a red herring; if the user disabled C++ exceptions, they should be disabled at compile time same as at runtime. Some users disable exceptions because of overhead, but plenty of users also disable exceptions because of difficulties with reasoning about flow control (some style guides explicitly require exceptions to not be used, for example).

Since we currently validate that the exception constructs are semantically valid, I think we may as well retain them in the AST for now. It's how we currently behave: https://godbolt.org/z/xPbEPWTK8 so it would be consistent to do so in discarded statements too.

@erichkeane
Copy link
Collaborator

I feel like this issue is a little too niche to be adding such complexity, maybe just covering those specific uses cases before codegen might be the way to go?

I think disabling the keywords entirely is a large ask (requires an RFC at a minimum) and so we don't need to go down that route right now.

I would be against said RFC. If you asked me in 1996/whatever, I could be talked into it. But we're 30+ years of this behavior.

I think constexpr exceptions are a red herring; if the user disabled C++ exceptions, they should be disabled at compile time same as at runtime. Some users disable exceptions because of overhead, but plenty of users also disable exceptions because of difficulties with reasoning about flow control (some style guides explicitly require exceptions to not be used, for example).

I'm coming around on this one, I think I agree. -fno-exceptions means no-exceptions. IF we get a further feature request to allow them to work at const-eval time, perhaps we can consider that, but it seems reasonable to do the disallow at the 'fully instantiated' point as I suggested above.

Since we currently validate that the exception constructs are semantically valid, I think we may as well retain them in the AST for now. It's how we currently behave: https://godbolt.org/z/xPbEPWTK8 so it would be consistent to do so in discarded statements too.

@AaronBallman
Copy link
Collaborator

I would be against said RFC. If you asked me in 1996/whatever, I could be talked into it. But we're 30+ years of this behavior.

Doesn't stop WG21 from changing the behavior 30 years later. ;-) My biggest concern is actually that C++ is changing exception handling such that we can no longer hand-wave what this flag means. Does it mean exceptions are enabled or not? If exceptions are not enabled, why are we requiring their use to be correct in source? And why do we want this flag to behave differently from other dialect flags like -fno-modules (https://godbolt.org/z/e77sG6Y7d), -fno-char8_t (https://godbolt.org/z/xan9d5M1P), -fno-wchar (https://godbolt.org/z/7nzeeY4Mq), etc. In fact, as best I can tell, this is the only language dialect mode where we don't act as though we don't know about the keywords.

I think the important question is: what breaks if we were to do this? As best I can tell, nothing. Existing code using the flag continues to behave exactly as it did before, same with existing code not using the flag. The only issue boils down to users writing new code and trying to combine it with other TUs that are not compiled with the flag; but that's invalid anyway for ABI reasons, so I'm not seeing where the concerns are.

@erichkeane
Copy link
Collaborator

I would be against said RFC. If you asked me in 1996/whatever, I could be talked into it. But we're 30+ years of this behavior.

Doesn't stop WG21 from changing the behavior 30 years later. ;-) My biggest concern is actually that C++ is changing exception handling such that we can no longer hand-wave what this flag means. Does it mean exceptions are enabled or not? If exceptions are not enabled, why are we requiring their use to be correct in source? And why do we want this flag to behave differently from other dialect flags like -fno-modules (https://godbolt.org/z/e77sG6Y7d), -fno-char8_t (https://godbolt.org/z/xan9d5M1P), -fno-wchar (https://godbolt.org/z/7nzeeY4Mq), etc. In fact, as best I can tell, this is the only language dialect mode where we don't act as though we don't know about the keywords.

I think the important question is: what breaks if we were to do this? As best I can tell, nothing. Existing code using the flag continues to behave exactly as it did before, same with existing code not using the flag. The only issue boils down to users writing new code and trying to combine it with other TUs that are not compiled with the flag; but that's invalid anyway for ABI reasons, so I'm not seeing where the concerns are.

I don't want to change the behavior that drastically. Folks are USED to this, and we should just stick to it. Putting them back into 'this could be an identifier' is a bit of extra FE headache at the cost of being 'different' on this than everyone else. IMO its not worth the effort nor difference. We should just stick to our current behavior, except allow discarded/uninstantiated statements.

@erichkeane
Copy link
Collaborator

So @Rajveer100 : I think I bottomed out with Aaron. I think the solution of 'create a function to diagnose these, but only if not in a dependent DeclContext', then call this both from where we do the current checking, plus during TreeTransform of these things is the 'right' way forward. So you're close!

We can discuss the REST of the proposals/ideas in the future if we get an RFC from someone.

In addition, I think there is value to considering an additional flag that works with -fno-exceptions/etc to allow in consteval functions, which I think gets us most of the constexpr uses that folks will care about, and be implimentable trivially after this patch is merged.

@AaronBallman
Copy link
Collaborator

So @Rajveer100 : I think I bottomed out with Aaron. I think the solution of 'create a function to diagnose these, but only if not in a dependent DeclContext', then call this both from where we do the current checking, plus during TreeTransform of these things is the 'right' way forward. So you're close!

Agreed, thank you for the discussion!

@Sirraide
Copy link
Member

In addition, I think there is value to considering an additional flag that works with -fno-exceptions/etc to allow in consteval functions, which I think gets us most of the constexpr uses that folks will care about, and be implimentable trivially after this patch is merged.

Yeah, I agree there should be some way to enable exceptions only during constant evaluation once we support that.

@Rajveer100 Rajveer100 force-pushed the try-throw-discarded-no-exceptions branch from 46a9b01 to 214aa7f Compare May 24, 2025 08:21
@Rajveer100
Copy link
Member Author

I have made the changes, let me know if this is fair enough.

@Rajveer100 Rajveer100 force-pushed the try-throw-discarded-no-exceptions branch 2 times, most recently from f3201c1 to 136f1c0 Compare May 24, 2025 16:18
Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

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

Mostly happy, handful of changes but none are design changes.

@Rajveer100 Rajveer100 force-pushed the try-throw-discarded-no-exceptions branch from 136f1c0 to 2fc6e7c Compare May 28, 2025 09:48
@Rajveer100
Copy link
Member Author

Done.

…scarded `try/catch/throw` blocks

Resolves llvm#138939

When enabling `--fno-exceptions` flag, discarded statements containing
`try/catch/throw` in an independent context can be avoided from being
rejected.
@Rajveer100 Rajveer100 force-pushed the try-throw-discarded-no-exceptions branch from 2fc6e7c to a385808 Compare May 28, 2025 09:50
Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

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

LGTM! I'll merge this for you, as I believe you dont' have commit rights.

@erichkeane erichkeane merged commit 59a5c1f into llvm:main May 28, 2025
11 checks passed
@Rajveer100
Copy link
Member Author

Thanks for merging, it would be nice if the members could help me with this:

#139365

sivan-shani pushed a commit to sivan-shani/llvm-project that referenced this pull request Jun 3, 2025
…scarded `try/catch/throw` blocks (llvm#139859)

Resolves llvm#138939

When enabling `--fno-exceptions` flag, discarded statements containing
`try/catch/throw` in an independent context can be avoided from being
rejected.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Clang++ rejects try-catch in discarded if constexpr branch with -fno-exceptions
6 participants