rsmith created this revision. rsmith added reviewers: rnk, aaron.ballman. Herald added subscribers: cfe-commits, mgorny. Herald added a project: clang.
Clang performs various recursive operations (such as template instantiation), and may use non-trivial amounts of stack space in each recursive step (for instance, due to recursive AST walks). While we try to keep the stack space used by such steps to a minimum and we have explicit limits on the number of such steps we perform, it's impractical to guarantee that we won't blow out the stack on deeply recursive template instantiations on complex ASTs, even with only a moderately high instantiation depth limit. The user experience in these cases is generally terrible: we crash with no hint of what went wrong. Under this patch, we attempt to do better: - Detect when the stack is nearly exhausted, and produce a warning with a nice template instantiation backtrace, telling the user that we might run slowly or crash. - For cases where we're forced to trigger recursive template instantiation in arbitrarily-deeply-nested contexts, check whether we're nearly out of stack space and allocate a new stack (by spawning a new thread) after producing the warning. Repository: rC Clang https://reviews.llvm.org/D66361 Files: include/clang/Basic/DiagnosticSemaKinds.td include/clang/Basic/Stack.h include/clang/Sema/Sema.h lib/Basic/CMakeLists.txt lib/Basic/Stack.cpp lib/Frontend/CompilerInstance.cpp lib/Sema/Sema.cpp lib/Sema/SemaExpr.cpp lib/Sema/SemaInit.cpp lib/Sema/SemaLookup.cpp lib/Sema/SemaTemplate.cpp lib/Sema/SemaTemplateDeduction.cpp lib/Sema/SemaTemplateInstantiate.cpp lib/Sema/SemaTemplateInstantiateDecl.cpp lib/Sema/SemaType.cpp test/SemaTemplate/stack-exhaustion.cpp tools/driver/driver.cpp
Index: tools/driver/driver.cpp =================================================================== --- tools/driver/driver.cpp +++ tools/driver/driver.cpp @@ -13,6 +13,7 @@ #include "clang/Driver/Driver.h" #include "clang/Basic/DiagnosticOptions.h" +#include "clang/Basic/Stack.h" #include "clang/Driver/Compilation.h" #include "clang/Driver/DriverDiagnostic.h" #include "clang/Driver/Options.h" @@ -319,6 +320,7 @@ } int main(int argc_, const char **argv_) { + noteBottomOfStack(); llvm::InitLLVM X(argc_, argv_); SmallVector<const char *, 256> argv(argv_, argv_ + argc_); Index: test/SemaTemplate/stack-exhaustion.cpp =================================================================== --- /dev/null +++ test/SemaTemplate/stack-exhaustion.cpp @@ -0,0 +1,16 @@ +// RUN: %clang_cc1 -verify %s +// expected-warning@* 0-1{{stack nearly exhausted}} +// expected-note@* 0+{{}} + +template<int N> struct X : X<N-1> {}; +template<> struct X<0> {}; +X<1000> x; + +template<typename ...T> struct tuple {}; +template<typename ...T> auto f(tuple<T...> t) -> decltype(f(tuple<T...>(t))) {} // expected-error {{exceeded maximum depth}} +void g() { f(tuple<int, int>()); } + +int f(X<0>); +template<int N> auto f(X<N>) -> f(X<N-1>()); + +int k = f(X<1000>()); Index: lib/Sema/SemaType.cpp =================================================================== --- lib/Sema/SemaType.cpp +++ lib/Sema/SemaType.cpp @@ -7715,7 +7715,9 @@ auto *Def = Var->getDefinition(); if (!Def) { SourceLocation PointOfInstantiation = E->getExprLoc(); - InstantiateVariableDefinition(PointOfInstantiation, Var); + runWithSufficientStackSpace(PointOfInstantiation, [&] { + InstantiateVariableDefinition(PointOfInstantiation, Var); + }); Def = Var->getDefinition(); // If we don't already have a point of instantiation, and we managed @@ -8053,9 +8055,11 @@ } else if (auto *ClassTemplateSpec = dyn_cast<ClassTemplateSpecializationDecl>(RD)) { if (ClassTemplateSpec->getSpecializationKind() == TSK_Undeclared) { - Diagnosed = InstantiateClassTemplateSpecialization( - Loc, ClassTemplateSpec, TSK_ImplicitInstantiation, - /*Complain=*/Diagnoser); + runWithSufficientStackSpace(Loc, [&] { + Diagnosed = InstantiateClassTemplateSpecialization( + Loc, ClassTemplateSpec, TSK_ImplicitInstantiation, + /*Complain=*/Diagnoser); + }); Instantiated = true; } } else { @@ -8066,10 +8070,12 @@ // This record was instantiated from a class within a template. if (MSI->getTemplateSpecializationKind() != TSK_ExplicitSpecialization) { - Diagnosed = InstantiateClass(Loc, RD, Pattern, - getTemplateInstantiationArgs(RD), - TSK_ImplicitInstantiation, - /*Complain=*/Diagnoser); + runWithSufficientStackSpace(Loc, [&] { + Diagnosed = InstantiateClass(Loc, RD, Pattern, + getTemplateInstantiationArgs(RD), + TSK_ImplicitInstantiation, + /*Complain=*/Diagnoser); + }); Instantiated = true; } } Index: lib/Sema/SemaTemplateInstantiateDecl.cpp =================================================================== --- lib/Sema/SemaTemplateInstantiateDecl.cpp +++ lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -3415,7 +3415,11 @@ if (D->isInvalidDecl()) return nullptr; - return Instantiator.Visit(D); + Decl *SubstD; + runWithSufficientStackSpace(D->getLocation(), [&] { + SubstD = Instantiator.Visit(D); + }); + return SubstD; } /// Instantiates a nested template parameter list in the current Index: lib/Sema/SemaTemplateInstantiate.cpp =================================================================== --- lib/Sema/SemaTemplateInstantiate.cpp +++ lib/Sema/SemaTemplateInstantiate.cpp @@ -19,6 +19,7 @@ #include "clang/AST/Expr.h" #include "clang/AST/PrettyDeclStackTrace.h" #include "clang/Basic/LangOptions.h" +#include "clang/Basic/Stack.h" #include "clang/Sema/DeclSpec.h" #include "clang/Sema/Initialization.h" #include "clang/Sema/Lookup.h" @@ -365,6 +366,11 @@ if (!Ctx.isInstantiationRecord()) ++NonInstantiationEntries; + + // Check to see if we're low on stack space. We can't do anything about this + // from here, but we can at least warn the user. + if (isStackNearlyExhausted()) + warnStackExhausted(Ctx.PointOfInstantiation); } void Sema::popCodeSynthesisContext() { Index: lib/Sema/SemaTemplateDeduction.cpp =================================================================== --- lib/Sema/SemaTemplateDeduction.cpp +++ lib/Sema/SemaTemplateDeduction.cpp @@ -4632,8 +4632,11 @@ // We might need to deduce the return type by instantiating the definition // of the operator() function. - if (CallOp->getReturnType()->isUndeducedType()) - InstantiateFunctionDefinition(Loc, CallOp); + if (CallOp->getReturnType()->isUndeducedType()) { + runWithSufficientStackSpace(Loc, [&] { + InstantiateFunctionDefinition(Loc, CallOp); + }); + } } if (CallOp->isInvalidDecl()) @@ -4654,8 +4657,11 @@ return false; } - if (FD->getTemplateInstantiationPattern()) - InstantiateFunctionDefinition(Loc, FD); + if (FD->getTemplateInstantiationPattern()) { + runWithSufficientStackSpace(Loc, [&] { + InstantiateFunctionDefinition(Loc, FD); + }); + } bool StillUndeduced = FD->getReturnType()->isUndeducedType(); if (StillUndeduced && Diagnose && !FD->isInvalidDecl()) { Index: lib/Sema/SemaTemplate.cpp =================================================================== --- lib/Sema/SemaTemplate.cpp +++ lib/Sema/SemaTemplate.cpp @@ -20,6 +20,7 @@ #include "clang/Basic/Builtins.h" #include "clang/Basic/LangOptions.h" #include "clang/Basic/PartialDiagnostic.h" +#include "clang/Basic/Stack.h" #include "clang/Basic/TargetInfo.h" #include "clang/Sema/DeclSpec.h" #include "clang/Sema/Lookup.h" Index: lib/Sema/SemaLookup.cpp =================================================================== --- lib/Sema/SemaLookup.cpp +++ lib/Sema/SemaLookup.cpp @@ -3016,8 +3016,11 @@ SpecialMemberCache.InsertNode(Result, InsertPoint); if (SM == CXXDestructor) { - if (RD->needsImplicitDestructor()) - DeclareImplicitDestructor(RD); + if (RD->needsImplicitDestructor()) { + runWithSufficientStackSpace(RD->getLocation(), [&] { + DeclareImplicitDestructor(RD); + }); + } CXXDestructorDecl *DD = RD->getDestructor(); assert(DD && "record without a destructor"); Result->setMethod(DD); @@ -3040,21 +3043,36 @@ if (SM == CXXDefaultConstructor) { Name = Context.DeclarationNames.getCXXConstructorName(CanTy); NumArgs = 0; - if (RD->needsImplicitDefaultConstructor()) - DeclareImplicitDefaultConstructor(RD); + if (RD->needsImplicitDefaultConstructor()) { + runWithSufficientStackSpace(RD->getLocation(), [&] { + DeclareImplicitDefaultConstructor(RD); + }); + } } else { if (SM == CXXCopyConstructor || SM == CXXMoveConstructor) { Name = Context.DeclarationNames.getCXXConstructorName(CanTy); - if (RD->needsImplicitCopyConstructor()) - DeclareImplicitCopyConstructor(RD); - if (getLangOpts().CPlusPlus11 && RD->needsImplicitMoveConstructor()) - DeclareImplicitMoveConstructor(RD); + if (RD->needsImplicitCopyConstructor()) { + runWithSufficientStackSpace(RD->getLocation(), [&] { + DeclareImplicitCopyConstructor(RD); + }); + } + if (getLangOpts().CPlusPlus11 && RD->needsImplicitMoveConstructor()) { + runWithSufficientStackSpace(RD->getLocation(), [&] { + DeclareImplicitMoveConstructor(RD); + }); + } } else { Name = Context.DeclarationNames.getCXXOperatorName(OO_Equal); - if (RD->needsImplicitCopyAssignment()) - DeclareImplicitCopyAssignment(RD); - if (getLangOpts().CPlusPlus11 && RD->needsImplicitMoveAssignment()) - DeclareImplicitMoveAssignment(RD); + if (RD->needsImplicitCopyAssignment()) { + runWithSufficientStackSpace(RD->getLocation(), [&] { + DeclareImplicitCopyAssignment(RD); + }); + } + if (getLangOpts().CPlusPlus11 && RD->needsImplicitMoveAssignment()) { + runWithSufficientStackSpace(RD->getLocation(), [&] { + DeclareImplicitMoveAssignment(RD); + }); + } } if (ConstArg) @@ -3211,12 +3229,14 @@ DeclContext::lookup_result Sema::LookupConstructors(CXXRecordDecl *Class) { // If the implicit constructors have not yet been declared, do so now. if (CanDeclareSpecialMemberFunction(Class)) { - if (Class->needsImplicitDefaultConstructor()) - DeclareImplicitDefaultConstructor(Class); - if (Class->needsImplicitCopyConstructor()) - DeclareImplicitCopyConstructor(Class); - if (getLangOpts().CPlusPlus11 && Class->needsImplicitMoveConstructor()) - DeclareImplicitMoveConstructor(Class); + runWithSufficientStackSpace(Class->getLocation(), [&] { + if (Class->needsImplicitDefaultConstructor()) + DeclareImplicitDefaultConstructor(Class); + if (Class->needsImplicitCopyConstructor()) + DeclareImplicitCopyConstructor(Class); + if (getLangOpts().CPlusPlus11 && Class->needsImplicitMoveConstructor()) + DeclareImplicitMoveConstructor(Class); + }); } CanQualType T = Context.getCanonicalType(Context.getTypeDeclType(Class)); Index: lib/Sema/SemaInit.cpp =================================================================== --- lib/Sema/SemaInit.cpp +++ lib/Sema/SemaInit.cpp @@ -6242,8 +6242,11 @@ // the definition for completely trivial constructors. assert(Constructor->getParent() && "No parent class for constructor."); if (Constructor->isDefaulted() && Constructor->isDefaultConstructor() && - Constructor->isTrivial() && !Constructor->isUsed(false)) - S.DefineImplicitDefaultConstructor(Loc, Constructor); + Constructor->isTrivial() && !Constructor->isUsed(false)) { + S.runWithSufficientStackSpace(Loc, [&] { + S.DefineImplicitDefaultConstructor(Loc, Constructor); + }); + } } ExprResult CurInit((Expr *)nullptr); Index: lib/Sema/SemaExpr.cpp =================================================================== --- lib/Sema/SemaExpr.cpp +++ lib/Sema/SemaExpr.cpp @@ -4832,8 +4832,10 @@ // default argument expression appears. ContextRAII SavedContext(*this, FD); LocalInstantiationScope Local(*this); - Result = SubstInitializer(UninstExpr, MutiLevelArgList, - /*DirectInit*/false); + runWithSufficientStackSpace(CallLoc, [&] { + Result = SubstInitializer(UninstExpr, MutiLevelArgList, + /*DirectInit*/false); + }); } if (Result.isInvalid()) return true; @@ -15065,6 +15067,17 @@ if (IsRecursiveCall && OdrUse == OdrUseContext::Used) OdrUse = OdrUseContext::FormallyOdrUsed; + // Trivial default constructors and destructors are never actually used. + // FIXME: What about other special members? + if (Func->isTrivial() && !Func->hasAttr<DLLExportAttr>() && + OdrUse == OdrUseContext::Used) { + if (auto *Constructor = dyn_cast<CXXConstructorDecl>(Func)) + if (Constructor->isDefaultConstructor()) + OdrUse = OdrUseContext::FormallyOdrUsed; + if (isa<CXXDestructorDecl>(Func)) + OdrUse = OdrUseContext::FormallyOdrUsed; + } + // C++20 [expr.const]p12: // A function [...] is needed for constant evaluation if it is [...] a // constexpr function that is named by an expression that is potentially @@ -15125,98 +15138,101 @@ // If we need a definition, try to create one. if (NeedDefinition && !Func->getBody()) { - if (CXXConstructorDecl *Constructor = dyn_cast<CXXConstructorDecl>(Func)) { - Constructor = cast<CXXConstructorDecl>(Constructor->getFirstDecl()); - if (Constructor->isDefaulted() && !Constructor->isDeleted()) { - if (Constructor->isDefaultConstructor()) { - if (Constructor->isTrivial() && - !Constructor->hasAttr<DLLExportAttr>()) + runWithSufficientStackSpace(Loc, [&] { + if (CXXConstructorDecl *Constructor = + dyn_cast<CXXConstructorDecl>(Func)) { + Constructor = cast<CXXConstructorDecl>(Constructor->getFirstDecl()); + if (Constructor->isDefaulted() && !Constructor->isDeleted()) { + if (Constructor->isDefaultConstructor()) { + if (Constructor->isTrivial() && + !Constructor->hasAttr<DLLExportAttr>()) + return; + DefineImplicitDefaultConstructor(Loc, Constructor); + } else if (Constructor->isCopyConstructor()) { + DefineImplicitCopyConstructor(Loc, Constructor); + } else if (Constructor->isMoveConstructor()) { + DefineImplicitMoveConstructor(Loc, Constructor); + } + } else if (Constructor->getInheritedConstructor()) { + DefineInheritingConstructor(Loc, Constructor); + } + } else if (CXXDestructorDecl *Destructor = + dyn_cast<CXXDestructorDecl>(Func)) { + Destructor = cast<CXXDestructorDecl>(Destructor->getFirstDecl()); + if (Destructor->isDefaulted() && !Destructor->isDeleted()) { + if (Destructor->isTrivial() && !Destructor->hasAttr<DLLExportAttr>()) return; - DefineImplicitDefaultConstructor(Loc, Constructor); - } else if (Constructor->isCopyConstructor()) { - DefineImplicitCopyConstructor(Loc, Constructor); - } else if (Constructor->isMoveConstructor()) { - DefineImplicitMoveConstructor(Loc, Constructor); + DefineImplicitDestructor(Loc, Destructor); } - } else if (Constructor->getInheritedConstructor()) { - DefineInheritingConstructor(Loc, Constructor); + if (Destructor->isVirtual() && getLangOpts().AppleKext) + MarkVTableUsed(Loc, Destructor->getParent()); + } else if (CXXMethodDecl *MethodDecl = dyn_cast<CXXMethodDecl>(Func)) { + if (MethodDecl->isOverloadedOperator() && + MethodDecl->getOverloadedOperator() == OO_Equal) { + MethodDecl = cast<CXXMethodDecl>(MethodDecl->getFirstDecl()); + if (MethodDecl->isDefaulted() && !MethodDecl->isDeleted()) { + if (MethodDecl->isCopyAssignmentOperator()) + DefineImplicitCopyAssignment(Loc, MethodDecl); + else if (MethodDecl->isMoveAssignmentOperator()) + DefineImplicitMoveAssignment(Loc, MethodDecl); + } + } else if (isa<CXXConversionDecl>(MethodDecl) && + MethodDecl->getParent()->isLambda()) { + CXXConversionDecl *Conversion = + cast<CXXConversionDecl>(MethodDecl->getFirstDecl()); + if (Conversion->isLambdaToBlockPointerConversion()) + DefineImplicitLambdaToBlockPointerConversion(Loc, Conversion); + else + DefineImplicitLambdaToFunctionPointerConversion(Loc, Conversion); + } else if (MethodDecl->isVirtual() && getLangOpts().AppleKext) + MarkVTableUsed(Loc, MethodDecl->getParent()); } - } else if (CXXDestructorDecl *Destructor = - dyn_cast<CXXDestructorDecl>(Func)) { - Destructor = cast<CXXDestructorDecl>(Destructor->getFirstDecl()); - if (Destructor->isDefaulted() && !Destructor->isDeleted()) { - if (Destructor->isTrivial() && !Destructor->hasAttr<DLLExportAttr>()) - return; - DefineImplicitDestructor(Loc, Destructor); - } - if (Destructor->isVirtual() && getLangOpts().AppleKext) - MarkVTableUsed(Loc, Destructor->getParent()); - } else if (CXXMethodDecl *MethodDecl = dyn_cast<CXXMethodDecl>(Func)) { - if (MethodDecl->isOverloadedOperator() && - MethodDecl->getOverloadedOperator() == OO_Equal) { - MethodDecl = cast<CXXMethodDecl>(MethodDecl->getFirstDecl()); - if (MethodDecl->isDefaulted() && !MethodDecl->isDeleted()) { - if (MethodDecl->isCopyAssignmentOperator()) - DefineImplicitCopyAssignment(Loc, MethodDecl); - else if (MethodDecl->isMoveAssignmentOperator()) - DefineImplicitMoveAssignment(Loc, MethodDecl); - } - } else if (isa<CXXConversionDecl>(MethodDecl) && - MethodDecl->getParent()->isLambda()) { - CXXConversionDecl *Conversion = - cast<CXXConversionDecl>(MethodDecl->getFirstDecl()); - if (Conversion->isLambdaToBlockPointerConversion()) - DefineImplicitLambdaToBlockPointerConversion(Loc, Conversion); - else - DefineImplicitLambdaToFunctionPointerConversion(Loc, Conversion); - } else if (MethodDecl->isVirtual() && getLangOpts().AppleKext) - MarkVTableUsed(Loc, MethodDecl->getParent()); - } - // Implicit instantiation of function templates and member functions of - // class templates. - if (Func->isImplicitlyInstantiable()) { - TemplateSpecializationKind TSK = - Func->getTemplateSpecializationKindForInstantiation(); - SourceLocation PointOfInstantiation = Func->getPointOfInstantiation(); - bool FirstInstantiation = PointOfInstantiation.isInvalid(); - if (FirstInstantiation) { - PointOfInstantiation = Loc; - Func->setTemplateSpecializationKind(TSK, PointOfInstantiation); - } else if (TSK != TSK_ImplicitInstantiation) { - // Use the point of use as the point of instantiation, instead of the - // point of explicit instantiation (which we track as the actual point - // of instantiation). This gives better backtraces in diagnostics. - PointOfInstantiation = Loc; - } + // Implicit instantiation of function templates and member functions of + // class templates. + if (Func->isImplicitlyInstantiable()) { + TemplateSpecializationKind TSK = + Func->getTemplateSpecializationKindForInstantiation(); + SourceLocation PointOfInstantiation = Func->getPointOfInstantiation(); + bool FirstInstantiation = PointOfInstantiation.isInvalid(); + if (FirstInstantiation) { + PointOfInstantiation = Loc; + Func->setTemplateSpecializationKind(TSK, PointOfInstantiation); + } else if (TSK != TSK_ImplicitInstantiation) { + // Use the point of use as the point of instantiation, instead of the + // point of explicit instantiation (which we track as the actual point + // of instantiation). This gives better backtraces in diagnostics. + PointOfInstantiation = Loc; + } - if (FirstInstantiation || TSK != TSK_ImplicitInstantiation || - Func->isConstexpr()) { - if (isa<CXXRecordDecl>(Func->getDeclContext()) && - cast<CXXRecordDecl>(Func->getDeclContext())->isLocalClass() && - CodeSynthesisContexts.size()) - PendingLocalImplicitInstantiations.push_back( - std::make_pair(Func, PointOfInstantiation)); - else if (Func->isConstexpr()) - // Do not defer instantiations of constexpr functions, to avoid the - // expression evaluator needing to call back into Sema if it sees a - // call to such a function. - InstantiateFunctionDefinition(PointOfInstantiation, Func); - else { - Func->setInstantiationIsPending(true); - PendingInstantiations.push_back( - std::make_pair(Func, PointOfInstantiation)); - // Notify the consumer that a function was implicitly instantiated. - Consumer.HandleCXXImplicitFunctionInstantiation(Func); + if (FirstInstantiation || TSK != TSK_ImplicitInstantiation || + Func->isConstexpr()) { + if (isa<CXXRecordDecl>(Func->getDeclContext()) && + cast<CXXRecordDecl>(Func->getDeclContext())->isLocalClass() && + CodeSynthesisContexts.size()) + PendingLocalImplicitInstantiations.push_back( + std::make_pair(Func, PointOfInstantiation)); + else if (Func->isConstexpr()) + // Do not defer instantiations of constexpr functions, to avoid the + // expression evaluator needing to call back into Sema if it sees a + // call to such a function. + InstantiateFunctionDefinition(PointOfInstantiation, Func); + else { + Func->setInstantiationIsPending(true); + PendingInstantiations.push_back( + std::make_pair(Func, PointOfInstantiation)); + // Notify the consumer that a function was implicitly instantiated. + Consumer.HandleCXXImplicitFunctionInstantiation(Func); + } + } + } else { + // Walk redefinitions, as some of them may be instantiable. + for (auto i : Func->redecls()) { + if (!i->isUsed(false) && i->isImplicitlyInstantiable()) + MarkFunctionReferenced(Loc, i, MightBeOdrUse); } } - } else { - // Walk redefinitions, as some of them may be instantiable. - for (auto i : Func->redecls()) { - if (!i->isUsed(false) && i->isImplicitlyInstantiable()) - MarkFunctionReferenced(Loc, i, MightBeOdrUse); - } - } + }); } // If this is the first "real" use, act on that. @@ -16378,7 +16394,9 @@ if (UsableInConstantExpr) { // Do not defer instantiations of variables that could be used in a // constant expression. - SemaRef.InstantiateVariableDefinition(PointOfInstantiation, Var); + SemaRef.runWithSufficientStackSpace(PointOfInstantiation, [&] { + SemaRef.InstantiateVariableDefinition(PointOfInstantiation, Var); + }); } else if (FirstInstantiation || isa<VarTemplateSpecializationDecl>(Var)) { // FIXME: For a specialization of a variable template, we don't Index: lib/Sema/Sema.cpp =================================================================== --- lib/Sema/Sema.cpp +++ lib/Sema/Sema.cpp @@ -22,6 +22,7 @@ #include "clang/AST/StmtCXX.h" #include "clang/Basic/DiagnosticOptions.h" #include "clang/Basic/PartialDiagnostic.h" +#include "clang/Basic/Stack.h" #include "clang/Basic/TargetInfo.h" #include "clang/Lex/HeaderSearch.h" #include "clang/Lex/Preprocessor.h" @@ -386,6 +387,19 @@ assert(DelayedTypos.empty() && "Uncorrected typos!"); } +void Sema::warnStackExhausted(SourceLocation Loc) { + // Only warn about this once. + if (!WarnedStackExhausted) { + Diag(Loc, diag::warn_stack_exhausted); + WarnedStackExhausted = true; + } +} + +void Sema::runWithSufficientStackSpace(SourceLocation Loc, + llvm::function_ref<void()> Fn) { + clang::runWithSufficientStackSpace([&] { warnStackExhausted(Loc); }, Fn); +} + /// makeUnavailableInSystemHeader - There is an error in the current /// context. If we're still in a system header, and we can plausibly /// make the relevant declaration unavailable instead of erroring, do Index: lib/Frontend/CompilerInstance.cpp =================================================================== --- lib/Frontend/CompilerInstance.cpp +++ lib/Frontend/CompilerInstance.cpp @@ -886,6 +886,11 @@ assert(!getFrontendOpts().ShowHelp && "Client must handle '-help'!"); assert(!getFrontendOpts().ShowVersion && "Client must handle '-version'!"); + // Mark this point as the bottom of the stack if we don't have somewhere + // better. We generally expect frontend actions to be invoked with (nearly) + // DesiredStackSpace available. + noteBottomOfStack(); + // FIXME: Take this as an argument, once all the APIs we used have moved to // taking it as an input instead of hard-coding llvm::errs. raw_ostream &OS = llvm::errs(); Index: lib/Basic/Stack.cpp =================================================================== --- /dev/null +++ lib/Basic/Stack.cpp @@ -0,0 +1,69 @@ +//===--- Stack.h - Utilities for dealing with stack space -------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// Defines utilities for dealing with stack allocation and stack space. +/// +//===----------------------------------------------------------------------===// + +#include "clang/Basic/Stack.h" +#include "llvm/ADT/Optional.h" +#include "llvm/Support/CrashRecoveryContext.h" + +static LLVM_THREAD_LOCAL void *BottomOfStack = nullptr; + +static void *getStackPointer() { +#if __GNUC__ || __has_builtin(__builtin_frame_address) + return __builtin_frame_address(0); +#else + char CharOnStack = 0; + // The volatile store here is intended to escape the local variable, to + // prevent the compiler from optimizing CharOnStack into anything other + // than a char on the stack. + // + // Tested on: MSVC 2015 - 2019, GCC 4.9 - 9, Clang 3.2 - 9, ICC 13 - 19. + char *volatile Ptr = &CharOnStack; + return Ptr; +#endif +} + +void clang::noteBottomOfStack() { + if (!BottomOfStack) + BottomOfStack = getStackPointer(); +} + +bool clang::isStackNearlyExhausted() { + // We consider 256 KiB to be sufficient for any code that runs between checks + // for stack size. + constexpr size_t SufficientStack = 256 << 10; + + // If we don't know where the bottom of the stack is, hope for the best. + if (!BottomOfStack) + return false; + + intptr_t StackDiff = (intptr_t)getStackPointer() - (intptr_t)BottomOfStack; + size_t StackUsage = (size_t)std::abs(StackDiff); + + // If the stack pointer has a surprising value, we do not understand this + // stack usage scheme. (Perhaps the target allocates new stack regions on + // demand for us.) Don't try to guess what's going on. + if (StackUsage > DesiredStackSize) + return false; + + return StackUsage >= DesiredStackSize - SufficientStack; +} + +void clang::runWithSufficientStackSpaceSlow(llvm::function_ref<void()> Diag, + llvm::function_ref<void()> Fn) { + llvm::CrashRecoveryContext CRC; + CRC.RunSafelyOnThread([&] { + noteBottomOfStack(); + Diag(); + Fn(); + }, DesiredStackSize); +} Index: lib/Basic/CMakeLists.txt =================================================================== --- lib/Basic/CMakeLists.txt +++ lib/Basic/CMakeLists.txt @@ -60,6 +60,7 @@ Sanitizers.cpp SourceLocation.cpp SourceManager.cpp + Stack.cpp TargetInfo.cpp Targets.cpp Targets/AArch64.cpp Index: include/clang/Sema/Sema.h =================================================================== --- include/clang/Sema/Sema.h +++ include/clang/Sema/Sema.h @@ -1272,6 +1272,8 @@ void addImplicitTypedef(StringRef Name, QualType T); + bool WarnedStackExhausted = false; + public: Sema(Preprocessor &pp, ASTContext &ctxt, ASTConsumer &consumer, TranslationUnitKind TUKind = TU_Complete, @@ -1303,6 +1305,16 @@ void PrintStats() const; + /// Warn that the stack is nearly exhausted. + void warnStackExhausted(SourceLocation Loc); + + /// Run some code with "sufficient" stack space. (Currently, at least 256K is + /// guaranteed). Produces a warning if we're low on stack space and allocates + /// more in that case. Use this in code that may recurse deeply (for example, + /// in template instantiation) to avoid stack overflow. + void runWithSufficientStackSpace(SourceLocation Loc, + llvm::function_ref<void()> Fn); + /// Helper class that creates diagnostics with optional /// template instantiation stacks. /// Index: include/clang/Basic/Stack.h =================================================================== --- include/clang/Basic/Stack.h +++ include/clang/Basic/Stack.h @@ -16,11 +16,35 @@ #include <cstddef> +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/Compiler.h" + namespace clang { /// The amount of stack space that Clang would like to be provided with. /// If less than this much is available, we may be unable to reach our /// template instantiation depth limit and other similar limits. constexpr size_t DesiredStackSize = 8 << 20; + + /// Call this once on each thread, as soon after starting the thread as + /// feasible, to note the approximate address of the bottom of the stack. + void noteBottomOfStack(); + + /// Determine whether the stack is nearly exhausted. + bool isStackNearlyExhausted(); + + void runWithSufficientStackSpaceSlow(llvm::function_ref<void()> Diag, + llvm::function_ref<void()> Fn); + + /// Run a given function on a stack with "sufficient" space. If stack space + /// is insufficient, calls Diag to emit a diagnostic before calling Fn. + inline void runWithSufficientStackSpace(llvm::function_ref<void()> Diag, + llvm::function_ref<void()> Fn) { + if (LLVM_UNLIKELY(isStackNearlyExhausted())) { + runWithSufficientStackSpaceSlow(Diag, Fn); + } else { + Fn(); + } + } } // end namespace clang #endif // LLVM_CLANG_BASIC_STACK_H Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -11,8 +11,12 @@ //===----------------------------------------------------------------------===// let Component = "Sema" in { -let CategoryName = "Semantic Issue" in { +def warn_stack_exhausted : Warning< + "stack nearly exhausted; compilation time may suffer, and " + "crashes due to stack overflow are likely">, + InGroup<DiagGroup<"stack-exhausted">>, NoSFINAE; +let CategoryName = "Semantic Issue" in { def note_previous_decl : Note<"%0 declared here">; def note_entity_declared_at : Note<"%0 declared here">; def note_callee_decl : Note<"%0 declared here">;
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits