Skip to content

Fix crash with modules and constexpr destructor #69076

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 3 commits into from
Jan 15, 2024

Conversation

hahnjo
Copy link
Member

@hahnjo hahnjo commented Oct 14, 2023

With modules, serialization might omit the outer ExprWithCleanups as it calls ParmVarDecl::getDefaultArg(). Complementary to fixing this in a separate change, make the code more robust by adding a FullExpressionRAII and avoid the llvm_unreachable in the added test clang/test/Modules/pr68702.cpp.

Closes #68702

@hahnjo hahnjo requested review from cor3ntin and shafik October 14, 2023 19:51
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules labels Oct 14, 2023
@llvmbot
Copy link
Member

llvmbot commented Oct 14, 2023

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-modules

Author: Jonas Hahnfeld (hahnjo)

Changes

Closes #68702


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

2 Files Affected:

  • (modified) clang/lib/AST/ExprConstant.cpp (+7-4)
  • (added) clang/test/Modules/pr68702.cpp (+65)
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index e5539dedec02a4b..a97e7bd8140890e 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -15604,10 +15604,13 @@ bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx,
     LValue LVal;
     LVal.set(VD);
 
-    if (!EvaluateInPlace(Value, Info, LVal, this,
-                         /*AllowNonLiteralTypes=*/true) ||
-        EStatus.HasSideEffects)
-      return false;
+    {
+      FullExpressionRAII Scope(Info);
+      if (!EvaluateInPlace(Value, Info, LVal, this,
+                           /*AllowNonLiteralTypes=*/true) ||
+          EStatus.HasSideEffects)
+        return false;
+    }
 
     // At this point, any lifetime-extended temporaries are completely
     // initialized.
diff --git a/clang/test/Modules/pr68702.cpp b/clang/test/Modules/pr68702.cpp
new file mode 100644
index 000000000000000..3f91a1001d1eecc
--- /dev/null
+++ b/clang/test/Modules/pr68702.cpp
@@ -0,0 +1,65 @@
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: split-file %s %t
+
+// RUN: %clang_cc1 -std=c++20 -emit-obj -fmodules -fimplicit-module-maps -fmodules-cache-path=%t %t/main.cpp -o %t/main.o
+
+//--- V.h
+#ifndef V_H
+#define V_H
+
+class A {
+public:
+  constexpr A() { }
+  constexpr ~A() { }
+};
+
+template <typename T>
+class V {
+public:
+  V() = default;
+
+  constexpr V(int n, const A& a = A()) {}
+};
+
+#endif
+
+//--- inst1.h
+#include "V.h"
+
+static void inst1() {
+  V<int> v;
+}
+
+//--- inst2.h
+#include "V.h"
+
+static void inst2() {
+  V<int> v(100);
+}
+
+//--- module.modulemap
+module "M" {
+  export *
+  module "V.h" {
+    export *
+    header "V.h"
+  }
+  module "inst1.h" {
+    export *
+    header "inst1.h"
+  }
+}
+
+module "inst2.h" {
+  export *
+  header "inst2.h"
+}
+
+//--- main.cpp
+#include "V.h"
+#include "inst2.h"
+
+static void m() {
+  static V<int> v(100);
+}

}

//--- module.modulemap
module "M" {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is that the new standard nowadays? Can we use #pragma clang module build and alike to express the module setup?

Copy link
Member Author

Choose a reason for hiding this comment

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

split-file is how many of the modules tests are written, and I find it very handy to reproduce setups with a number of smaller files...

Copy link
Collaborator

Choose a reason for hiding this comment

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

(not expressing any strong opinion on this patch in particular, but it'd be great if the pragmas were more used (but also, they're currently not perfect - my understanding is that -frewrite-imports isn't totally robust yet/needs some more investment) so we had more test coverage for it because it'd be really great if it worked well/was considered when adding new features, etc - and I think it'd be a more direct way to write tests than needing to use the extra split-file invocation, etc (like you could copy a single file/command line to run a modules test, instead of splitting things, having a directory of files to deal with/copy/whatever, etc))

@hahnjo
Copy link
Member Author

hahnjo commented Oct 25, 2023

ping @cor3ntin @shafik, could you have a look here?

Copy link
Member

@ChuanqiXu9 ChuanqiXu9 left a comment

Choose a reason for hiding this comment

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

While the change itself looks neat, I am curious about the reason how this interact with modules.

EStatus.HasSideEffects)
return false;
{
FullExpressionRAII Scope(Info);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we need to add a comment here similar to the one in EvaluateAsConstexpr(...) in this case it would be A full-expression is ... an init-declarator ([dcl.decl]) or a mem-initializer see https://eel.is/c++draft/intro.execution#5.4

CC @cor3ntin for second look

@shafik
Copy link
Collaborator

shafik commented Oct 26, 2023

Please make sure you write a more complete description of the problem and why this solves the problem. The description is usually what goes into the git log and that is very useful for quickly understanding commits and tracking down problems.

I know some folks edit the description on commit but honestly it is very helpful for the reviewers of your PR to have a full description up front.

@shafik
Copy link
Collaborator

shafik commented Oct 26, 2023

I think this change makes sense but I am not crazy about how we deal w/ full-expressions right now with these FullExpressionRAII, it feels very ad-hoc and it takes a bunch of time to understand why they are needed where they are.

@shafik
Copy link
Collaborator

shafik commented Oct 26, 2023

While the change itself looks neat, I am curious about the reason how this interact with modules.

IIUC modules is incidental to the problem, it is just a way we run into an underlying issue that we are not dealing with full-expressions properly in this case.

@hahnjo
Copy link
Member Author

hahnjo commented Oct 30, 2023

I can add the comment as requested, but for the other questions related to full expressions and modules I'd really need input from experts...

@hahnjo
Copy link
Member Author

hahnjo commented Nov 15, 2023

ping @shafik @cor3ntin @ChuanqiXu9, how can we make progress here?

@ChuanqiXu9
Copy link
Member

The key point now is the rationale why this change relates to modules. It looks not good to proceed without understanding this. Otherwise we're playing with a black box... And the concrete method, ..., well, I guess we had no choice but debugging it hardly. For example, comparing the behavior of clang with and without the change.

With modules, serialization might omit the outer ExprWithCleanups
as it calls ParmVarDecl::getDefaultArg(). Complementary to fixing
this in a separate change, make the code more robust by adding a
FullExpressionRAII and avoid the llvm_unreachable in the added test
clang/test/Modules/pr68702.cpp.

Closes llvm#68702
@hahnjo hahnjo force-pushed the constexpr-destructor-modules branch from b567f28 to a55ca99 Compare December 27, 2023 22:32
@hahnjo
Copy link
Member Author

hahnjo commented Dec 27, 2023

I finally had time to debug this: The reason for modules being involved here is because the serialization code calls ParmVarDecl::getDefaultArg() which strips the outermost FullExpr, such as ExprWithCleanups. A potential fix is in #76473 though I'm not really convinced by this asymmetry between getInit() but calling setDefaultArg(). However, removing the handling of FullExpr in setDefaultArg() causes a total 29 test failures, so that's not an (easy) option...

Personally, I would argue that adding FullExpressionRAII makes the code more robust against the absence of ExprWithCleanups so that's maybe a good thing to have regardless of fixing the serialization code.

@ChuanqiXu9
Copy link
Member

I finally had time to debug this: The reason for modules being involved here is because the serialization code calls ParmVarDecl::getDefaultArg() which strips the outermost FullExpr, such as ExprWithCleanups. A potential fix is in #76473 though I'm not really convinced by this asymmetry between getInit() but calling setDefaultArg(). However, removing the handling of FullExpr in setDefaultArg() causes a total 29 test failures, so that's not an (easy) option...

Personally, I would argue that adding FullExpressionRAII makes the code more robust against the absence of ExprWithCleanups so that's maybe a good thing to have regardless of fixing the serialization code.

Thanks for looking into this. Good enough for me. Yeah the asymmetry between getInit() but calling setDefaultArg() is a concerning. Also it may be not easy to understand and fix the underlying problem (why getDefaultArg() will strip the outmost FullExpr) properly.

So personally I am fine with the current workaround with a FIXME. Let's wait for the opinion from @cor3ntin and @shafik

@hahnjo
Copy link
Member Author

hahnjo commented Jan 8, 2024

Ping, is this ok to be accepted and landed?

So personally I am fine with the current workaround with a FIXME.

You mean next to the comment I already added referring to the C++ standard? Can you formulate what I should put there?

// A full-expression is ... an init-declarator ([dcl.decl]) or a
// mem-initializer.
// So we need to make sure temporary objects are destroyed after having
// evaluated the expression (per C++23 [class.temporary]/p4).
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// evaluated the expression (per C++23 [class.temporary]/p4).
// evaluated the expression (per C++23 [class.temporary]/p4).
//
// FIXME: Otherwise this may break test/Modules/pr68702.cpp. because the serialization code
// calls ParmVarDecl::getDefaultArg() which strips the outermost FullExpr, such as ExprWithCleanups.

I mean the reason why this is related to modules. I feel the analysis is valuable.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

@ChuanqiXu9
Copy link
Member

ChuanqiXu9 commented Jan 8, 2024

Ping, is this ok to be accepted and landed?

If it is not hurry, I prefer to wait @cor3ntin to have a look. But given the scale of the patch, it should be good too to land it in 1~2 weeks if there is no other comments.

@hahnjo
Copy link
Member Author

hahnjo commented Jan 8, 2024

Well, this patch is up since almost three months now (!). Sure, we can keep carrying a similar fix downstream, but ideally I would really like to get rid of as many local changes as possible. That's not possible without proper review, but the current situation is quite unsatisfactory...

@ChuanqiXu9
Copy link
Member

Well, this patch is up since almost three months now (!). Sure, we can keep carrying a similar fix downstream, but ideally I would really like to get rid of as many local changes as possible. That's not possible without proper review, but the current situation is quite unsatisfactory...

Yeah, fully understood. I have a lot of similar experiences... 1~2 weeks is not long in comparing with 3 months.

@shafik
Copy link
Collaborator

shafik commented Jan 9, 2024

ping @shafik @cor3ntin @ChuanqiXu9, how can we make progress here?

Please add a release note and address my previous comment: #69076 (comment)

CC @cor3ntin

@hahnjo
Copy link
Member Author

hahnjo commented Jan 10, 2024

address my previous comment: #69076 (comment)

I had already expanded the commit message with the full details, now also copied to the PR summary. Is that sufficient to address the comment?

@cor3ntin
Copy link
Contributor

This change needs a release note.
Please add an entry to clang/docs/ReleaseNotes.rst in the section the most adapted to the change, and referencing any Github issue this change fixes. Thanks!

@hahnjo
Copy link
Member Author

hahnjo commented Jan 10, 2024

Please add a release note

This change needs a release note. Please add an entry to clang/docs/ReleaseNotes.rst in the section the most adapted to the change, and referencing any Github issue this change fixes. Thanks!

Done.

Copy link
Member

@ChuanqiXu9 ChuanqiXu9 left a comment

Choose a reason for hiding this comment

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

LGTM. Thanks for your patience.

@hahnjo hahnjo merged commit 844f833 into llvm:main Jan 15, 2024
@hahnjo hahnjo deleted the constexpr-destructor-modules branch January 15, 2024 07:40
justinfargnoli pushed a commit to justinfargnoli/llvm-project that referenced this pull request Jan 28, 2024
With modules, serialization might omit the outer ExprWithCleanups
as it calls ParmVarDecl::getDefaultArg(). Complementary to fixing
this in a separate change, make the code more robust by adding a
FullExpressionRAII and avoid the llvm_unreachable in the added test
clang/test/Modules/pr68702.cpp.

Closes llvm#68702
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:modules C++20 modules and Clang Header Modules clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Crash with modules and constexpr destructor
7 participants