diff --git a/clang/include/clang-c/Index.h b/clang/include/clang-c/Index.h --- a/clang/include/clang-c/Index.h +++ b/clang/include/clang-c/Index.h @@ -2600,7 +2600,11 @@ */ CXCursor_OMPGenericLoopDirective = 295, - CXCursor_LastStmt = CXCursor_OMPGenericLoopDirective, + /** String injection statement + */ + CXCursor_StringInjectionStmt = 296, + + CXCursor_LastStmt = CXCursor_StringInjectionStmt, /** * Cursor that represents the translation unit itself. @@ -2684,8 +2688,14 @@ * a friend declaration. */ CXCursor_FriendDecl = 603, + + /** + * a metaprogram decl (consteval {...}) + */ + CXCursor_Metaprogram = 604, + CXCursor_FirstExtraDecl = CXCursor_ModuleImportDecl, - CXCursor_LastExtraDecl = CXCursor_FriendDecl, + CXCursor_LastExtraDecl = CXCursor_Metaprogram, /** * A code completion overload candidate. diff --git a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def --- a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def +++ b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def @@ -249,4 +249,10 @@ /// base classes or fields have a no-return destructor FIELD(IsAnyDestructorNoReturn, 1, NO_MERGE) +/// True if this is the templated decl of a ClassTemplateDecl, +/// or a ClassTemplatePartialSpecializationDecl and contains +/// at least one dependent metaprogram with at least one code +/// injection statement. +FIELD(HasDependentCodeInjectingMetaprograms, 1, NO_MERGE) + #undef FIELD diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -2671,6 +2671,54 @@ setInstantiationOfMemberFunction(getASTContext(), FD, TSK); } + /// Whether this function is the underlying implementation + /// of a metaprogram (consteval {...}) + bool isMetaprogram() const { + return getCanonicalDecl()->FunctionDeclBits.IsMetaprogram; + } + + /// Specify that this function is the underlying implementation + /// of a metaprogram (`consteval {...}`) + void setIsMetaprogram(bool V = true) { + FunctionDeclBits.IsMetaprogram = V; + } + + /// Whether this function is an instantiatable pattern and contains + /// at least one dependent metaprogram with at least one + /// code injection statement + /// \code + /// template void f() { consteval { } } + /// template consteval void g() { __inj(...) } + /// template void h() { consteval { f() } } + /// template class Foo { i() { consteval { f(); } } }; + /// \endcode + /// f(): false + /// g(): false + /// h(): true + /// Foo::i(): true + bool hasDependentCodeInjectingMetaprograms() const { + return FunctionDeclBits.HasDependentCodeInjectingMetaprograms; + } + /// Specify that this function is an instantiatable pattern and + /// contains a MetaprogramDecl . + void setHasDependentCodeInjectingMetaprograms(bool V = true) { + FunctionDeclBits.HasDependentCodeInjectingMetaprograms = V; + } + + /// Whether this function is a consteval function which + /// contains at least one code injection statement or + /// call to a function containing one. + bool isCodeInjectingMetafunction() const { + return getCanonicalDecl()->FunctionDeclBits.IsCodeInjectingMetafunction; + } + + /// Specify that this function is a consteval function which + /// contains at least one code injection statement or + /// call to a function containing one. + void setIsCodeInjectingMetafunction(bool V = true) { + FunctionDeclBits.IsCodeInjectingMetafunction = V; + } + /// Retrieves the function template that is described by this /// function declaration. /// diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h --- a/clang/include/clang/AST/DeclBase.h +++ b/clang/include/clang/AST/DeclBase.h @@ -1620,10 +1620,23 @@ /// Indicates if the function uses Floating Point Constrained Intrinsics uint64_t UsesFPIntrin : 1; + + /// Indicates that the function is the underlying implementation of a + /// MetaprogramDecl. + uint64_t IsMetaprogram : 1; + + /// Indicates if the function is an instantiatable pattern containing a + /// MetaprogramDecl (consteval {...}) + uint64_t HasDependentCodeInjectingMetaprograms : 1; + + /// Whether this function is a consteval function which + /// contains at least one code injection statement or + /// call to a function containing one. + uint64_t IsCodeInjectingMetafunction : 1; }; /// Number of non-inherited bits in FunctionDeclBitfields. - enum { NumFunctionDeclBits = 27 }; + enum { NumFunctionDeclBits = 29 }; /// Stores the bits used by CXXConstructorDecl. If modified /// NumCXXConstructorDeclBits and the accessor @@ -1640,7 +1653,7 @@ /// exactly 64 bits and thus the width of NumCtorInitializers /// will need to be shrunk if some bit is added to NumDeclContextBitfields, /// NumFunctionDeclBitfields or CXXConstructorDeclBitfields. - uint64_t NumCtorInitializers : 21; + uint64_t NumCtorInitializers : 19; uint64_t IsInheritingConstructor : 1; /// Whether this constructor has a trail-allocated explicit specifier. diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -31,9 +31,11 @@ #include "clang/Basic/LLVM.h" #include "clang/Basic/Lambda.h" #include "clang/Basic/LangOptions.h" +#include "clang/Basic/MetaprogramContext.h" #include "clang/Basic/OperatorKinds.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/Specifiers.h" +#include "clang/Sema/Ownership.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/PointerIntPair.h" @@ -987,6 +989,31 @@ return data().NeedOverloadResolutionForDestructor; } + /// Returns whether this record is an instantiatable pattern containing + /// at least one dependent metaprogram with at least one code injection + /// statement; e.g. + /// \code + /// template consteval foo() { __inj(...); } + /// template class Foo { consteval { foo() } }; + /// template class Foo> { consteval { foo() } }; + /// template + // class Bar { + // class Inner { consteval { foo() } }; + // }; + /// \endcode + /// For Foo and Foo>, and Bar::Inner all have + /// hasDependentCodeInjectingMetaprograms() = true. + /// (For Bar it is false.) + bool hasDependentCodeInjectingMetaprograms() const { + return data().HasDependentCodeInjectingMetaprograms; + } + + /// Specify that this record is an instantiatable pattern containing + /// a MetaprogramDecl among its child decls. + void setHasDependentCodeInjectingMetaprograms(bool V = true) { + data().HasDependentCodeInjectingMetaprograms = V; + } + /// Determine whether this class describes a lambda function object. bool isLambda() const { // An update record can't turn a non-lambda into a lambda. @@ -4188,6 +4215,174 @@ static bool classofKind(Kind K) { return K == Decl::MSGuid; } }; +/// \brief Represents a metaprogram. +/// +/// A metaprogram contains a sequence of statements that are evaluated +/// at compile-time. +/// \code +/// constval { +/// // statements +/// } +/// \endcode +/// +/// A MetaprogramDecl is a "Decl" only in the sense that it will be created +/// during ParseDeclaration. It is really a meta-statement, that will +/// apply side-effects outside its scope by introducing other Decls +/// (via injection statements) in the context in which it was encountered. +/// It is permitted in namespace, class, and function scopes, and is generally +/// used in a dependent/templated context. +class MetaprogramDecl : public Decl { + virtual void anchor(); + + /// The de-sugared form of the declaration. + llvm::PointerUnion Representation; + + /// The de-sugared call expression. + CallExpr *Call; + + /// Carries some enclosing context information, such as the access + /// level within a class with the metaprogram was encountered, so + /// that newly generated decls from the metaprogram can use them. + MetaprogramContext MetaCtx; + + /// Used by metaprograms within function bodies, accessed in ActOnDeclStmt. + StmtResult sr; + + /// Also used by metaprograms within function bodies. + Expr *LambdaExpr; + + /// The metaprogram from which this declaration was instantiated, if any. + MetaprogramDecl *InstantiatedFrom; + + /// Keeps track of whether the metaprogram statements contain dependencies. + bool IsDependent; + + /// Keeps track of whether the metaprogram has been performed + bool AlreadyRun; + + MetaprogramDecl(DeclContext *DC, SourceLocation KeywordLoc); + MetaprogramDecl(DeclContext *DC, SourceLocation KeywordLoc, + const MetaprogramContext &MetaCtx, FunctionDecl *Fn, + MetaprogramDecl *InstantiatedFrom); + MetaprogramDecl(DeclContext *DC, SourceLocation KeywordLoc, + const MetaprogramContext &MetaCtx, CXXRecordDecl *Closure, + MetaprogramDecl *InstantiatedFrom); + +public: + static MetaprogramDecl *Create(ASTContext &Ctx, DeclContext *DC, + SourceLocation KeywordLoc, + const MetaprogramContext &MetaCtx, + FunctionDecl *Fn, + MetaprogramDecl *InstFrom = nullptr) { + return new (Ctx, DC) MetaprogramDecl(DC, KeywordLoc, MetaCtx, + Fn, InstFrom); + } + static MetaprogramDecl *Create(ASTContext &Ctx, DeclContext *DC, + SourceLocation KeywordLoc, + const MetaprogramContext &MetaCtx, + CXXRecordDecl *Closure, + MetaprogramDecl *InstFrom = nullptr) { + return new (Ctx, DC) MetaprogramDecl(DC, KeywordLoc, MetaCtx, + Closure, InstFrom); + } + static MetaprogramDecl *CreateDeserialized(ASTContext &Ctx, unsigned ID) { + return new (Ctx, ID) MetaprogramDecl(nullptr, SourceLocation()); + } + + /// Provides various context details present at the location of the + /// metaprogram decl (e.g. the current access within a class) + /// to aid the interpretation of generated members. + MetaprogramContext getMetaprogramContext() const { return MetaCtx; } + + /// Metaprograms within function bodies use a lambda representation; + /// this will return a nonnull expression only for such metaprograms. + Expr *getImplicitLambdaExpr() const { return LambdaExpr; } + void setImplicitLambdaExpr(Expr *val) { LambdaExpr = val; } + + /// For metaprograms within function bodies, this holds the + /// the meta-parsed StmtResult, so it can be passed along during + /// ActOnDeclStmt for this MetaprogramDecl. (This isn't needed in + /// e.g. class templates because we can meta-parse directly + /// into their instantiations; only within function templates + /// must we deal with an intermediary StmtResult.) + StmtResult getStmtResult() const { return sr; } + void setStmtResult(StmtResult val) { sr = val; } + + /// Whether the metaprogram statements contain dependencies (such + /// that it is not ready to be evaluated.) + bool isDependent() const { return IsDependent; } + void setDependent(bool V = true) { IsDependent = V; } + + /// Whether the metaprogram has been run yet (always false if isDependent()). + bool alreadyRun() const { return AlreadyRun; } + void setAlreadyRun(bool V = true) { AlreadyRun = V; } + + /// Whether this is represented as a function + /// (indicating it was either written inside a class, or in the + /// global context -- i.e. *not* written inside a function). + bool hasFunctionRepresentation() const { + return Representation.is(); + } + + /// Whether if this is represented as a lambda expression + /// (indicating it was written inside a function). + bool hasLambdaRepresentation() const { + return Representation.is(); + } + + /// Returns the function representation of the declaration. + FunctionDecl *getImplicitFunctionDecl() const { + return Representation.get(); + } + void setImplicitFunctionDecl(FunctionDecl *D) { + Representation = D; + } + + /// Returns the closure declaration for the lambda expression. + CXXRecordDecl *getImplicitClosureDecl() const { + return Representation.get(); + } + void setImplicitClosureDecl(CXXRecordDecl *D) { + Representation = D; + } + + /// Returns the call operator of the closure. + CXXMethodDecl *getImplicitClosureCallOperator() const { + assert(hasLambdaRepresentation() && + "metaprogram is not represented by a lambda expression"); + return getImplicitClosureDecl()->getLambdaCallOperator(); + } + + /// Returns \c true if the metaprogram has a body. + bool hasBody() const override; + + /// Returns the body of the metaprogram. + Stmt *getBody() const override; + + /// Returns the expression that evaluates the metaprogram. + CallExpr *getImplicitCallExpr() const { return Call; } + void setImplicitCallExpr(CallExpr *E) { Call = E; } + + /// Returns the dependent MetaprogramDecl from which + /// this was instantiated, if any. + MetaprogramDecl *getInstantiatedFrom() const { + return InstantiatedFrom; + } + void setInstantiatedFrom(MetaprogramDecl *D) { + InstantiatedFrom = D; + } + + /// Returns whether this was created by a template instantiation + bool isInstantiation() const { return InstantiatedFrom; } + + SourceRange getSourceRange() const override; + + static bool classof(const Decl *D) { return classofKind(D->getKind()); } + static bool classofKind(Kind K) { return K == Metaprogram; } + + friend class ASTDeclReader; +}; + /// Insertion operator for diagnostics. This allows sending an AccessSpecifier /// into a diagnostic with <<. const StreamingDiagnostic &operator<<(const StreamingDiagnostic &DB, diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -598,8 +598,13 @@ /// expression *is* a constant expression, no notes will be produced. SmallVectorImpl *Diag; + /// This will be nonnull and nonempty after evaluating a \c MetaprogramDecl that + /// contains \c StringInjectionStmt s. + SmallVectorImpl *StringInjectionChunks; + EvalStatus() - : HasSideEffects(false), HasUndefinedBehavior(false), Diag(nullptr) {} + : HasSideEffects(false), HasUndefinedBehavior(false), Diag(nullptr), + StringInjectionChunks(nullptr) {} // hasSideEffects - Return true if the evaluated expression has // side effects. @@ -660,6 +665,9 @@ SideEffectsKind AllowSideEffects = SE_NoSideEffects, bool InConstantContext = false) const; + /// EvaluateAsVoid - Return true if completely folded. + bool EvaluateAsVoid(EvalResult &Result, const ASTContext &Ctx) const; + /// isEvaluatable - Call EvaluateAsRValue to see if this expression can be /// constant folded without side-effects, but discard the result. bool isEvaluatable(const ASTContext &Ctx, diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h --- a/clang/include/clang/AST/RecursiveASTVisitor.h +++ b/clang/include/clang/AST/RecursiveASTVisitor.h @@ -2187,6 +2187,37 @@ DEF_TRAVERSE_DECL(RequiresExprBodyDecl, {}) +DEF_TRAVERSE_DECL(MetaprogramDecl, { + // If the metaprogram has not yet run (usually because it D->isDependent()), + // we want to visit the underlying body of statements. But we want to treat + // the invisible function or lambda wrapping those statements (named + // __metaprogdef) as implicit. + if (!D->alreadyRun()) { + if (getDerived().shouldVisitImplicitCode()) { + // Either of these should handle traversing the + // D->getBody() content: + if (D->hasFunctionRepresentation()) + TRY_TO(TraverseDecl(D->getImplicitFunctionDecl())); + else + // This should handle traversing the getClosureDecl as well + TRY_TO(TraverseStmt(D->getImplicitLambdaExpr())); + + // The expression that calls the metaprogram: + TRY_TO(TraverseStmt(D->getImplicitCallExpr())); + } else + // Traversal of dependent metaprogram *less* the implicit stuff: + // just traverse the enclosed statements. + TRY_TO(TraverseStmt(D->getBody())); + } + // Already-run metaprogram: this can no longer have any effect on the + // program; we keep it around only in case we want to reference it as sugar + // of the declarations it introduced. + // I.e. this metaprogram is really just a "husk" at this point, so we + // will not visit it at all. (Arguably, we could treat it as implicit + // and visit it conditionally, but it is not really implicit, as implicit + // implies it does *something* implicitly, whereas this does not.) +}) + #undef DEF_TRAVERSE_DECL // ----------------- Stmt traversal ----------------- @@ -2740,6 +2771,23 @@ DEF_TRAVERSE_STMT(CXXFoldExpr, {}) DEF_TRAVERSE_STMT(AtomicExpr, {}) +DEF_TRAVERSE_STMT(StringInjectionStmt, { + if (StringLiteral *WrittenFirstArg = S->getWrittenFirstArg()) { + assert(S->isSpelledWithF()); + TRY_TO_TRAVERSE_OR_ENQUEUE_STMT(WrittenFirstArg); + if (!getDerived().shouldVisitImplicitCode()) { + // Traverse only the odd Args (the even ones are + // implicitly generated sub strings of WrittenFirstArg) + ShouldVisitChildren = false; + for (unsigned I = 1; I < S->getNumArgs(); I+=2) + TRY_TO_TRAVERSE_OR_ENQUEUE_STMT(S->getArg(I)); + } + } + // If !WrittenFirstArg (__inj), or if visiting + // implicit code you will visit all of getArgs() as + // children +}) + DEF_TRAVERSE_STMT(MaterializeTemporaryExpr, { if (S->getLifetimeExtendedTemporaryDecl()) { TRY_TO(TraverseLifetimeExtendedTemporaryDecl( diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -13,6 +13,7 @@ #ifndef LLVM_CLANG_AST_STMT_H #define LLVM_CLANG_AST_STMT_H +#include "clang/AST/ASTContext.h" #include "clang/AST/DeclGroup.h" #include "clang/AST/DependenceFlags.h" #include "clang/AST/StmtIterator.h" @@ -999,6 +1000,27 @@ SourceLocation Loc; }; + //===--- Generic bitfields ---===// + class HasArbitraryArgsBitfields { + friend class ASTStmtReader; + template + friend class HasArbitraryParenExprArgs; + + unsigned : NumExprBits; + + unsigned : 32 - 1 - NumExprBits; + + /// The number of arguments used in the statement or expression. + unsigned NumArgs; + }; + enum { NumHasArbitraryArgsBits = NumExprBits }; + + // If you need to add special bitfields for Stmts or Exprs with arbitrary + // args, keep these updated, and static assert that your bitfield doesn't + // encroach on NumArgs: + enum { NumStmtWithArbitraryArgsBits = NumHasArbitraryArgsBits }; + enum { NumExprWithArbitraryArgsBits = NumHasArbitraryArgsBits }; + union { // Same order as in StmtNodes.td. // Statements @@ -1076,6 +1098,10 @@ // Clang Extensions OpaqueValueExprBitfields OpaqueValueExprBits; + + // Bits for a generic statement/expression that can have a variable + // number of arguments. + HasArbitraryArgsBitfields HasArbitraryArgsBits; }; public: @@ -3715,6 +3741,238 @@ const_child_range children() const; }; +//===--- Helper CRTP bases for Stmts/Exprs with common structure ---===// +/// HasArbitraryParenExprArgs - a CRTP template which may be used as a base +/// for statements or expressions that feature an arbitrary number of Expr * +/// arguments, written in parentheses. +/// The intent of this template is to establish a standard interface for +/// such statements. +template +class HasArbitraryParenExprArgs + : public BASE, + public llvm::TrailingObjects { +protected: + using TrailingObjectsBase = llvm::TrailingObjects; + + // We have to publicly inherit llvm::TrailingObjects for casting reasons, + // so we'll manually move each of its public members out of the public + // interface: + using TrailingObjectsBase::getTrailingObjects; + using TrailingObjectsBase::additionalSizeToAlloc; + using TrailingObjectsBase::totalSizeToAlloc; + + friend class ASTStmtReader; + + template + friend class TrailingObjects; + + /// The location of the left parentheses ('('). + SourceLocation LParenLoc; + + /// The location of the right parentheses (')'). + SourceLocation RParenLoc; + + /// LParenLoc, Args, and RParenLoc, followed by the BASE's params: + template + HasArbitraryParenExprArgs(SourceLocation LParenLoc, + ArrayRef Args, + SourceLocation RParenLoc, + ARGs&&... baseargs) + : BASE(std::forward(baseargs)...), + LParenLoc(LParenLoc), + RParenLoc(RParenLoc) + { + Stmt::HasArbitraryArgsBits.NumArgs = Args.size(); + auto **StoredArgs = TrailingObjectsBase:: + template getTrailingObjects(); + std::copy(Args.begin(), Args.end(), StoredArgs); + } + + /// Construct an empty object. Pass e.g. EmptyShell etc. as the base args. + template + HasArbitraryParenExprArgs(std::size_t NumArgs, ARGs&&... baseargs) + : BASE(std::forward(baseargs)...) { + Stmt::HasArbitraryArgsBits.NumArgs = NumArgs; + } + + template + static CRTP *Create(const ASTContext &Context, + SourceLocation LParenLoc, ArrayRef Args, + SourceLocation RParenLoc, Ts&&... otherargs) { + void *Mem = Context.Allocate(TrailingObjectsBase::template + totalSizeToAlloc(Args.size())); + return new (Mem) CRTP(LParenLoc, Args, RParenLoc, + std::forward(otherargs)...); + } + +public: + /// Creates an empty statement/expression. + static CRTP *CreateEmpty(const ASTContext &Context, unsigned NumArgs) { + void *Mem = Context.Allocate(TrailingObjectsBase::template + totalSizeToAlloc(NumArgs)); + return new (Mem) CRTP(Stmt::EmptyShell(), NumArgs); + } + + /// Retrieve the number of arguments. + std::size_t arg_size() const { return Stmt::HasArbitraryArgsBits.NumArgs; } + std::size_t getNumArgs() const { return Stmt::HasArbitraryArgsBits.NumArgs; } + + using arg_iterator = Expr **; + using arg_range = llvm::iterator_range; + + arg_iterator arg_begin() { + return TrailingObjectsBase::template getTrailingObjects(); + } + arg_iterator arg_end() { return arg_begin() + arg_size(); } + arg_range arguments() { return arg_range(arg_begin(), arg_end()); } + arg_range getArgs() { return arguments(); } + Expr *getArg(std::size_t I) { + assert(I < arg_size() && "Argument index out-of-range"); + return arg_begin()[I]; + } + + using const_arg_iterator = const Expr* const *; + using const_arg_range = llvm::iterator_range; + + const_arg_iterator arg_begin() const { + return TrailingObjectsBase::template getTrailingObjects(); + } + const_arg_iterator arg_end() const { return arg_begin() + arg_size(); } + const_arg_range arguments() const { + return const_arg_range(arg_begin(), arg_end()); + } + const_arg_range getArgs() const { return arguments(); } + const Expr *getArg(std::size_t I) const { + assert(I < arg_size() && "Argument index out-of-range"); + return arg_begin()[I]; + } + + void setArg(std::size_t I, Expr *E) { + assert(I < arg_size() && "Argument index out-of-range"); + arg_begin()[I] = E; + } + + SourceLocation getEndLoc() const LLVM_READONLY { + if (!RParenLoc.isValid() && arg_size() > 0) + return getArg(arg_size() - 1)->getEndLoc(); + return RParenLoc; + } + + // Iterators + Stmt::child_range children() { + auto **begin = reinterpret_cast(arg_begin()); + return Stmt::child_range(begin, begin + arg_size()); + } + + Stmt::const_child_range children() const { + auto **begin = reinterpret_cast( + const_cast(this)->arg_begin()); + return Stmt::const_child_range(begin, begin + arg_size()); + } + + // Parentheses: + /// Retrieve the location of the left parentheses ('(') that + /// precedes the argument list. + SourceLocation getLParenLoc() const { return LParenLoc; } + void setLParenLoc(SourceLocation L) { LParenLoc = L; } + + /// Retrieve the location of the right parentheses (')') that + /// follows the argument list. + SourceLocation getRParenLoc() const { return RParenLoc; } + void setRParenLoc(SourceLocation L) { RParenLoc = L; } + + using Stmt::StmtClass; + using Stmt::EmptyShell; +}; + +/// Same as HasArbitraryParenExprArgs, but adds a KeywordLoc param -- +/// so this can help implement any Stmt/Expr which consists of a keyword +/// followed by arbitrary Expr * arguments in parentheses. +template +class HasKWAndArbitraryParenExprArgs + : public HasArbitraryParenExprArgs { + using ImplBase = HasArbitraryParenExprArgs; +protected: + friend class ASTStmtReader; + friend class ASTStmtWriter; + using TrailingObjectsBase = llvm::TrailingObjects; + + SourceLocation KeywordLoc; + + /// KeywordLoc arg followed by same args as HasArbitraryParenExprArgs ctor + template + HasKWAndArbitraryParenExprArgs(SourceLocation KeywordLoc, + SourceLocation LParenLoc, + ArrayRef Args, + SourceLocation RParenLoc, + ARGs&&... baseargs) + : ImplBase(LParenLoc, Args, RParenLoc, + std::forward(baseargs)...), + KeywordLoc(KeywordLoc) {} + + /// Construct an empty statement/expression. + template + HasKWAndArbitraryParenExprArgs(std::size_t NumArgs, ARGs&&... baseargs) + : ImplBase(NumArgs, std::forward(baseargs)...) {} + + template + static CRTP *Create(const ASTContext &Context, SourceLocation KeywordLoc, + SourceLocation LParenLoc,ArrayRef Args, + SourceLocation RParenLoc, ARGs&&... otherargs) { + void *Mem = Context.Allocate(TrailingObjectsBase::template + totalSizeToAlloc(Args.size())); + return new (Mem) CRTP(KeywordLoc, LParenLoc, Args, + RParenLoc, std::forward(otherargs)...); + } + +public: + /// Creates an empty statement/expression. + static CRTP *CreateEmpty(const ASTContext &Context, unsigned NumArgs) { + void *Mem = Context.Allocate(TrailingObjectsBase::template + totalSizeToAlloc(NumArgs)); + return new (Mem) CRTP(Stmt::EmptyShell(), NumArgs); + } + + SourceLocation getKeywordLoc() const { return KeywordLoc; } + void setKeywordLoc(SourceLocation L) { KeywordLoc = L; } + + SourceLocation getBeginLoc() const { return KeywordLoc; } + + using typename ImplBase::arg_iterator; + using typename ImplBase::arg_range; + using typename ImplBase::const_arg_iterator; + using typename ImplBase::const_arg_range; +}; + +///A statement instance of the above template. +template +class StmtWithKWAndArbitraryParenExprArgs + : public HasKWAndArbitraryParenExprArgs { + using ImplBase = HasKWAndArbitraryParenExprArgs; +public: + using ImplBase::Create; + using ImplBase::CreateEmpty; + using typename ImplBase::arg_iterator; + using typename ImplBase::arg_range; + using typename ImplBase::const_arg_iterator; + using typename ImplBase::const_arg_range; + using typename Stmt::StmtClass; + using typename Stmt::EmptyShell; + +protected: + StmtWithKWAndArbitraryParenExprArgs(SourceLocation KeywordLoc, + SourceLocation LParenLoc, + ArrayRef Args, + SourceLocation RParenLoc, + Stmt::StmtClass SC) + : ImplBase(KeywordLoc, LParenLoc, Args, RParenLoc, SC) {} + + // Construct an empty statement. + StmtWithKWAndArbitraryParenExprArgs(std::size_t NumArgs, + StmtClass SC, EmptyShell Empty) + : ImplBase(NumArgs, SC, Empty) {} +}; + } // namespace clang #endif // LLVM_CLANG_AST_STMT_H diff --git a/clang/include/clang/AST/StmtCXX.h b/clang/include/clang/AST/StmtCXX.h --- a/clang/include/clang/AST/StmtCXX.h +++ b/clang/include/clang/AST/StmtCXX.h @@ -513,6 +513,98 @@ } }; +/// Represents an __inj or __injf statement +/// (enabled via LangOpts.StringInjection). +/// +/// When evaluated, queues the new source code to be +/// lexed and processed at the end of the enclosing +/// MetaprogramDecl (or the MetaprogramDecl enclosing the +/// CallExpr calling some consteval function which +/// contains the __inj/__injf statement). +/// +/// String literal and integer literal arguments are +/// both acceptable for all but the first arg of +/// __injf, which must be a string literal. +/// +/// Example: +/// \code +/// consteval f() { +// __inj("int bar = 4;"); +// __injf("int {} = {};", "baz", 42); +// } +/// consteval { // (MetaprogramDecl) +/// const char *name = "foo"; +/// __inj("int {} = {};", name, "34"); +/// //assert(foo==34); //ERROR +/// f(); +/// } // queued injections performed here when non-dependent... +/// assert(foo==34); +/// assert(bar==4); +/// assert(baz==42); +/// \endcode +class StringInjectionStmt final : + public StmtWithKWAndArbitraryParenExprArgs { + + using ImplBase = StmtWithKWAndArbitraryParenExprArgs; + template + friend class HasKWAndArbitraryParenExprArgs; + + /// Only nonnull if isSpelledWithF. If so, this is the original + /// first argument, which we will have subdivided, removing the + /// placeholders, into Args. + StringLiteral *WrittenFirstArg; + + StringInjectionStmt(SourceLocation KeywordLoc, SourceLocation LParenLoc, + ArrayRef Args, SourceLocation RParenLoc, + StringLiteral *WrittenFirstArg) + : ImplBase(KeywordLoc, LParenLoc, Args, RParenLoc, + StringInjectionStmtClass), WrittenFirstArg(WrittenFirstArg) {} + + StringInjectionStmt(EmptyShell Empty, unsigned NumArgs) + : ImplBase(NumArgs, StringInjectionStmtClass, Empty) {} + +public: + static StringInjectionStmt *Create(ASTContext &C, + SourceLocation KeywordLoc, + SourceLocation LParenLoc, + ArrayRef Args, + SourceLocation RParenLoc, + StringLiteral *WrittenFirstArg) { + return ImplBase::Create(C, KeywordLoc, LParenLoc, + Args, RParenLoc, + WrittenFirstArg); + } + + static StringInjectionStmt *CreateEmpty(const ASTContext &C, + unsigned NumArgs) { + return ImplBase::CreateEmpty(C, NumArgs); + } + + /// If true, the keyword used was __injf; if false, __inj. + /// If false, Args represents the original written arguments. + /// If true, getWrittenFirstArg() represents the written first + /// arg, and Args alternates between sub-strings + /// of WrittenFirstArg, divided at the placeholders, + /// and the original remaining args in order. + bool isSpelledWithF() const { return WrittenFirstArg; } + + /// Returns null if the keyword used was __inj; otherwise + /// returns the string literal first argument of the __injf + /// statement. + StringLiteral *getWrittenFirstArg() const { + return WrittenFirstArg; + } + void setWrittenFirstArg(StringLiteral *v) { WrittenFirstArg = v; } + + // Returns the string that will be interpreted as a placeholder + // for __injf statements. + static StringRef PlaceholderStr() { return StringRef("{}", 2); }; + + static bool classof(const Stmt *T) { + return T->getStmtClass() == StringInjectionStmtClass; + } +}; + } // end namespace clang #endif diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -829,6 +829,8 @@ /// Return true if this is a trivially copyable type (C++0x [basic.types]p9) bool isTriviallyCopyableType(const ASTContext &Context) const; + /// Returns true if this is 'const char*' or 'const char[N]'. + bool isCXXStringLiteralType() const; /// Returns true if it is a class and it might be dynamic. bool mayBeDynamicClass() const; diff --git a/clang/include/clang/Basic/DeclNodes.td b/clang/include/clang/Basic/DeclNodes.td --- a/clang/include/clang/Basic/DeclNodes.td +++ b/clang/include/clang/Basic/DeclNodes.td @@ -107,4 +107,5 @@ def Empty : DeclNode; def RequiresExprBody : DeclNode, DeclContext; def LifetimeExtendedTemporary : DeclNode; +def Metaprogram : DeclNode; diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -591,8 +591,18 @@ def warn_unnecessary_packed : Warning< "packed attribute is unnecessary for %0">, InGroup, DefaultIgnore; +// String Injection +def err_constexpr_eval_failed : Error< + "Failed to constant-evaluate expression">; //FIXME redundant or pathological +def note___inj_outside_metaprogram : Note< + "__inj/__injf in function called outside consteval {}">; +def err_metaparse_extraneous_chars : Error< + "extraneous character(s) in __metaparse_expr, beginning with %0">; +def note_during_metaparse_expr_of_str : Note< + "during __metaparse_expr of this string: '%0'">; + // -Wunaligned-access def warn_unaligned_access : Warning< "field %1 within %0 is less aligned than %2 and is usually due to %0 being " "packed, which can lead to unaligned accesses">, InGroup, DefaultIgnore; -} +} \ No newline at end of file diff --git a/clang/include/clang/Basic/DiagnosticIDs.h b/clang/include/clang/Basic/DiagnosticIDs.h --- a/clang/include/clang/Basic/DiagnosticIDs.h +++ b/clang/include/clang/Basic/DiagnosticIDs.h @@ -34,7 +34,7 @@ DIAG_SIZE_FRONTEND = 150, DIAG_SIZE_SERIALIZATION = 120, DIAG_SIZE_LEX = 400, - DIAG_SIZE_PARSE = 600, + DIAG_SIZE_PARSE = 750, DIAG_SIZE_AST = 250, DIAG_SIZE_COMMENT = 100, DIAG_SIZE_CROSSTU = 100, diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td --- a/clang/include/clang/Basic/DiagnosticLexKinds.td +++ b/clang/include/clang/Basic/DiagnosticLexKinds.td @@ -274,6 +274,12 @@ def err_lexing_numeric : Error<"failure when lexing a numeric literal">; def err_placeholder_in_source : Error<"editor placeholder in source file">; +// [StringInjection] +def err_unterminated_string_injection : Error< + "unterminated string injection">; +def err_metaparse_unsupported_use : Error< + "unsupported metaparse use: %0">; + //===----------------------------------------------------------------------===// // Preprocessor Diagnostics //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -1582,6 +1582,11 @@ "the total number of preprocessor source tokens (%0) exceeds the token limit (%1)">, InGroup, DefaultIgnore; +def err___injf_first_arg_not_str : Error< + "the first argument to __injf(...) must be a non-dependent string literal">; +def err___injf_wrong_num_args : Error< + "Mismatch between placeholder count (%0) and additional arguments (%1)">; + def note_max_tokens_total_override : Note<"total token limit set here">; } // end of Parser diagnostics diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -2870,6 +2870,17 @@ def err_placeholder_constraints_not_satisfied : Error< "deduced type %0 does not satisfy %1">; +// String Injection +def err_expected_int_or_str : Error< + "expected either integer or string operand">; +def err_metaprogram_eval_failure : Error< + "Not all statements in consteval {} could be constant-evaluated">; + +// Custom diagnostics +def err_user_defined_error : Error<"((%0))">; +def warn_user_defined_warning : Warning<"((%0))">; +def note_user_defined_note : Note<"((%0))">; + // C++11 char16_t/char32_t def warn_cxx98_compat_unicode_type : Warning< "'%0' type specifier is incompatible with C++98">, diff --git a/clang/include/clang/Basic/Features.def b/clang/include/clang/Basic/Features.def --- a/clang/include/clang/Basic/Features.def +++ b/clang/include/clang/Basic/Features.def @@ -265,6 +265,7 @@ EXTENSION(cxx_attributes_on_using_declarations, LangOpts.CPlusPlus11) FEATURE(cxx_abi_relative_vtable, LangOpts.CPlusPlus && LangOpts.RelativeCXXABIVTables) +EXTENSION(string_injection, LangOpts.StringInjection) #undef EXTENSION #undef FEATURE diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -147,6 +147,7 @@ LANGOPT(NoBuiltin , 1, 0, "disable builtin functions") LANGOPT(NoMathBuiltin , 1, 0, "disable math builtin functions") LANGOPT(GNUAsm , 1, 1, "GNU-style inline assembly") +LANGOPT(StringInjection , 1, 0, "String injection") LANGOPT(Coroutines , 1, 0, "C++20 coroutines") LANGOPT(DllExportInlines , 1, 1, "dllexported classes dllexport inline methods") LANGOPT(RelaxedTemplateTemplateArgs, 1, 0, "C++17 relaxed matching of template template arguments") diff --git a/clang/include/clang/Basic/MetaprogramContext.h b/clang/include/clang/Basic/MetaprogramContext.h new file mode 100644 --- /dev/null +++ b/clang/include/clang/Basic/MetaprogramContext.h @@ -0,0 +1,80 @@ +//===--- MetaprogramContext.h -----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +// Defines MetaprogramContext, a discriminated union which transmits context +// info from a MetaprogramDecl to its StringInjectionStmts, for proper +// interpretation of their parsed content. +// +//===----------------------------------------------------------------------===// + + +#ifndef LLVM_CLANG_BASIC_METAPROGRAMCONTEXT_H +#define LLVM_CLANG_BASIC_METAPROGRAMCONTEXT_H + +#include "clang/Basic/Specifiers.h" //for AccessSpecifier + +namespace clang { + +struct ParsedAttributesWithRange; +class ParsingDeclSpec; + +/// Carries parser and context info to any metaparse instances +/// encountered while parsing a MetaprogramDecl. +struct MetaprogramContext { + union { + struct { + // FIXME needed? + ParsedAttributesWithRange &attrs; + ParsingDeclSpec *DS; + } extdecl; + struct { + ParsedAttributesWithRange &AccessAttrs; + AccessSpecifier AS; + } cls; + struct { + //nothing for now + } fcn; + }; + enum { + MC_file, + MC_cls, + MC_fcn + } kind; + + AccessSpecifier getAccessUnsafe() const { + if (kind == MC_cls) + return cls.AS; + else + return AS_none; + } + + /// Call only when you know \c isClassContext()==true . + AccessSpecifier getAccess() const { + assert(kind == MC_cls); + return AS_none; + } + + bool isClassContext() const { return kind == MC_cls; } + bool isFunctionContext() const { return kind == MC_fcn; } + bool isFileContext() const { return kind == MC_file; } + + MetaprogramContext(ParsedAttributesWithRange &attrs, + ParsingDeclSpec *DS) + : extdecl{attrs, DS}, kind(MC_file) + {} + MetaprogramContext(ParsedAttributesWithRange &AccessAttrs, + AccessSpecifier AS) + : cls{AccessAttrs, AS}, kind(MC_cls) + {} + MetaprogramContext() + : fcn{}, kind(MC_fcn) + {} +}; + +} //namespace clang + +#endif //LLVM_CLANG_BASIC_METAPROGRAMCONTEXT_H diff --git a/clang/include/clang/Basic/StmtNodes.td b/clang/include/clang/Basic/StmtNodes.td --- a/clang/include/clang/Basic/StmtNodes.td +++ b/clang/include/clang/Basic/StmtNodes.td @@ -171,6 +171,9 @@ def ConceptSpecializationExpr : StmtNode; def RequiresExpr : StmtNode; +// [StringInjection] +def StringInjectionStmt : StmtNode; + // Obj-C Expressions. def ObjCStringLiteral : StmtNode; def ObjCBoxedExpr : StmtNode; diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -261,6 +261,7 @@ // KEYCXX20 - This is a C++ keyword introduced to C++ in C++20 // KEYCONCEPTS - This is a keyword if the C++ extensions for concepts // are enabled. +// KEYSTRINGINJ - This is a string injection metaprogramming is enabled. // KEYMODULES - This is a keyword if the C++ extensions for modules // are enabled. // KEYGNU - This is a keyword if GNU extensions are enabled @@ -399,6 +400,10 @@ COROUTINES_KEYWORD(co_return) COROUTINES_KEYWORD(co_yield) +// String Injection keywords +KEYWORD(__inj , KEYSTRINGINJ) +KEYWORD(__injf , KEYSTRINGINJ) + // C++ modules TS keywords MODULES_KEYWORD(module) MODULES_KEYWORD(import) diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -2206,6 +2206,11 @@ defm pch_debuginfo: OptInCC1FFlag<"pch-debuginfo", "Generate ", "Do not generate ", "debug info for types in an object file built from this PCH and do not generate them elsewhere">; +defm string_injection : BoolFOption<"string-injection", + LangOpts<"StringInjection">, DefaultFalse, + PosFlag, + NegFlag>; + def fimplicit_module_maps : Flag <["-"], "fimplicit-module-maps">, Group, Flags<[NoXarchOption, CC1Option, CoreOption]>, HelpText<"Implicitly search the file system for module map files.">, diff --git a/clang/include/clang/Lex/Lexer.h b/clang/include/clang/Lex/Lexer.h --- a/clang/include/clang/Lex/Lexer.h +++ b/clang/include/clang/Lex/Lexer.h @@ -128,6 +128,10 @@ bool HasLeadingEmptyMacro; + /// True if the buffer being lexed is a constant evaluated string literal + /// generated earlier in the compilation by string injection. + bool BufferIsInjectedStr = false; + /// True if this is the first time we're lexing the input file. bool IsFirstTimeLexingFile; @@ -209,6 +213,17 @@ return BufferPtr == BufferEnd; } + /// True if the buffer being lexed is a constant evaluated string literal + /// generated earlier in the compilation by string injection. + bool ParsingFromInjectedStr() const { return BufferIsInjectedStr; } + + /// Lexer constructor - Create a new lexer object for lexing the contents of + /// an injected string literal (which does not have an associated file + /// object, nor SourceLocations associated with each lexed character). + Lexer(SourceLocation fileloc, const LangOptions &langOpts, + const char *BufStart, const char *BufPtr, const char *BufEnd, + Preprocessor &PP); + /// isKeepWhitespaceMode - Return true if the lexer should return tokens for /// every character in the file, including whitespace and comments. This /// should only be used in raw mode, as the preprocessor is not prepared to diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h --- a/clang/include/clang/Lex/Preprocessor.h +++ b/clang/include/clang/Lex/Preprocessor.h @@ -130,6 +130,7 @@ class Preprocessor { friend class VAOptDefinitionContext; friend class VariadicMacroScopeGuard; + friend class ParserBrickWallRAII; llvm::unique_function OnToken; std::shared_ptr PPOpts; @@ -529,13 +530,21 @@ CLK_Lexer, CLK_TokenLexer, CLK_CachingLexer, - CLK_LexAfterModuleImport + CLK_LexAfterModuleImport, + CLK_InjectedStrLexer, + CLK_TerminatorPretendLexer } CurLexerKind = CLK_Lexer; /// If the current lexer is for a submodule that is being built, this /// is that submodule. Module *CurLexerSubmodule = nullptr; + /// If the current lexer is a CLK_TerminatorPretendLexer, artifically lexing + /// different terminator characters (')', '}', etc.) to recover from an error + /// encountered during string injection, this specifies which particular + /// terminator should be lexed next. + unsigned CurTerminator = 0; + /// Keeps track of the stack of files currently /// \#included, and macros currently being expanded from, not counting /// CurLexer/CurTokenLexer. @@ -878,6 +887,16 @@ std::unique_ptr TokenLexerCache[TokenLexerCacheSize]; /// \} + /// \{ + /// Cache of injected string lexers to reduce malloc traffic. + /// Very big cache relative to TokenLexerCacheSize, because we often need + /// to push a big stack of these in any MetaprogramDecl call that + /// contains StringInjectionStmts. + enum { InjectedStrLexerCacheSize = 64 }; + unsigned NumCachedInjectedStrLexers; + std::unique_ptr InjectedStrLexerCache[InjectedStrLexerCacheSize]; + /// \} + /// Keeps macro expanded tokens for TokenLexers. // /// Works like a stack; a TokenLexer adds the macro expanded tokens that is @@ -923,6 +942,26 @@ /// of that list. MacroInfoChain *MIChainHead = nullptr; + /// True if Lex.ParsingFromInjectedStr(). + bool ParsingFromInjectedStrBool = false; + + /// True if while parsing an injected string we encountered an error. + bool ErrorWhileParsingFromInjectedStrBool = false; + + /// True if we have finished parsing all available injected strings + /// produced by the metaprogram currently being evaluated. + bool NoMoreInjectedStrsAvailableBool = false; + + /// The location to assign to tokens parsed from an injected source string. + SourceLocation CurInjectedStrLoc = SourceLocation(); + + /// This variable is used to ensure that, while temporarily lexing + /// a template instantiation's expand statement-generated source + /// strings, you know exactly when those strings have been fully + /// lexed so that upon returning to the instantiation point the + /// lexer stack is in the same state is was before. + size_t TotalNumLexersAtRAIIConstruction = 0; + void updateOutOfDateIdentifier(IdentifierInfo &II) const; public: @@ -1400,6 +1439,56 @@ void EnterMacro(Token &Tok, SourceLocation ILEnd, MacroInfo *Macro, MacroArgs *Args); + // These defs are trivial, but we put them into the cpp so we + // can insert debug messages without incurring huge extra compile + // times due to altering a much-used header: + size_t getTotalNumLexers() const; + + /// True if Lex.ParsingFromInjectedStr(). + bool ParsingFromInjectedStr() const; + + /// True if while parsing an injected string we encountered an error. + bool ErrorWhileParsingFromInjectedStr() const; + + /// True if we have finished parsing all available injected strings + /// produced by the metaprogram currently being evaluated. + bool NoMoreInjectedStrsAvailable() const; + + + void setNoMoreInjectedStrsAvailable(bool val); + void setErrorWhileParsingFromInjectedStr(bool val); + + /// The location to assign to tokens parsed from an injected string, + /// for diagnostic purposes + SourceLocation curInjectedStrLoc() const; + + /// Used for diagnostics in case of errors while parsing injected strings: + void setCurInjectedStrLoc(SourceLocation loc); + + /// True if we are currently lexing artifically generated terminator + /// characters (')', '}') in an attempt to recover from an error encountered + /// while parsing injected strings. + bool LexingPretendTerminators() const { + return CurLexerKind == CLK_TerminatorPretendLexer; + } + + /// Builds a new Lexer to parse an injected string and calls + /// EnterSourceFileWithLexer, thus pushing the string onto the + /// lexer stack to be processed. + void PushGeneratedSrcStr(StringRef str, SourceLocation loc); + + /// This will be called before storing (via copying!) + /// CachedTokens and other cache info when constructing a + /// PreprocessorBrickWallRAII. + /// If there is anything to be done to clear out unneeded tokens + /// etc. before this copy, do those tasks within this function. + void CleanUpCache(); + +#ifndef NDEBUG + void debugDispCachedTokens(const char *msg = "") const; + void debugDispLexerKinds(const char *msg = "") const; +#endif + private: /// Add a "macro" context to the top of the include stack, /// which will cause the lexer to start returning the specified tokens. @@ -2100,23 +2189,8 @@ private: friend void TokenLexer::ExpandFunctionArguments(); - void PushIncludeMacroStack() { - assert(CurLexerKind != CLK_CachingLexer && "cannot push a caching lexer"); - IncludeMacroStack.emplace_back(CurLexerKind, CurLexerSubmodule, - std::move(CurLexer), CurPPLexer, - std::move(CurTokenLexer), CurDirLookup); - CurPPLexer = nullptr; - } - - void PopIncludeMacroStack() { - CurLexer = std::move(IncludeMacroStack.back().TheLexer); - CurPPLexer = IncludeMacroStack.back().ThePPLexer; - CurTokenLexer = std::move(IncludeMacroStack.back().TheTokenLexer); - CurDirLookup = IncludeMacroStack.back().TheDirLookup; - CurLexerSubmodule = IncludeMacroStack.back().TheSubmodule; - CurLexerKind = IncludeMacroStack.back().CurLexerKind; - IncludeMacroStack.pop_back(); - } + void PushIncludeMacroStack(); + void PopIncludeMacroStack(); void PropagateLineStartLeadingSpaceInfo(Token &Result); diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -65,6 +65,20 @@ friend class ObjCDeclContextSwitch; friend class ParenBraceBracketBalancer; friend class BalancedDelimiterTracker; + friend class ParserBrickWallRAII; + friend class TempParseIntoClassInstantiation; + friend bool Sema:: + InstantiateClass(SourceLocation PointOfInstantiation, + CXXRecordDecl *Instantiation, CXXRecordDecl *Pattern, + const MultiLevelTemplateArgumentList &TemplateArgs, + TemplateSpecializationKind TSK, + bool Complain); + friend void Sema::ActOnFinishMetaprogramDecl(Scope *S, Decl *D, Stmt *Body); + friend bool Sema:: + InjectQueuedStrings(SourceLocation POI, + ArrayRef InjectedStringChunks, + MetaprogramDecl *MD); + Preprocessor &PP; @@ -407,6 +421,10 @@ /// just a regular sub-expression. SourceLocation ExprStatementTokLoc; + /// True if we are parsing code from an injected string literal evaluated + /// earlier in the compilation. + bool ParsingFromInjectedStrVal = false; + /// Flags describing a context in which we're parsing a statement. enum class ParsedStmtContext { /// This context permits declarations in language modes where declarations @@ -434,6 +452,10 @@ Parser(Preprocessor &PP, Sema &Actions, bool SkipFunctionBodies); ~Parser() override; + /// True if we are parsing code from an injected string literal evaluated + /// earlier in the compilation. + bool ParsingFromInjectedStr() const { return ParsingFromInjectedStrVal; } + const LangOptions &getLangOpts() const { return PP.getLangOpts(); } const TargetInfo &getTargetInfo() const { return PP.getTargetInfo(); } Preprocessor &getPreprocessor() const { return PP; } @@ -568,7 +590,7 @@ /// Return the current token to the token stream and make the given /// token the current token. - void UnconsumeToken(Token &Consumed) { + void UnconsumeToken(const Token &Consumed) { Token Next = Tok; PP.EnterToken(Consumed, /*IsReinject*/true); PP.Lex(Tok); @@ -3122,6 +3144,7 @@ SourceLocation AttrFixitLoc, unsigned TagType, Decl *TagDecl); + void HandleLateParsing(); void ParseCXXMemberSpecification(SourceLocation StartLoc, SourceLocation AttrFixitLoc, ParsedAttributesWithRange &Attrs, @@ -3512,6 +3535,33 @@ ExprResult ParseArrayTypeTrait(); ExprResult ParseExpressionTrait(); + //===--------------------------------------------------------------------===// + // StringInjection + bool ParseArbitraryConstexprExprArgs(SmallVectorImpl &Args, + bool &AnyDependent); + bool ParseArbitraryParensConstexprExprArgs(SourceLocation &LParenLoc, + SmallVectorImpl &Args, + SourceLocation &RParenLoc, + bool &AnyDependent); + + /// Parse an __inj(...) or __injf(...) statement + StmtResult ParseStringInjectionStmt(bool IsSpelledWithF); + + /// Parse a __metaparse_cast expression + ExprResult ParseCXXMetaparseCastExpr(); + + /// Parse a metaprogram. + /// \param MetaCtx: holds data needed by the Parser for injected string parsing + /// \param Nested: if this is being parsed while processing another + /// metaprogram (I.e., if this is called while doing a template instantiation + /// that was needed while parsing another metaprogram. "Nested" does *not* + /// refer to e.g. constexpr { constexpr { ... } }, but rather to e.g. + /// \code + /// constexpr { __inj("constexpr { ... }"); } //Nested = true for inner + /// \endcode + DeclGroupPtrTy ParseMetaprogram(struct MetaprogramContext &MetaCtx, + bool Nested); + //===--------------------------------------------------------------------===// // Preprocessor code-completion pass-through void CodeCompleteDirective(bool InConditional) override; diff --git a/clang/include/clang/Parse/RAIIObjectsForParser.h b/clang/include/clang/Parse/RAIIObjectsForParser.h --- a/clang/include/clang/Parse/RAIIObjectsForParser.h +++ b/clang/include/clang/Parse/RAIIObjectsForParser.h @@ -459,6 +459,168 @@ } void skipToEnd(); }; + + + /// ParserBrickWallRAII - stores old values of many Parser state variables, + /// to permit temporary parsing in a different context, without affecting + /// the old parser state. + /// + /// The goal is to erect a "brick wall" separating the old context from + /// the temporary one. New lexers can then be pushed "in front of" the + /// brick wall and the temporary parsing can proceed with the assurance + /// that parsing will not accidentally eat into the content beyond the wall, + /// even in the event of errors. + /// + /// This is used to process __inj statements encountered in metaprograms; + /// e.g.: + /// \code + /// template + /// class MyClass { + /// int varA; + /// consteval { + /// __inj( "int whoops = ", I, "[;" ); ( + /// } + /// }; + /// + /// MyClass<3> /*to instantiate, we have to parse foo above. Before doing + /// so we create a brick wall. This helps us detect the + /// unterminated '[' in the __inj and recover without + /// eating into\ the m below. */ + /// m; + /// + /// \endocode + /// + /// Note that these can be constructed recursively, which is + /// necessary when we encounter nested metaprograms: + /// \code + /// consteval { + /// __inj("consteval { __inj(\"int i = 3;\"); }"); + /// } + /// \endcode + /// + class ParserBrickWallRAII { + class JustTheParserVariables { + friend class ParserBrickWallRAII; + Parser *TheParser; + bool Valid = true; + bool ShouldCallDestructor = true; + + public: + JustTheParserVariables(Parser &P); + JustTheParserVariables(JustTheParserVariables &&other); + ~JustTheParserVariables(); + void setInvalid(); + } SavedParseState; + + class PreprocessorBrickWallRAII { + friend class ParserBrickWallRAII; + Preprocessor &PP; + const size_t OldTotalNumLexersAtRAIIConstruction; + bool OldParsingFromInjectedStrBool; + decltype(PP.CachedTokens) SavedCachedTokens; + decltype(PP.CachedLexPos) SavedCachedLexPos; + decltype(PP.BacktrackPositions) SavedBacktrackPositions; + bool Valid = true; + bool ShouldCallDestructor = true; + public: + PreprocessorBrickWallRAII(Preprocessor &ThePP); + PreprocessorBrickWallRAII(PreprocessorBrickWallRAII &&other); + ~PreprocessorBrickWallRAII(); + /// Used in ~ParserBrickWallRAII() (NOT in ~JustTheParserVariables()) + /// BEFORE the SavedPreprocessorState destruction + /// to clear out any dead lexers -- (only TokenLexers I think) left over + /// by late parsing. + void setInvalid(); + + void clearAnyDeadLexers(); + bool TotalLexers_Equals_TotalLexersAtRAIIConstruction() const { + return PP.getTotalNumLexers() == PP.TotalNumLexersAtRAIIConstruction; + } + } SavedPreprocessorState; + bool Valid = true; + public: + ParserBrickWallRAII(Parser &P); + ParserBrickWallRAII(ParserBrickWallRAII &&other); + ParserBrickWallRAII(const ParserBrickWallRAII &) = delete; + ~ParserBrickWallRAII(); + + void setInvalid(); + bool PPTotalLexers_Equals_TotalLexersAtRAIIConstruction() const { + return SavedPreprocessorState. + TotalLexers_Equals_TotalLexersAtRAIIConstruction(); + } + }; + + /// Saves/restores the state of the Sema::PII field (i.e. the thing returned + /// by \c S.getParsingIntoInstantiationStatus() ). + class SemaPIIRAII { + Sema &SemaRef; + decltype(SemaRef.PII) OldPII; + bool Exited = false; + public: + SemaPIIRAII(Sema &SemaRef) : SemaRef(SemaRef), OldPII(SemaRef.PII) {} + SemaPIIRAII(Sema &SemaRef, decltype(SemaRef.PII) NewPII); + SemaPIIRAII(const SemaPIIRAII &) = delete; + SemaPIIRAII(SemaPIIRAII &&other); + ~SemaPIIRAII(); + /// Exit early + void Exit(); + }; + + /// A \c ParserBrickWallRAII subclass which also manages Sema scope fields + /// for proper lookup; then on destruction returns + /// the parser and TheSema.CurScope to their original states. + class TempParseIntoDiffScope : public ParserBrickWallRAII { + Sema &TheSema; + Scope *OldScope; + SemaPIIRAII SavedPIIState; + bool ShouldCallDestructor = true; + public: + TempParseIntoDiffScope(Sema &S, Sema::ParsingIntoInstantiation NewPII, + unsigned ScopeFlags); + TempParseIntoDiffScope(TempParseIntoDiffScope &&other); + TempParseIntoDiffScope(const TempParseIntoDiffScope &) = delete; + ~TempParseIntoDiffScope(); + }; + + /// A \c TempParseIntoDiffScope subclass which also manages any Sema + /// fields needed to parse into a class instantiation. + /// + /// \code + /// template + /// struct Tmpl { + /// T varA; + /// consteval { + /// __inj("int varB"); + /// } + /// char varD; + /// }; + /// Tmpl /*TempParseIntoClassInstantiation CREATED HERE, + /// *in InstantiateClass*/ myinstantiation; + /// \endcode + /// + /// During instantiation of Tmpl (which occurs just before parsing + /// the identifier "myinstantiation"), we will need to do temporary parsing. + /// We want a brick wall to be sure our parsing doesn't creep beyond the + /// current parse state (i.e. into myinstantiation); however we need more + /// than that: we will also to temporarily set up the context and scope of the + /// instantiation so that we may parse directly into it and do lookup from it, + /// and then when we're done restore the original parse state, context, scope, + /// etc. + class TempParseIntoClassInstantiation : public TempParseIntoDiffScope { + Parser::ParsingClassDefinition Pcd; + public: + TempParseIntoClassInstantiation(Sema &S, bool IsInterface); + ~TempParseIntoClassInstantiation(); + }; + + /// A \c TempParseIntoDiffScope subclass which also manages Sema + /// details needed for parsing into a function instantiation. + class TempParseIntoFuncInstantiation : public TempParseIntoDiffScope { + public: + TempParseIntoFuncInstantiation(Sema &S); + ~TempParseIntoFuncInstantiation(); + }; } // end namespace clang #endif diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -146,6 +146,7 @@ class LocalInstantiationScope; class LookupResult; class MacroInfo; + struct MetaprogramContext; typedef ArrayRef> ModuleIdPath; class ModuleLoader; class MultiLevelTemplateArgumentList; @@ -176,6 +177,7 @@ class OverloadExpr; class ParenListExpr; class ParmVarDecl; + class Parser; class Preprocessor; class PseudoDestructorTypeStorage; class PseudoObjectExpr; @@ -9613,6 +9615,121 @@ DeclContext *FindInstantiatedContext(SourceLocation Loc, DeclContext *DC, const MultiLevelTemplateArgumentList &TemplateArgs); + //===--- String injection ---===// + /// A number of expression/statement types take an arbitrary + /// number of string and or integer arguments. This function standardizes + /// the Sema processing of them. + /// \returns false if errors encountered. + /// \param Args points to a container whose contents may be changed + /// by this method. + bool PrepareStrIntArgsForEval(ArrayRef Args); + + /// ParsingIntoInstantiation - when you are parsing declarations or + /// statements *into* a template instantiation + /// (due to metaprogram decls with __inj statements in the template), + /// this is set to either PII_class or PII_func, depending on the kind of + /// template. + /// This is used when we need to instruct Sema to keep track of things needed + /// by the Parser (e.g. Scope and IdResolver with the proper instantiated + /// Decls, for proper Lookup when parsing Stmts when PII==PII_func) that + /// ordinarily Sema would not keep track of during template instantiation, + /// when the Parser usually sits idle. + enum ParsingIntoInstantiation { + /// We are not presently parsing new members into a template instantiation. + /// \code + /// template + /// class Foo { + /// consteval { + /// __inj("class Bar { " + /// " consteval {" + /// " __inj(\"int bar = \", N, \";\");" + /// " }" + /// "};"); + /// } + /// }; + /// int main() { + /// Foo<3> f; //PII = PII_class during the outer __inj processing, + /// //but = PII_false during the inner __inj. + /// } + /// \endcode + PII_false = 0, + /// We are presently parsing member decls into a class template + /// instantiation, + /// template + /// class Foo { + /// consteval { __inj("int bar = ", N, ";"); } + /// } + /// int main() { + /// Foo<3> f; //PII = PII_class during the __inj processing + /// } + /// \endcode + PII_class, + /// We are presently parsing statements into a function + /// template instantiation. E.g. + /// \code + /// template + /// void f() { + /// consteval { __inj("int foo = ", N, ";"); } + /// } + /// int main() { + /// f<3>; //PII = PII_false during the __inj processing + /// } + /// \endcode + PII_func + }; + + ParsingIntoInstantiation getParsingIntoInstantiationStatus() const { + return PII; + } + + Decl *ActOnMetaprogramDecl(Scope *S, SourceLocation ConstevalLoc, + unsigned &ScopeFlags, + const MetaprogramContext &MetaCtx, + bool Nested); + void ActOnStartMetaprogramDecl(Scope *S, Decl *MD); + void ActOnFinishMetaprogramDecl(Scope *S, Decl *MD, Stmt *Body); + void ActOnMetaprogramDeclError(Scope *S, Decl *MD); + + bool EvaluateMetaprogramDecl(MetaprogramDecl *MD, FunctionDecl *D); + bool EvaluateMetaprogramDecl(MetaprogramDecl *MD, Expr *E); + bool EvaluateMetaprogramDeclCall(MetaprogramDecl *MD, CallExpr *Call); + + StmtResult ActOnStringInjectionStmt(SourceLocation KeywordLoc, + SourceLocation LParenLoc, + ArrayRef Args, + SourceLocation RParenLoc, + bool AnyDependent, + StringLiteral *WrittenFirstArg = + nullptr); + + bool InjectQueuedStrings(SourceLocation POI, + ArrayRef StringInjectionChunks, + MetaprogramDecl *MD); + + Parser &getParser() { + assert(TheParser && "Expected setParser to have been " + "called when the Parser was first constructed "); + return *TheParser; + } + void setParser(Parser *P) { + assert(P && "Expected non-null parser in setParser call"); + TheParser = P; + } + void TheParserEnterScope(unsigned ScopeFlags); + void TheParserExitScope(); + +private: + ParsingIntoInstantiation PII = PII_false; + + /// For parsing generated source strings using the original parser: + class Parser *TheParser; + + // These are needed by these RAII objects to manage the CurScope and PII + // fields, respectively, during string injection. + friend class TempParseIntoDiffScope; + friend class SemaPIIRAII; + +public: // Objective-C declarations. enum ObjCContainerKind { OCK_None = -1, diff --git a/clang/include/clang/Sema/Template.h b/clang/include/clang/Sema/Template.h --- a/clang/include/clang/Sema/Template.h +++ b/clang/include/clang/Sema/Template.h @@ -316,6 +316,14 @@ /// lookup will search our outer scope. bool CombineWithOuterScope; + /// Whether InstantiatedLocal etc. methods should not only add + /// their decls to the LocalDecls member, but also to + /// SemaRef.Scope and SemaRef.IdResolver. + /// We need this for proper lookup whenever instantiating + /// a function with metaprograms containing __inj + /// statements. + const bool ShouldCopyToSemaScopeEtc; + /// If non-NULL, the template parameter pack that has been /// partially substituted per C++0x [temp.arg.explicit]p9. NamedDecl *PartiallySubstitutedPack = nullptr; @@ -332,7 +340,9 @@ public: LocalInstantiationScope(Sema &SemaRef, bool CombineWithOuterScope = false) : SemaRef(SemaRef), Outer(SemaRef.CurrentInstantiationScope), - CombineWithOuterScope(CombineWithOuterScope) { + CombineWithOuterScope(CombineWithOuterScope), + ShouldCopyToSemaScopeEtc( + SemaRef.getParsingIntoInstantiationStatus() == Sema::PII_func) { SemaRef.CurrentInstantiationScope = this; } @@ -469,6 +479,8 @@ const MultiLevelTemplateArgumentList &TemplateArgs; Sema::LateInstantiatedAttrVec* LateAttrs = nullptr; LocalInstantiationScope *StartingScope = nullptr; + //FIXME this may not be needed (StringInjection) + ParsedAttributesWithRange *AccessAttrs = nullptr; /// A list of out-of-line class template partial /// specializations that will need to be instantiated after the @@ -492,6 +504,19 @@ SubstIndex(SemaRef, SemaRef.ArgumentPackSubstitutionIndex), Owner(Owner), TemplateArgs(TemplateArgs) {} + // FIXME may not be needed (StringInjection) + /// Possibly needed for parsing into a template + /// instantiation over multiple distinct metaprograms. + ParsedAttributesWithRange &getAccessAttrs() { + assert(AccessAttrs && + "Only call this if you've constructed this " + "TemplateDeclInstantiator with nonnull AccessAttrs"); + return *AccessAttrs; + } + void setAccessAttrs(ParsedAttributesWithRange &attrs) { + AccessAttrs = &attrs; + } + // Define all the decl visitors using DeclNodes.inc #define DECL(DERIVED, BASE) \ Decl *Visit ## DERIVED ## Decl(DERIVED ## Decl *D); diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2911,6 +2911,9 @@ FunctionDeclBits.IsMultiVersion = false; FunctionDeclBits.IsCopyDeductionCandidate = false; FunctionDeclBits.HasODRHash = false; + FunctionDeclBits.IsMetaprogram = false; + FunctionDeclBits.HasDependentCodeInjectingMetaprograms = false; + FunctionDeclBits.IsCodeInjectingMetafunction = false; if (TrailingRequiresClause) setTrailingRequiresClause(TrailingRequiresClause); } diff --git a/clang/lib/AST/DeclBase.cpp b/clang/lib/AST/DeclBase.cpp --- a/clang/lib/AST/DeclBase.cpp +++ b/clang/lib/AST/DeclBase.cpp @@ -858,6 +858,7 @@ case Empty: case LifetimeExtendedTemporary: case RequiresExprBody: + case Metaprogram: // Never looked up by name. return 0; } @@ -1569,17 +1570,21 @@ void DeclContext::addDecl(Decl *D) { addHiddenDecl(D); - if (auto *ND = dyn_cast(D)) - ND->getDeclContext()->getPrimaryContext()-> - makeDeclVisibleInContextWithFlags(ND, false, true); + if (auto *ND = dyn_cast(D)) { + ND->getDeclContext() + ->getPrimaryContext() + ->makeDeclVisibleInContextWithFlags(ND, false, true); + } } void DeclContext::addDeclInternal(Decl *D) { addHiddenDecl(D); - if (auto *ND = dyn_cast(D)) - ND->getDeclContext()->getPrimaryContext()-> - makeDeclVisibleInContextWithFlags(ND, true, true); + if (auto *ND = dyn_cast(D)) { + ND->getDeclContext() + ->getPrimaryContext() + ->makeDeclVisibleInContextWithFlags(ND, true, true); + } } /// buildLookup - Build the lookup data structure with all of the diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -32,6 +32,7 @@ #include "clang/Basic/IdentifierTable.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/LangOptions.h" +#include "clang/Basic/MetaprogramContext.h" #include "clang/Basic/OperatorKinds.h" #include "clang/Basic/PartialDiagnostic.h" #include "clang/Basic/SourceLocation.h" @@ -108,9 +109,9 @@ ImplicitCopyAssignmentHasConstParam(true), HasDeclaredCopyConstructorWithConstParam(false), HasDeclaredCopyAssignmentWithConstParam(false), - IsAnyDestructorNoReturn(false), IsLambda(false), - IsParsingBaseSpecifiers(false), ComputedVisibleConversions(false), - HasODRHash(false), Definition(D) {} + IsAnyDestructorNoReturn(false), HasDependentCodeInjectingMetaprograms(false), + IsLambda(false), IsParsingBaseSpecifiers(false), + ComputedVisibleConversions(false), HasODRHash(false), Definition(D) {} CXXBaseSpecifier *CXXRecordDecl::DefinitionData::getBasesSlowCase() const { return Bases.get(Definition->getASTContext().getExternalSource()); @@ -3363,6 +3364,69 @@ return APVal; } +void MetaprogramDecl::anchor() {} + +MetaprogramDecl::MetaprogramDecl(DeclContext *DC, SourceLocation KeywordLoc) + : Decl(Metaprogram, DC, KeywordLoc), + Representation(), Call(nullptr), MetaCtx(), + sr(StmtEmpty()), LambdaExpr(nullptr), + InstantiatedFrom(nullptr), + IsDependent(false), AlreadyRun(false) +{} + +MetaprogramDecl::MetaprogramDecl(DeclContext *DC, SourceLocation KeywordLoc, + const MetaprogramContext &MetaCtx, + FunctionDecl *Fn, + MetaprogramDecl *InstantiatedFrom) + : Decl(Decl::Metaprogram, DC, KeywordLoc), + Representation(Fn), Call(nullptr), MetaCtx(MetaCtx), + sr(StmtEmpty()), LambdaExpr(nullptr), + InstantiatedFrom(InstantiatedFrom), + IsDependent(false), AlreadyRun(false) +{ + if (MetaCtx.isClassContext()) + setAccess(MetaCtx.cls.AS); +} + +MetaprogramDecl::MetaprogramDecl(DeclContext *DC, SourceLocation KeywordLoc, + const MetaprogramContext &MetaCtx, + CXXRecordDecl *Closure, + MetaprogramDecl *InstantiatedFrom) + : Decl(Decl::Metaprogram, DC, KeywordLoc), + Representation(Closure), Call(nullptr), MetaCtx(MetaCtx), + sr(StmtEmpty()), LambdaExpr(nullptr), + InstantiatedFrom(InstantiatedFrom), + IsDependent(false), AlreadyRun(false) +{ + if (MetaCtx.isClassContext()) + setAccess(MetaCtx.cls.AS); +} + +bool MetaprogramDecl::hasBody() const { + if (Representation.isNull()) + return false; + const FunctionDecl *FD = hasFunctionRepresentation() + ? getImplicitFunctionDecl() + : getImplicitClosureCallOperator(); + return FD->hasBody(); +} + +Stmt *MetaprogramDecl::getBody() const { + if (Representation.isNull()) + return nullptr; + const FunctionDecl *FD = hasFunctionRepresentation() + ? getImplicitFunctionDecl() + : getImplicitClosureCallOperator(); + return FD->getBody(); +} + +SourceRange MetaprogramDecl::getSourceRange() const { + SourceLocation RangeEnd = getLocation(); + if (Stmt *Body = getBody()) + RangeEnd = Body->getEndLoc(); + return SourceRange(getLocation(), RangeEnd); +} + static const char *getAccessName(AccessSpecifier AS) { switch (AS) { case AS_none: diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp --- a/clang/lib/AST/Expr.cpp +++ b/clang/lib/AST/Expr.cpp @@ -1115,7 +1115,15 @@ NumConcatenated * sizeof(SourceLocation)); // Initialize the trailing array of char holding the string data. - std::memcpy(getTrailingObjects(), Str.data(), ByteLength); + // [StringInjection]: +1 to ByteLength to null-terminate + // (necessary only for any string literals which are parsed vian __inj + // statements, though we impose the cost on all string literals + // since that cost seems small). + if (Ctx.getLangOpts().StringInjection) { + std::memcpy(getTrailingObjects(), Str.data(), ByteLength + 1); + getTrailingObjects()[ByteLength] = '\0'; + } else + std::memcpy(getTrailingObjects(), Str.data(), ByteLength); setDependence(ExprDependence::None); } @@ -1133,7 +1141,8 @@ const SourceLocation *Loc, unsigned NumConcatenated) { void *Mem = Ctx.Allocate(totalSizeToAlloc( - 1, NumConcatenated, Str.size()), + 1, NumConcatenated, Str.size() + + (Ctx.getLangOpts().StringInjection ? 1 : 0)), alignof(StringLiteral)); return new (Mem) StringLiteral(Ctx, Str, Kind, Pascal, Ty, Loc, NumConcatenated); diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -4873,10 +4873,77 @@ return Scope.destroy(); } +//===--- String literal evaluation and creation ---===// +static bool EvaluateAsString(const StringLiteral *&StrLitRes, + EvalInfo &Info, const Expr *E) { + assert(!E->isValueDependent()); + assert(!E->isTypeDependent()); + + APValue Res; + if (!Evaluate(Res, Info, E)) { + Info.FFDiag(E->getBeginLoc(), diag::err_constexpr_eval_failed); + return false; + } + + assert(Res.isLValue() && + "Expected an LValue here, but you can try " + "commenting this out to see what happens"); + + // Fetch the StringLiteral from the evaluation result: + APValue::LValueBase Base = Res.getLValueBase(); + + if (Base.is()) { + const Expr *BaseExpr = Base.get(); + assert(isa(BaseExpr) && "Not a string literal"); + StrLitRes = cast(BaseExpr); + assert(StrLitRes); + return true; + } + else { + const ValueDecl *D = Base.get(); + assert(D && "Expected the LValueBase to either " + "be an Expr or a ValueDecl"); +// D->dump(); + llvm_unreachable( + "The Evaluation result was just a ValueDecl, " + "e.g. a ParmVarDecl, rather than the *value of* that Decl." + "You probably should have done " + "defaultFunctionArrayLValueConversion " + "during the ActOn... that handles this expression."); + } +} + +// Creates a c-string of type const char[N]. +static StringLiteral * +MakeString(ASTContext &Ctx, StringRef Str, SourceLocation Loc) { + QualType StrLitTy = Ctx.getConstantArrayType( + Ctx.CharTy.withConst(), llvm::APInt(32, Str.size() + 1), nullptr, + ArrayType::Normal, 0); + + // Create a string literal of type const char [L] where L + // is the number of characters in the StringRef. + return StringLiteral::Create( + Ctx, Str, StringLiteral::Ascii, false, StrLitTy, Loc); +} + +// Creates a c-string of type const char *. +LLVM_ATTRIBUTE_UNUSED +static Expr * +MakeConstCharPointer(ASTContext& Ctx, StringRef Str, SourceLocation Loc) { + // Create an implicit cast expr so that we convert our const char [L] + // into an actual const char * for proper evaluation. + QualType StrTy = Ctx.getPointerType(Ctx.getConstType(Ctx.CharTy)); + return ImplicitCastExpr::Create( + Ctx, StrTy, CK_ArrayToPointerDecay, + MakeString(Ctx, Str, Loc), /*BasePath=*/nullptr, + VK_PRValue, FPOptionsOverride()); +} + + namespace { /// A location where the result (returned value) of evaluating a /// statement should be stored. -struct StmtResult { +struct StmtResultStorage { /// The APValue that should be filled in with the returned value. APValue &Value; /// The location containing the result, if any (used to support RVO). @@ -4897,12 +4964,12 @@ } -static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info, +static EvalStmtResult EvaluateStmt(StmtResultStorage &Result, EvalInfo &Info, const Stmt *S, const SwitchCase *SC = nullptr); /// Evaluate the body of a loop, and translate the result as appropriate. -static EvalStmtResult EvaluateLoopBody(StmtResult &Result, EvalInfo &Info, +static EvalStmtResult EvaluateLoopBody(StmtResultStorage &Result, EvalInfo &Info, const Stmt *Body, const SwitchCase *Case = nullptr) { BlockScopeRAII Scope(Info); @@ -4926,7 +4993,7 @@ } /// Evaluate a switch statement. -static EvalStmtResult EvaluateSwitch(StmtResult &Result, EvalInfo &Info, +static EvalStmtResult EvaluateSwitch(StmtResultStorage &Result, EvalInfo &Info, const SwitchStmt *SS) { BlockScopeRAII Scope(Info); @@ -5004,7 +5071,7 @@ } // Evaluate a statement. -static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info, +static EvalStmtResult EvaluateStmt(StmtResultStorage &Result, EvalInfo &Info, const Stmt *S, const SwitchCase *Case) { if (!Info.nextStep(S)) return ESR_Failed; @@ -5174,10 +5241,9 @@ // We know we returned, but we don't know what the value is. return ESR_Failed; } - if (RetExpr && - !(Result.Slot - ? EvaluateInPlace(Result.Value, Info, *Result.Slot, RetExpr) - : Evaluate(Result.Value, Info, RetExpr))) + if (RetExpr && !(Result.Slot ? EvaluateInPlace(Result.Value, Info, + *Result.Slot, RetExpr) + : Evaluate(Result.Value, Info, RetExpr))) return ESR_Failed; return Scope.destroy() ? ESR_Returned : ESR_Failed; } @@ -5434,6 +5500,43 @@ case Stmt::CXXTryStmtClass: // Evaluate try blocks by evaluating all sub statements. return EvaluateStmt(Result, Info, cast(S)->getTryBlock(), Case); + + case Stmt::StringInjectionStmtClass: { + if (Info.checkingPotentialConstantExpression()) + return ESR_Succeeded; + + if (!Info.EvalStatus.StringInjectionChunks) { + // Only metaprograms can generate code. + Info.CCEDiag(S->getBeginLoc(), diag::note___inj_outside_metaprogram); + return ESR_Failed; + } + + // Compute the source string to parse, and store it in Result. + const StringInjectionStmt *SIS = cast(S); + const StringLiteral *CurStrLit; + SmallString<128> S; + for (const Expr *E : SIS->getArgs()) { + if (E->getType().isCXXStringLiteralType()) { + if (!EvaluateAsString(CurStrLit, Info, E)) + return ESR_Failed; + } else { + assert(E->getType()->isIntegerType() && + "Should have ensured int/strlit types before now"); + llvm::APSInt Res; + if (!EvaluateInteger(E, Res, Info)) { + return ESR_Failed; + } + Res.toString(S, 10); + CurStrLit = MakeString(Info.Ctx, S.str(), E->getBeginLoc()); + S.clear(); + } + assert(CurStrLit); + + // Queue the generated code string for later parsing + Info.EvalStatus.StringInjectionChunks->push_back(CurStrLit); + } + return ESR_Succeeded; + } } } @@ -6144,7 +6247,7 @@ Frame.LambdaThisCaptureField); } - StmtResult Ret = {Result, ResultSlot}; + StmtResultStorage Ret = {Result, ResultSlot}; EvalStmtResult ESR = EvaluateStmt(Ret, Info, Body); if (ESR == ESR_Succeeded) { if (Callee->getReturnType()->isVoidType()) @@ -6178,7 +6281,7 @@ // FIXME: Creating an APValue just to hold a nonexistent return value is // wasteful. APValue RetVal; - StmtResult Ret = {RetVal, nullptr}; + StmtResultStorage Ret = {RetVal, nullptr}; // If it's a delegating constructor, delegate. if (Definition->isDelegatingConstructor()) { @@ -6495,7 +6598,7 @@ // FIXME: Creating an APValue just to hold a nonexistent return value is // wasteful. APValue RetVal; - StmtResult Ret = {RetVal, nullptr}; + StmtResultStorage Ret = {RetVal, nullptr}; if (EvaluateStmt(Ret, Info, Definition->getBody()) == ESR_Failed) return false; @@ -7906,7 +8009,7 @@ } APValue ReturnValue; - StmtResult Result = { ReturnValue, nullptr }; + StmtResultStorage Result = { ReturnValue, nullptr }; EvalStmtResult ESR = EvaluateStmt(Result, Info, *BI); if (ESR != ESR_Succeeded) { // FIXME: If the statement-expression terminated due to 'return', @@ -14934,6 +15037,12 @@ return true; } +bool Expr::EvaluateAsVoid(EvalResult &Result, const ASTContext &Ctx) const { + EvalInfo Info(Ctx, Result, EvalInfo::EM_ConstantExpression); + Info.InConstantContext = true; + return ::EvaluateVoid(this, Info); +} + static bool EvaluateDestruction(const ASTContext &Ctx, APValue::LValueBase Base, APValue DestroyedValue, QualType Type, SourceLocation Loc, Expr::EvalStatus &EStatus, diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp --- a/clang/lib/AST/StmtPrinter.cpp +++ b/clang/lib/AST/StmtPrinter.cpp @@ -2424,6 +2424,28 @@ PrintExpr(S->getOperand()); } +void StmtPrinter::VisitStringInjectionStmt(StringInjectionStmt *S) { + if (S->isSpelledWithF()) { + OS << " __injf("; + PrintExpr(S->getWrittenFirstArg()); + // Only write the odd args (the even ones are + // substrings of the written first arg, which is not + // represented among Args): + for (unsigned i = 1; i < S->arg_size(); i = i+2) { + OS << ", "; + PrintExpr(S->getArg(i)); + } + } else { + OS << "__inj("; + for (unsigned i = 0; i < S->arg_size(); ++i) { + if (i) + OS << ", "; + PrintExpr(S->getArg(i)); + } + OS << ");"; + } +} + // Obj-C void StmtPrinter::VisitObjCStringLiteral(ObjCStringLiteral *Node) { diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp --- a/clang/lib/AST/StmtProfile.cpp +++ b/clang/lib/AST/StmtProfile.cpp @@ -2158,6 +2158,10 @@ void StmtProfiler::VisitRecoveryExpr(const RecoveryExpr *E) { VisitExpr(E); } +void StmtProfiler::VisitStringInjectionStmt(const StringInjectionStmt *S) { + VisitStmt(S); +} + void StmtProfiler::VisitObjCStringLiteral(const ObjCStringLiteral *S) { VisitExpr(S); } diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -2495,6 +2495,27 @@ return false; } +static bool isConstCharPtr(QualType Ty) { + if (const PointerType *Ptr = dyn_cast(Ty)) + Ty = Ptr->getPointeeType(); + return Ty.isConstQualified() && Ty->isCharType(); +} + +static bool isCharArray(QualType Ty) { + if (Ty->isConstantArrayType()) + Ty = cast(Ty.getTypePtr())->getElementType(); + return Ty->isCharType(); +} + +bool QualType::isCXXStringLiteralType() const { + QualType CanonTy = getCanonicalType(); + if (isConstCharPtr(CanonTy)) + return true; + if (isCharArray(CanonTy)) + return true; + return false; +} + bool QualType::isNonWeakInMRRWithObjCWeak(const ASTContext &Context) const { return !Context.getLangOpts().ObjCAutoRefCount && Context.getLangOpts().ObjCWeak && diff --git a/clang/lib/Basic/IdentifierTable.cpp b/clang/lib/Basic/IdentifierTable.cpp --- a/clang/lib/Basic/IdentifierTable.cpp +++ b/clang/lib/Basic/IdentifierTable.cpp @@ -107,6 +107,7 @@ KEYCXX20 = 0x200000, KEYOPENCLCXX = 0x400000, KEYMSCOMPAT = 0x800000, + KEYSTRINGINJ = 0x4000000, KEYSYCL = 0x1000000, KEYALLCXX = KEYCXX | KEYCXX11 | KEYCXX20, KEYALL = (0x1ffffff & ~KEYNOMS18 & @@ -152,6 +153,7 @@ if (LangOpts.ObjC && (Flags & KEYOBJC)) return KS_Enabled; if (LangOpts.CPlusPlus20 && (Flags & KEYCONCEPTS)) return KS_Enabled; if (LangOpts.Coroutines && (Flags & KEYCOROUTINES)) return KS_Enabled; + if (LangOpts.StringInjection && (Flags & KEYSTRINGINJ)) return KS_Extension; if (LangOpts.ModulesTS && (Flags & KEYMODULES)) return KS_Enabled; if (LangOpts.CPlusPlus && (Flags & KEYALLCXX)) return KS_Future; if (LangOpts.CPlusPlus && !LangOpts.CPlusPlus20 && (Flags & CHAR8SUPPORT)) diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp --- a/clang/lib/CodeGen/CGDecl.cpp +++ b/clang/lib/CodeGen/CGDecl.cpp @@ -100,6 +100,7 @@ case Decl::ObjCTypeParam: case Decl::Binding: case Decl::UnresolvedUsingIfExists: + case Decl::Metaprogram: llvm_unreachable("Declaration should not be in declstmts!"); case Decl::Record: // struct/union/class X; case Decl::CXXRecord: // struct/union/class X; [C++] diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -106,6 +106,7 @@ case Stmt::DefaultStmtClass: case Stmt::CaseStmtClass: case Stmt::SEHLeaveStmtClass: + case Stmt::StringInjectionStmtClass: llvm_unreachable("should have emitted these statements as simple"); #define STMT(Type, Base) diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -5969,6 +5969,7 @@ break; case Decl::StaticAssert: + case Decl::Metaprogram: // Nothing to do. break; diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -6243,6 +6243,13 @@ Args.AddLastArg(CmdArgs, options::OPT_fdouble_square_bracket_attributes, options::OPT_fno_double_square_bracket_attributes); + // -fstring-injection is off by default, as it is experimental. + if (Args.hasFlag(options::OPT_fstring_injection, + options::OPT_fno_string_injection, false) && + types::isCXX(InputType)) { + CmdArgs.push_back("-fstring-injection"); + } + // -faccess-control is default. if (Args.hasFlag(options::OPT_fno_access_control, options::OPT_faccess_control, false)) diff --git a/clang/lib/Frontend/TextDiagnostic.cpp b/clang/lib/Frontend/TextDiagnostic.cpp --- a/clang/lib/Frontend/TextDiagnostic.cpp +++ b/clang/lib/Frontend/TextDiagnostic.cpp @@ -1037,7 +1037,11 @@ // range that just exists in whitespace. That most likely means we have // a multi-line highlighting range that covers a blank line. if (StartColNo > EndColNo) { - assert(StartLineNo != EndLineNo && "trying to highlight whitespace"); + // FIXME: SourceLocations for injected strings always point to the + // beginning of the string, for now - ideally they should point inside + // the string, then we wouldn't need to disable this assert. + if (!LangOpts.StringInjection) + assert(StartLineNo != EndLineNo && "trying to highlight whitespace"); StartColNo = EndColNo; } } diff --git a/clang/lib/Lex/Lexer.cpp b/clang/lib/Lex/Lexer.cpp --- a/clang/lib/Lex/Lexer.cpp +++ b/clang/lib/Lex/Lexer.cpp @@ -175,6 +175,18 @@ SetCommentRetentionState(PP->getCommentRetentionState()); } +/// Create a new lexer object for lexing injected strings. +Lexer::Lexer(SourceLocation fileloc, const LangOptions &langOpts, + const char *BufStart, const char *BufPtr, const char *BufEnd, + Preprocessor &PP) + : PreprocessorLexer(&PP, FileID()), + FileLoc(fileloc), + LangOpts(langOpts), + BufferIsInjectedStr(true) { + InitLexer(BufStart, BufPtr, BufEnd); +} + + /// Create_PragmaLexer: Lexer constructor - Create a new lexer object for /// _Pragma expansion. This has a variety of magic semantics that this method /// sets up. It returns a new'd Lexer that must be delete'd when done. @@ -1153,8 +1165,12 @@ // In the normal case, we're just lexing from a simple file buffer, return // the file id from FileLoc with the offset specified. unsigned CharNo = Loc-BufferStart; - if (FileLoc.isFileID()) + + if (FileLoc.isFileID()) { + if (BufferIsInjectedStr) + ++CharNo; // Skip over the initial " of the injected string literal return FileLoc.getLocWithOffset(CharNo); + } // Otherwise, this is the _Pragma lexer case, which pretends that all of the // tokens are lexed from where the _Pragma was defined. @@ -2904,7 +2920,10 @@ SourceLocation EndLoc = getSourceLocation(BufferEnd); // C99 5.1.1.2p2: If the file is non-empty and didn't end in a newline, issue // a pedwarn. - if (CurPtr != BufferStart && (CurPtr[-1] != '\n' && CurPtr[-1] != '\r')) { + if (CurPtr != BufferStart && (CurPtr[-1] != '\n' && CurPtr[-1] != '\r') + //...except if this is an injected source string, in which case this + // isn't really a file. + && !BufferIsInjectedStr) { DiagnosticsEngine &Diags = PP->getDiagnostics(); unsigned DiagID; diff --git a/clang/lib/Lex/PPCaching.cpp b/clang/lib/Lex/PPCaching.cpp --- a/clang/lib/Lex/PPCaching.cpp +++ b/clang/lib/Lex/PPCaching.cpp @@ -161,3 +161,18 @@ CachedTokens.erase(CachedTokens.begin() + CachedLexPos - 1 + NewToks.size()); CachedLexPos += NewToks.size() - 1; } + +// Called before creating a PreprocessorBrickWallRAII. +// If there are any cached properties in the Preprocessor which do not need +// to be saved and restored while we temporarily parse in a different context, +// clear them here, for better efficiency. +void Preprocessor::CleanUpCache() { + if (!isBacktrackEnabled() && CachedLexPos == CachedTokens.size()) { + // FIXME: so far this if condition ALWAYS seems to be true when + // entering a metaprogram decl or instantiation, so perhaps the "if" can + // be removed. + CachedTokens.clear(); + CachedLexPos = 0; + assert(BacktrackPositions.empty()); + } +} \ No newline at end of file diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp --- a/clang/lib/Lex/PPDirectives.cpp +++ b/clang/lib/Lex/PPDirectives.cpp @@ -2633,6 +2633,16 @@ MacroInfo *Preprocessor::ReadOptionalMacroParameterListAndBody( const Token &MacroNameTok, const bool ImmediatelyAfterHeaderGuard) { + // Trying to usefunction-like macros defined + // within a metaparse statement results in crashes, hence this: + if (ParsingFromInjectedStr()) { + Diag(CurInjectedStrLoc, diag::err_metaparse_unsupported_use) + << "(Cannot #define a macro within a metaparse statement.)"; + setErrorWhileParsingFromInjectedStr(true); + DiscardUntilEndOfDirective(); + return nullptr; + } + Token LastTok = MacroNameTok; // Create the new macro. MacroInfo *const MI = AllocateMacroInfo(MacroNameTok.getLocation()); diff --git a/clang/lib/Lex/PPLexerChange.cpp b/clang/lib/Lex/PPLexerChange.cpp --- a/clang/lib/Lex/PPLexerChange.cpp +++ b/clang/lib/Lex/PPLexerChange.cpp @@ -338,6 +338,31 @@ } } +void Preprocessor::PushIncludeMacroStack() { + assert(CurLexerKind != CLK_CachingLexer && "cannot push a caching lexer"); + IncludeMacroStack.emplace_back(CurLexerKind, CurLexerSubmodule, + std::move(CurLexer), CurPPLexer, + std::move(CurTokenLexer), CurDirLookup); + CurPPLexer = nullptr; +} + +void Preprocessor::PopIncludeMacroStack() { + CurLexer = std::move(IncludeMacroStack.back().TheLexer); + CurPPLexer = IncludeMacroStack.back().ThePPLexer; + CurTokenLexer = std::move(IncludeMacroStack.back().TheTokenLexer); + CurDirLookup = IncludeMacroStack.back().TheDirLookup; + CurLexerSubmodule = IncludeMacroStack.back().TheSubmodule; + CurLexerKind = IncludeMacroStack.back().CurLexerKind; + IncludeMacroStack.pop_back(); + // [StringInjection] + assert((getTotalNumLexers() >= TotalNumLexersAtRAIIConstruction) + || ErrorWhileParsingFromInjectedStr() //unterminated metaparse expr + && "Either the last PreprocessorBrickWallRAII object wasn't destroyed " + "when it should have been or you have loaded a lexer from " + "the previous state, such that you're about to lex/parse" + "at a totally different and unexpected/invalid location!"); +} + /// HandleEndOfFile - This callback is invoked when the lexer hits the end of /// the current file. This either returns the EOF token or pops a level off /// the include stack and keeps going. @@ -346,6 +371,56 @@ assert(!CurTokenLexer && "Ending a file when currently in a macro!"); + if (ParsingFromInjectedStr()) { + if (NoMoreInjectedStrsAvailable()) { + // This seems to indicates an ill-formed __metaparse_expr that is + // trying to parse beyond what has been provided. + Diag(CurInjectedStrLoc, diag::err_unterminated_string_injection); + ErrorWhileParsingFromInjectedStrBool = true; + // We will temporarily redirect lexing to a "terminator" lexer, + // which cycles through terminators until the metaparse requests + // are sated; at that point normal lexing will return. + // Instead of pushing the macro stack, we're just going to set the + // lexer kind differently, the recompute it when we need to: + CurLexerKind = CLK_TerminatorPretendLexer; + Lex(Result); + return true; + } + + // Normal injected string parsing: + assert(!CurPPLexer || CurLexerKind == CLK_InjectedStrLexer + && "It seems CurLexerKind was not set/pushed/popped " + "properly for this InjectedStrLexer"); + // Delete or cache the now-dead meta-source string lexer + // (copied this from TokenLexerCache handling in + // HandleEndOfTokenLexer) + if (NumCachedInjectedStrLexers == InjectedStrLexerCacheSize) { + CurLexer.reset(); + } else if (CurLexer) { + // ^ Hack-ish: with nested __inj calls, CurLexer + // has sometimes already been nullified when getting here, + // hence the `if (CurLexer)` test. + InjectedStrLexerCache[NumCachedInjectedStrLexers++] = std::move(CurLexer); + } + if (IncludeMacroStack.size()) { + PopIncludeMacroStack(); + } else { + CurPPLexer = nullptr; + CurTokenLexer.reset(); + } + assert(getTotalNumLexers() >= TotalNumLexersAtRAIIConstruction + || ErrorWhileParsingFromInjectedStrBool + // ^ specifically, an unterminated metaparse + // will eat until it is sated, no stopping it. + && "You must have called PopIncludeMacroStack somewhere " + "else but here, or PopIncludeMacroStack is popping more " + "than one or something"); + if (getTotalNumLexers() == TotalNumLexersAtRAIIConstruction) { + setNoMoreInjectedStrsAvailable(true); + } + return true; + } //if ParsingFromInjectedStr() + // If we have an unclosed module region from a pragma at the end of a // module, complain and close it now. const bool LeavingSubmodule = CurLexer && CurLexerSubmodule; @@ -620,6 +695,15 @@ TokenLexerCache[NumCachedTokenLexers++] = std::move(CurTokenLexer); } + if (CurLexerKind==CLK_InjectedStrLexer) { + // Delete or cache the now-dead injected string lexer. + if (NumCachedInjectedStrLexers == InjectedStrLexerCacheSize) { + CurLexer.reset(); + } else if (CurLexer) { //see note in HandleEndOfFile + InjectedStrLexerCache[NumCachedInjectedStrLexers++] = std::move(CurLexer); + } + } + PopIncludeMacroStack(); } diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp --- a/clang/lib/Lex/Preprocessor.cpp +++ b/clang/lib/Lex/Preprocessor.cpp @@ -106,6 +106,7 @@ ArgMacro = nullptr; InMacroArgPreExpansion = false; NumCachedTokenLexers = 0; + NumCachedInjectedStrLexers = 0; PragmasEnabled = true; ParsingIfOrElifDirective = false; PreprocessedOutput = false; @@ -183,6 +184,9 @@ std::fill(TokenLexerCache, TokenLexerCache + NumCachedTokenLexers, nullptr); CurTokenLexer.reset(); + // Free any cached string injection lexers. + std::fill(InjectedStrLexerCache, InjectedStrLexerCache + NumCachedInjectedStrLexers, nullptr); + // Free any cached MacroArgs. for (MacroArgs *ArgList = MacroArgCache; ArgList;) ArgList = ArgList->deallocate(); @@ -377,7 +381,8 @@ void Preprocessor::recomputeCurLexerKind() { if (CurLexer) - CurLexerKind = CLK_Lexer; + CurLexerKind = (ParsingFromInjectedStr() ? + CLK_InjectedStrLexer : CLK_Lexer); else if (CurTokenLexer) CurLexerKind = CLK_TokenLexer; else @@ -643,6 +648,10 @@ case CLK_LexAfterModuleImport: LexAfterModuleImport(Tok); break; + case CLK_InjectedStrLexer: + case CLK_TerminatorPretendLexer: + // FIXME do these need to be handled here? + llvm_unreachable("Unhandled lexer kind"); } if (Tok.is(tok::eof) && !InPredefines) { ReachedMainFileEOF = true; @@ -904,6 +913,37 @@ case CLK_LexAfterModuleImport: ReturnedToken = LexAfterModuleImport(Result); break; + case CLK_InjectedStrLexer: + if (!ParsingFromInjectedStr()) { + recomputeCurLexerKind(); + continue; + } + assert(CurLexer); + assert(CurLexer->ParsingFromInjectedStr()); + ReturnedToken = CurLexer->Lex(Result); + if (Result.getLocation().isInvalid()) + ReturnedToken = NoMoreInjectedStrsAvailableBool; + break; + case CLK_TerminatorPretendLexer: + // The purpose of this lexer is to recover from an injected + // string parsing error involving unbalanced delimiters; it simply + // alternates through possible closing characters as long as necessary. + if (++CurTerminator > 100) + llvm_unreachable("Either something is calling Lex " + "indiscriminately (i.e. without parsing " + "anything), or you need to add another " + "terminator token kind to the switch " + "below"); + switch (CurTerminator % 4) { + //^ be sure to increment the modulus if you + // add a case below + default: Result.setKind(tok::r_paren); break; // ) + case 1: Result.setKind(tok::r_square); break;// ] + case 2: Result.setKind(tok::greater); break; // > + case 3: Result.setKind(tok::r_brace); break; // } + } + ReturnedToken = true; + break; } } while (!ReturnedToken); @@ -1465,3 +1505,82 @@ Record = new PreprocessingRecord(getSourceManager()); addPPCallbacks(std::unique_ptr(Record)); } + +void Preprocessor::PushGeneratedSrcStr(StringRef str, SourceLocation loc) { + assert(ParsingFromInjectedStr() && + "This bool should have been set previously"); + assert(*str.end() == '\0' && + "InjectedStr not null terminated!"); + + // Create a new lexer and add it to the include stack. + if (CurPPLexer || CurTokenLexer) { + PushIncludeMacroStack(); + } + assert(!CurLexer && + "Expected this to be null after PushIncludeMacroStack"); + + // We make use of a cache of lexers to reduce malloc traffic, + // just like with the TokenLexers used to expand macros. + // We would otherwise have to do a new allocation for each + // __inj("...") statement, which would be + // a significant burden I have to imagine. + if (NumCachedInjectedStrLexers == 0) { + CurLexer = std::make_unique(loc, getLangOpts(), + str.begin(), str.begin(), + str.end(), *this); + } else { + assert(NumCachedInjectedStrLexers < InjectedStrLexerCacheSize); + assert(InjectedStrLexerCache[NumCachedInjectedStrLexers - 1] + && "Should not have added a null lexer to the cache!"); + CurLexer = std::move(InjectedStrLexerCache[ + --NumCachedInjectedStrLexers]); + // Is it right to use FileLoc for the location? + // Seems to work okay, but perhaps not right. + CurLexer->FileLoc = loc; + CurLexer->InitLexer(str.begin(), str.begin(), str.end()); + } + CurPPLexer = CurLexer.get(); + assert(CurPPLexer); + assert(CurLexerKind != CLK_LexAfterModuleImport + || !ParsingFromInjectedStr() && + "May be harmless, but didn't expect " + "CurLexerKind==CLK_LexAfterModuleImport while beginning " + "to process a generated source string -- revisit this " + "code to be sure CurLexerKind is set correctly."); + CurLexerKind = CLK_InjectedStrLexer; +} +SourceLocation Preprocessor::curInjectedStrLoc() const { + return CurInjectedStrLoc; +} +void Preprocessor::setCurInjectedStrLoc(SourceLocation loc) { + CurInjectedStrLoc = loc; +} + +size_t Preprocessor::getTotalNumLexers() const { + assert(!CurPPLexer || !CurTokenLexer.get() + && "Expected only one of these at most could be nonnull"); + return IncludeMacroStack.size() + (bool)CurPPLexer + + (bool)CurTokenLexer.get(); +} +bool Preprocessor::ParsingFromInjectedStr() const { + return ParsingFromInjectedStrBool; +} +bool Preprocessor::ErrorWhileParsingFromInjectedStr() const { + return ErrorWhileParsingFromInjectedStrBool; +} +bool Preprocessor::NoMoreInjectedStrsAvailable() const { + assert(!InCachingLexMode() || this->ParsingFromInjectedStr() + && "If we're in caching lex mode when asking about this," + "it's probably an error -- but if we're also requesting " + "a metaparse string, we'll handle that error soon -- but " + "I didn't expect you to ask about this in a case when we " + "would not handle that error, so need to address this."); + + return NoMoreInjectedStrsAvailableBool; +} +void Preprocessor::setNoMoreInjectedStrsAvailable(bool val) { + NoMoreInjectedStrsAvailableBool = val; +} +void Preprocessor::setErrorWhileParsingFromInjectedStr(bool val) { + ErrorWhileParsingFromInjectedStrBool = val; +} \ No newline at end of file diff --git a/clang/lib/Parse/CMakeLists.txt b/clang/lib/Parse/CMakeLists.txt --- a/clang/lib/Parse/CMakeLists.txt +++ b/clang/lib/Parse/CMakeLists.txt @@ -13,6 +13,7 @@ ParseExpr.cpp ParseExprCXX.cpp ParseInit.cpp + ParseMetaprogram.cpp ParseObjc.cpp ParseOpenMP.cpp ParsePragma.cpp @@ -21,6 +22,7 @@ ParseTemplate.cpp ParseTentative.cpp Parser.cpp + ParserBrickWallRAII.cpp LINK_LIBS clangAST diff --git a/clang/lib/Parse/ParseAST.cpp b/clang/lib/Parse/ParseAST.cpp --- a/clang/lib/Parse/ParseAST.cpp +++ b/clang/lib/Parse/ParseAST.cpp @@ -131,6 +131,7 @@ std::unique_ptr ParseOP( new Parser(S.getPreprocessor(), S, SkipFunctionBodies)); Parser &P = *ParseOP.get(); + S.setParser(&P); llvm::CrashRecoveryContextCleanupRegistrar CleanupPrettyStack(llvm::SavePrettyStackState()); diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -1748,6 +1748,14 @@ ProhibitAttributes(attrs); SingleDecl = ParseStaticAssertDeclaration(DeclEnd); break; + case tok::kw_consteval: + // Metaprogram in function context + if (getLangOpts().StringInjection && NextToken().is(tok::l_brace)) { + auto MetaCtx = MetaprogramContext(); + assert(MetaCtx.isFunctionContext()); + return ParseMetaprogram(MetaCtx, /*Nested=*/ParsingFromInjectedStr()); + } + LLVM_FALLTHROUGH; //call ParseSimpleDeclaration if not a metaprogram default: return ParseSimpleDeclaration(Context, DeclEnd, attrs, true, nullptr, DeclSpecStart); @@ -6265,6 +6273,14 @@ D.setInvalidType(true); } + // FIXME; this is hack to avoid the assert below fail when parsing (in + // a metaprogram) injected strings containing certain unbalanced delimiters + if (!D.isPastIdentifier() && PP.ErrorWhileParsingFromInjectedStr()) { + D.SetIdentifier(nullptr, Tok.getLocation()); + D.setInvalidType(true); + return; + } + PastIdentifier: assert(D.isPastIdentifier() && "Haven't past the location of the identifier yet?"); diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -16,6 +16,7 @@ #include "clang/AST/PrettyDeclStackTrace.h" #include "clang/Basic/Attributes.h" #include "clang/Basic/CharInfo.h" +#include "clang/Basic/MetaprogramContext.h" #include "clang/Basic/OperatorKinds.h" #include "clang/Basic/TargetInfo.h" #include "clang/Parse/ParseDiagnostic.h" @@ -2711,6 +2712,15 @@ UsingLoc, DeclEnd, attrs, AS); } + // Metaprogram in class context + if (getLangOpts().StringInjection && + Tok.is(tok::kw_consteval) && + NextToken().is(tok::l_brace)) { + auto MetaCtx = MetaprogramContext(attrs, AS); + assert(MetaCtx.isClassContext()); + return ParseMetaprogram(MetaCtx, /*Nested=*/ParsingFromInjectedStr()); + } + // Hold late-parsed attributes so we can attach a Decl to them later. LateParsedAttrList CommonLateParsedAttrs; @@ -3539,22 +3549,7 @@ // brace-or-equal-initializers for non-static data members (including such // things in nested classes). if (TagDecl && NonNestedClass) { - // We are not inside a nested class. This class and its nested classes - // are complete and we can parse the delayed portions of method - // declarations and the lexed inline method definitions, along with any - // delayed attributes. - - SourceLocation SavedPrevTokLocation = PrevTokLocation; - ParseLexedPragmas(getCurrentClass()); - ParseLexedAttributes(getCurrentClass()); - ParseLexedMethodDeclarations(getCurrentClass()); - - // We've finished with all pending member declarations. - Actions.ActOnFinishCXXMemberDecls(); - - ParseLexedMemberInitializers(getCurrentClass()); - ParseLexedMethodDefs(getCurrentClass()); - PrevTokLocation = SavedPrevTokLocation; + HandleLateParsing(); // We've finished parsing everything, including default argument // initializers. @@ -3569,6 +3564,25 @@ ClassScope.Exit(); } +void Parser::HandleLateParsing() { + // We are not inside a nested class. This class and its nested classes + // are complete and we can parse the delayed portions of method + // declarations and the lexed inline method definitions, along with any + // delayed attributes. + + SourceLocation SavedPrevTokLocation = PrevTokLocation; + ParseLexedPragmas(getCurrentClass()); + ParseLexedAttributes(getCurrentClass()); + ParseLexedMethodDeclarations(getCurrentClass()); + + // We've finished with all pending member declarations. + Actions.ActOnFinishCXXMemberDecls(); + + ParseLexedMemberInitializers(getCurrentClass()); + ParseLexedMethodDefs(getCurrentClass()); + PrevTokLocation = SavedPrevTokLocation; +} + void Parser::DiagnoseUnexpectedNamespace(NamedDecl *D) { assert(Tok.is(tok::kw_namespace)); diff --git a/clang/lib/Parse/ParseMetaprogram.cpp b/clang/lib/Parse/ParseMetaprogram.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/Parse/ParseMetaprogram.cpp @@ -0,0 +1,219 @@ +//===--- ParseMetaprogram.cpp - (C++) consteval {...} parser implem. ------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This file implements the parsing of consteval {...} metaprograms and +// string injection facilities used within it. +// (Note that the parsing of the *content of* string literals passed in +// string injection statements is handled in Sema/SemaMetaprogram.cpp.) +// +//===----------------------------------------------------------------------===// + + +#include "clang/AST/ASTContext.h" +#include "clang/Basic/MetaprogramContext.h" +#include "clang/Parse/ParseDiagnostic.h" +#include "clang/Parse/Parser.h" +#include "clang/Parse/RAIIObjectsForParser.h" +#include "clang/AST/PrettyDeclStackTrace.h" + +using namespace clang; + +/// Parse a metaprogram. +/// +/// \verbatim +/// metaprogram: +/// 'consteval' compound-statement +/// \endverbatim +Parser::DeclGroupPtrTy Parser::ParseMetaprogram(MetaprogramContext &MetaCtx, + bool Nested) { + SourceLocation KeywordLoc = ConsumeToken(); + + if (!Tok.is(tok::l_brace)) { + Diag(Tok, diag::err_expected) << tok::l_brace; + return nullptr; + } + unsigned ScopeFlags; + Decl *D = Actions.ActOnMetaprogramDecl(getCurScope(), KeywordLoc, ScopeFlags, + MetaCtx, Nested); + + // Enter a scope for the metaprogram declaration body. + ParseScope BodyScope(this, ScopeFlags); + + Actions.ActOnStartMetaprogramDecl(getCurScope(), D); + + PrettyDeclStackTraceEntry CrashInfo(Actions.Context, D, KeywordLoc, + "parsing metaprogram declaration body"); + + // Parse the body of the metaprogram. + StmtResult Body(ParseCompoundStatementBody()); + + if (!Body.isInvalid()) + Actions.ActOnFinishMetaprogramDecl(getCurScope(), D, Body.get()); + else + Actions.ActOnMetaprogramDeclError(getCurScope(), D); + + return Actions.ConvertDeclToDeclGroup(D); +} + +/// Helper: parses any number of constexpr Expr arguments, +/// param packs allowed. +/// Returns true if an error is encountered. +bool Parser::ParseArbitraryConstexprExprArgs(SmallVectorImpl &Args, + bool &AnyDependent) { + do { + ExprResult Expr = ParseConstantExpression(); //ParseConditionalExpression? + if (Tok.is(tok::ellipsis)) + Expr = Actions.ActOnPackExpansion(Expr.get(), ConsumeToken()); + if (Expr.isInvalid()) + return true; + if (!AnyDependent && + ( Expr.get()->isTypeDependent() || + Expr.get()->isValueDependent() )) + AnyDependent = true; + + Args.push_back(Expr.get()); + + } while (TryConsumeToken(tok::comma)); + return false; +} + +/// Helper: parses any number of constexpr Expr arguments, +/// param packs allowed, within parentheses, and +/// sets the paren locs. +/// Returns true if an error is encountered. +bool Parser::ParseArbitraryParensConstexprExprArgs(SourceLocation &LParenLoc, + SmallVectorImpl &Args, + SourceLocation &RParenLoc, + bool &AnyDependent) { + BalancedDelimiterTracker Parens(*this, tok::l_paren); + if (Parens.expectAndConsume()) + return true; + LParenLoc = Parens.getOpenLocation(); + + if (ParseArbitraryConstexprExprArgs(Args, AnyDependent)) { + Parens.skipToEnd(); + return true; + } + + if (Parens.consumeClose()) + return true; + + RParenLoc = Parens.getCloseLocation(); + return false; +} + +// Creates a c-string of type const char[N]. +static StringLiteral * +MakeString(ASTContext &Ctx, StringRef Str, SourceLocation Loc) { + QualType StrLitTy = Ctx.getConstantArrayType( + Ctx.CharTy.withConst(), llvm::APInt(32, Str.size() + 1), nullptr, + ArrayType::Normal, 0); + + // Create a string literal of type const char [L] where L + // is the number of characters in the StringRef. + return StringLiteral::Create( + Ctx, Str, StringLiteral::Ascii, false, StrLitTy, Loc); +} + +/// \brief Parse an __inj(...) or __injf(...) statement. +/// +/// string-injection-statement: +/// '__inj' '(' constant-argument-list ')' ';' +/// '__injf' '(' string-literal, constant-argument-list ')' ';' +/// +/// Each argument must be a string literal or integer constant expression, +/// except the first argument of __injf which can only be a string literal. +/// When in a non-dependent metaprogram, the strings will be parsed *with +/// whitespace in between* each argument. +/// +/// Note that the statement parser will collect the trailing semicolon. +/// +StmtResult Parser::ParseStringInjectionStmt(bool IsSpelledWithF) { + SourceLocation KeywordLoc = ConsumeToken(); + SourceLocation LParenLoc, RParenLoc; + SmallVector Args; + bool AnyDependent = false; + + if (ParseArbitraryParensConstexprExprArgs(LParenLoc, Args, + RParenLoc, AnyDependent)) + return StmtError(); + + if (!IsSpelledWithF) + return Actions.ActOnStringInjectionStmt(KeywordLoc, LParenLoc, + Args, RParenLoc, AnyDependent); + + // We have just parsed an __injf(...) statement, e.g. + // __injf("int {} = {};", "foo", 42); + // We want to interpret that as these sequential arguments, i.e. as if + // it had been written + // __inj("int ", "foo", " = ", 42, ";") + + // Make sure the first arg is a non-dependent string literal. + StringLiteral *WrittenFirstArg = dyn_cast(Args[0]); + if (!WrittenFirstArg) { + Diag(LParenLoc.getLocWithOffset(1), + diag::err___injf_first_arg_not_str); + return StmtError(); + } + + SmallVector ReorderedArgs; + static const StringRef PlaceholderStr = + StringInjectionStmt::PlaceholderStr(); + static const size_t PlaceholderSize = PlaceholderStr.size(); + StringRef FirstArgStr = WrittenFirstArg->getString(); + SourceLocation FirstArgLoc = WrittenFirstArg->getBeginLoc(); + const unsigned NumArgs = Args.size(); + + unsigned SubstArgIdx = 0; + StringRef FirstArgSubStr; + SourceLocation FirstArgSubLoc; + size_t CurBegin = 0, CurEnd = FirstArgStr.find(PlaceholderStr, 0); + + while (CurEnd != StringRef::npos) { + // Make sure we don't have more placeholders than remaining args + if (SubstArgIdx + 1 >= NumArgs) { + Diag(RParenLoc, diag::err___injf_wrong_num_args) + << "at least " + std::to_string(SubstArgIdx + 1) + << std::to_string(NumArgs-1); + return StmtError(); + } + // Create a string literal from a substring of the first argument's + // string literal, and push it onto ReorderedArgs. + FirstArgSubStr = FirstArgStr.substr(CurBegin, CurEnd-CurBegin); + FirstArgSubLoc = FirstArgLoc.getLocWithOffset(CurBegin); + StringLiteral *FirstArgSubStrLit = + MakeString(Actions.getASTContext(), FirstArgSubStr, FirstArgSubLoc); + ReorderedArgs.push_back(FirstArgSubStrLit); + + // Push the next of the remaining args onto ReorderedArgs; in this way + // it serves as the substitute for the placeholder. + ReorderedArgs.push_back(Args[++SubstArgIdx]); + + // Iterate + CurBegin = CurEnd + PlaceholderSize; + CurEnd = FirstArgStr.find(PlaceholderStr, CurBegin); + } + // Make sure we don't have fewer placeholders than remaining args + if (SubstArgIdx + 1 < Args.size()) { + Diag(Args[SubstArgIdx]->getExprLoc(), diag::err___injf_wrong_num_args) + << std::to_string(SubstArgIdx + 1) + << std::to_string(NumArgs-1); + return StmtError(); + } + + // Add the final substring + FirstArgSubStr = FirstArgStr.substr(CurBegin, FirstArgStr.size()-CurBegin); + FirstArgSubLoc = FirstArgLoc.getLocWithOffset(CurBegin); + StringLiteral *FirstArgSubStrLit = + MakeString(Actions.getASTContext(), FirstArgSubStr, FirstArgSubLoc); + ReorderedArgs.push_back(FirstArgSubStrLit); + + return Actions.ActOnStringInjectionStmt(KeywordLoc, LParenLoc, + ReorderedArgs, RParenLoc, + AnyDependent, WrittenFirstArg); +} \ No newline at end of file diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -296,6 +296,15 @@ SemiError = "co_return"; break; + case tok::kw___inj: // StringInjection + Res = ParseStringInjectionStmt(/*SpelledWithF=*/false); + SemiError = "__inj"; + break; + case tok::kw___injf: // StringInjection + Res = ParseStringInjectionStmt(/*SpelledWithF=*/true); + SemiError = "__injf"; + break; + case tok::kw_asm: { ProhibitAttributes(Attrs); bool msAsm = false; diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -921,6 +921,16 @@ } goto dont_know; + case tok::kw_consteval: + // Metaprogram in file context + if (getLangOpts().StringInjection && + NextToken().is(tok::l_brace)) { + auto MetaCtx = MetaprogramContext(attrs, DS); + assert(MetaCtx.isFileContext()); + return ParseMetaprogram(MetaCtx, /*Nested=*/ParsingFromInjectedStr()); + } + goto dont_know; + case tok::kw_inline: if (getLangOpts().CPlusPlus) { tok::TokenKind NextKind = NextToken().getKind(); diff --git a/clang/lib/Parse/ParserBrickWallRAII.cpp b/clang/lib/Parse/ParserBrickWallRAII.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/Parse/ParserBrickWallRAII.cpp @@ -0,0 +1,312 @@ +//===--- ParserBrickWallRAII.cpp - saves full state of Parser -------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements RAII objects needed to save & restore the state of +// the Parser, Preprocessor, and Sema objects for parsing into +// template instantiations. +// +//===----------------------------------------------------------------------===// + +#include "clang/Parse/RAIIObjectsForParser.h" + +using namespace clang; + + +ParserBrickWallRAII::JustTheParserVariables::JustTheParserVariables(Parser &P) + : TheParser(&P) { + // Note The PreprocessorBrickWallRAII will NOT have been constructed yet, + // so TheParser->PP is still in its old state. + +#ifndef NDEBUG + unsigned DEBUGorignumlexers = P.PP.getTotalNumLexers(); +#endif + + // If there is anything to be done to clear out unneeded tokens + // etc. before we save the state of the Preprocessor -- i.e. anything which + // does not need to be restored when this RAII is destroyed -- those + // tasks should be done in Preprocessor::CleanUpCache() (defined + // in PPCaching.cpp). + P.PP.CleanUpCache(); + + // We unconsume the current token to help us restore the state during + // destruction via a simple ConsumeToken call. + // Note that this may push an extra lexer temporarily onto the + // IncludeMacroStack than we had originally -- but since the + // PreprocessorBrickWallRAII will be constructed after this, it will register + // the proper num lexers as the TotalNumLexersAtRAIIConstruction it uses to + // identify exactly when it is finished processing the current batch of + // generated source strings. I.e. this code shouldn't affect the behavior + // of the Preprocessor. + Token EmptyToken = {}; + P.UnconsumeToken(EmptyToken); + assert(P.Tok.getLocation().isInvalid() && + "We rely on this dummy token having an invalid location as a signal " + "that the ParserBrickWallRAII has been created before injected string parsing " + "(see lib/Sema/SemaMetaprogram.cpp)"); + + // Calling UnconsumeToken will have put us in CachingLexMode, such that the + // next Lex call would fetch the old P.Tok we just cached. We don't want + // to do that -- we want to preserve that token for when we are done with + // injected string parsing. Exiting caching lex mode will do this. + P.PP.ExitCachingLexMode(); + + assert(P.PP.CachedTokens.size() > 0); + assert(P.PP.getTotalNumLexers() == DEBUGorignumlexers); +} + +ParserBrickWallRAII::JustTheParserVariables:: +JustTheParserVariables(JustTheParserVariables &&other) + : TheParser(other.TheParser), + Valid(other.Valid), + ShouldCallDestructor(other.ShouldCallDestructor) { + // Prevent the destructor from being called in the temporary + other.ShouldCallDestructor = false; +} + +ParserBrickWallRAII::JustTheParserVariables::~JustTheParserVariables() { + if (ShouldCallDestructor) { + // Note the PreprocessorBrickWallRAII will have been destroyed BEFORE this, + // so TheParser->PP should be back to its original state + // (and all the asserts about the numlexers being the same before + // and after will have passed.) + + bool WasInCachingLexMode = TheParser->PP.InCachingLexMode(); + // DWR FIXME if this assert never fails, then get rid of the WasInCachingLexMode test; + // otherwise remove the assert + assert(!WasInCachingLexMode); + + // Reverse our P.UnconsumeToken call from the ctor above. + // We need to EnterCachingLexMode since the UnconsumeToken call during + // construction would have cached the token; we need to enter caching + // lex mode to fetch from that cache. + TheParser->PP.EnterCachingLexMode(); + TheParser->ConsumeAnyToken(); + + if (!WasInCachingLexMode) + TheParser->PP.ExitCachingLexMode(); + } +} + +void ParserBrickWallRAII::JustTheParserVariables::setInvalid() { + Valid = false; +} + +ParserBrickWallRAII::PreprocessorBrickWallRAII:: +PreprocessorBrickWallRAII(Preprocessor &ThePP) + : PP(ThePP), + OldTotalNumLexersAtRAIIConstruction(PP.TotalNumLexersAtRAIIConstruction), + OldParsingFromInjectedStrBool(PP.ParsingFromInjectedStrBool), + SavedCachedTokens(PP.CachedTokens), + SavedCachedLexPos(PP.CachedLexPos), + SavedBacktrackPositions(PP.BacktrackPositions) +{ + PP.TotalNumLexersAtRAIIConstruction = PP.getTotalNumLexers(); + + assert(!PP.NoMoreInjectedStrsAvailableBool + && "Did not expect to construct a PreprocessorBrickWallRAII while " + "DoneProcessing... was true; was that value perhaps not reset " + "correctly after the last constexpr decl processing?"); + PP.ParsingFromInjectedStrBool = true; + + assert(PP.isBacktrackEnabled() == !SavedBacktrackPositions.empty()); //sanity + + // Clear the cache + PP.CachedTokens.clear(); + PP.CachedLexPos = 0; + PP.BacktrackPositions.clear(); +} + +ParserBrickWallRAII::PreprocessorBrickWallRAII:: +PreprocessorBrickWallRAII(PreprocessorBrickWallRAII &&other) + : PP(other.PP), + OldTotalNumLexersAtRAIIConstruction( + other.OldTotalNumLexersAtRAIIConstruction), + OldParsingFromInjectedStrBool(other.OldParsingFromInjectedStrBool), + SavedCachedTokens(std::move(other.SavedCachedTokens)), + SavedCachedLexPos(other.SavedCachedLexPos), + SavedBacktrackPositions(std::move(other.SavedBacktrackPositions)), + ShouldCallDestructor(other.ShouldCallDestructor) { + other.ShouldCallDestructor = false; +} + +ParserBrickWallRAII::PreprocessorBrickWallRAII::~PreprocessorBrickWallRAII() { + assert (PP.getTotalNumLexers() >= PP.TotalNumLexersAtRAIIConstruction + && "You ate past the brick wall!"); + + if (ShouldCallDestructor) { + clearAnyDeadLexers(); + + PP.TotalNumLexersAtRAIIConstruction = OldTotalNumLexersAtRAIIConstruction; + PP.ParsingFromInjectedStrBool = OldParsingFromInjectedStrBool; + + assert(PP.CachedTokens.size() == PP.CachedLexPos || + PP.ErrorWhileParsingFromInjectedStr() && + "Error - finished parsing injected strings, with no errors reported, " + "and num lexers was same before and after (per previous assert), " + "but it appears you somehow cached some extra tokens that you didn't " + "use, indicating you ate into the enclosing source code " + "after the generated source. So, somehow the code has a bug AND the " + "previous assert isn't doing its job! (Well, assuming that the " + "CachedLexPos < CachedTokens.size(); should never be greater.)"); + + PP.setErrorWhileParsingFromInjectedStr(false); + + PP.CachedTokens = SavedCachedTokens; + PP.CachedLexPos = SavedCachedLexPos; + PP.BacktrackPositions = SavedBacktrackPositions; + } +} + +/// Used in ~ParserBrickWallRAII() to clear out any dead lexers (should only +/// be TokenLexers I think, left over by late parsing) before proceeding +/// to the ~PreprocessorBrickWallRAII() call, where we will double-check +/// our work by asserting that the numlexers is the same as when it was +/// first constructed. +/// +/// Also, if errors are encountered during processing, such that you've +/// called setInvalid on this object, this will clear away the "live lexers" +/// as well. +void ParserBrickWallRAII::PreprocessorBrickWallRAII::clearAnyDeadLexers() { + assert(ShouldCallDestructor && + "Calling this after you've already exited the RAII -- " + "this risks tripping asserts, because restoring the " + "old token via ConsumeToken will add a lexer we don't " + "want to clear."); + // If we've been lexing from a terminator lexer to kill off an unterminated + // metaparse expression, we can stop now. Must do this before any more + // PP.Lex calls or we'll never make any progress. + if (PP.CurLexerKind == Preprocessor::CLK_TerminatorPretendLexer) { + // Since we didn't PushIncludeMacroStack when we first set + // CLK_TerminatorPretendLexer as the kind, we can just recompute + // to get back to the original state. + // First must set the ParsingFromInjectedStrBool val; we'll do that + // again later but we definitely need to do it now before recomputing, + // becuase the introduction of the terminator lexer wiped out that + // info: + PP.ParsingFromInjectedStrBool = OldParsingFromInjectedStrBool; + PP.recomputeCurLexerKind(); + assert(PP.CurLexerKind != Preprocessor::CLK_TerminatorPretendLexer); + } + + // After lexing from injected source strings, the old lexers will still be in + // place, only with no content left -- the lexer change will only ... + // FIXME maybe we should just call Preprocessor::HandleEndOfFile + // directly here + Token DummyToken; + + while (PP.getTotalNumLexers() > PP.TotalNumLexersAtRAIIConstruction) + PP.Lex(DummyToken); + + PP.setNoMoreInjectedStrsAvailable(false); +} + +void ParserBrickWallRAII::PreprocessorBrickWallRAII::setInvalid() { + // Hack, possibly - this seems to be left as true sometimes during + // error recovery, so we'll make absolutely sure it is false here. + PP.setNoMoreInjectedStrsAvailable(false); + Valid = false; +} + +ParserBrickWallRAII::ParserBrickWallRAII(Parser &P) + : SavedParseState(P), // Must be constructed BEFORE + // SavedPreprocessorState and destroyed AFTER it. + SavedPreprocessorState(P.PP) { +} +ParserBrickWallRAII::ParserBrickWallRAII(ParserBrickWallRAII &&other) + // SavedParseState must be constructed BEFORE SavedPreprocessorState + // and destroyed AFTER it. + : SavedParseState(std::move(other.SavedParseState)), + SavedPreprocessorState(std::move(other.SavedPreprocessorState)) { +} + +ParserBrickWallRAII::~ParserBrickWallRAII() { +} + +/// Called when we encounter an error during string injection, and need to +/// recover, by e.g. clearing out any pending injected string lexers to +/// get back to the original parser state. +void ParserBrickWallRAII::setInvalid() { + Valid = false; + SavedPreprocessorState.setInvalid(); + SavedParseState.setInvalid(); +} + +SemaPIIRAII::SemaPIIRAII(Sema &SemaRef, decltype(SemaRef.PII) NewPII) + : SemaRef(SemaRef), OldPII(SemaRef.PII) { + SemaRef.PII = NewPII; +} + +SemaPIIRAII::SemaPIIRAII(SemaPIIRAII &&other) + : SemaRef(other.SemaRef), OldPII(other.OldPII), Exited(other.Exited) { + other.Exited = true; +} + +SemaPIIRAII::~SemaPIIRAII() { + if (!Exited) + SemaRef.PII = OldPII; +} + +void SemaPIIRAII::Exit() { + if (!Exited) { + SemaRef.PII = OldPII; + Exited = true; + } +} + + +TempParseIntoDiffScope:: +TempParseIntoDiffScope(Sema &S, Sema::ParsingIntoInstantiation NewPII, + unsigned ScopeFlags) + : ParserBrickWallRAII(S.getParser()), + TheSema(S), + OldScope(S.CurScope), + SavedPIIState(S, NewPII) { + // Enter the scope of TagOrTemplate, using the TUScope as the parent: + assert(S.TUScope); + S.CurScope = S.TUScope; + S.getParser().EnterScope(ScopeFlags); + S.CurScope->setEntity(S.CurContext); +} + +TempParseIntoDiffScope:: +TempParseIntoDiffScope(TempParseIntoDiffScope &&other) + : ParserBrickWallRAII(std::move(other)), + TheSema(other.TheSema), + OldScope(other.OldScope), + SavedPIIState(std::move(other.SavedPIIState)), + ShouldCallDestructor(other.ShouldCallDestructor) { + other.ShouldCallDestructor = false; +} + +TempParseIntoDiffScope::~TempParseIntoDiffScope() { + if (ShouldCallDestructor) { + TheSema.getParser().ExitScope(); + assert(TheSema.CurScope == TheSema.TUScope && + "Expected to be back at TUScope -- was a scope " + "imbalance somehow introduced?"); + TheSema.CurScope = OldScope; + } +} + +TempParseIntoClassInstantiation::TempParseIntoClassInstantiation( + Sema &S, bool IsInterface) + : TempParseIntoDiffScope(S, Sema::PII_class, + Scope::ClassScope|Scope::DeclScope), + Pcd(S.getParser(), Decl::castFromDeclContext(S.CurContext), + true/*always treat instantiations as non-nested classes*/, + IsInterface) {} + +TempParseIntoClassInstantiation::~TempParseIntoClassInstantiation() {} + +TempParseIntoFuncInstantiation::TempParseIntoFuncInstantiation(Sema &S) + : TempParseIntoDiffScope(S, Sema::PII_func, + Scope::FnScope | Scope::DeclScope | + Scope::CompoundStmtScope) {} + +TempParseIntoFuncInstantiation::~TempParseIntoFuncInstantiation() {} \ No newline at end of file diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt --- a/clang/lib/Sema/CMakeLists.txt +++ b/clang/lib/Sema/CMakeLists.txt @@ -46,6 +46,7 @@ SemaInit.cpp SemaLambda.cpp SemaLookup.cpp + SemaMetaprogram.cpp SemaModule.cpp SemaObjCProperty.cpp SemaOpenMP.cpp diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp --- a/clang/lib/Sema/SemaCodeComplete.cpp +++ b/clang/lib/Sema/SemaCodeComplete.cpp @@ -4016,6 +4016,8 @@ return CXCursor_UsingDirective; case Decl::StaticAssert: return CXCursor_StaticAssert; + case Decl::Metaprogram: + return CXCursor_Metaprogram; case Decl::Friend: return CXCursor_FriendDecl; case Decl::TranslationUnit: diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -14593,7 +14593,8 @@ /// body. class ExitFunctionBodyRAII { public: - ExitFunctionBodyRAII(Sema &S, bool IsLambda) : S(S), IsLambda(IsLambda) {} + ExitFunctionBodyRAII(Sema &S, bool IsLambda) + : S(S), IsLambda(IsLambda) {} ~ExitFunctionBodyRAII() { if (!IsLambda) S.PopExpressionEvaluationContext(); @@ -14972,13 +14973,18 @@ } } +#ifndef NDEBUG assert(ExprCleanupObjects.size() == ExprEvalContexts.back().NumCleanupObjects && "Leftover temporaries in function"); assert(!Cleanup.exprNeedsCleanups() && "Unaccounted cleanups in function"); + for (auto E : MaybeODRUseExprs) + E->dump(); assert(MaybeODRUseExprs.empty() && - "Leftover expressions for odr-use checking"); + "Leftover expressions for odr-use checking; see dumps ^. " + "(Maybe you did not EnterExpressionEvaluationContext before eval?)"); +#endif } } // Pops the ExitFunctionBodyRAII scope, which needs to happen before we pop // the declaration context below. Otherwise, we're unable to transform diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -1834,10 +1834,12 @@ case Decl::UnresolvedUsingTypename: case Decl::UnresolvedUsingValue: case Decl::UsingEnum: + case Decl::Metaprogram: // - static_assert-declarations // - using-declarations, // - using-directives, // - using-enum-declaration + // - metaprogram continue; case Decl::Typedef: @@ -2105,6 +2107,11 @@ return false; return true; + case Stmt::StringInjectionStmtClass: + // __inj/__injf are compile-time statements. + return true; + + case Stmt::SwitchStmtClass: case Stmt::CaseStmtClass: case Stmt::DefaultStmtClass: diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp --- a/clang/lib/Sema/SemaExceptionSpec.cpp +++ b/clang/lib/Sema/SemaExceptionSpec.cpp @@ -1406,6 +1406,7 @@ case Expr::SourceLocExprClass: case Expr::ConceptSpecializationExprClass: case Expr::RequiresExprClass: + case Stmt::StringInjectionStmtClass: // These expressions can never throw. return CT_Cannot; diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -6418,6 +6418,17 @@ } } + // If the callee is a code injecting metafunction, so is the caller. + if (LangOpts.StringInjection) { + if (auto CE = dyn_cast(Call.get()->IgnoreImplicitAsWritten())) { + const FunctionDecl *FD = CE->getDirectCallee(); + if (FD && FD->isCodeInjectingMetafunction()) { + assert(isa(CurContext)); + cast(CurContext)->setIsCodeInjectingMetafunction(); + } + } + } + if (LangOpts.OpenMP) Call = ActOnOpenMPCall(Call, Scope, LParenLoc, ArgExprs, RParenLoc, ExecConfig); @@ -16718,8 +16729,16 @@ /// walking the AST looking for it in simple cases. if (auto *Call = dyn_cast(E.get()->IgnoreImplicit())) if (auto *DeclRef = - dyn_cast(Call->getCallee()->IgnoreImplicit())) + dyn_cast(Call->getCallee()->IgnoreImplicit())) { ExprEvalContexts.back().ReferenceToConsteval.erase(DeclRef); + auto *FD = dyn_cast(DeclRef->getDecl()); + + // If this function is a consteval function which injects code, + // we do not want to evaluate it yet; we will handle that in the + // enclosing metaprogram. + if (FD && FD->isCodeInjectingMetafunction()) + return E; + } E = MaybeCreateExprWithCleanups(E); @@ -17674,7 +17693,6 @@ return !Invalid; } - /// Capture the given variable in the captured region. static bool captureInCapturedRegion( CapturedRegionScopeInfo *RSI, VarDecl *Var, SourceLocation Loc, @@ -17916,6 +17934,14 @@ } } +static bool isMetaprogram(DeclContext *DC) { + if (auto FD = dyn_cast(DC)) { + return FD->getDeclName().isIdentifier() + && FD->getName() == "__metaprogdef"; + } + return false; +} + bool Sema::tryCaptureVariable( VarDecl *Var, SourceLocation ExprLoc, TryCaptureKind Kind, SourceLocation EllipsisLoc, bool BuildAndDiagnose, QualType &CaptureType, @@ -17966,7 +17992,19 @@ bool Nested = false; bool Explicit = (Kind != TryCapture_Implicit); unsigned FunctionScopesIndex = MaxFunctionScopesIndex; + bool origVarDCwasMetaprog = isMetaprogram(VarDC); + // If VarDC is a metaprogram, set it to its parent -- that is the + // only way DC can possibly find VarDC. + if (origVarDCwasMetaprog) + VarDC = VarDC->getParent(); do { + if (isMetaprogram(DC)) + DC = DC->getParent(); + if (origVarDCwasMetaprog && VarDC == DC) + break; //i.e. don't try to see if the new VarDC is a lambda scope, + // whose captures you need to look through -- + // just move along to the end game. + // Only block literals, captured statements, and lambda expressions can // capture; other scopes don't work. DeclContext *ParentDC = getParentOfCapturingContextOrNull(DC, Var, diff --git a/clang/lib/Sema/SemaMetaprogram.cpp b/clang/lib/Sema/SemaMetaprogram.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/Sema/SemaMetaprogram.cpp @@ -0,0 +1,634 @@ +//===----- SemaMetaprogram.cpp - (C++) consteval {...} Sema implems ------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Implements semantics for metaprograms and for various +// non-reflection metaprogramming expressions/statements +// +//===----------------------------------------------------------------------===// + + +#include "clang/AST/ASTLambda.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Basic/MetaprogramContext.h" +#include "clang/Parse/Parser.h" +#include "clang/Parse/RAIIObjectsForParser.h" +#include "clang/Sema/SemaInternal.h" +#include "clang/Sema/ScopeInfo.h" + +using namespace clang; +using namespace sema; + +// FIXME: it would be nice to add some sugar indicating the effect of the +// metaprogram on the AST. So: +// Declarations introduced via an injection statement should store +// a pointer to that statement (as sugar). And, injection statements should +// store a pointer to their parent metaprogram. Then, you should have a +// method that tests whether a declaration was introduced by a given +// metaprogramdecl. +// Oh and MetaprogramDecls should have an instantiatedFrom MetaprogramDecl +// field too, if they do not already. + +/*-----------------------------------------------------------------*/ +// Sema MetaprogramDecl implems (ActOn, Evaluate, etc.) + +/// Returns true if a metaprogram-declaration in declaration context DC +/// would be represented using a function (vs. a lambda). +static inline bool NeedsFunctionRepresentation(DeclContext *DC) { + return (DC->isFileContext() || DC->isRecord()); +} + +/// Create a metaprogram-declaration to hold the content of +/// the metaprogram. +/// +/// \p ScopeFlags is set to the value that should be used to create the scope +/// containing the metaprogram-declaration body. +Decl *Sema::ActOnMetaprogramDecl(Scope *S, SourceLocation ConstevalLoc, + unsigned &ScopeFlags, + const MetaprogramContext &MetaCtx, + bool Nested) { + MetaprogramDecl *MpD; + + // Note: we'll handle pushing function scopes here, since + // the lambda representation seems to need it here, but we'll + // defer PushExpressionEvaluationContext until ActOnStartMetaprogramDecl. + if (NeedsFunctionRepresentation(CurContext)) { + assert(!MetaCtx.isFunctionContext()); + ScopeFlags = Scope::FnScope | Scope::DeclScope; + + PushFunctionScope(); + + // Build the function + // + // consteval void __metaprogdef() compound-statement + // + // where compound-statement is the body of the + // metaprogram-declaration. + IdentifierInfo *II = &PP.getIdentifierTable().get("__metaprogdef"); + DeclarationName Name(II); + DeclarationNameInfo NameInfo(Name, ConstevalLoc); + + FunctionProtoType::ExtProtoInfo EPI( + Context.getDefaultCallingConvention(/*IsVariadic=*/false, + /*IsCXXMethod=*/false)); + QualType FunctionTy = Context.getFunctionType(Context.VoidTy, None, EPI); + TypeSourceInfo *FunctionTyInfo = + Context.getTrivialTypeSourceInfo(FunctionTy); + + FunctionDecl *Function = + FunctionDecl::Create(Context, CurContext, ConstevalLoc, NameInfo, + FunctionTy, FunctionTyInfo, SC_None, + /*UsesFPIntrin=*/false, + /*isInlineSpecified=*/false, + /*hasWrittenPrototype=*/true, + ConstexprSpecKind::Consteval, + /*TrailingRequiresClause=*/nullptr); + + Function->setImplicit(); + Function->setIsMetaprogram(); + + // Build the metaprogram declaration around the function. + MpD = MetaprogramDecl::Create(Context, CurContext, ConstevalLoc, + MetaCtx, Function); + + } else if (CurContext->isFunctionOrMethod() || Nested) { + assert(MetaCtx.isFunctionContext()); + ScopeFlags = Scope::BlockScope | Scope::FnScope | Scope::DeclScope; + LambdaScopeInfo *LSI = PushLambdaScope(); + + // Build the expression + // + // []() -> void compound-statement + // + // where compound-statement is the as-of-yet parsed body of the + // metaprogram-declaration. Note that the return type is not deduced (it + // doesn't need to be). + // + // TODO: It would be great if we could only capture constexpr declarations, + // but C++ doesn't have a constexpr default. + const bool KnownDependent = S->getTemplateParamParent(); + + FunctionProtoType::ExtProtoInfo EPI( + Context.getDefaultCallingConvention(/*IsVariadic=*/false, + /*IsCXXMethod=*/true)); + EPI.HasTrailingReturn = true; + EPI.TypeQuals.addConst(); + QualType MethodTy = Context.getFunctionType(Context.VoidTy, None, EPI); + TypeSourceInfo *MethodTyInfo = Context.getTrivialTypeSourceInfo(MethodTy); + + LambdaIntroducer Intro; + Intro.Range = SourceRange(ConstevalLoc); + // ByRef is sufficient, as this will be called just after it is defined, + // so no issue with referenced temporaries being out of scope at call time: + Intro.Default = LCD_ByRef; + + CXXRecordDecl *Closure = createLambdaClosureType( + Intro.Range, MethodTyInfo, KnownDependent, Intro.Default); + + CXXMethodDecl *Method = + startLambdaDefinition(Closure, Intro.Range, MethodTyInfo, ConstevalLoc, + None, ConstexprSpecKind::Consteval, + /*TrailingRequiresClause=*/nullptr); + buildLambdaScope(LSI, Method, Intro.Range, Intro.Default, Intro.DefaultLoc, + /*ExplicitParams=*/false, + /*ExplicitResultType=*/true, + /*Mutable=*/false); + Method->setIsMetaprogram(); + + // NOTE: The call operator is not yet attached to the closure type. That + // happens in ActOnFinishMetaprogramDecl(). The operator is, however, + // available in the LSI. + MpD = MetaprogramDecl::Create(Context, CurContext, ConstevalLoc, + MetaCtx, Closure); + } else { + Decl::castFromDeclContext(CurContext)->dump(); + llvm_unreachable("metaprogram declaration in unsupported context"); + } + + CurContext->addDecl(MpD); + return MpD; +} + +/// Called just prior to parsing the body of a metaprogram-declaration. +/// +/// This ensures that the declaration context is pushed with the appropriate +/// scope. +void Sema::ActOnStartMetaprogramDecl(Scope *S, Decl *D) { + MetaprogramDecl *MpD = cast(D); + if (MpD->hasFunctionRepresentation()) { + if (S) + PushDeclContext(S, MpD->getImplicitFunctionDecl()); + else + CurContext = MpD->getImplicitFunctionDecl(); + } else { + LambdaScopeInfo *LSI = cast(FunctionScopes.back()); + if (S) + PushDeclContext(S, LSI->CallOperator); + else + CurContext = LSI->CallOperator; + } + // We already handled PushFunctionScope etc. + // in ActOnMetaprogramDecl. All that remains to do + // is PushExpressionEvaluationContext: + // FIXME should this be EM_ConstantExpression? + PushExpressionEvaluationContext( + ExpressionEvaluationContext::PotentiallyEvaluated); +} + +/// Called immediately after parsing the body of a metaprogram-declaration. +/// +/// The statements within the body are evaluated here. +void Sema::ActOnFinishMetaprogramDecl(Scope *S, Decl *D, Stmt *Body) { + MetaprogramDecl *MpD = cast(D); + + if (MpD->hasFunctionRepresentation()) { + FunctionDecl *Fn = MpD->getImplicitFunctionDecl(); + // ActOnFinishFunctionBody expects we will have pushed an expression + // evaluation context during ActOnStartOfFunctionDef; since we don't + // call that we need to enter here. + EnterExpressionEvaluationContext ConstantEvaluated( + *this, Sema::ExpressionEvaluationContext::ConstantEvaluated); + ActOnFinishFunctionBody(Fn, Body); + if (!CurContext->isDependentContext()) { + assert(!MpD->isDependent()); + ParserBrickWallRAII SavedParserState(getParser()); + EvaluateMetaprogramDecl(MpD, Fn); + if (cast(CurContext)->isInvalidDecl()) + SavedParserState.setInvalid(); + MpD->setAlreadyRun(true); + } else { + MpD->setDependent(); + assert(!MpD->alreadyRun()); + // Tell the owning class whether this metaprogram contains code injection + // statements. + if (Fn->isCodeInjectingMetafunction()) + cast(CurContext)->setHasDependentCodeInjectingMetaprograms(); + } + } else { + ExprResult Lambda = ActOnLambdaExpr(MpD->getLocation(), Body, S); + CXXMethodDecl *Fn = MpD->getImplicitClosureCallOperator(); + if (!CurContext->isDependentContext()) { + assert(!MpD->isDependent()); + ParserBrickWallRAII SavedParserState(getParser()); + EvaluateMetaprogramDecl(MpD, Lambda.get()); + if (cast(CurContext)->isInvalidDecl()) + SavedParserState.setInvalid(); + MpD->setAlreadyRun(true); + } else { + MpD->setDependent(); + assert(!MpD->alreadyRun()); + // Tell the owning function whether this metaprogram contains code + // injection statements. + if (Fn->isCodeInjectingMetafunction()) + cast(CurContext)->setHasDependentCodeInjectingMetaprograms(); + MpD->setImplicitLambdaExpr(Lambda.get()); + } + } + + // If we didn't have a scope when building this, we need to restore the + // current context. + if (!S) + CurContext = MpD->getDeclContext(); +} + +/// Called when an error occurs while parsing the metaprogram-declaration body. +void Sema::ActOnMetaprogramDeclError(Scope *S, Decl *D) { + MetaprogramDecl *MpD = cast(D); + MpD->setInvalidDecl(); + if (MpD->hasFunctionRepresentation()) { + // ActOnFinishFunctionBody expects we will have pushed an expression + // evaluation context during ActOnStartOfFunctionDef; since we don't + // call that we need to enter here. + EnterExpressionEvaluationContext ConstantEvaluated( + *this, Sema::ExpressionEvaluationContext::ConstantEvaluated); + ActOnFinishFunctionBody(MpD->getImplicitFunctionDecl(), nullptr); + } else { + ActOnLambdaError(MpD->getLocation(), S); + } + MpD->setAlreadyRun(true); +} + +/// Evaluate a metaprogram-declaration. +/// +/// This takes an unnamed consteval void function whose body is that of +/// the metaprogram-declaration, and evaluates a call to that function. +bool Sema::EvaluateMetaprogramDecl(MetaprogramDecl *MpD, + FunctionDecl *D) { + QualType FunctionTy = D->getType(); + DeclRefExpr *Ref = + new (Context) DeclRefExpr(Context, D, + /*RefersToEnclosingVariableOrCapture=*/false, + FunctionTy, VK_LValue, SourceLocation()); + + QualType PtrTy = Context.getPointerType(FunctionTy); + ImplicitCastExpr *Cast = + ImplicitCastExpr::Create(Context, PtrTy, CK_FunctionToPointerDecay, Ref, + /*BasePath=*/nullptr, VK_PRValue, + FPOptionsOverride()); + + CallExpr *Call = + CallExpr::Create(Context, Cast, ArrayRef(), Context.VoidTy, + VK_PRValue, SourceLocation(), FPOptionsOverride()); + + return EvaluateMetaprogramDeclCall(MpD, Call); +} + +/// Evaluate a metaprogram-declaration. +/// +/// This builds an unnamed consteval lambda whose body is that of +/// the metaprogram-declaration, and evaluates a call to that lambda. +bool Sema::EvaluateMetaprogramDecl(MetaprogramDecl *MpD, + Expr *E) { + LambdaExpr *Lambda = cast(E); + CXXMethodDecl *Method = Lambda->getCallOperator(); + QualType MethodTy = Method->getType(); + DeclRefExpr *Ref = new (Context) + DeclRefExpr(Context, Method, + /*RefersToEnclosingVariableOrCapture=*/false, + MethodTy, VK_LValue, SourceLocation()); + QualType PtrTy = Context.getPointerType(MethodTy); + ImplicitCastExpr *Cast = + ImplicitCastExpr::Create(Context, PtrTy, CK_FunctionToPointerDecay, Ref, + /*BasePath=*/nullptr, VK_PRValue, + FPOptionsOverride()); + CallExpr *Call = CXXOperatorCallExpr::Create(Context, OO_Call, + Cast, {Lambda}, + Context.VoidTy, + VK_PRValue, + SourceLocation(), + FPOptions()); + return EvaluateMetaprogramDeclCall(MpD, Call); +} + +/// Evaluate the CallExpr referring to the metaprogram. +/// +/// \returns \c true if the expression \p E can be evaluated, \c false +/// otherwise. +/// +bool Sema::EvaluateMetaprogramDeclCall(MetaprogramDecl *MpD, + CallExpr *Call) { + // Associate the call expression with the declaration. + MpD->setImplicitCallExpr(Call); + + SmallVector Notes; + + Expr::EvalResult Result; + Result.Diag = &Notes; + + SmallVector StringInjectionChunks; + Result.StringInjectionChunks = &StringInjectionChunks; + + assert(Call->getType()->isVoidType()); + if (!Call->EvaluateAsVoid(Result, Context)) { + + // Sometimes we may have a fold failure without other errors, + // due to e.g. the condition on a non-constexpr if encountering + // an error. We want an error to stop the program in such cases. + // TODO if we could check if other errors had already been + // raised, we should avoid this error in such cases, as it's + // redundant. + if (Notes.empty()) + Diag(MpD->getEndLoc(), diag::err_metaprogram_eval_failure); + else { + // If we got a compiler error, then just emit that. + if (Notes[0].second.getDiagID() == diag::err_user_defined_error) + Diag(MpD->getBeginLoc(), Notes[0].second); + else { + Diag(MpD->getEndLoc(), diag::err_metaprogram_eval_failure); + for (const PartialDiagnosticAt &Note : Notes) + Diag(Note.first, Note.second); + } + } + } + + SourceLocation POI = MpD->getSourceRange().getEnd(); + + // Perform injected string parsing if necessary: + if (!StringInjectionChunks.empty()) { + ParserBrickWallRAII SavedParserState(getParser()); + InjectQueuedStrings(POI, StringInjectionChunks, MpD); + if (Decl::castFromDeclContext(CurContext)->isInvalidDecl()) + SavedParserState.setInvalid(); //disables certain checks on destruction + } + + // FIXME: Do we really want to remove the metaprogram after evaluation? Or + // should we just mark it completed? + MpD->getDeclContext()->removeDecl(MpD); + + return !Notes.empty(); +} + +StmtResult Sema::ActOnStringInjectionStmt(SourceLocation KeywordLoc, + SourceLocation LParenLoc, + ArrayRef Args, + SourceLocation RParenLoc, + bool AnyDependent, + StringLiteral *WrittenFirstArg) { + if (!AnyDependent && !PrepareStrIntArgsForEval(Args)) + return StmtError(); + cast(CurContext)->setIsCodeInjectingMetafunction(true); + return StringInjectionStmt::Create(Context, KeywordLoc, LParenLoc, + Args, RParenLoc, WrittenFirstArg); +} + +void Sema::TheParserEnterScope(unsigned int ScopeFlags) { + TheParser->EnterScope(ScopeFlags); +} + +void Sema::TheParserExitScope() { + TheParser->ExitScope(); +} + +static DeclSpec::TST getTypeSpecForTagTypeKind(TagTypeKind Kind) { + switch (Kind) { + case TTK_Class: return TST_class; + case TTK_Enum: return TST_enum; + case TTK_Struct: return TST_struct; + case TTK_Interface: return TST_interface; + case TTK_Union: return TST_union; + } +} + +bool Sema:: +InjectQueuedStrings(SourceLocation POI, + ArrayRef StringInjectionChunks, + MetaprogramDecl *MpD) { + assert(!StringInjectionChunks.empty() && + "Should have checked if there were any " + "StringInjectionChunks before calling InjectQueuedStrings"); + + assert(TheParser && + "Should have called setParser(...) on the Sema object " + "after constructing the parser; we need it for " + "processing generated source strings."); + + assert(MpD); + MetaprogramContext MetaCtx = MpD->getMetaprogramContext(); + + bool Ok = true; + + { //scope + // First, we'll push an r_brace token onto the stack of + // generated source code strings to process -- + // this will be processed last, and will signal to the + // parser that it has reached the end: + static const SmallString<4> finaldummystr("}"); + static const QualType RBraceStrLitTy = Context.getConstantArrayType( + Context.CharTy.withConst(), + llvm::APInt(32, finaldummystr.size() + 1), + nullptr, + ArrayType::Normal, 0); + + SourceLocation rbraceloc = MpD->getEndLoc(); + assert(rbraceloc.isValid() && + "The loc assigned to the rbrace has to be valid, " + "since we test for invalid locs to identify terminators."); + + // For preprocessor diagnostics (e.g. unterminated metaparses): + PP.setCurInjectedStrLoc(rbraceloc); + + const StringLiteral *rbracestrlit = StringLiteral::Create( + Context, finaldummystr, StringLiteral::Ascii, + false, RBraceStrLitTy, rbraceloc); + PP.PushGeneratedSrcStr(rbracestrlit->getString(), rbraceloc); + + for (auto strlitit = StringInjectionChunks.rbegin(); + strlitit != StringInjectionChunks.rend(); + ++strlitit) + { + const StringLiteral *strlit = *strlitit; + assert(strlit); + + StringRef Str = strlit->getString(); + + assert(Str.data()); + assert(*Str.end() == '\0' && + "Expected each string literal to be null terminated"); + assert(strlit->getBeginLoc().isValid()); + + PP.PushGeneratedSrcStr(Str, strlit->getBeginLoc()); + } + } //end scope + + assert(TheParser); + assert(TheParser->Tok.getLocation().isInvalid() && + "Expected an invalid dummy token before we begin parsing " + "(to signal the ParserBrickWallRAII has been properly " + "created)"); //NB this is different from the dummy rbrace + + // Ensure CurContext gets restored at end, + // since we might have to adjust it while parsing, in case we + // encounter e.g. new metaprograms decls within the injected strings. + Sema::ContextRAII(*this, CurContext); + assert(CurContext); + + // For metaprograms in a non-dependent context, temporarily pop the scope + // to escape the implicit function declaration scope, to ensure proper lookup + // and injection. (We do not have to worry about this for template + // instantiations.) + llvm::SaveAndRestore ScopeRAII(CurScope); + if (!MpD->isInstantiation()) + CurScope = CurScope->getParent(); + + if (MetaCtx.isFunctionContext()) { + assert(CurContext->isFunctionOrMethod()); + + // ParseCompoundStatementBody expects the current tok to be + // an l_brace, so we make it so: + Token initlbracetok; + initlbracetok.startToken(); + initlbracetok.setKind(tok::l_brace); + TheParser->Tok = initlbracetok; + + // ParseCompoundStatementBody should consume all the contents of + // the queued metaparses, unless it encounters an extra r_brace, + // in which case we'll get an error on clearAnyDeadLexers. + // Note that ParseCompoundStatementBody takes a + // "bool isStmtExpr = false" param, but after looking at + // the code that seems to only be true when processing a statement + // in parentheses, which we needn't worry about for the metaprogram, + // so we can safely leave it to the default false (rather than + // passing it via MetaprogramContext). + StmtResult sr(TheParser->ParseCompoundStatementBody()); + + if (sr.isInvalid()) { + Ok = false; + } + // This will allow this result to be properly acted on + // in ActOnDeclStmt: + MpD->setStmtResult(sr); + + } else { //kind is cls or extdecl: + assert(!CurContext->isFunctionOrMethod() && + "Either MetaprogramContext wasn't constructed correctly, " + "or this is a nested case and you perhaps need to " + "temporarily set CurContext to teh parent context here"); + + // For external declarations and class member declarations, + // we'll be handling the loop ourselves, so we consume + // the initial token to get straight to the declarations + // (i.e. no need to set up an l_brace tok like above). + TheParser->ConsumeToken(); + + Parser::DeclGroupPtrTy ADecl; + bool isclass = MetaCtx.isClassContext(); + assert(isclass || MetaCtx.isFileContext()); + assert(isclass == CurContext->isRecord() + && "MetaprogramContext seems to have been incorrectly constructed"); + CXXRecordDecl *InstantiationOrClass = isclass ? + cast(CurContext) : nullptr; + + while (TheParser->Tok.isNot(tok::r_brace)) { + if (PP.NoMoreInjectedStrsAvailable() && Ok) { + TheParser->Diag(MpD->getEndLoc(), + diag::err_extraneous_closing_brace); + PP.setNoMoreInjectedStrsAvailable(false); + if (InstantiationOrClass) + InstantiationOrClass->setInvalidDecl(); + return false; + } + assert(TheParser->getCurToken().getLocation().isValid()); + if (InstantiationOrClass) { + DeclSpec::TST TagType = getTypeSpecForTagTypeKind( + InstantiationOrClass->getTagKind()); + ADecl = TheParser->ParseCXXClassMemberDeclarationWithPragmas( + MetaCtx.cls.AS, MetaCtx.cls.AccessAttrs, TagType, + InstantiationOrClass); + } + else { //ext decl: + // NB we don't actually use MetaContextVar's attrs now; + // we locally construct. But we've left the original in + // MetaprogramContext in case those attributes are important -- + // can't remember if I tested it. TESTME. + ParsedAttributesWithRange attrs(TheParser->AttrFactory); + TheParser->MaybeParseCXX11Attributes(attrs); + ADecl = TheParser->ParseExternalDeclaration(attrs); + } + if (ADecl) { + assert(ADecl.get().isSingleDecl() && + "Didn't expect ADecl to not be a SingleDecl"); + Decl *SingleDecl = ADecl.get().getSingleDecl(); + assert(SingleDecl); + if (SingleDecl->isInvalidDecl()) { + Ok = false; + if (InstantiationOrClass) + InstantiationOrClass->setInvalidDecl(); + } + } + + // When you have multiple statements/decls + // in a single argument of an __inj(...), + // e.g. __inj("int i; float f;"), + // then you won't have an invalid token after + // parsing the first statement/decl. + // But after the last one in the __inj(...) + // statement you WILL have an invalid token. + // BUT that's not all -- if you use a call to a constexpr + // function that contains an __inj(...) statement, that + // seems to add an ADDITIONAL invalid token -- hence the need + // for a while loop here (AND in the ParseCompoundStmtBody version). + while (TheParser->getCurToken().getLocation().isInvalid() + && !TheParser->getCurToken().is(tok::r_brace) + //^ DWR HACK: some of the dummy rbraces have invalid locs, + // don't skip over them. + ) { + TheParser->ConsumeToken(); + } + } //end injected string parsing loop + + // Consume the dummy r_brace we entered above, and make sure + // that concludes the metaprogram + assert(TheParser->Tok.is(tok::r_brace)); + auto RBraceTok = TheParser->Tok; + TheParser->ConsumeBrace(); + if (!PP.NoMoreInjectedStrsAvailable() && Ok) { + TheParser->Diag(RBraceTok, diag::err_extraneous_closing_brace); + Ok = false; + } + PP.setNoMoreInjectedStrsAvailable(false); + + } //end else + + return Ok; +} + +static bool IsIntOrStr(QualType T) { + if (T.isCXXStringLiteralType()) + return true; + if (T->isIntegerType()) + return true; + return false; +} + +static bool CheckIsIntOrStr(Sema &SemaRef, Expr *E) { + if (IsIntOrStr(E->getType())) + return true; + E->dump(); //[DELETEME] + E->getType()->dump(); + SemaRef.Diag(E->getExprLoc(), diag::err_expected_int_or_str); + return false; +} + +/// Returns false if errors encountered. +/// This will change Args. +/// Only call when all Args are non-dependent. +bool Sema::PrepareStrIntArgsForEval(ArrayRef Args) { + // Convert operands to rvalues to prepare for evaluation: + for (auto argit = Args.begin(); argit != Args.end(); ++argit) { + // Decay arrays first. + ExprResult R = DefaultFunctionArrayLvalueConversion(*argit); + if (R.isInvalid()) + return false; + // Check that the operand type is acceptable. + if (!CheckIsIntOrStr(*this, *argit)) + return false; + + //Replace the array entry with the converted Expr *: + const_cast(*argit) = R.get(); + } + return true; //No error +} \ No newline at end of file diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -77,6 +77,20 @@ // If we have an invalid decl, just return an error. if (DG.isNull()) return StmtError(); + // If this was a MetaprogramDecl, which provides a non-empty getStmtResult() + // (indicating it had some injected content), repace it with the generated + // StmtResult: + if (DG.isSingleDecl()) { + if (MetaprogramDecl *MpD = dyn_cast(DG.getSingleDecl())) { + if (!MpD->isDependent()) { + auto sr = MpD->getStmtResult(); + if (sr.isUsable()) + return sr; + return ActOnNullStmt(StartLoc); + } + } + } + return new (Context) DeclStmt(DG, StartLoc, EndLoc); } @@ -317,6 +331,14 @@ R2, /*isCtor=*/false)) return; } + } else if (const auto *LE = dyn_cast(E)) { + // FIXME In a function context a metaprogram (`consteval {...}`) will be + // represented as a lambda which is called via constant evaluation; even + // though the type of the call operator is void, a warning about this + // result being unused shows up unless we manually suppress by returning + // here. + if (LE->getCallOperator()->isMetaprogram()) + return; } else if (ShouldSuppress) return; diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -21,9 +21,12 @@ #include "clang/Basic/LangOptions.h" #include "clang/Basic/Stack.h" #include "clang/Basic/TargetInfo.h" +#include "clang/Parse/RAIIObjectsForParser.h" +#include "clang/Sema/CXXFieldCollector.h" #include "clang/Sema/DeclSpec.h" #include "clang/Sema/Initialization.h" #include "clang/Sema/Lookup.h" +#include "clang/Sema/Scope.h" #include "clang/Sema/SemaConcept.h" #include "clang/Sema/SemaInternal.h" #include "clang/Sema/Template.h" @@ -2709,74 +2712,161 @@ Instantiator.enableLateAttributeInstantiation(&LateAttrs); bool MightHaveConstexprVirtualFunctions = false; - for (auto *Member : Pattern->decls()) { - // Don't instantiate members not belonging in this semantic context. - // e.g. for: - // @code - // template class A { - // class B *g; - // }; - // @endcode - // 'class B' has the template as lexical context but semantically it is - // introduced in namespace scope. - if (Member->getDeclContext() != Pattern) - continue; - // BlockDecls can appear in a default-member-initializer. They must be the - // child of a BlockExpr, so we only know how to instantiate them from there. - // Similarly, lambda closure types are recreated when instantiating the - // corresponding LambdaExpr. - if (isa(Member) || - (isa(Member) && cast(Member)->isLambda())) - continue; + auto AddMembersToInstantiation = [&, Pattern, TSK, PointOfInstantiation]() { + FieldCollector->StartClass(); + for (auto *Member : Pattern->decls()) { + // Don't instantiate members not belonging in this semantic context. + // e.g. for: + // @code + // template class A { + // class B *g; + // }; + // @endcode + // 'class B' has the template as lexical context but semantically it is + // introduced in namespace scope. + if (Member->getDeclContext() != Pattern) + continue; - if (Member->isInvalidDecl()) { - Instantiation->setInvalidDecl(); - continue; - } + // BlockDecls can appear in a default-member-initializer. They must be the + // child of a BlockExpr, so we only know how to instantiate them from there. Similarly, lambda closure types are recreated when instantiating the corresponding LambdaExpr. + if (isa(Member) || (isa(Member) && + cast(Member)->isLambda())) + continue; + + if (Member->isInvalidDecl()) { + Instantiation->setInvalidDecl(); + continue; + } - Decl *NewMember = Instantiator.Visit(Member); - if (NewMember) { - if (FieldDecl *Field = dyn_cast(NewMember)) { - Fields.push_back(Field); - } else if (EnumDecl *Enum = dyn_cast(NewMember)) { - // C++11 [temp.inst]p1: The implicit instantiation of a class template - // specialization causes the implicit instantiation of the definitions - // of unscoped member enumerations. - // Record a point of instantiation for this implicit instantiation. - if (TSK == TSK_ImplicitInstantiation && !Enum->isScoped() && - Enum->isCompleteDefinition()) { - MemberSpecializationInfo *MSInfo =Enum->getMemberSpecializationInfo(); - assert(MSInfo && "no spec info for member enum specialization"); - MSInfo->setTemplateSpecializationKind(TSK_ImplicitInstantiation); - MSInfo->setPointOfInstantiation(PointOfInstantiation); + Decl *NewMember = Instantiator.Visit(Member); + if (NewMember) { + if (FieldDecl *Field = dyn_cast(NewMember)) { + Fields.push_back(Field); + } else if (EnumDecl *Enum = dyn_cast(NewMember)) { + // C++11 [temp.inst]p1: The implicit instantiation of a class template + // specialization causes the implicit instantiation of the definitions + // of unscoped member enumerations. + // Record a point of instantiation for this implicit instantiation. + if (TSK == TSK_ImplicitInstantiation && !Enum->isScoped() && + Enum->isCompleteDefinition()) { + MemberSpecializationInfo *MSInfo = + Enum->getMemberSpecializationInfo(); + assert(MSInfo && "no spec info for member enum specialization"); + MSInfo->setTemplateSpecializationKind(TSK_ImplicitInstantiation); + MSInfo->setPointOfInstantiation(PointOfInstantiation); + } + } else if (StaticAssertDecl *SA = + dyn_cast(NewMember)) { + if (SA->isFailed()) { + // A static_assert failed. Bail out; instantiating this + // class is probably not meaningful. + Instantiation->setInvalidDecl(); + break; + } + } else if (CXXMethodDecl *MD = dyn_cast(NewMember)) { + if (MD->isConstexpr() && !MD->getFriendObjectKind() && + (MD->isVirtualAsWritten() || Instantiation->getNumBases())) + MightHaveConstexprVirtualFunctions = true; } - } else if (StaticAssertDecl *SA = dyn_cast(NewMember)) { - if (SA->isFailed()) { - // A static_assert failed. Bail out; instantiating this - // class is probably not meaningful. + + if (NewMember->isInvalidDecl()) Instantiation->setInvalidDecl(); - break; - } - } else if (CXXMethodDecl *MD = dyn_cast(NewMember)) { - if (MD->isConstexpr() && !MD->getFriendObjectKind() && - (MD->isVirtualAsWritten() || Instantiation->getNumBases())) - MightHaveConstexprVirtualFunctions = true; + } else { + // FIXME: Eventually, a NULL return will mean that one of the + // instantiations was a semantic disaster, and we'll want to mark the + // declaration invalid. + // For now, we expect to skip some members that we can't yet handle. } + } - if (NewMember->isInvalidDecl()) - Instantiation->setInvalidDecl(); - } else { - // FIXME: Eventually, a NULL return will mean that one of the - // instantiations was a semantic disaster, and we'll want to mark the - // declaration invalid. - // For now, we expect to skip some members that we can't yet handle. + // Finish checking fields. + ActOnFields(nullptr, Instantiation->getLocation(), Instantiation, + llvm::makeArrayRef( + // strict aliasing violation! + reinterpret_cast(FieldCollector->getCurFields()), + FieldCollector->getCurNumFields()), + SourceLocation(), SourceLocation(), ParsedAttributesView()); + + FieldCollector->FinishClass(); + }; + + // Regardless of whether the Pattern->hasDependentCodeInjectingMetaprograms(), + // and whether that metaprogram contains __inj statements, + // we will set the PII (the ParsingIntoInstantiation state) + // and restore it upon exiting. (If IsPatternWithMetapgoram, + // we'll set it to PII_class; if not, PII_false.) + SemaPIIRAII SavedPIIState(*this); + + if (Pattern->hasDependentCodeInjectingMetaprograms()) { + // Pass along the AccessAttrs; this is intended to hold attributes + // collected while parsing injected strings. + // FIXME I'm not sure if this is really needed, or if we are making + // use of it properly. + ParsedAttributesWithRange AccessAttrs(TheParser->getAttrFactory()); + Instantiator.setAccessAttrs(AccessAttrs); + + assert(CurContext == Instantiation); //sanity + + // Save the parser state & CurScope and enter a state in + // which any parsing adds members to the Instantiation + // (When we exit this block and the RAII is destroyed the + // original parser state/scope will be automatically restored): + TempParseIntoClassInstantiation TemporarilyParseIntoInstantiation( + *this, Pattern->getTagKind() == TTK_Interface); + assert(PII = PII_class); + + // Just to be sure: + assert(TemporarilyParseIntoInstantiation. + PPTotalLexers_Equals_TotalLexersAtRAIIConstruction()); + +#ifndef NDEBUG + auto DEBUGClassStackSize = TheParser->ClassStack.size(); +#endif + + AddMembersToInstantiation(); + + bool Valid = true; + if (Instantiation->isInvalidDecl()) { + TemporarilyParseIntoInstantiation.setInvalid(); + Valid = false; } + + // Be sure you lexed all the injected string lexers you added to the lexer + // stack, and no further: + assert(TemporarilyParseIntoInstantiation + .PPTotalLexers_Equals_TotalLexersAtRAIIConstruction() + || !Valid && + "Expected PP.getTotalNumLexers() == " + "PP.TotalLexersAtRAIIConstruction after finishing initial " + "parsing of metaprograms -- are there perhaps some dead " + "lexers left over?"); + + assert(CurContext == Instantiation + && "CurContext was changed somewhere before late parsing began!"); + + assert(TheParser->ClassStack.size() == DEBUGClassStackSize + && "Class stack size was changed while adding members to " + "instantiation!"); + + TheParser->HandleLateParsing(); + + assert(CurContext == Instantiation + && "CurContext was changed during late parsing!"); + + } else { + // This template does not require any additional parsing; i.e. it is + // a normal template without any __inj(...) statements. + // Temporarily set PII to PII_false, in case this "normal" template + // instantiation is occuring while parsing into an outer instantiation, s.t. + // it is not already PII_false. + PII = PII_false; + + AddMembersToInstantiation(); } - // Finish checking fields. - ActOnFields(nullptr, Instantiation->getLocation(), Instantiation, Fields, - SourceLocation(), SourceLocation(), ParsedAttributesView()); + SavedPIIState.Exit(); //return PII to its original value. + CheckCompletedCXXClass(nullptr, Instantiation); // Default arguments are parsed, if not instantiated. We can go instantiate @@ -3237,9 +3327,13 @@ if (Function->hasAttr()) continue; - MemberSpecializationInfo *MSInfo = Function->getMemberSpecializationInfo(); + + // Members instantiated from injected strings will have no MSInfo. + if (LangOpts.StringInjection && !MSInfo) + continue; + assert(MSInfo && "No member specialization information?"); if (MSInfo->getTemplateSpecializationKind() == TSK_ExplicitSpecialization) @@ -3284,6 +3378,11 @@ continue; MemberSpecializationInfo *MSInfo = Var->getMemberSpecializationInfo(); + + // Members instantiated from injected strings will have no MSInfo. + if (LangOpts.StringInjection && !MSInfo) + continue; + assert(MSInfo && "No member specialization information?"); if (MSInfo->getTemplateSpecializationKind() == TSK_ExplicitSpecialization) @@ -3327,6 +3426,11 @@ continue; MemberSpecializationInfo *MSInfo = Record->getMemberSpecializationInfo(); + + // Members instantiated from injected strings will have no MSInfo. + if (LangOpts.StringInjection && !MSInfo) + continue; + assert(MSInfo && "No member specialization information?"); if (MSInfo->getTemplateSpecializationKind() @@ -3389,6 +3493,11 @@ TSK); } else if (auto *Enum = dyn_cast(D)) { MemberSpecializationInfo *MSInfo = Enum->getMemberSpecializationInfo(); + + // Members instantiated from injected strings will have no MSInfo. + if (LangOpts.StringInjection && !MSInfo) + continue; + assert(MSInfo && "No member specialization information?"); if (MSInfo->getTemplateSpecializationKind() @@ -3614,6 +3723,15 @@ return nullptr; } +static void CopyToSemaScopeEtc(Decl *Inst, Sema &SemaRef) { + if (auto InstND = dyn_cast(Inst)) { + if (InstND->getDeclName()) { + SemaRef.PushOnScopeChains(InstND, SemaRef.getCurScope(), + false/*don't add to CurContext*/); + } + } +} + void LocalInstantiationScope::InstantiatedLocal(const Decl *D, Decl *Inst) { D = getCanonicalParmVarDecl(D); llvm::PointerUnion &Stored = LocalDecls[D]; @@ -3633,6 +3751,8 @@ } else { assert(Stored.get() == Inst && "Already instantiated this local"); } + if (ShouldCopyToSemaScopeEtc) + CopyToSemaScopeEtc(Inst, SemaRef); } void LocalInstantiationScope::InstantiatedLocalPackArg(const Decl *D, @@ -3640,6 +3760,8 @@ D = getCanonicalParmVarDecl(D); DeclArgumentPack *Pack = LocalDecls[D].get(); Pack->push_back(Inst); + if (ShouldCopyToSemaScopeEtc) + CopyToSemaScopeEtc(Inst, SemaRef); } void LocalInstantiationScope::MakeInstantiatedLocalArgPack(const Decl *D) { diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -20,8 +20,10 @@ #include "clang/AST/ExprCXX.h" #include "clang/AST/PrettyDeclStackTrace.h" #include "clang/AST/TypeLoc.h" +#include "clang/Basic/MetaprogramContext.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/TargetInfo.h" +#include "clang/Parse/RAIIObjectsForParser.h" #include "clang/Sema/Initialization.h" #include "clang/Sema/Lookup.h" #include "clang/Sema/ScopeInfo.h" @@ -1353,6 +1355,110 @@ D->isFailed()); } +Decl *TemplateDeclInstantiator::VisitMetaprogramDecl(MetaprogramDecl *D) { + // Note an irony here: both function templates and class templates + // will be processed here, but it is only the *class* templates that should + // have D->hasFunctionRepresentation() == true; MetaprogramDecls from + // *function* templates will paradoxically have + // D->hasFunctionRepresentation() == false. + // The reason is that you can declare functions in a class context, but not + // within another function -- in the latter case you need lambdas, which are + // classes -- i.e. the representation needed is the opposite of the current + // context. + // Bottom line note that D->hasFunctionRepresentation() means + // D was declared in a class template; and D->hasLambdaRepresentation() means + // D was declared in a function template. + if (D->hasFunctionRepresentation()) { + assert(D->getMetaprogramContext().isClassContext() + && "Expected MetaprogarmDecls from class contexts to have " + "function representations"); + FunctionDecl *Fn = D->getImplicitFunctionDecl(); + // Instantiate the nested function. + // + // FIXME: The point of instantiation is probably wrong. + Decl *NewD = SemaRef.SubstDecl(Fn, Owner, TemplateArgs); + if (!NewD) + return nullptr; + FunctionDecl *NewFn = cast(NewD); + if (NewFn->isInvalidDecl()) + return nullptr; + StmtResult NewBody; + { + Sema::ContextRAII Switch(SemaRef, NewFn); + SemaRef.PushFunctionScope(); + Sema::FunctionScopeRAII FunctionScopeCleanup(SemaRef); + NewBody = SemaRef.SubstStmt(Fn->getBody(), TemplateArgs); + } + if (NewBody.isInvalid()) + return nullptr; + NewFn->setBody(NewBody.get()); + + // Build the metaprogram declaration. + assert(D->getAccessUnsafe() != AS_none + && "Need to set the access properly for a metaprogram " + "in a class context!"); + auto MetaCtx = MetaprogramContext(this->getAccessAttrs(), D->getAccess()); + MetaprogramDecl *MpD = MetaprogramDecl::Create(SemaRef.Context, Owner, + Fn->getLocation(), + MetaCtx, NewFn, + /*InstantiatedFrom=*/D); + assert(SemaRef.CurContext == Owner + && "Expected these to be the same -- need to revisit " + "whether to addDecl to Owner or CurContext"); + Owner->addDecl(MpD); + + // Evaluate it if instantiation has made it non-dependent. + if (!NewFn->isDependentContext()) { + assert(MpD->getAccessUnsafe() != AS_none + && "Should have set access during ConstexprDecl::Create!"); + SemaRef.EvaluateMetaprogramDecl(MpD, NewFn); + } else { + MpD->setDependent(); + // Tell the owning class whether this metaprogram contains code injection + // statements. + if (Fn->isCodeInjectingMetafunction()) + cast(Owner)->setHasDependentCodeInjectingMetaprograms(); + } + return MpD; + } else { + assert(D->hasLambdaRepresentation()); + assert(SemaRef.CurContext == Owner); + + Expr *OldLambdaExpr = D->getImplicitLambdaExpr(); + StmtResult NewLambdaStmtRes = + SemaRef.SubstStmt(OldLambdaExpr, TemplateArgs); + assert(dyn_cast(NewLambdaStmtRes.get())); + LambdaExpr *NewLambdaExpr = cast(NewLambdaStmtRes.get()); + + MetaprogramDecl *MpD = + MetaprogramDecl::Create(SemaRef.Context, Owner, + D->getLocation(), + D->getMetaprogramContext(), /*!*/ + NewLambdaExpr->getLambdaClass(), + /*InstantiatedFrom=*/D); + MpD->setImplicitLambdaExpr(NewLambdaExpr); + Owner->addDecl(MpD); + + // And evaluate it if needed. + if (!NewLambdaExpr->isTypeDependent() && + !NewLambdaExpr->isValueDependent()) { + SemaRef.EvaluateMetaprogramDecl(MpD, NewLambdaExpr); + } else { + assert(Owner->isDependentContext() + && "I assumed if the NewLambdaExpr was type dependent, " + "the Owner would also be dependent"); + MpD->setDependent(); + // Tell the owning function whether this metaprogram contains code injection + // statements. + CXXMethodDecl *Fn = MpD->getImplicitClosureCallOperator(); + if (Fn->isCodeInjectingMetafunction()) + cast(Owner)->setHasDependentCodeInjectingMetaprograms(); + MpD->setImplicitLambdaExpr(NewLambdaExpr); + } + return MpD; + } +} + Decl *TemplateDeclInstantiator::VisitEnumDecl(EnumDecl *D) { EnumDecl *PrevDecl = nullptr; if (EnumDecl *PatternPrev = getPreviousDeclForInstantiation(D)) { @@ -4931,6 +5037,32 @@ EnterExpressionEvaluationContext EvalContext( *this, Sema::ExpressionEvaluationContext::PotentiallyEvaluated); + // Temporarily set the PII (the thing returned by + // SemaRef.getParsingIntoInstantiationStatus()) to either + // PII_func or PII_false, depending on whether + // PatternDecl->hasDependentCodeInjectingMetaprograms(). + SemaPIIRAII SavedPIIState(*this); + + // Construct an RAII object to save the parser state and enter the proper + // scope *if* this PatternDecl needs to access the parser. + // This RAII object must be created before LocalInstantiationScope and + // anything that might add to that scope (i.e. + // addInstantiatedParametersToScope), so that the proper Parser Scope + // is created and LocalInstantiationScope can copy its contents to it + // as they are added (via InstantiatedLocal(...) etc.) + // FIXME a pointer is a lousy way of doing this + std::unique_ptr + TemporarilyParseIntoInstantiation; + + if (PatternDecl->hasDependentCodeInjectingMetaprograms()) { + TemporarilyParseIntoInstantiation = + std::make_unique(*this); + assert(PII == PII_func); //just to be sure we set PII correctly + } else { + // Explicitly turn PII off. + PII = PII_false; //read "ParsingIntoInstantiation_false" + } + // Introduce a new scope where local variable instantiations will be // recorded, unless we're actually a member function within a local // class, in which case we need to merge our results with the parent @@ -5055,11 +5187,15 @@ if (Body.isInvalid()) Function->setInvalidDecl(); + + if (TemporarilyParseIntoInstantiation) + TemporarilyParseIntoInstantiation->setInvalid(); } // FIXME: finishing the function body while in an expression evaluation // context seems wrong. Investigate more. ActOnFinishFunctionBody(Function, Body.get(), /*IsInstantiation=*/true); + assert(PatternDecl->isDependentContext()); //sanity PerformDependentDiagnostics(PatternDecl, TemplateArgs); if (auto *Listener = getASTMutationListener()) @@ -5075,6 +5211,13 @@ // instantiation within this scope. LocalInstantiations.perform(); Scope.Exit(); + + // Delete the Parser/Scope RAII object IF it existed, returning the + // Parser and Scope etc. back to their original states. Same with PII. + if (TemporarilyParseIntoInstantiation) + TemporarilyParseIntoInstantiation.reset(); + SavedPIIState.Exit(); + GlobalInstantiations.perform(); } diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -437,7 +437,8 @@ /// \returns true if an error occurred, false otherwise. bool TransformExprs(Expr *const *Inputs, unsigned NumInputs, bool IsCall, SmallVectorImpl &Outputs, - bool *ArgChanged = nullptr); + bool *ArgChanged = nullptr, + bool *DependentOutputs = nullptr); /// Transform the given declaration, which is referenced from a type /// or expression. @@ -1497,6 +1498,17 @@ return getSema().BuildCoroutineBodyStmt(Args); } + /// Build a new __inj(...) statement. + StmtResult RebuildStringInjectionStmt(SourceLocation KeywordLoc, + SourceLocation LParenLoc, + SmallVectorImpl &Args, + SourceLocation RParenLoc, + bool AnyDependent, + StringLiteral *WrittenFirstArg) { + return getSema().ActOnStringInjectionStmt( + KeywordLoc, LParenLoc, Args, RParenLoc, AnyDependent, WrittenFirstArg); + } + /// Build a new Objective-C \@try statement. /// /// By default, performs semantic analysis to build the new statement. @@ -3949,7 +3961,15 @@ unsigned NumInputs, bool IsCall, SmallVectorImpl &Outputs, - bool *ArgChanged) { + bool *ArgChanged, + bool *DependentOutputs) { + auto UpdateDependence = [DependentOutputs](ExprResult Out) { + if (DependentOutputs && !*DependentOutputs && + ( Out.get()->isValueDependent() || + Out.get()->isTypeDependent() )) + *DependentOutputs = true; //an expansion is dependent + }; + for (unsigned I = 0; I != NumInputs; ++I) { // If requested, drop call arguments that need to be dropped. if (IsCall && getDerived().DropCallArgument(Inputs[I])) { @@ -3996,6 +4016,8 @@ if (ArgChanged) *ArgChanged = true; + + UpdateDependence(Out); Outputs.push_back(Out.get()); continue; } @@ -4019,6 +4041,7 @@ return true; } + UpdateDependence(Out); Outputs.push_back(Out.get()); } @@ -4036,6 +4059,7 @@ if (Out.isInvalid()) return true; + UpdateDependence(Out); Outputs.push_back(Out.get()); } @@ -4051,6 +4075,7 @@ if (Result.get() != Inputs[I] && ArgChanged) *ArgChanged = true; + UpdateDependence(Result); Outputs.push_back(Result.get()); } @@ -8484,6 +8509,26 @@ return S; } +template +StmtResult +TreeTransform:: +TransformStringInjectionStmt(StringInjectionStmt *S) { + SmallVector NewArgs; + bool ArgChanged = false; + bool AnyDependent = false; + if (getDerived().TransformExprs(S->arguments().begin(), S->arg_size(), + /*IsCall*/true, NewArgs, &ArgChanged, &AnyDependent)) + return StmtError(); + + // If nothing changed, just retain the existing statement. + if (!getDerived().AlwaysRebuild() && !ArgChanged) + return S; + + return RebuildStringInjectionStmt(S->getKeywordLoc(), S->getLParenLoc(), + NewArgs, S->getRParenLoc(), AnyDependent, + S->getWrittenFirstArg()); +} + //===----------------------------------------------------------------------===// // OpenMP directive transformation //===----------------------------------------------------------------------===// @@ -12947,6 +12992,9 @@ E->getCallOperator()->getConstexprKind(), NewTrailingRequiresClause.get()); + if (E->getCallOperator()->isMetaprogram()) + NewCallOperator->setIsMetaprogram(); + LSI->CallOperator = NewCallOperator; getDerived().transformAttrs(E->getCallOperator(), NewCallOperator); diff --git a/clang/lib/Serialization/ASTCommon.cpp b/clang/lib/Serialization/ASTCommon.cpp --- a/clang/lib/Serialization/ASTCommon.cpp +++ b/clang/lib/Serialization/ASTCommon.cpp @@ -415,6 +415,7 @@ case Decl::Friend: case Decl::FriendTemplate: case Decl::StaticAssert: + case Decl::Metaprogram: case Decl::Block: case Decl::Captured: case Decl::ClassScopeFunctionSpecialization: diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -492,6 +492,18 @@ SubExpr = Record.readSubStmt(); } +void ASTStmtReader::VisitStringInjectionStmt(StringInjectionStmt *S) { + VisitStmt(S); + S->setWrittenFirstArg(cast(Record.readSubExpr())); + unsigned NumArgs = Record.readInt(); + assert((NumArgs == S->getNumArgs()) && "Wrong NumArgs!"); + S->setKeywordLoc(readSourceLocation()); + S->setLParenLoc(readSourceLocation()); + S->setRParenLoc(readSourceLocation()); + for (unsigned I = 0; I != NumArgs; ++I) + S->setArg(I, Record.readSubExpr()); +} + void ASTStmtReader::VisitCapturedStmt(CapturedStmt *S) { VisitStmt(S); Record.skipInts(1); diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -395,6 +395,18 @@ Code = serialization::EXPR_DEPENDENT_COAWAIT; } +void ASTStmtWriter::VisitStringInjectionStmt(StringInjectionStmt *S) { + VisitStmt(S); + Record.AddStmt(S->getWrittenFirstArg()); + Record.push_back(S->getNumArgs()); + Record.AddSourceLocation(S->getKeywordLoc()); + Record.AddSourceLocation(S->getLParenLoc()); + Record.AddSourceLocation(S->getRParenLoc()); + for (auto Arg = S->arg_begin(), ArgEnd = S->arg_end(); + Arg != ArgEnd; ++Arg) + Record.AddStmt(*Arg); +} + static void addConstraintSatisfaction(ASTRecordWriter &Record, const ASTConstraintSatisfaction &Satisfaction) { diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -1233,6 +1233,7 @@ case Stmt::DependentCoawaitExprClass: case Stmt::CoreturnStmtClass: case Stmt::CoyieldExprClass: + case Stmt::StringInjectionStmtClass: case Stmt::SEHTryStmtClass: case Stmt::SEHExceptStmtClass: case Stmt::SEHLeaveStmtClass: diff --git a/clang/test/CXX/meta/stringinj/inj01.cpp b/clang/test/CXX/meta/stringinj/inj01.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CXX/meta/stringinj/inj01.cpp @@ -0,0 +1,307 @@ +// RUN: %clang_cc1 -std=c++2a -fstring-injection -verify %s +// expected-no-diagnostics + +#define assert(expr) ((expr) ? (void)(0) : __builtin_abort()) + +static_assert(__has_extension(string_injection), + "Pass -fstring-injection to compiler"); + +static constexpr const char *five = "5"; +static const char *fiveB = "5"; +const char *fiveC = "5"; + +// String injection in top level context +consteval { + __inj(""); + __injf(""); + __inj("static const int h = 2;"); + __inj("static const int i = h + ", 1, ";"); + __injf("constexpr int {} = {} + {} + {};", "j", 4, five, "i"); + __inj("//does nothing"); + + __injf("//single arg to injf okay"); +// __inj("int k = ", fiveB, ";"); //ERROR (DWR TODO turn these into exp error tests...) +// __inj("int k = ", fiveC, ";"); //ERROR +// __inj("void f() {}>"); //ERROR +// __inj("void f() {}]"); //ERROR +// __inj("void f() {})"); //ERROR +// __inj("void f() {}}"); //ERROR + + // If not terminated via subsequent __inj/__injf statements in this same + // consteval {}, the following should produce errors: +// __inj("void f("); //ERROR (FIXME: diagnostics suck) +// __inj("template>"); //ERROR +// __inj("template"); //ERROR but no crash //FIXME pointed-to location wrong +// __inj("\""); //ERROR +// __inj("/*"); //ERROR + + // So long as each injected entity is terminated by the + // end of this consteval {} you're okay: + __inj("void foo("); + __injf("int {}, int {}", "parm1", "parm2"); + __inj("){"); + __inj("}"); +} + +static_assert(h==2); +static_assert(i==3); +static_assert(j==12); + +// Namespace context +namespace test1 { +consteval { + __inj("static const int i", " = ", 3, ";"); + __injf("constexpr int {} = {} + {} + {};", "j", 4, five, "i"); +} +} +static_assert(test1::i==3); +static_assert(test1::j==12); + +// Non-dependent class context +struct test2 { + consteval { + __inj("static const int i", " = ", 3, ";"); + __injf("static const int {} = {} + {} + {};", "j", 4, five, "i"); + } +}; +static_assert(test2::i==3); +static_assert(test2::j==12); + +// Non-dependent function context +void f(int b) { + if (b) { + consteval { + __inj("int v = b + 1;"); + __inj("static const int fi", " = ", 3, ";"); + __injf("static const int {} = {} + {} + {};", "fj", 4, five, "fi"); + } + v += 0; // Referencing v here is only possible because the metaprogram was + // non-dependent, and so could be evaluated immediately + static_assert(fi==3); + static_assert(fj==12); + } +} + +// Dependent function context +template +bool g() { + bool res; + consteval { + __inj(""); + __inj("static const int v = 2;"); + __inj("static const int fi = 33 + v;"); + __injf("res = (v == 2 + {});", I); + } +// static const int w = v + I; //ERROR + consteval { + __injf("static const int w = v + {};", I); //Okay + } + return res; +} +template +struct Wrapper { + bool g() { + bool res; + consteval { + __inj(""); + __inj("static const int v = 2;"); + __inj("static const int fi = 33 + v;"); + __inj("res = (v==2+", I, ");"); + } +// static const int w = v + I; //ERROR + consteval { + __injf("static const int w = v + {};", I); //Okay + } + return res; + } +}; +void dummy() { + bool res = g<3>(); + res = Wrapper<4>().g(); +} + +// Dependent class context +template +struct B { + static const int init = 0; + consteval { + __inj("static const int v = 2 + ", I, ";"); + __inj("static const decltype(init) fi = 33 + v;"); + __inj("static const int fj=",44,"+",five,";"); + } +// static const int w = v + I; //ERROR + consteval { + __injf("static const int w = v + {};", I); //Okay + } + struct Inner { + consteval { + __injf("static const int x = w + v + {};", I); //Okay + } + }; +}; +static_assert(B<3>::fi==38); +static_assert(B<3>::fj==49); +static_assert(B<3>::w==8); +static_assert(B<3>::Inner::x==16); + + + +// Macros shouldn't be used within injection statements; +// instead, stringize and send as a separate argument. + +/// This expands any macros in x and stringizes the result. +# define PP_EMPTY() +# define PP_DEFER(id) id PP_EMPTY() +# define PP_STRINGIZE_SIMPLE(x) #x +# define PP_EXPAND(x) x +#define STRINGIZE(x) PP_EXPAND(PP_DEFER(PP_STRINGIZE_SIMPLE)(x)) + +static const int x = 8; +#define MYMACROA ((2+4) * x) +#define MYMACRO_AA(a,b) a-b +#define MYMACRO_AAA MYMACRO_AA(5,MYMACROA) + 6 +consteval { + __inj("static const int y = 3 + ", STRINGIZE(MYMACROA), ";"); +// __inj("static_assert(MYMACROA==2);"); //ERROR (bad diagnostics) +// __inj("static_assert(MYMACRO_AAA==9);"); //ERROR (bad diagnostics) +// __inj("static_assert(MYMACRO_AA(5,3)==2;"); //ERROR (bad diags) +// __inj("#define MYMACRO_BB(a,b) a+b"); //ERROR (good diags) +// __inj("#define MYMACRO_B 3"); // ERROR (good diags) +// __inj("int asdf3 = MYMACRO_AAA;"); //ERROR (bad diags) +// __inj("#undef MY_MACRO_AA"); //this actually works, but shouldn't +} +static_assert(y==51); + + + + + + + + + +// Injection via meta functions +namespace metafuncinj { + consteval void a42() { + __inj("int a = 42;"); + } + consteval void declare_intvar(const char *name, int val) { + __injf("int {} = {};", name, val); + } + template + consteval void declare_intorstrvar(const char *name, INTORSTR val) { + __injf("static constexpr auto {} = {};", name, val); + } + constexpr auto lambda_declare_intvar = [](const char *maybeinline, const char *name) -> void { + __injf("static {} int {} = 0;", maybeinline, name); + }; + template + consteval auto set_var(const char *name, INTORSTR val) { + __injf("{} = {};", name, val); + } + + // Namespace context + consteval { + a42(); + declare_intvar("b", 43); + declare_intorstrvar("c", "\"asdf\""); + lambda_declare_intvar("inline", "d"); + __inj("void d_increment_by_2() {"); + set_var("d", "d+2"); + __inj("}"); + } + void testA() { + assert(a == 42); + assert(b == 43); + assert(c[0] == 'a' && c[3] == 'f'); + metafuncinj::d = 0; + assert(d == 0); + d_increment_by_2(); + assert(d == 2); + } + + // Non-dependent class context + struct Foo { + consteval { + a42(); + declare_intvar("b", 43); + declare_intorstrvar("c", "\"asdf\""); + lambda_declare_intvar("inline", "d"); + __inj("void d_increment_by_2() {"); + set_var("d", "d+2"); + __inj("}"); + } + void test() { + assert(a == 42); + assert(b == 43); + assert(c[0] == 'a' && c[3] == 'f'); + assert(d == 0); + this->d_increment_by_2(); + assert(d == 2); + } + }; + + // Non-dependent function context + void testB(int param2 = 2) { + static const int local43 = 43; + if (param2 == 2) { + int localVarInScope = 1; + consteval { + __inj("localVarInScope += 1;"); + a42(); + declare_intvar("b", local43); + declare_intorstrvar("c", "\"asdf\""); + lambda_declare_intvar("", "d"); + __inj("auto d_increment_by_2 = [&](int zero = 0){"); + set_var("d", "d + param2 + zero"); + __inj("};"); + } + assert(localVarInScope==2); + assert(a == 42); + assert(b == 43); + assert(c[0] == 'a' && c[3] == 'f'); + assert(d == 0); + d_increment_by_2(0); + assert(d == 2); + } + } + + // Dependent class context + template + struct B { + static const int memberTwo = 2; + consteval { + const char *name = I==3 ? "\"asdf\"" : "\"qwer\""; + a42(); + declare_intvar("b", 40 + I); + declare_intorstrvar("c", name); + lambda_declare_intvar("inline", "d"); + __inj("void d_increment_by_2() {"); + set_var("d", "d + memberTwo"); + __inj("}"); + } + void test() { + assert(a == 42); + assert(b == 40 + I); + assert(c[0] == 'a' && c[3] == 'f'); + assert(d == 0); + this->d_increment_by_2(); + assert(d == 2); + } + }; + + // Explicit instantiation (this is needed to avoid + // a linker error regarding the static d, since it + // is not seen until instantiation - FIXME?) + template class B<3>; +} + +int main() { + metafuncinj::testA(); + metafuncinj::Foo foo; + foo.test(); + metafuncinj::testB(2); + metafuncinj::B<3> b3; + b3.test(); +} \ No newline at end of file diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp --- a/clang/tools/libclang/CIndex.cpp +++ b/clang/tools/libclang/CIndex.cpp @@ -5460,6 +5460,8 @@ return cxstring::createRef("SEHFinallyStmt"); case CXCursor_SEHLeaveStmt: return cxstring::createRef("SEHLeaveStmt"); + case CXCursor_StringInjectionStmt: + return cxstring::createRef("StringInjectionStmt"); case CXCursor_NullStmt: return cxstring::createRef("NullStmt"); case CXCursor_InvalidFile: @@ -5730,6 +5732,8 @@ return cxstring::createRef("StaticAssert"); case CXCursor_FriendDecl: return cxstring::createRef("FriendDecl"); + case CXCursor_Metaprogram: + return cxstring::createRef("Metaprogram"); case CXCursor_ConvergentAttr: return cxstring::createRef("attribute(convergent)"); case CXCursor_WarnUnusedAttr: @@ -6482,6 +6486,7 @@ case Decl::ObjCPropertyImpl: case Decl::FileScopeAsm: case Decl::StaticAssert: + case Decl::Metaprogram: case Decl::Block: case Decl::Captured: case Decl::OMPCapturedExpr: diff --git a/clang/tools/libclang/CXCursor.cpp b/clang/tools/libclang/CXCursor.cpp --- a/clang/tools/libclang/CXCursor.cpp +++ b/clang/tools/libclang/CXCursor.cpp @@ -293,6 +293,10 @@ K = CXCursor_UnexposedStmt; break; + case Stmt::StringInjectionStmtClass: + K = CXCursor_StringInjectionStmt; + break; + case Stmt::ArrayTypeTraitExprClass: case Stmt::AsTypeExprClass: case Stmt::AtomicExprClass: