Skip to content
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

[Serialization] Check for stack exhaustion when reading declarations #79875

Merged
merged 4 commits into from
Jun 3, 2024

Conversation

ilya-biryukov
Copy link
Contributor

Particular example that lead to this is a very long chain of UsingShadowDecls that we hit in our codebase in generated code.

To avoid that, check for stack exhaustion when deserializing the declaration. At that point, we can point to source location of a particular declaration that is being deserialized.

@ilya-biryukov ilya-biryukov added the clang:frontend Language frontend issues, e.g. anything involving "Sema" label Jan 29, 2024
@ilya-biryukov ilya-biryukov self-assigned this Jan 29, 2024
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:modules C++20 modules and Clang Header Modules labels Jan 29, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented Jan 29, 2024

@llvm/pr-subscribers-clang-modules

@llvm/pr-subscribers-clang

Author: Ilya Biryukov (ilya-biryukov)

Changes

Particular example that lead to this is a very long chain of UsingShadowDecls that we hit in our codebase in generated code.

To avoid that, check for stack exhaustion when deserializing the declaration. At that point, we can point to source location of a particular declaration that is being deserialized.


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

2 Files Affected:

  • (modified) clang/lib/Serialization/ASTReaderDecl.cpp (+3-1)
  • (added) clang/test/Modules/lots-of-using-shadow-decls.cpp (+43)
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index 547eb77930b4eec..e2507d7c14a15d0 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -4099,7 +4099,9 @@ Decl *ASTReader::ReadDeclRecord(DeclID ID) {
   // calls to Decl::getASTContext() by Decl's methods will find the
   // TranslationUnitDecl without crashing.
   D->setDeclContext(Context.getTranslationUnitDecl());
-  Reader.Visit(D);
+
+  // Reading some declarations can result in deep recursion.
+  SemaObj->runWithSufficientStackSpace(DeclLoc, [&] { Reader.Visit(D); });
 
   // If this declaration is also a declaration context, get the
   // offsets for its tables of lexical and visible declarations.
diff --git a/clang/test/Modules/lots-of-using-shadow-decls.cpp b/clang/test/Modules/lots-of-using-shadow-decls.cpp
new file mode 100644
index 000000000000000..c3048352842e3f9
--- /dev/null
+++ b/clang/test/Modules/lots-of-using-shadow-decls.cpp
@@ -0,0 +1,43 @@
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: split-file %s %t
+
+// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/usings.cppm -o %t/usings.pcm
+// RUN: %clang_cc1 -std=c++20 -fmodule-file=usings=%t/usings.pcm %t/use.cpp -verify -fsyntax-only -Wno-stack-exhausted
+
+// expected-no-diagnostics
+
+//--- usings.cppm
+export module usings;
+
+#define TYPES1(NAME) DECLARE(NAME##a) DECLARE(NAME##b) DECLARE(NAME##c) \
+    DECLARE(NAME##d) DECLARE(NAME##e) DECLARE(NAME##f) DECLARE(NAME##g) \
+    DECLARE(NAME##h) DECLARE(NAME##i) DECLARE(NAME##j) 
+#define TYPES2(NAME) TYPES1(NAME##a) TYPES1(NAME##b) TYPES1(NAME##c) \
+    TYPES1(NAME##d) TYPES1(NAME##e) TYPES1(NAME##f) TYPES1(NAME##g) \
+    TYPES1(NAME##h) TYPES1(NAME##i) TYPES1(NAME##j) 
+#define TYPES3(NAME) TYPES2(NAME##a) TYPES2(NAME##b) TYPES2(NAME##c) \
+    TYPES2(NAME##d) TYPES2(NAME##e) TYPES2(NAME##f) TYPES2(NAME##g) \
+    TYPES2(NAME##h) TYPES2(NAME##i) TYPES2(NAME##j) 
+#define TYPES4(NAME) TYPES3(NAME##a) TYPES3(NAME##b) TYPES3(NAME##c) \
+    TYPES3(NAME##d) TYPES3(NAME##e) TYPES3(NAME##f) TYPES3(NAME##g)
+
+#define DECLARE(NAME) struct NAME {};
+TYPES4(Type)
+
+export struct Base {
+#undef DECLARE
+#define DECLARE(NAME) void func(NAME*);
+TYPES4(Type)
+};
+
+export struct Derived : Base {
+    using Base::func;
+};
+
+//--- use.cpp
+import usings;
+void test() {
+    Derived().func(nullptr); // expected-error{{ambiguous}}
+    // expected-note@* + {{candidate function}}
+}

Copy link
Contributor Author

@ilya-biryukov ilya-biryukov left a comment

Choose a reason for hiding this comment

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

I think the alternative we could try to chase is turning the chain of using-shadow-decls into an array somehow, so we can process it in a loop instead of recursion.

I am not sure if there is an an approach that does not involve structural changes to the AST (i.e. rewriting the ASTReaderDecl somehow, but keeping the AST the same). Maybe someone more experienced with the serialization could suggest something useful here, that would probably be ideal.

clang/test/Modules/lots-of-using-shadow-decls.cpp Outdated Show resolved Hide resolved
Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

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

I think it makes sense to make this change without tests

@dwblaikie
Copy link
Collaborator

Could you show the stack (omitting/annotating the repeated part) that leads to failure? and/or the AST shape that leads to failure?

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.

To make this testable, maybe we can refactor clang::runWithSufficientStackSpace a little bit to make DesiredStackSize and isStackNearlyExhausted::SufficientStack configurable.

Reader.Visit(D);

// Reading some declarations can result in deep recursion.
SemaObj->runWithSufficientStackSpace(DeclLoc, [&] { Reader.Visit(D); });
Copy link
Member

Choose a reason for hiding this comment

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

Sema may not meaningful in ASTReader (e.g., we're compiling a pcm file).

Suggested change
SemaObj->runWithSufficientStackSpace(DeclLoc, [&] { Reader.Visit(D); });
clang::runWithSufficientStackSpace(...)

Also in this way, we can get better diagnotsics.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could you elaborate in which sense it is non-meaningful for the purpose of calling this method?
I believe SemaObj is never null here (because it's used during deserialization of declarations). The only logic we have in runWithSufficientStackSpace, is to warn about stack exhaustion only once and actually show this warning.

I would still be using Wstack-exhausted here, not sure there is much useful context to add here (maybe a note about deserializing declarations, but this case is so rare I think we can live without it).

Copy link
Member

Choose a reason for hiding this comment

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

As I said, for example, if we're compiling a pcm file to an object file. It makes sense that the Sema is not needed here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did not realize Sema can be null, I have updated the code as you suggested, but it now duplicates the logic we have in Sema to avoid showing the error multiple times.
Please take a look and see if the new version makes sense.

@ChuanqiXu9
Copy link
Member

Another idea is to limit (or check) the threshold for ASTReader::NumCurrentElementsDeserializing. Then we still can print the stack trace by some utils in LLVM also we can get better performance than this.

@ilya-biryukov
Copy link
Contributor Author

Could you show the stack (omitting/annotating the repeated part) that leads to failure? and/or the AST shape that leads to failure?

See the test I added. All you need is ~10k overloads of a method in a class and a using Base::func in the derived class.
The AST represents that as a linked list inside UsingShadowDecl (each shadow decl references the next one). Serializing them works fine, but deserialization is recursive and blows up.

Feel free to just call -ast-dump on the test I posted (it only crashes when reading the pcm, so outputing AST should be fine)

@ilya-biryukov
Copy link
Contributor Author

To make this testable, maybe we can refactor clang::runWithSufficientStackSpace a little bit to make DesiredStackSize and isStackNearlyExhausted::SufficientStack configurable.

Maybe... As long as we only use this in tests.
However, for this particular case, we could also maybe track deserialization depth (similar to template instantiation depth).

I actually landed a patch internally that increased the stack size and it immediately started crashing on another test. I suspect that what happened there is that increasing the stack size causes us to have more recursive calls before we issue a warning and start a new thread. In turn, those recursive calls may lead to stack overflow before we get to the next call of runWithSufficientStackSpace.

I am not saying it's a bad idea to add this for testing purposes, but given how fragile this approach is, I think we should not provide configuration knobs to users there. At least everyone sees crashes at roughly the same points right now (although variation is still present because depending on inlining decisions stacks sizes might vary even with the same code and same limits).

@ChuanqiXu9
Copy link
Member

I am not saying it's a bad idea to add this for testing purposes, but given how fragile this approach is, I think we should not provide configuration knobs to users there. At least everyone sees crashes at roughly the same points right now (although variation is still present because depending on inlining decisions stacks sizes might vary even with the same code and same limits).

Sorry for not being clear enough. What I want is to add parameters to clang::runWithSufficientStackSpace and we can invoke it and test this in unittest. It won't be visible to users.

BTW, how about the idea of using ASTReader::NumCurrentElementsDeserializing?

@ilya-biryukov
Copy link
Contributor Author

ilya-biryukov commented Jan 30, 2024

BTW, how about the idea of using ASTReader::NumCurrentElementsDeserializing?

Oh, sorry, I somehow missed this. I think it's a good idea and somewhat similar to what I was suggesting when mentioning a template instantiation limit we have. There are a few downsides that I see too:

  • with stack exhaustion warning, the compiler can continue (albeit being slow or unstable). The depth limit here will be a hard restriction and so there will be no workaround if the code reaches it.
  • if we make the limit configurable, increasing may simply cause stack overflow.
  • it's weird if we have these limits for reading, but not writing, of pcms. I know it's rooted in the details of our implementation, but it's probably not a great thing from a user perspective the error is coming too late, i.e. I import some code that I don't own and it suddenly errors on me, even though including it worked fine and PCM file was produced fine as well (e.g. the original author might not have even noticed this).

So I think it's a good idea, but looks complementary to this patch. If we use runWithSufficientStackSpace than most of the problems above can at least be worked around. (Except the error should ideally be happening when we write PCM, but that seems hard).

I am curious about performance concerns. I feel that the amount of work in ASTReaderDecl is large enough that this extra call should not add any noticeable overhead on a happy path (when we have enough stack). Or do you think the overhead might be problematic?

Sorry for not being clear enough. What I want is to add parameters to clang::runWithSufficientStackSpace and we can invoke it and test this in unittest. It won't be visible to users.

Just to make sure I got everything right this time. So you mean testing the clang::runWithSufficientStackSpace itself? That seems very reasonable. If we're talking about unit-testing the ASTReaderDecl, this seems like it's quite a bit of work (too much setup code that needs to be put around it).

@ilya-biryukov
Copy link
Contributor Author

ilya-biryukov commented Jan 30, 2024

For the code we ran this into, we would definitely need a workaround that is slow and creates an extra thread. Unfortunately, there is no easy way to change the code generator there so it produces less methods :(

@ChuanqiXu9
Copy link
Member

with stack exhaustion warning, the compiler can continue (albeit being slow or unstable). The depth limit here will be a hard restriction and so there will be no workaround if the code reaches it.

It is a surprise to me that this is only a warning instead of a hard error. I never thought it can continue when the stack runs out. Sorry for confusing.

I am curious about performance concerns. I feel that the amount of work in ASTReaderDecl is large enough that this extra call should not add any noticeable overhead on a happy path (when we have enough stack). Or do you think the overhead might be problematic?

In my imagination, with ASTReader::NumCurrentElementsDeserializing? will only require us to insert a compare between intergers (the value of NumCurrentElementsDeserializing` and the threshold) on the hot path. So it will be simpler than the current method. But given you said above, maybe it doesn't matter so much. I believe the overhead is under control.

Except the error should ideally be happening when we write PCM, but that seems hard

What's the meaning? Isn't this patch about reading?

Just to make sure I got everything right this time. So you mean testing the clang::runWithSufficientStackSpace itself? That seems very reasonable. If we're talking about unit-testing the ASTReaderDecl, this seems like it's quite a bit of work (too much setup code that needs to be put around it).

I mean something like:

First, refactor the signature of clang::runWithSufficientStackSpace to:

namespace clang {
    clang::runWithSufficientStackSpace(function_call_back_for_diagnostic, function_to_run, desired_stack_size = default_value, sufficient_stack_size = default_value);
}

then add two setters to ASTReader

class ASTReader {
    std::optional<size_t> desired_stack_size;
    std::optional<size_t> sufficient_stack_size;
public:
     void set_desired_stack_size(size_t) {...}
     void set_sufficient_stack_size(size_t) {...}
}

then in the unittest under clang/unittests/Serialization, we can generate the BMI for the codes in your current patch (but with smaller scale) like other unit test did.

And we can try to parse the use.cpp in the current patch, we need to construct the CompilerInstance and get the ASTReader and set the corresponding value for desired_stack_size and sufficient_stack_size. Since we're calling clang::runWithSufficientStackSpace with a call back to the diagnostic function, it is easy to test the diagnostic function is called or it shouldn't be hard to test the actual diagnostic message.

This is the code in my mind. I don't feel it would be pretty hard. How do you think about it?

@dwblaikie
Copy link
Collaborator

Could you show the stack (omitting/annotating the repeated part) that leads to failure? and/or the AST shape that leads to failure?

See the test I added. All you need is ~10k overloads of a method in a class and a using Base::func in the derived class. The AST represents that as a linked list inside UsingShadowDecl (each shadow decl references the next one). Serializing them works fine, but deserialization is recursive and blows up.

Feel free to just call -ast-dump on the test I posted (it only crashes when reading the pcm, so outputing AST should be fine)

Yeah, I was hoping to have it in the text of the discussion here without having to do it myself since you've already got the repro locally, presumably... so we can all see/discuss it. But perhaps it's not sufficiently helpful/constructive to worry about - not sure.

@cor3ntin
Copy link
Contributor

cor3ntin commented May 3, 2024

I think we should apply @ChuanqiXu9 suggestion and merge that without tests. At worse it's harmless, at best it solves an actual issue for users. We have precedent for not being able to test resource exhaustion fixes.

@ilya-biryukov
Copy link
Contributor Author

Sorry for loosing track of this change. I will go through the comments and try to land this some time next week.

@AaronBallman
Copy link
Collaborator

I think we should apply @ChuanqiXu9 suggestion and merge that without tests. At worse it's harmless, at best it solves an actual issue for users. We have precedent for not being able to test resource exhaustion fixes.

I would not be opposed to landing with a test, but I think such a test would be challenging because stack exhaustion is often slow to trigger and highly machine-dependent (so I can imagine the test failing "randomly" for different configurations or under different workloads, or being a very slow test to execute). So if there's a simple, quick-to-execute, reliably-failing test we can add, then great! But if there's not, I would be fine landing without tests.

@cor3ntin
Copy link
Contributor

@ilya-biryukov ping!

@ilya-biryukov
Copy link
Contributor Author

Yeah, I was hoping to have it in the text of the discussion here without having to do it myself since you've already got the repro locally, presumably... so we can all see/discuss it. But perhaps it's not sufficiently helpful/constructive to worry about - not sure.

@dwblaikie sorry, I was responding to the AST and somehow missed the stack part. I should have definitely included it from the start, my bad. Here it is:

Click to expand the stack trace
#0  0x000055555b15abdd in clang::ASTReader::ReadDeclRecord(unsigned int) ()
#1  0x000055555bec8504 in clang::ASTReader::GetDecl(unsigned int) [clone .cold] ()
#2  0x000055555b2028de in clang::ASTDeclReader::VisitUsingShadowDecl(clang::UsingShadowDecl*) ()
#3  0x000055555b164667 in clang::ASTReader::ReadDeclRecord(unsigned int) ()
#4  0x000055555bec8504 in clang::ASTReader::GetDecl(unsigned int) [clone .cold] ()
#5  0x000055555b2028de in clang::ASTDeclReader::VisitUsingShadowDecl(clang::UsingShadowDecl*) ()
.............................................
#11814 0x000055555b164667 in clang::ASTReader::ReadDeclRecord(unsigned int) ()
#11815 0x000055555bec8504 in clang::ASTReader::GetDecl(unsigned int) [clone .cold] ()
#11816 0x000055555b206e7a in clang::ASTDeclReader::VisitUsingDecl(clang::UsingDecl*) ()
#11817 0x000055555b164994 in clang::ASTReader::ReadDeclRecord(unsigned int) ()
#11818 0x000055555bae90d8 in clang::ASTReader::FindExternalLexicalDecls(clang::DeclContext const*, llvm::function_ref<bool (clang::Decl::Kind)>, llvm::SmallVectorImpl<clang::Decl*>&) ()
#11819 0x000055555bb0bc69 in clang::DeclContext::LoadLexicalDeclsFromExternalStorage() const ()
#11820 0x000055555bb6b87d in clang::Sema::SetCtorInitializers(clang::CXXConstructorDecl*, bool, llvm::ArrayRef<clang::CXXCtorInitializer*>) ()
#11821 0x000055555bb6e84e in clang::Sema::DefineImplicitMoveConstructor(clang::SourceLocation, clang::CXXConstructorDecl*) ()
#11822 0x000055555961f28c in void llvm::function_ref<void ()>::callback_fn<clang::Sema::MarkFunctionReferenced(clang::SourceLocation, clang::FunctionDecl*, bool)::$_0>(long) [clone .__uniq.244473529911816543723239142225852553696] [clone .llvm.16089407488121690861] ()
#11823 0x000055555ba821b8 in clang::Sema::MarkFunctionReferenced(clang::SourceLocation, clang::FunctionDecl*, bool) ()
#11824 0x000055555bda0be9 in clang::Sema::BuildCXXConstructExpr(clang::SourceLocation, clang::QualType, clang::NamedDecl*, clang::CXXConstructorDecl*, llvm::MutableArrayRef<clang::Expr*>, bool, bool, bool, bool, clang::CXXConstructionKind, clang::SourceRange) ()
#11825 0x000055555ba1fcef in clang::InitializationSequence::Perform(clang::Sema&, clang::InitializedEntity const&, clang::InitializationKind const&, llvm::MutableArrayRef<clang::Expr*>, clang::QualType*) ()
#11826 0x000055555c76f7dd in CollectFieldInitializer(clang::Sema&, (anonymous namespace)::BaseAndFieldInfo&, clang::FieldDecl*, clang::IndirectFieldDecl*) [clone .__uniq.266194649099037377127634867174551623720] [clone .cold] ()
#11827 0x000055555bb6c0ba in clang::Sema::SetCtorInitializers(clang::CXXConstructorDecl*, bool, llvm::ArrayRef<clang::CXXCtorInitializer*>) ()
#11828 0x000055555bb6e84e in clang::Sema::DefineImplicitMoveConstructor(clang::SourceLocation, clang::CXXConstructorDecl*) ()
#11829 0x000055555961f28c in void llvm::function_ref<void ()>::callback_fn<clang::Sema::MarkFunctionReferenced(clang::SourceLocation, clang::FunctionDecl*, bool)::$_0>(long) [clone .__uniq.244473529911816543723239142225852553696] [clone .llvm.16089407488121690861] ()
#11830 0x000055555ba821b8 in clang::Sema::MarkFunctionReferenced(clang::SourceLocation, clang::FunctionDecl*, bool) ()
#11831 0x000055555bda0be9 in clang::Sema::BuildCXXConstructExpr(clang::SourceLocation, clang::QualType, clang::NamedDecl*, clang::CXXConstructorDecl*, llvm::MutableArrayRef<clang::Expr*>, bool, bool, bool, bool, clang::CXXConstructionKind, clang::SourceRange) ()
#11832 0x000055555ba1fcef in clang::InitializationSequence::Perform(clang::Sema&, clang::InitializedEntity const&, clang::InitializationKind const&, llvm::MutableArrayRef<clang::Expr*>, clang::QualType*) ()
#11833 0x000055555bb6dfd8 in BuildImplicitBaseInitializer(clang::Sema&, clang::CXXConstructorDecl*, ImplicitInitializerKind, clang::CXXBaseSpecifier*, bool, clang::CXXCtorInitializer*&) [clone .__uniq.266194649099037377127634867174551623720] ()
#11834 0x000055555bb6b798 in clang::Sema::SetCtorInitializers(clang::CXXConstructorDecl*, bool, llvm::ArrayRef<clang::CXXCtorInitializer*>) ()
#11835 0x000055555bb6e84e in clang::Sema::DefineImplicitMoveConstructor(clang::SourceLocation, clang::CXXConstructorDecl*) ()
#11836 0x000055555961f28c in void llvm::function_ref<void ()>::callback_fn<clang::Sema::MarkFunctionReferenced(clang::SourceLocation, clang::FunctionDecl*, bool)::$_0>(long) [clone .__uniq.244473529911816543723239142225852553696] [clone .llvm.16089407488121690861] ()
#11837 0x000055555ba821b8 in clang::Sema::MarkFunctionReferenced(clang::SourceLocation, clang::FunctionDecl*, bool) ()
#11838 0x000055555bda0be9 in clang::Sema::BuildCXXConstructExpr(clang::SourceLocation, clang::QualType, clang::NamedDecl*, clang::CXXConstructorDecl*, llvm::MutableArrayRef<clang::Expr*>, bool, bool, bool, bool, clang::CXXConstructionKind, clang::SourceRange) ()
#11839 0x000055555ba1fcef in clang::InitializationSequence::Perform(clang::Sema&, clang::InitializedEntity const&, clang::InitializationKind const&, llvm::MutableArrayRef<clang::Expr*>, clang::QualType*) ()
#11840 0x000055555bb6dfd8 in BuildImplicitBaseInitializer(clang::Sema&, clang::CXXConstructorDecl*, ImplicitInitializerKind, clang::CXXBaseSpecifier*, bool, clang::CXXCtorInitializer*&) [clone .__uniq.266194649099037377127634867174551623720] ()
#11841 0x000055555bb6b798 in clang::Sema::SetCtorInitializers(clang::CXXConstructorDecl*, bool, llvm::ArrayRef<clang::CXXCtorInitializer*>) ()
#11842 0x000055555bb6e84e in clang::Sema::DefineImplicitMoveConstructor(clang::SourceLocation, clang::CXXConstructorDecl*) ()
#11843 0x000055555961f28c in void llvm::function_ref<void ()>::callback_fn<clang::Sema::MarkFunctionReferenced(clang::SourceLocation, clang::FunctionDecl*, bool)::$_0>(long) [clone .__uniq.244473529911816543723239142225852553696] [clone .llvm.16089407488121690861] ()
#11844 0x000055555ba821b8 in clang::Sema::MarkFunctionReferenced(clang::SourceLocation, clang::FunctionDecl*, bool) ()
#11845 0x000055555bda0be9 in clang::Sema::BuildCXXConstructExpr(clang::SourceLocation, clang::QualType, clang::NamedDecl*, clang::CXXConstructorDecl*, llvm::MutableArrayRef<clang::Expr*>, bool, bool, bool, bool, clang::CXXConstructionKind, clang::SourceRange) ()
#11846 0x000055555ba1fcef in clang::InitializationSequence::Perform(clang::Sema&, clang::InitializedEntity const&, clang::InitializationKind const&, llvm::MutableArrayRef<clang::Expr*>, clang::QualType*) ()
#11847 0x000055555bb6dfd8 in BuildImplicitBaseInitializer(clang::Sema&, clang::CXXConstructorDecl*, ImplicitInitializerKind, clang::CXXBaseSpecifier*, bool, clang::CXXCtorInitializer*&) [clone .__uniq.266194649099037377127634867174551623720] ()
#11848 0x000055555bb6b798 in clang::Sema::SetCtorInitializers(clang::CXXConstructorDecl*, bool, llvm::ArrayRef<clang::CXXCtorInitializer*>) ()
#11849 0x000055555bb6e84e in clang::Sema::DefineImplicitMoveConstructor(clang::SourceLocation, clang::CXXConstructorDecl*) ()
#11850 0x000055555961f28c in void llvm::function_ref<void ()>::callback_fn<clang::Sema::MarkFunctionReferenced(clang::SourceLocation, clang::FunctionDecl*, bool)::$_0>(long) [clone .__uniq.244473529911816543723239142225852553696] [clone .llvm.16089407488121690861] ()
#11851 0x000055555ba821b8 in clang::Sema::MarkFunctionReferenced(clang::SourceLocation, clang::FunctionDecl*, bool) ()
#11852 0x000055555bda0be9 in clang::Sema::BuildCXXConstructExpr(clang::SourceLocation, clang::QualType, clang::NamedDecl*, clang::CXXConstructorDecl*, llvm::MutableArrayRef<clang::Expr*>, bool, bool, bool, bool, clang::CXXConstructionKind, clang::SourceRange) ()
#11853 0x000055555ba1fcef in clang::InitializationSequence::Perform(clang::Sema&, clang::InitializedEntity const&, clang::InitializationKind const&, llvm::MutableArrayRef<clang::Expr*>, clang::QualType*) ()
#11854 0x000055555bb6dfd8 in BuildImplicitBaseInitializer(clang::Sema&, clang::CXXConstructorDecl*, ImplicitInitializerKind, clang::CXXBaseSpecifier*, bool, clang::CXXCtorInitializer*&) [clone .__uniq.266194649099037377127634867174551623720] ()
#11855 0x000055555bb6b798 in clang::Sema::SetCtorInitializers(clang::CXXConstructorDecl*, bool, llvm::ArrayRef<clang::CXXCtorInitializer*>) ()
#11856 0x000055555bb6e84e in clang::Sema::DefineImplicitMoveConstructor(clang::SourceLocation, clang::CXXConstructorDecl*) ()
#11857 0x000055555961f28c in void llvm::function_ref<void ()>::callback_fn<clang::Sema::MarkFunctionReferenced(clang::SourceLocation, clang::FunctionDecl*, bool)::$_0>(long) [clone .__uniq.244473529911816543723239142225852553696] [clone .llvm.16089407488121690861] ()
#11858 0x000055555ba821b8 in clang::Sema::MarkFunctionReferenced(clang::SourceLocation, clang::FunctionDecl*, bool) ()
#11859 0x000055555bda0be9 in clang::Sema::BuildCXXConstructExpr(clang::SourceLocation, clang::QualType, clang::NamedDecl*, clang::CXXConstructorDecl*, llvm::MutableArrayRef<clang::Expr*>, bool, bool, bool, bool, clang::CXXConstructionKind, clang::SourceRange) ()
#11860 0x000055555ba1fcef in clang::InitializationSequence::Perform(clang::Sema&, clang::InitializedEntity const&, clang::InitializationKind const&, llvm::MutableArrayRef<clang::Expr*>, clang::QualType*) ()
#11861 0x000055555bb6dfd8 in BuildImplicitBaseInitializer(clang::Sema&, clang::CXXConstructorDecl*, ImplicitInitializerKind, clang::CXXBaseSpecifier*, bool, clang::CXXCtorInitializer*&) [clone .__uniq.266194649099037377127634867174551623720] ()
#11862 0x000055555bb6b798 in clang::Sema::SetCtorInitializers(clang::CXXConstructorDecl*, bool, llvm::ArrayRef<clang::CXXCtorInitializer*>) ()
#11863 0x000055555bb6e84e in clang::Sema::DefineImplicitMoveConstructor(clang::SourceLocation, clang::CXXConstructorDecl*) ()
#11864 0x000055555961f28c in void llvm::function_ref<void ()>::callback_fn<clang::Sema::MarkFunctionReferenced(clang::SourceLocation, clang::FunctionDecl*, bool)::$_0>(long) [clone .__uniq.244473529911816543723239142225852553696] [clone .llvm.16089407488121690861] ()
#11865 0x000055555ba821b8 in clang::Sema::MarkFunctionReferenced(clang::SourceLocation, clang::FunctionDecl*, bool) ()
#11866 0x000055555bda0be9 in clang::Sema::BuildCXXConstructExpr(clang::SourceLocation, clang::QualType, clang::NamedDecl*, clang::CXXConstructorDecl*, llvm::MutableArrayRef<clang::Expr*>, bool, bool, bool, bool, clang::CXXConstructionKind, clang::SourceRange) ()
#11867 0x000055555ba1fcef in clang::InitializationSequence::Perform(clang::Sema&, clang::InitializedEntity const&, clang::InitializationKind const&, llvm::MutableArrayRef<clang::Expr*>, clang::QualType*) ()
#11868 0x000055555c76f7dd in CollectFieldInitializer(clang::Sema&, (anonymous namespace)::BaseAndFieldInfo&, clang::FieldDecl*, clang::IndirectFieldDecl*) [clone .__uniq.266194649099037377127634867174551623720] [clone .cold] ()
#11869 0x000055555bb6c0ba in clang::Sema::SetCtorInitializers(clang::CXXConstructorDecl*, bool, llvm::ArrayRef<clang::CXXCtorInitializer*>) ()
#11870 0x000055555bb6e84e in clang::Sema::DefineImplicitMoveConstructor(clang::SourceLocation, clang::CXXConstructorDecl*) ()
#11871 0x000055555961f28c in void llvm::function_ref<void ()>::callback_fn<clang::Sema::MarkFunctionReferenced(clang::SourceLocation, clang::FunctionDecl*, bool)::$_0>(long) [clone .__uniq.244473529911816543723239142225852553696] [clone .llvm.16089407488121690861] ()
#11872 0x000055555ba821b8 in clang::Sema::MarkFunctionReferenced(clang::SourceLocation, clang::FunctionDecl*, bool) ()
#11873 0x000055555bda0be9 in clang::Sema::BuildCXXConstructExpr(clang::SourceLocation, clang::QualType, clang::NamedDecl*, clang::CXXConstructorDecl*, llvm::MutableArrayRef<clang::Expr*>, bool, bool, bool, bool, clang::CXXConstructionKind, clang::SourceRange) ()
#11874 0x000055555ba1fcef in clang::InitializationSequence::Perform(clang::Sema&, clang::InitializedEntity const&, clang::InitializationKind const&, llvm::MutableArrayRef<clang::Expr*>, clang::QualType*) ()
#11875 0x000055555bce8f6a in clang::Sema::BuildCXXNew(clang::SourceRange, bool, clang::SourceLocation, llvm::MutableArrayRef<clang::Expr*>, clang::SourceLocation, clang::SourceRange, clang::QualType, clang::TypeSourceInfo*, std::__u::optional<clang::Expr*>, clang::SourceRange, clang::Expr*) ()
#11876 0x000055555ca73bec in clang::TreeTransform<(anonymous namespace)::TemplateInstantiator>::TransformCXXNewExpr(clang::CXXNewExpr*) [clone .__uniq.16014532493918845222783194145290083557] [clone .cold] ()
#11877 0x000055555998793d in clang::TreeTransform<(anonymous namespace)::TemplateInstantiator>::TransformStmt(clang::Stmt*, clang::TreeTransform<(anonymous namespace)::TemplateInstantiator>::StmtDiscardKind) [clone .__uniq.16014532493918845222783194145290083557] [clone .llvm.10034200992258561765] ()
#11878 0x000055555ba198e3 in clang::TreeTransform<(anonymous namespace)::TemplateInstantiator>::TransformCompoundStmt(clang::CompoundStmt*, bool) [clone .__uniq.16014532493918845222783194145290083557] ()
#11879 0x000055555bae2b42 in clang::Sema::InstantiateFunctionDefinition(clang::SourceLocation, clang::FunctionDecl*, bool, bool, bool) ()

I have updated the code to account for SemaObj potentially being null, but it now contains the same logic that we have in Sema to avoid showing the warning about stack exhaustion twice (and also reuses the one in Sema, when it's available). This is slightly trickier than I would want it to be and the behavior is not ideal, but the best I could come up with so far. Happy to hear alternative suggestions.

@ChuanqiXu9 I would like to experiment with your idea of unit-testing this in a follow-up and land this without tests for now (people seem to be ok landing this without tests, so I hope it's not controversial). I like the idea of unit-testing this, but the amount of moving pieces needed to set this up worries me, I wonder if I can find a way to write a relatively small test for this (specifically, I'm hopeful that there will be some way to reuse existing helpers to make setting up the compiler-instance mostly boilerplate-free).

I will be out until the rest of the week, but my colleague @usx95 can help land this if the PR is approved.

@dwblaikie
Copy link
Collaborator

there's a couple of tests that use ulimit (clang/test/SemaCXX/PR51712-large-array-constexpr-check-oom.cpp and clang/test/PCH/leakfiles.test) - so that technique could be used to test this in a way that's fast enough to check in?

@dwblaikie
Copy link
Collaborator

(a bunch of compiler-rt tests also use ulimit, but doesn't look like any llvm core tests do... )

@ilya-biryukov
Copy link
Contributor Author

there's a couple of tests that use ulimit (clang/test/SemaCXX/PR51712-large-array-constexpr-check-oom.cpp and clang/test/PCH/leakfiles.test) - so that technique could be used to test this in a way that's fast enough to check in?

oh, I didn't realize I can use ulimit in tests, thanks! I will try it out next week.

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.

If we don't insist on testing this in this commit, them LGTM.

clang/lib/Serialization/ASTReader.cpp Outdated Show resolved Hide resolved
@ilya-biryukov
Copy link
Contributor Author

I have tried ulimit and found that Clang relies on a very intricate balance between the stack size, the desired stack size (the cutoff at which we create a new thread with a larger stack) and the points in code where we call runWithSufficientStackSpace.

If the stack limit is very small, we crash on some other code path or even do something weird in the code path checking for stack exhaustion (it currently checks if there's less than 256K stack available, clearly not the right thing if I request a stack of 128K with ulimit in the first place).
If the stack limit is larger, the amount of code required to trigger the crash is high and the test is too slow for checking in (IMO).

I think @ChuanqiXu9 's suggestion of making the parameters (desireableStackSize, in particular) of these checks customizable for unit testing would allow to have a more fine-grained knob and actually have a test for this. I'll try to experiment with this approach and report my findings.

In the meantime, I have addressed the last comment and plan to land this without tests for now.

Particular example that lead to this is a very long chain of
`UsingShadowDecl`s that we hit in our codebase in generated code.

To avoid that, check for stack exhaustion when deserializing the
declaration. At that point, we can point to source location of a
particular declaration that is being deserialized.
…ations

Use clang::runWithSufficientStackSpace, fallback to Sema only when
available.
…ations

remove the test, it was there for illustrative purposes
…ations

Update the comment according to the review suggestion.
@ilya-biryukov ilya-biryukov merged commit 3315f0a into llvm:main Jun 3, 2024
4 of 5 checks passed
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.

None yet

6 participants