diff --git a/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp index 07bb08166a006..f1b4682c397ab 100644 --- a/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp @@ -241,6 +241,12 @@ static bool isIdenticalStmt(const ASTContext &Ctx, const Stmt *Stmt1, return false; return true; } + case Stmt::DeferStmtClass: { + const auto *DefStmt1 = cast(Stmt1); + const auto *DefStmt2 = cast(Stmt2); + return isIdenticalStmt(Ctx, DefStmt1->getBody(), DefStmt2->getBody(), + IgnoreSideEffects); + } case Stmt::CompoundStmtClass: { const auto *CompStmt1 = cast(Stmt1); const auto *CompStmt2 = cast(Stmt2); diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 70c82b090107a..5195292cfe420 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -165,6 +165,9 @@ Resolutions to C++ Defect Reports C Language Changes ------------------ +- Implemented the ``defer`` Technical Specification (n3589); it is enabled + in C mode by passing ``-fdefer-ts``. + C2y Feature Support ^^^^^^^^^^^^^^^^^^^ - Clang now supports `N3355 `_ Named Loops. diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h index 1d1b7f183f75a..a7a89e8338af5 100644 --- a/clang/include/clang/AST/RecursiveASTVisitor.h +++ b/clang/include/clang/AST/RecursiveASTVisitor.h @@ -2560,6 +2560,7 @@ DEF_TRAVERSE_STMT(DefaultStmt, {}) DEF_TRAVERSE_STMT(DoStmt, {}) DEF_TRAVERSE_STMT(ForStmt, {}) DEF_TRAVERSE_STMT(GotoStmt, {}) +DEF_TRAVERSE_STMT(DeferStmt, {}) DEF_TRAVERSE_STMT(IfStmt, {}) DEF_TRAVERSE_STMT(IndirectGotoStmt, {}) DEF_TRAVERSE_STMT(LabelStmt, {}) diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index 76942f1a84f9a..655e7aff0d55c 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -317,6 +317,16 @@ class alignas(void *) Stmt { SourceLocation KeywordLoc; }; + class DeferStmtBitfields { + friend class DeferStmt; + + LLVM_PREFERRED_TYPE(StmtBitfields) + unsigned : NumStmtBits; + + /// The location of the "defer". + SourceLocation DeferLoc; + }; + //===--- Expression bitfields classes ---===// class ExprBitfields { @@ -1318,6 +1328,7 @@ class alignas(void *) Stmt { LoopControlStmtBitfields LoopControlStmtBits; ReturnStmtBitfields ReturnStmtBits; SwitchCaseBitfields SwitchCaseBits; + DeferStmtBitfields DeferStmtBits; // Expressions ExprBitfields ExprBits; @@ -3232,6 +3243,47 @@ class ReturnStmt final } }; +/// DeferStmt - This represents a deferred statement. +class DeferStmt : public Stmt { + friend class ASTStmtReader; + + /// The deferred statement. + Stmt *Body; + +public: + DeferStmt(SourceLocation DeferLoc, Stmt *Body) : Stmt(DeferStmtClass) { + setDeferLoc(DeferLoc); + setBody(Body); + } + + explicit DeferStmt(EmptyShell Empty) : Stmt(DeferStmtClass, Empty) {} + + SourceLocation getDeferLoc() const { return DeferStmtBits.DeferLoc; } + void setDeferLoc(SourceLocation DeferLoc) { + DeferStmtBits.DeferLoc = DeferLoc; + } + + Stmt *getBody() { return Body; } + const Stmt *getBody() const { return Body; } + void setBody(Stmt *S) { + assert(S && "defer body must not be null"); + Body = S; + } + + SourceLocation getBeginLoc() const { return getDeferLoc(); } + SourceLocation getEndLoc() const { return Body->getEndLoc(); } + + child_range children() { return child_range(&Body, &Body + 1); } + + const_child_range children() const { + return const_child_range(&Body, &Body + 1); + } + + static bool classof(const Stmt *S) { + return S->getStmtClass() == DeferStmtClass; + } +}; + /// AsmStmt is the base class for GCCAsmStmt and MSAsmStmt. class AsmStmt : public Stmt { protected: diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index 4d9e123eb4ef1..c2f221dab53d9 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -347,6 +347,8 @@ def err_address_of_label_outside_fn : Error< "use of address-of-label extension outside of a function body">; def err_asm_operand_wide_string_literal : Error< "cannot use %select{unicode|wide}0 string literal in 'asm'">; +def err_defer_ts_labeled_stmt : Error< + "substatement of 'defer' must not be a label">; def err_asm_expected_string : Error< "expected string literal %select{or parenthesized constant expression |}0in 'asm'">; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index bd896524321d1..28649ae789196 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -6793,6 +6793,7 @@ def note_protected_by_objc_weak_init : Note< "jump bypasses initialization of __weak variable">; def note_protected_by_non_trivial_c_struct_init : Note< "jump bypasses initialization of variable of non-trivial C struct type">; +def note_protected_by_defer_stmt : Note<"jump bypasses defer statement">; def note_enters_block_captures_cxx_obj : Note< "jump enters lifetime of block which captures a destructible C++ object">; def note_enters_block_captures_strong : Note< @@ -6806,6 +6807,7 @@ def note_enters_compound_literal_scope : Note< "jump enters lifetime of a compound literal that is non-trivial to destruct">; def note_enters_statement_expression : Note< "jump enters a statement expression">; +def note_enters_defer_stmt : Note<"jump enters a defer statement">; def note_exits_cleanup : Note< "jump exits scope of variable with __attribute__((cleanup))">; @@ -6851,6 +6853,16 @@ def note_exits_block_captures_non_trivial_c_struct : Note< "to destroy">; def note_exits_compound_literal_scope : Note< "jump exits lifetime of a compound literal that is non-trivial to destruct">; +def note_exits_defer_stmt : Note<"jump exits a defer statement">; +def err_jump_out_of_defer_stmt : Error< + "cannot %enum_select{" + "%Break{break out of a}|" + "%Continue{continue loop outside of enclosing}|" + "%Return{return from a}|" + "%SEHLeave{__leave a}" + "}0 defer statement">; +def err_defer_invalid_sjlj : Error< + "cannot use %0 inside a defer statement">; def err_func_returning_qualified_void : ExtWarn< "function cannot return qualified void type %0">, @@ -10926,6 +10938,8 @@ def err_switch_explicit_conversion : Error< def err_switch_incomplete_class_type : Error< "switch condition has incomplete class type %0">; +// TODO: It ought to be possible to refactor these to be a single warning that +// uses %enum_select. def warn_empty_if_body : Warning< "if statement has empty body">, InGroup; def warn_empty_for_body : Warning< @@ -10936,6 +10950,8 @@ def warn_empty_while_body : Warning< "while loop has empty body">, InGroup; def warn_empty_switch_body : Warning< "switch statement has empty body">, InGroup; +def warn_empty_defer_body : Warning< + "defer statement has empty body">, InGroup; def note_empty_body_on_separate_line : Note< "put the semicolon on a separate line to silence this warning">; diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index 84f5ab3443a59..3ba281a68fcb3 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -194,6 +194,7 @@ LANGOPT(NoSignedZero , 1, 0, Benign, "Permit Floating Point optimization wi LANGOPT(AllowRecip , 1, 0, Benign, "Permit Floating Point reciprocal") LANGOPT(ApproxFunc , 1, 0, Benign, "Permit Floating Point approximation") LANGOPT(NamedLoops , 1, 0, Benign, "Permit named break/continue") +LANGOPT(DeferTS , 1, 0, Benign, "C 'defer' Technical Specification") ENUM_LANGOPT(ComplexRange, ComplexRangeKind, 3, CX_None, NotCompatible, "Enable use of range reduction for complex arithmetics.") diff --git a/clang/include/clang/Basic/StmtNodes.td b/clang/include/clang/Basic/StmtNodes.td index dd1a24405fae7..04f8f2ee1687e 100644 --- a/clang/include/clang/Basic/StmtNodes.td +++ b/clang/include/clang/Basic/StmtNodes.td @@ -17,6 +17,7 @@ def ForStmt : StmtNode; def GotoStmt : StmtNode; def IndirectGotoStmt : StmtNode; def ReturnStmt : StmtNode; +def DeferStmt : StmtNode; def DeclStmt : StmtNode; def SwitchCase : StmtNode; def CaseStmt : StmtNode; diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index 9d1a23d1af218..d6dbddec6b783 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -293,6 +293,7 @@ PUNCTUATOR(greatergreatergreater, ">>>") // CHAR8SUPPORT - This is a keyword if 'char8_t' is a built-in type // KEYFIXEDPOINT - This is a keyword according to the N1169 fixed point // extension. +// KEYDEFERTS - This is a keyword if the C 'defer' TS is enabled // KEYZOS - This is a keyword in C/C++ on z/OS // KEYWORD(auto , KEYALL) @@ -441,6 +442,9 @@ KEYWORD(_Float16 , KEYALL) C23_KEYWORD(typeof , KEYGNU) C23_KEYWORD(typeof_unqual , 0) +// 'defer' TS +KEYWORD(defer , KEYDEFERTS) + // ISO/IEC JTC1 SC22 WG14 N1169 Extension KEYWORD(_Accum , KEYFIXEDPOINT) KEYWORD(_Fract , KEYFIXEDPOINT) @@ -731,6 +735,7 @@ ALIAS("__typeof_unqual" , typeof_unqual, KEYALL) ALIAS("__typeof_unqual__", typeof_unqual, KEYALL) ALIAS("__volatile" , volatile , KEYALL) ALIAS("__volatile__" , volatile , KEYALL) +ALIAS("_Defer" , defer , KEYNOCXX) // Type nullability. KEYWORD(_Nonnull , KEYALL) diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 16e1c396fedbe..4dcebeb4f828d 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1653,6 +1653,14 @@ defm named_loops PosFlag, NegFlag>; +// C 'defer' TS +defm defer_ts : BoolFOption<"defer-ts", + LangOpts<"DeferTS">, DefaultFalse, + PosFlag, + NegFlag>, + ShouldParseIf; + // C++ Coroutines defm coroutines : BoolFOption<"coroutines", LangOpts<"Coroutines">, Default, diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 30edd303e1824..52d8a0238cb2a 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -7501,6 +7501,16 @@ class Parser : public CodeCompletionHandler { StmtResult ParseBreakOrContinueStatement(bool IsContinue); + /// ParseDeferStatement + /// \verbatim + /// defer-statement: + /// 'defer' deferred-block + /// + /// deferred-block: + /// unlabeled-statement + /// \endverbatim + StmtResult ParseDeferStatement(SourceLocation *TrailingElseLoc); + StmtResult ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx, SourceLocation *TrailingElseLoc, ParsedAttributes &Attrs, diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index d017d1f829015..1634ccf97f603 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -10915,6 +10915,10 @@ class Sema final : public SemaBase { /// Stack of active SEH __finally scopes. Can be empty. SmallVector CurrentSEHFinally; + /// Stack of 'defer' statements that are currently being parsed, as well + /// as the locations of their 'defer' keywords. Can be empty. + SmallVector, 2> CurrentDefer; + StmtResult ActOnExprStmt(ExprResult Arg, bool DiscardedValue = true); StmtResult ActOnExprStmtError(); @@ -11061,6 +11065,10 @@ class Sema final : public SemaBase { StmtResult ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope, LabelDecl *Label, SourceLocation LabelLoc); + void ActOnStartOfDeferStmt(SourceLocation DeferLoc, Scope *CurScope); + void ActOnDeferStmtError(Scope *CurScope); + StmtResult ActOnEndOfDeferStmt(Stmt *Body, Scope *CurScope); + struct NamedReturnInfo { const VarDecl *Candidate; diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h index 441047d64f48c..b287539681ded 100644 --- a/clang/include/clang/Serialization/ASTBitCodes.h +++ b/clang/include/clang/Serialization/ASTBitCodes.h @@ -2060,6 +2060,7 @@ enum StmtCode { // HLSL Constructs EXPR_HLSL_OUT_ARG, + STMT_DEFER, }; /// The kinds of designators that can occur in a diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp index 2c9c3581a2962..843110bc93f59 100644 --- a/clang/lib/AST/StmtPrinter.cpp +++ b/clang/lib/AST/StmtPrinter.cpp @@ -491,6 +491,11 @@ void StmtPrinter::VisitBreakStmt(BreakStmt *Node) { if (Policy.IncludeNewlines) OS << NL; } +void StmtPrinter::VisitDeferStmt(DeferStmt *Node) { + Indent() << "defer"; + PrintControlledStmt(Node->getBody()); +} + void StmtPrinter::VisitReturnStmt(ReturnStmt *Node) { Indent() << "return"; if (Node->getRetValue()) { diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp index 37c4d43ec0b2f..10794d9dcba26 100644 --- a/clang/lib/AST/StmtProfile.cpp +++ b/clang/lib/AST/StmtProfile.cpp @@ -323,6 +323,8 @@ void StmtProfiler::VisitReturnStmt(const ReturnStmt *S) { VisitStmt(S); } +void StmtProfiler::VisitDeferStmt(const DeferStmt *S) { VisitStmt(S); } + void StmtProfiler::VisitGCCAsmStmt(const GCCAsmStmt *S) { VisitStmt(S); ID.AddBoolean(S->isVolatile()); diff --git a/clang/lib/Basic/IdentifierTable.cpp b/clang/lib/Basic/IdentifierTable.cpp index 4a2b77cd16bfc..79f9eb696d9e8 100644 --- a/clang/lib/Basic/IdentifierTable.cpp +++ b/clang/lib/Basic/IdentifierTable.cpp @@ -110,7 +110,8 @@ enum TokenKey : unsigned { KEYNOZOS = 0x4000000, KEYHLSL = 0x8000000, KEYFIXEDPOINT = 0x10000000, - KEYMAX = KEYFIXEDPOINT, // The maximum key + KEYDEFERTS = 0x20000000, + KEYMAX = KEYDEFERTS, // The maximum key KEYALLCXX = KEYCXX | KEYCXX11 | KEYCXX20, KEYALL = (KEYMAX | (KEYMAX - 1)) & ~KEYNOMS18 & ~KEYNOOPENCL & ~KEYNOZOS // KEYNOMS18, KEYNOOPENCL, KEYNOZOS are excluded. @@ -215,6 +216,8 @@ static KeywordStatus getKeywordStatusHelper(const LangOptions &LangOpts, return KS_Unknown; case KEYFIXEDPOINT: return LangOpts.FixedPoint ? KS_Enabled : KS_Disabled; + case KEYDEFERTS: + return LangOpts.DeferTS ? KS_Enabled : KS_Disabled; default: llvm_unreachable("Unknown KeywordStatus flag"); } diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp index aeff73d525c10..6f9c002b2c369 100644 --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -114,6 +114,7 @@ void CodeGenFunction::EmitStmt(const Stmt *S, ArrayRef Attrs) { case Stmt::ContinueStmtClass: case Stmt::DefaultStmtClass: case Stmt::CaseStmtClass: + case Stmt::DeferStmtClass: case Stmt::SEHLeaveStmtClass: case Stmt::SYCLKernelCallStmtClass: llvm_unreachable("should have emitted these statements as simple"); @@ -536,6 +537,9 @@ bool CodeGenFunction::EmitSimpleStmt(const Stmt *S, case Stmt::CaseStmtClass: EmitCaseStmt(cast(*S), Attrs); break; + case Stmt::DeferStmtClass: + EmitDeferStmt(cast(*S)); + break; case Stmt::SEHLeaveStmtClass: EmitSEHLeaveStmt(cast(*S)); break; @@ -1997,6 +2001,21 @@ void CodeGenFunction::EmitDefaultStmt(const DefaultStmt &S, EmitStmt(S.getSubStmt()); } +namespace { +struct EmitDeferredStatement final : EHScopeStack::Cleanup { + const DeferStmt &Stmt; + EmitDeferredStatement(const DeferStmt *Stmt) : Stmt(*Stmt) {} + + void Emit(CodeGenFunction &CGF, Flags) override { + CGF.EmitStmt(Stmt.getBody()); + } +}; +} // namespace + +void CodeGenFunction::EmitDeferStmt(const DeferStmt &S) { + EHStack.pushCleanup(NormalAndEHCleanup, &S); +} + /// CollectStatementsForCase - Given the body of a 'switch' statement and a /// constant value that is being switched on, see if we can dead code eliminate /// the body of the switch to a simple series of statements to emit. Basically, diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index 727487b46054f..5e032eae37f11 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -3606,6 +3606,7 @@ class CodeGenFunction : public CodeGenTypeCache { void EmitDefaultStmt(const DefaultStmt &S, ArrayRef Attrs); void EmitCaseStmt(const CaseStmt &S, ArrayRef Attrs); void EmitCaseStmtRange(const CaseStmt &S, ArrayRef Attrs); + void EmitDeferStmt(const DeferStmt &S); void EmitAsmStmt(const AsmStmt &S); const BreakContinue *GetDestForLoopControlStmt(const LoopControlStmt &S); diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index f67454ee517bd..169127a71e76e 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -7066,6 +7066,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, types::isCXX(InputType)) CmdArgs.push_back("-fcoro-aligned-allocation"); + if (Args.hasFlag(options::OPT_fdefer_ts, options::OPT_fno_defer_ts, false)) + CmdArgs.push_back("-fdefer-ts"); + Args.AddLastArg(CmdArgs, options::OPT_fdouble_square_bracket_attributes, options::OPT_fno_double_square_bracket_attributes); diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp index edf0a091e087c..3c4b2fbfb8760 100644 --- a/clang/lib/Frontend/InitPreprocessor.cpp +++ b/clang/lib/Frontend/InitPreprocessor.cpp @@ -529,6 +529,9 @@ static void InitializeStandardPredefinedMacros(const TargetInfo &TI, Builder.defineMacro("__STDC_EMBED_EMPTY__", llvm::itostr(static_cast(EmbedResult::Empty))); + if (LangOpts.DeferTS) + Builder.defineMacro("__STDC_DEFER_TS25755__", "1"); + if (LangOpts.ObjC) Builder.defineMacro("__OBJC__"); diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index 2e7af1219547e..4c99f9d454ac9 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -28,6 +28,7 @@ #include "clang/Sema/SemaOpenMP.h" #include "clang/Sema/TypoCorrection.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/ScopeExit.h" #include using namespace clang; @@ -312,6 +313,8 @@ StmtResult Parser::ParseStatementOrDeclarationAfterAttributes( Res = ParseReturnStatement(); SemiError = "co_return"; break; + case tok::kw_defer: // C defer TS: defer-statement + return ParseDeferStatement(TrailingElseLoc); case tok::kw_asm: { for (const ParsedAttr &AL : CXX11Attrs) @@ -2376,6 +2379,27 @@ StmtResult Parser::ParseReturnStatement() { return Actions.ActOnReturnStmt(ReturnLoc, R.get(), getCurScope()); } +StmtResult Parser::ParseDeferStatement(SourceLocation *TrailingElseLoc) { + assert(Tok.is(tok::kw_defer)); + SourceLocation DeferLoc = ConsumeToken(); + Actions.ActOnStartOfDeferStmt(DeferLoc, getCurScope()); + auto OnError = llvm::make_scope_exit( + [&] { Actions.ActOnDeferStmtError(getCurScope()); }); + + StmtResult Res = ParseStatement(TrailingElseLoc); + if (!Res.isUsable()) + return StmtError(); + + // The grammar specifically calls for an unlabeled-statement here. + if (auto *L = dyn_cast(Res.get())) { + Diag(L->getIdentLoc(), diag::err_defer_ts_labeled_stmt); + return StmtError(); + } + + OnError.release(); + return Actions.ActOnEndOfDeferStmt(Res.get(), getCurScope()); +} + StmtResult Parser::ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx, SourceLocation *TrailingElseLoc, diff --git a/clang/lib/Sema/JumpDiagnostics.cpp b/clang/lib/Sema/JumpDiagnostics.cpp index 36704c3826dfd..36c9d9afb37f1 100644 --- a/clang/lib/Sema/JumpDiagnostics.cpp +++ b/clang/lib/Sema/JumpDiagnostics.cpp @@ -590,6 +590,27 @@ void JumpScopeChecker::BuildScopeInformation(Stmt *S, break; } + case Stmt::DeferStmtClass: { + auto *D = cast(S); + + { + // Disallow jumps over defer statements. + unsigned NewParentScope = Scopes.size(); + Scopes.emplace_back(ParentScope, diag::note_protected_by_defer_stmt, 0, + D->getDeferLoc()); + origParentScope = NewParentScope; + } + + // Disallow jumps into or out of defer statements. + { + unsigned NewParentScope = Scopes.size(); + Scopes.emplace_back(ParentScope, diag::note_enters_defer_stmt, + diag::note_exits_defer_stmt, D->getDeferLoc()); + BuildScopeInformation(D->getBody(), NewParentScope); + } + return; + } + case Stmt::CaseStmtClass: case Stmt::DefaultStmtClass: case Stmt::LabelStmtClass: @@ -972,7 +993,7 @@ void JumpScopeChecker::CheckJump(Stmt *From, Stmt *To, SourceLocation DiagLoc, // Common case: exactly the same scope, which is fine. if (FromScope == ToScope) return; - // Warn on gotos out of __finally blocks. + // Warn on gotos out of __finally blocks and defer statements. if (isa(From) || isa(From)) { // If FromScope > ToScope, FromScope is more nested and the jump goes to a // less nested scope. Check if it crosses a __finally along the way. @@ -990,6 +1011,10 @@ void JumpScopeChecker::CheckJump(Stmt *From, Stmt *To, SourceLocation DiagLoc, S.Diag(From->getBeginLoc(), diag::err_goto_into_protected_scope); S.Diag(Scopes[I].Loc, diag::note_acc_branch_out_of_compute_construct); return; + } else if (Scopes[I].OutDiag == diag::note_exits_defer_stmt) { + S.Diag(From->getBeginLoc(), diag::err_goto_into_protected_scope); + S.Diag(Scopes[I].Loc, diag::note_exits_defer_stmt); + return; } } } diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp index 552c92996dc2e..784ff462bf3c8 100644 --- a/clang/lib/Sema/SemaExceptionSpec.cpp +++ b/clang/lib/Sema/SemaExceptionSpec.cpp @@ -1537,6 +1537,7 @@ CanThrowResult Sema::canThrow(const Stmt *S) { case Stmt::SEHTryStmtClass: case Stmt::SwitchStmtClass: case Stmt::WhileStmtClass: + case Stmt::DeferStmtClass: return canSubStmtsThrow(*this, S); case Stmt::DeclStmtClass: { diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 3b267c1b1693d..bcdf02c766162 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -6851,6 +6851,34 @@ ExprResult Sema::BuildResolvedCallExpr(Expr *Fn, NamedDecl *NDecl, FunctionDecl *FDecl = dyn_cast_or_null(NDecl); unsigned BuiltinID = (FDecl ? FDecl->getBuiltinID() : 0); + auto IsSJLJ = [&] { + switch (BuiltinID) { + case Builtin::BI__builtin_longjmp: + case Builtin::BI__builtin_setjmp: + case Builtin::BI__sigsetjmp: + case Builtin::BI_longjmp: + case Builtin::BI_setjmp: + case Builtin::BIlongjmp: + case Builtin::BIsetjmp: + case Builtin::BIsiglongjmp: + case Builtin::BIsigsetjmp: + return true; + default: + return false; + } + }; + + // Forbid any call to setjmp/longjmp and friends inside a 'defer' statement. + if (!CurrentDefer.empty() && IsSJLJ()) { + // Note: If we ever start supporting 'defer' in C++ we'll have to check + // for more than just blocks (e.g. lambdas, nested classes...). + Scope *DeferParent = CurrentDefer.back().first; + Scope *Block = CurScope->getBlockParent(); + if (DeferParent->Contains(*CurScope) && + (!Block || !DeferParent->Contains(*Block))) + Diag(Fn->getExprLoc(), diag::err_defer_invalid_sjlj) << FDecl; + } + // Functions with 'interrupt' attribute cannot be called directly. if (FDecl) { if (FDecl->hasAttr()) { diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp index ae0bb616beb82..e79ceefcb418b 100644 --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -3267,12 +3267,22 @@ Sema::ActOnIndirectGotoStmt(SourceLocation GotoLoc, SourceLocation StarLoc, return new (Context) IndirectGotoStmt(GotoLoc, StarLoc, E); } -static void CheckJumpOutOfSEHFinally(Sema &S, SourceLocation Loc, - const Scope &DestScope) { +static void CheckJumpOutOfSEHFinallyOrDefer(Sema &S, SourceLocation Loc, + const Scope &DestScope, + unsigned DeferJumpKind) { if (!S.CurrentSEHFinally.empty() && DestScope.Contains(*S.CurrentSEHFinally.back())) { S.Diag(Loc, diag::warn_jump_out_of_seh_finally); } + + if (!S.CurrentDefer.empty()) { + Scope *Parent = S.CurrentDefer.back().first; + + // Note: We don't create a new scope for defer statements, so 'Parent' + // is actually the scope that contains the 'defer'. + if (DestScope.Contains(*Parent) || &DestScope == Parent) + S.Diag(Loc, diag::err_jump_out_of_defer_stmt) << DeferJumpKind; + } } static Scope *FindLabeledBreakContinueScope(Sema &S, Scope *CurScope, @@ -3343,7 +3353,8 @@ StmtResult Sema::ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope, Diag(ContinueLoc, diag::err_acc_branch_in_out_compute_construct) << /*branch*/ 0 << /*out of */ 0); - CheckJumpOutOfSEHFinally(*this, ContinueLoc, *S); + CheckJumpOutOfSEHFinallyOrDefer(*this, ContinueLoc, *S, + diag::DeferJumpKind::Continue); return new (Context) ContinueStmt(ContinueLoc, LabelLoc, Target); } @@ -3384,7 +3395,8 @@ StmtResult Sema::ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope, Diag(BreakLoc, diag::err_acc_branch_in_out_compute_construct) << /*branch*/ 0 << /*out of */ 0); - CheckJumpOutOfSEHFinally(*this, BreakLoc, *S); + CheckJumpOutOfSEHFinallyOrDefer(*this, BreakLoc, *S, + diag::DeferJumpKind::Break); return new (Context) BreakStmt(BreakLoc, LabelLoc, Target); } @@ -3924,11 +3936,30 @@ Sema::ActOnReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp, CurScope->updateNRVOCandidate(VD); - CheckJumpOutOfSEHFinally(*this, ReturnLoc, *CurScope->getFnParent()); + CheckJumpOutOfSEHFinallyOrDefer(*this, ReturnLoc, *CurScope->getFnParent(), + diag::DeferJumpKind::Return); return R; } +void Sema::ActOnStartOfDeferStmt(SourceLocation DeferLoc, Scope *CurScope) { + CurrentDefer.emplace_back(CurScope, DeferLoc); +} + +void Sema::ActOnDeferStmtError([[maybe_unused]] Scope *CurScope) { + assert(!CurrentDefer.empty() && CurrentDefer.back().first == CurScope); + CurrentDefer.pop_back(); +} + +StmtResult Sema::ActOnEndOfDeferStmt(Stmt *Body, + [[maybe_unused]] Scope *CurScope) { + assert(!CurrentDefer.empty() && CurrentDefer.back().first == CurScope); + SourceLocation DeferLoc = CurrentDefer.pop_back_val().second; + DiagnoseEmptyStmtBody(DeferLoc, Body, diag::warn_empty_defer_body); + setFunctionHasBranchProtectedScope(); + return new (Context) DeferStmt(DeferLoc, Body); +} + static bool CheckSimplerImplicitMovesMSVCWorkaround(const Sema &S, const Expr *E) { if (!E || !S.getLangOpts().CPlusPlus23 || !S.getLangOpts().MSVCCompat) @@ -4546,7 +4577,8 @@ Sema::ActOnSEHLeaveStmt(SourceLocation Loc, Scope *CurScope) { SEHTryParent = SEHTryParent->getParent(); if (!SEHTryParent) return StmtError(Diag(Loc, diag::err_ms___leave_not_in___try)); - CheckJumpOutOfSEHFinally(*this, Loc, *SEHTryParent); + CheckJumpOutOfSEHFinallyOrDefer(*this, Loc, *SEHTryParent, + diag::DeferJumpKind::SEHLeave); return new (Context) SEHLeaveStmt(Loc); } diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 242ffb09af006..0749ec14a2e37 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -8475,6 +8475,14 @@ TreeTransform::TransformBreakStmt(BreakStmt *S) { BreakStmt(S->getKwLoc(), S->getLabelLoc(), cast(LD)); } +template +StmtResult TreeTransform::TransformDeferStmt(DeferStmt *S) { + StmtResult Result = getDerived().TransformStmt(S->getBody()); + if (!Result.isUsable()) + return StmtError(); + return new (getSema().Context) DeferStmt(S->getDeferLoc(), Result.get()); +} + template StmtResult TreeTransform::TransformReturnStmt(ReturnStmt *S) { diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp index 213c2c2148f64..d8baa62fa4da7 100644 --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -335,6 +335,12 @@ void ASTStmtReader::VisitContinueStmt(ContinueStmt *S) { void ASTStmtReader::VisitBreakStmt(BreakStmt *S) { VisitLoopControlStmt(S); } +void ASTStmtReader::VisitDeferStmt(DeferStmt *S) { + VisitStmt(S); + S->setDeferLoc(readSourceLocation()); + S->setBody(Record.readSubStmt()); +} + void ASTStmtReader::VisitReturnStmt(ReturnStmt *S) { VisitStmt(S); @@ -3131,6 +3137,10 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) { S = new (Context) BreakStmt(Empty); break; + case STMT_DEFER: + S = new (Context) DeferStmt(Empty); + break; + case STMT_RETURN: S = ReturnStmt::CreateEmpty( Context, /* HasNRVOCandidate=*/Record[ASTStmtReader::NumStmtFields]); diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp index 21c04ddbc2c7a..7db1e8ad7260b 100644 --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -330,6 +330,13 @@ void ASTStmtWriter::VisitBreakStmt(BreakStmt *S) { Code = serialization::STMT_BREAK; } +void ASTStmtWriter::VisitDeferStmt(DeferStmt *S) { + VisitStmt(S); + Record.AddSourceLocation(S->getDeferLoc()); + Record.AddStmt(S->getBody()); + Code = serialization::STMT_DEFER; +} + void ASTStmtWriter::VisitReturnStmt(ReturnStmt *S) { VisitStmt(S); diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index 785cdfa15bf04..2385346c4dba5 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -1868,6 +1868,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::NullStmtClass: case Stmt::SwitchStmtClass: case Stmt::WhileStmtClass: + case Stmt::DeferStmtClass: case Expr::MSDependentExistsStmtClass: llvm_unreachable("Stmt should not be in analyzer evaluation loop"); case Stmt::ImplicitValueInitExprClass: diff --git a/clang/test/AST/ast-dump-defer-ts.c b/clang/test/AST/ast-dump-defer-ts.c new file mode 100644 index 0000000000000..cd11d92f23e8f --- /dev/null +++ b/clang/test/AST/ast-dump-defer-ts.c @@ -0,0 +1,27 @@ +// Test without serialization: +// RUN: %clang_cc1 -std=c23 -fdefer-ts -ast-dump %s -triple x86_64-linux-gnu \ +// RUN: | FileCheck %s +// +// Test with serialization: +// RUN: %clang_cc1 -std=c23 -fdefer-ts -triple x86_64-linux-gnu -emit-pch -o %t %s +// RUN: %clang_cc1 -std=c23 -fdefer-ts -triple x86_64-linux-gnu -include-pch %t -ast-dump-all /dev/null \ +// RUN: | FileCheck %s + +static inline void f() { + defer 3; + defer { 4; } + defer defer if (true) {} +} + +// CHECK-LABEL: f 'void (void)' static inline +// CHECK-NEXT: `-CompoundStmt {{.*}} +// CHECK-NEXT: |-DeferStmt {{.*}} +// CHECK-NEXT: | `-IntegerLiteral {{.*}} 'int' 3 +// CHECK-NEXT: |-DeferStmt {{.*}} +// CHECK-NEXT: | `-CompoundStmt {{.*}} +// CHECK-NEXT: | `-IntegerLiteral {{.*}} 'int' 4 +// CHECK-NEXT: `-DeferStmt {{.*}} +// CHECK-NEXT: `-DeferStmt {{.*}} +// CHECK-NEXT: `-IfStmt {{.*}} +// CHECK-NEXT: |-CXXBoolLiteralExpr {{.*}} 'bool' true +// CHECK-NEXT: `-CompoundStmt {{.*}} diff --git a/clang/test/AST/ast-print-defer-ts.c b/clang/test/AST/ast-print-defer-ts.c new file mode 100644 index 0000000000000..be59fe1059a2a --- /dev/null +++ b/clang/test/AST/ast-print-defer-ts.c @@ -0,0 +1,33 @@ +// RUN: %clang_cc1 -std=c23 -fdefer-ts -ast-print %s | FileCheck %s + +void g(); + +// CHECK: void f +void f() { + // CHECK-NEXT: defer + // CHECK-NEXT: g(); + // CHECK-NEXT: defer + // CHECK-NEXT: defer + // CHECK-NEXT: g(); + // CHECK-NEXT: defer { + // CHECK-NEXT: } + // CHECK-NEXT: defer { + // CHECK-NEXT: int x; + // CHECK-NEXT: } + // CHECK-NEXT: defer + // CHECK-NEXT: if (1) { + // CHECK-NEXT: } + defer + g(); + defer + defer + g(); + defer { + } + defer { + int x; + } + defer + if (1) { + } +} diff --git a/clang/test/CodeGen/defer-ts-musttail.c b/clang/test/CodeGen/defer-ts-musttail.c new file mode 100644 index 0000000000000..bda423b851159 --- /dev/null +++ b/clang/test/CodeGen/defer-ts-musttail.c @@ -0,0 +1,7 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux -std=c23 -fdefer-ts -emit-llvm %s -o /dev/null -verify + +int bar() { return 12; } +int foo() { + defer {}; + [[clang::musttail]] return bar(); // expected-error {{cannot compile this tail call skipping over cleanups yet}} +} diff --git a/clang/test/CodeGen/defer-ts-seh.c b/clang/test/CodeGen/defer-ts-seh.c new file mode 100644 index 0000000000000..57f216bbe64cb --- /dev/null +++ b/clang/test/CodeGen/defer-ts-seh.c @@ -0,0 +1,44 @@ +// RUN: %clang_cc1 -triple x86_64-windows-msvc -std=c23 -fdefer-ts -fms-compatibility -emit-llvm %s -o - | FileCheck %s + +void g(); +void h(); + +void f() { + __try { + defer h(); + g(); + } __finally { + + } +} + +// CHECK-LABEL: define {{.*}} void @f() {{.*}} personality ptr @__C_specific_handler +// CHECK: entry: +// CHECK: invoke void @g() #4 +// CHECK: to label %invoke.cont unwind label %ehcleanup +// CHECK: invoke.cont: +// CHECK: invoke void @h() #4 +// CHECK: to label %invoke.cont1 unwind label %ehcleanup3 +// CHECK: invoke.cont1: +// CHECK: %0 = call ptr @llvm.localaddress() +// CHECK: call void @"?fin$0@0@f@@"(i8 {{.*}} 0, ptr {{.*}} %0) +// CHECK: ret void +// CHECK: ehcleanup: +// CHECK: %1 = cleanuppad within none [] +// CHECK: invoke void @h() #4 [ "funclet"(token %1) ] +// CHECK: to label %invoke.cont2 unwind label %ehcleanup3 +// CHECK: invoke.cont2: +// CHECK: cleanupret from %1 unwind label %ehcleanup3 +// CHECK: ehcleanup3: +// CHECK: %2 = cleanuppad within none [] +// CHECK: %3 = call ptr @llvm.localaddress() +// CHECK: call void @"?fin$0@0@f@@"(i8 {{.*}} 1, ptr {{.*}} %3) [ "funclet"(token %2) ] +// CHECK: cleanupret from %2 unwind to caller + +// CHECK-LABEL: define {{.*}} void @"?fin$0@0@f@@"(i8 {{.*}} %abnormal_termination, ptr {{.*}} %frame_pointer) +// CHECK: entry: +// CHECK: %frame_pointer.addr = alloca ptr, align 8 +// CHECK: %abnormal_termination.addr = alloca i8, align 1 +// CHECK: store ptr %frame_pointer, ptr %frame_pointer.addr, align 8 +// CHECK: store i8 %abnormal_termination, ptr %abnormal_termination.addr, align 1 +// CHECK: ret void diff --git a/clang/test/CodeGen/defer-ts.c b/clang/test/CodeGen/defer-ts.c new file mode 100644 index 0000000000000..f72eb5bca8539 --- /dev/null +++ b/clang/test/CodeGen/defer-ts.c @@ -0,0 +1,427 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux -std=c23 -fdefer-ts -emit-llvm %s -o - | FileCheck %s + +void a(); +void b(); +void c(); +void x(int q); + +// CHECK-LABEL: define {{.*}} void @f1() +void f1() { + // CHECK: call void @c() + // CHECK: call void @b() + // CHECK: call void @a() + defer a(); + defer b(); + defer c(); +} + +// CHECK-LABEL: define {{.*}} void @f2() +void f2() { + // CHECK: call void @x(i32 {{.*}} 1) + // CHECK: call void @x(i32 {{.*}} 2) + // CHECK: call void @x(i32 {{.*}} 3) + // CHECK: call void @x(i32 {{.*}} 4) + // CHECK: call void @x(i32 {{.*}} 5) + defer x(5); + { + defer x(4); + { + defer x(2); + defer x(1); + } + x(3); + } +} + +// CHECK-LABEL: define {{.*}} void @f3(i1 {{.*}} %ret) +void f3(bool ret) { + // CHECK: %ret.addr = alloca i8, align 1 + // CHECK: %cleanup.dest.slot = alloca i32, align 4 + // CHECK: %storedv = zext i1 %ret to i8 + // CHECK: store i8 %storedv, ptr %ret.addr, align 1 + // CHECK: %0 = load i8, ptr %ret.addr, align 1 + // CHECK: %loadedv = trunc i8 %0 to i1 + // CHECK: br i1 %loadedv, label %if.then, label %if.end + // CHECK: if.then: + // CHECK: store i32 1, ptr %cleanup.dest.slot, align 4 + // CHECK: br label %cleanup + // CHECK: if.end: + // CHECK: call void @x(i32 {{.*}} 1) + // CHECK: store i32 0, ptr %cleanup.dest.slot, align 4 + // CHECK: br label %cleanup + // CHECK: cleanup: + // CHECK: call void @x(i32 {{.*}} 2) + // CHECK: %cleanup.dest = load i32, ptr %cleanup.dest.slot, align 4 + // CHECK: switch i32 %cleanup.dest, label %unreachable [ + // CHECK: i32 0, label %cleanup.cont + // CHECK: i32 1, label %cleanup.cont + // CHECK: ] + // CHECK: cleanup.cont: + // CHECK: ret void + // CHECK: unreachable: + // CHECK: unreachable + defer x(2); + if (ret) return; + defer x(1); +} + +// CHECK-LABEL: define {{.*}} void @ts_g() +void ts_g() { + // CHECK-NEXT: entry: + // CHECK-NEXT: ret void + // CHECK-NEXT: } + return; + defer x(42); +} + +// CHECK-LABEL: define {{.*}} void @ts_h() +void ts_h() { + // CHECK-NEXT: entry: + // CHECK-NEXT: br label %b + // CHECK-EMPTY: + goto b; + { + defer x(42); + } + + // CHECK-NEXT: b: + // CHECK-NEXT: ret void + // CHECK-NEXT: } + b: +} + +// CHECK-LABEL: define {{.*}} void @ts_i() +void ts_i() { + // CHECK: entry: + // CHECK: %cleanup.dest.slot = alloca i32, align 4 + // CHECK: store i32 2, ptr %cleanup.dest.slot, align 4 + // CHECK: call void @x(i32 {{.*}} 42) + // CHECK: %cleanup.dest = load i32, ptr %cleanup.dest.slot, align 4 + // CHECK: switch i32 %cleanup.dest, label %unreachable [ + // CHECK: i32 2, label %b + // CHECK: ] + // CHECK: b: + // CHECK: ret void + // CHECK: unreachable: + // CHECK: unreachable + { + defer { x(42); } + goto b; + } + b: +} + + +// CHECK-LABEL: define {{.*}} void @ts_m() +void ts_m() { + // CHECK: entry: + // CHECK: br label %b + // CHECK: b: + // CHECK: call void @x(i32 {{.*}} 1) + // CHECK: ret void + goto b; + { + b: + defer x(1); + } +} + +// CHECK-LABEL: define {{.*}} void @ts_p() +void ts_p() { + // CHECK: entry: + // CHECK: br label %b + // CHECK: b: + // CHECK: ret void + { + goto b; + defer x(42); + } + b: +} + +// CHECK-LABEL: define {{.*}} void @ts_r() +void ts_r() { + // CHECK: entry: + // CHECK: br label %b + // CHECK: b: + // CHECK: call void @x(i32 {{.*}} 42) + // CHECK: br label %b + { + b: + defer x(42); + } + goto b; +} + +// CHECK-LABEL: define {{.*}} i32 @return_value() +int return_value() { + // CHECK: entry: + // CHECK: %r = alloca i32, align 4 + // CHECK: %p = alloca ptr, align 8 + // CHECK: store i32 4, ptr %r, align 4 + // CHECK: store ptr %r, ptr %p, align 8 + // CHECK: %0 = load ptr, ptr %p, align 8 + // CHECK: %1 = load i32, ptr %0, align 4 + // CHECK: %2 = load ptr, ptr %p, align 8 + // CHECK: store i32 5, ptr %2, align 4 + // CHECK: ret i32 %1 + int r = 4; + int* p = &r; + defer { *p = 5; } + return *p; +} + +void* malloc(__SIZE_TYPE__ size); +void free(void* ptr); +int use_buffer(__SIZE_TYPE__ size, void* ptr); + +// CHECK-LABEL: define {{.*}} i32 @malloc_free_example() +int malloc_free_example() { + // CHECK: entry: + // CHECK: %size = alloca i32, align 4 + // CHECK: %buf = alloca ptr, align 8 + // CHECK: store i32 20, ptr %size, align 4 + // CHECK: %call = call ptr @malloc(i64 {{.*}} 20) + // CHECK: store ptr %call, ptr %buf, align 8 + // CHECK: %0 = load ptr, ptr %buf, align 8 + // CHECK: %call1 = call i32 @use_buffer(i64 {{.*}} 20, ptr {{.*}} %0) + // CHECK: %1 = load ptr, ptr %buf, align 8 + // CHECK: call void @free(ptr {{.*}} %1) + // CHECK: ret i32 %call1 + const int size = 20; + void* buf = malloc(size); + defer { free(buf); } + return use_buffer(size, buf); +} + +// CHECK-LABEL: define {{.*}} void @sequencing_1() +void sequencing_1() { + // CHECK: entry: + // CHECK: call void @x(i32 {{.*}} 1) + // CHECK: call void @x(i32 {{.*}} 2) + // CHECK: call void @x(i32 {{.*}} 3) + // CHECK: ret void + { + defer { + x(3); + } + if (true) + defer x(1); + x(2); + } +} + +// CHECK-LABEL: define {{.*}} void @sequencing_2() +void sequencing_2() { + // CHECK: entry: + // CHECK: %arr = alloca [3 x i32], align 4 + // CHECK: %i = alloca i32, align 4 + // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %arr, ptr align 4 @__const.sequencing_2.arr, i64 12, i1 false) + // CHECK: store i32 0, ptr %i, align 4 + // CHECK: br label %for.cond + // CHECK: for.cond: + // CHECK: %0 = load i32, ptr %i, align 4 + // CHECK: %cmp = icmp ult i32 %0, 3 + // CHECK: br i1 %cmp, label %for.body, label %for.end + // CHECK: for.body: + // CHECK: %1 = load i32, ptr %i, align 4 + // CHECK: %idxprom = zext i32 %1 to i64 + // CHECK: %arrayidx = getelementptr inbounds nuw [3 x i32], ptr %arr, i64 0, i64 %idxprom + // CHECK: %2 = load i32, ptr %arrayidx, align 4 + // CHECK: call void @x(i32 {{.*}} %2) + // CHECK: br label %for.inc + // CHECK: for.inc: + // CHECK: %3 = load i32, ptr %i, align 4 + // CHECK: %inc = add i32 %3, 1 + // CHECK: store i32 %inc, ptr %i, align 4 + // CHECK: br label %for.cond + // CHECK: for.end: + // CHECK: call void @x(i32 {{.*}} 4) + // CHECK: call void @x(i32 {{.*}} 5) + // CHECK: ret void + { + int arr[] = {1, 2, 3}; + defer { + x(5); + } + for (unsigned i = 0; i < 3; ++i) + defer x(arr[i]); + x(4); + } +} + +// CHECK-LABEL: define {{.*}} void @sequencing_3() +void sequencing_3() { + // CHECK: entry: + // CHECK: %r = alloca i32, align 4 + // CHECK: store i32 0, ptr %r, align 4 + // CHECK: %0 = load i32, ptr %r, align 4 + // CHECK: %add = add nsw i32 %0, 1 + // CHECK: store i32 %add, ptr %r, align 4 + // CHECK: %1 = load i32, ptr %r, align 4 + // CHECK: %mul = mul nsw i32 %1, 2 + // CHECK: store i32 %mul, ptr %r, align 4 + // CHECK: %2 = load i32, ptr %r, align 4 + // CHECK: %add1 = add nsw i32 %2, 3 + // CHECK: store i32 %add1, ptr %r, align 4 + // CHECK: %3 = load i32, ptr %r, align 4 + // CHECK: %mul2 = mul nsw i32 %3, 4 + // CHECK: store i32 %mul2, ptr %r, align 4 + // CHECK: ret void + int r = 0; + { + defer { + defer r *= 4; + r *= 2; + defer { + r += 3; + } + } + defer r += 1; + } +} + +// CHECK-LABEL: define {{.*}} void @defer_stmt(i32 {{.*}} %q) +void defer_stmt(int q) { + // CHECK: entry: + // CHECK: %q.addr = alloca i32, align 4 + // CHECK: store i32 %q, ptr %q.addr, align 4 + // CHECK: %0 = load i32, ptr %q.addr, align 4 + // CHECK: %cmp = icmp eq i32 %0, 3 + // CHECK: br i1 %cmp, label %if.then, label %if.end + // CHECK: if.then: + // CHECK: call void @x(i32 {{.*}} 42) + // CHECK: br label %if.end + // CHECK: if.end: + // CHECK: ret void + defer if (q == 3) x(42); +} + +// CHECK-LABEL: define {{.*}} void @defer_defer() +void defer_defer() { + // CHECK: entry: + // CHECK: call void @x(i32 {{.*}} 0) + // CHECK: call void @x(i32 {{.*}} 1) + // CHECK: call void @x(i32 {{.*}} 2) + // CHECK: call void @x(i32 {{.*}} 3) + // CHECK: call void @x(i32 {{.*}} 4) + // CHECK: ret void + defer x(4); + defer defer x(3); + defer defer defer x(2); + defer defer defer defer x(1); + x(0); +} + +// CHECK-LABEL: define {{.*}} i32 @vla(ptr {{.*}} %p, i32 {{.*}} %x) +int vla(int* p, int x) { + // CHECK: entry: + // CHECK: %retval = alloca i32, align 4 + // CHECK: %p.addr = alloca ptr, align 8 + // CHECK: %x.addr = alloca i32, align 4 + // CHECK: %cleanup.dest.slot = alloca i32, align 4 + // CHECK: %saved_stack = alloca ptr, align 8 + // CHECK: %__vla_expr0 = alloca i64, align 8 + // CHECK: %saved_stack2 = alloca ptr, align 8 + // CHECK: %__vla_expr1 = alloca i64, align 8 + // CHECK: store ptr %p, ptr %p.addr, align 8 + // CHECK: store i32 %x, ptr %x.addr, align 4 + // CHECK: %0 = load i32, ptr %x.addr, align 4 + // CHECK: %cmp = icmp slt i32 %0, 5 + // CHECK: br i1 %cmp, label %if.then, label %if.end + // CHECK: if.then: + // CHECK: store i32 10, ptr %retval, align 4 + // CHECK: store i32 1, ptr %cleanup.dest.slot, align 4 + // CHECK: br label %cleanup + // CHECK: if.end: + // CHECK: store i32 7, ptr %retval, align 4 + // CHECK: store i32 1, ptr %cleanup.dest.slot, align 4 + // CHECK: %1 = load i32, ptr %x.addr, align 4 + // CHECK: %2 = zext i32 %1 to i64 + // CHECK: %3 = call ptr @llvm.stacksave.p0() + // CHECK: store ptr %3, ptr %saved_stack, align 8 + // CHECK: %vla = alloca i32, i64 %2, align 16 + // CHECK: store i64 %2, ptr %__vla_expr0, align 8 + // CHECK: %arrayidx = getelementptr inbounds i32, ptr %vla, i64 2 + // CHECK: store i32 4, ptr %arrayidx, align 8 + // CHECK: %arrayidx1 = getelementptr inbounds i32, ptr %vla, i64 2 + // CHECK: %4 = load i32, ptr %arrayidx1, align 8 + // CHECK: %5 = load ptr, ptr %p.addr, align 8 + // CHECK: store i32 %4, ptr %5, align 4 + // CHECK: %6 = load ptr, ptr %saved_stack, align 8 + // CHECK: call void @llvm.stackrestore.p0(ptr %6) + // CHECK: br label %cleanup + // CHECK: cleanup: + // CHECK: %7 = load i32, ptr %x.addr, align 4 + // CHECK: %8 = zext i32 %7 to i64 + // CHECK: %9 = call ptr @llvm.stacksave.p0() + // CHECK: store ptr %9, ptr %saved_stack2, align 8 + // CHECK: %vla3 = alloca i32, i64 %8, align 16 + // CHECK: store i64 %8, ptr %__vla_expr1, align 8 + // CHECK: %arrayidx4 = getelementptr inbounds i32, ptr %vla3, i64 2 + // CHECK: store i32 3, ptr %arrayidx4, align 8 + // CHECK: %arrayidx5 = getelementptr inbounds i32, ptr %vla3, i64 2 + // CHECK: %10 = load i32, ptr %arrayidx5, align 8 + // CHECK: %11 = load ptr, ptr %p.addr, align 8 + // CHECK: store i32 %10, ptr %11, align 4 + // CHECK: %12 = load ptr, ptr %saved_stack2, align 8 + // CHECK: call void @llvm.stackrestore.p0(ptr %12) + // CHECK: %13 = load i32, ptr %retval, align 4 + // CHECK: ret i32 %13 + defer { + int a[x]; + a[2] = 3; + *p = a[2]; + } + if (x < 5) { return 10; } + defer { + int b[x]; + b[2] = 4; + *p = b[2]; + } + return 7; +} + +[[noreturn]] void exit(); +[[noreturn]] void _Exit(); +[[noreturn]] void foobar(); + +// CHECK-LABEL: define {{.*}} i32 @call_exit() +int call_exit() { + // CHECK: entry: + // CHECK: call void @exit() + // CHECK: unreachable + defer x(1); + exit(); +} + +// CHECK-LABEL: define {{.*}} i32 @call__Exit() +int call__Exit() { + // CHECK: entry: + // CHECK: call void @_Exit() + // CHECK: unreachable + defer x(1); + _Exit(); +} + +// CHECK-LABEL: define {{.*}} i32 @call_foobar() +int call_foobar() { + // CHECK: entry: + // CHECK: call void @foobar() + // CHECK: unreachable + defer x(1); + foobar(); +} + +// CHECK-LABEL: define {{.*}} i32 @main() +int main() { + // CHECK: entry: + // CHECK: %retval = alloca i32, align 4 + // CHECK: store i32 0, ptr %retval, align 4 + // CHECK: store i32 5, ptr %retval, align 4 + // CHECK: call void @x(i32 {{.*}} 42) + // CHECK: %0 = load i32, ptr %retval, align 4 + // CHECK: ret i32 %0 + defer x(42); + return 5; +} diff --git a/clang/test/Lexer/defer-keyword.cpp b/clang/test/Lexer/defer-keyword.cpp new file mode 100644 index 0000000000000..1c90519bd8911 --- /dev/null +++ b/clang/test/Lexer/defer-keyword.cpp @@ -0,0 +1,5 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s +// RUN: %clang_cc1 -fsyntax-only -verify -fdefer-ts %s + +// expected-no-diagnostics +int defer; diff --git a/clang/test/Parser/defer-ts.c b/clang/test/Parser/defer-ts.c new file mode 100644 index 0000000000000..57d84e4ecff7a --- /dev/null +++ b/clang/test/Parser/defer-ts.c @@ -0,0 +1,56 @@ +// RUN: %clang_cc1 -std=c11 -fsyntax-only -fdefer-ts -verify %s +// RUN: %clang_cc1 -std=c23 -fsyntax-only -fdefer-ts -verify %s + +int g(void); +int h(int x); + +void f1(void) { + defer 1; // expected-warning {{expression result unused}} + defer 1 + 1; // expected-warning {{expression result unused}} + defer "a"; // expected-warning {{expression result unused}} + defer "a" "b" "c"; // expected-warning {{expression result unused}} + defer defer 1; // expected-warning {{expression result unused}} + defer defer defer defer 1; // expected-warning {{expression result unused}} + defer (int) 4; // expected-warning {{expression result unused}} + defer g(); + + defer {} + defer { defer {} } + defer { defer {} defer {} } + + defer if (g()) g(); + defer while (g()) g(); + defer for (int i = 0; i < 10; i++) h(i); + defer switch (g()) { case 1: g(); } + + defer; // expected-warning {{defer statement has empty body}} expected-note {{put the semicolon on a separate line}} + defer + ; + + defer a: g(); // expected-error {{substatement of 'defer' must not be a label}} + defer b: {} // expected-error {{substatement of 'defer' must not be a label}} + defer { c: g(); } + + if (g()) defer g(); + while (g()) defer g(); + defer ({}); + ({ defer g(); }); + + defer int x; // expected-error {{expected expression}} + defer void q() {} // expected-error {{expected expression}} +} + +void f2(void) { + [[some, attributes]] defer g(); // expected-warning 2 {{unknown attribute}} + __attribute__((some_attribute)) defer g(); // expected-warning {{unknown attribute}} + [[some, attributes]] defer { g(); } // expected-warning 2 {{unknown attribute}} + __attribute__((some_attribute)) defer { g(); } // expected-warning {{unknown attribute}} +} + +void f3(void) { + _Defer 1; // expected-warning {{expression result unused}} + _Defer {} + _Defer _Defer {} + _Defer { defer {} _Defer {} } + _Defer if (g()) g(); +} diff --git a/clang/test/Parser/defer-ts.cpp b/clang/test/Parser/defer-ts.cpp new file mode 100644 index 0000000000000..2d72c5d940ae3 --- /dev/null +++ b/clang/test/Parser/defer-ts.cpp @@ -0,0 +1,5 @@ +// RUN: %clang_cc1 -std=c++20 -fsyntax-only -fdefer-ts -verify %s + +void f() { + defer {} // expected-error {{use of undeclared identifier 'defer'}} +} diff --git a/clang/test/Preprocessor/defer-ts.c b/clang/test/Preprocessor/defer-ts.c new file mode 100644 index 0000000000000..98b654b7707fa --- /dev/null +++ b/clang/test/Preprocessor/defer-ts.c @@ -0,0 +1,9 @@ +// RUN: %clang_cc1 -fsyntax-only -fdefer-ts -verify=enabled %s +// RUN: %clang_cc1 -fsyntax-only -verify=disabled %s +// RUN: %clang_cc1 -x c++ -fsyntax-only -fdefer-ts -verify=disabled %s +// RUN: %clang_cc1 -x c++ -fsyntax-only -verify=disabled %s +// enabled-no-diagnostics +#if __STDC_DEFER_TS25755__ != 1 +// disabled-error@+1 {{Should have defined __STDC_DEFER_TS25755__}} +# error Should have defined __STDC_DEFER_TS25755__ +#endif diff --git a/clang/test/Sema/defer-ts-seh.c b/clang/test/Sema/defer-ts-seh.c new file mode 100644 index 0000000000000..015306ca107d7 --- /dev/null +++ b/clang/test/Sema/defer-ts-seh.c @@ -0,0 +1,17 @@ +// RUN: %clang_cc1 -std=c23 -fdefer-ts -fms-compatibility -triple x86_64-windows-msvc -fsyntax-only -verify %s + +void f() { + __try { + defer { + __leave; // expected-error {{cannot __leave a defer statement}} + } + } __finally {} + + __try { + defer { + __try { + __leave; + } __finally {} + } + } __finally {} +} diff --git a/clang/test/Sema/defer-ts-sjlj.c b/clang/test/Sema/defer-ts-sjlj.c new file mode 100644 index 0000000000000..2b642696e21b2 --- /dev/null +++ b/clang/test/Sema/defer-ts-sjlj.c @@ -0,0 +1,52 @@ +// RUN: %clang_cc1 -triple x86_64-windows-msvc -std=gnu23 -fdefer-ts -fsyntax-only -fblocks -verify %s + +typedef void** jmp_buf; +typedef void** sigjmp_buf; + +int setjmp(jmp_buf env); +int _setjmp(jmp_buf env); +int sigsetjmp(sigjmp_buf env, int savesigs); +int __sigsetjmp(sigjmp_buf env, int savesigs); +void longjmp(jmp_buf env, int val); +void _longjmp(jmp_buf env, int val); +void siglongjmp(sigjmp_buf env, int val); + +jmp_buf x; +sigjmp_buf y; +void f() { + defer { + __builtin_setjmp(x); // expected-error {{cannot use '__builtin_setjmp' inside a defer statement}} + __builtin_longjmp(x, 1); // expected-error {{cannot use '__builtin_longjmp' inside a defer statement}} + setjmp(x); // expected-error {{cannot use 'setjmp' inside a defer statement}} + _setjmp(x); // expected-error {{cannot use '_setjmp' inside a defer statement}} + sigsetjmp(y, 0); // expected-error {{cannot use 'sigsetjmp' inside a defer statement}} + __sigsetjmp(y, 0); // expected-error {{cannot use '__sigsetjmp' inside a defer statement}} + longjmp(x, 0); // expected-error {{cannot use 'longjmp' inside a defer statement}} + _longjmp(x, 0); // expected-error {{cannot use '_longjmp' inside a defer statement}} + siglongjmp(y, 0); // expected-error {{cannot use 'siglongjmp' inside a defer statement}} + + (void) ^{ + __builtin_setjmp(x); + __builtin_longjmp(x, 1); + setjmp(x); + _setjmp(x); + sigsetjmp(y, 0); + __sigsetjmp(y, 0); + longjmp(x, 0); + _longjmp(x, 0); + siglongjmp(y, 0); + + defer { + __builtin_setjmp(x); // expected-error {{cannot use '__builtin_setjmp' inside a defer statement}} + __builtin_longjmp(x, 1); // expected-error {{cannot use '__builtin_longjmp' inside a defer statement}} + setjmp(x); // expected-error {{cannot use 'setjmp' inside a defer statement}} + _setjmp(x); // expected-error {{cannot use '_setjmp' inside a defer statement}} + sigsetjmp(y, 0); // expected-error {{cannot use 'sigsetjmp' inside a defer statement}} + __sigsetjmp(y, 0); // expected-error {{cannot use '__sigsetjmp' inside a defer statement}} + longjmp(x, 0); // expected-error {{cannot use 'longjmp' inside a defer statement}} + _longjmp(x, 0); // expected-error {{cannot use '_longjmp' inside a defer statement}} + siglongjmp(y, 0); // expected-error {{cannot use 'siglongjmp' inside a defer statement}} + } + }; + } +} diff --git a/clang/test/Sema/defer-ts.c b/clang/test/Sema/defer-ts.c new file mode 100644 index 0000000000000..0a7623e2d9b21 --- /dev/null +++ b/clang/test/Sema/defer-ts.c @@ -0,0 +1,170 @@ +// RUN: %clang_cc1 -std=c23 -fdefer-ts -fsyntax-only -verify %s + +void a(); + +void f1() { + defer { + goto l1; + l1: + } + + defer { + l2: + goto l2; + } +} + +void f2() { + goto l1; // expected-error {{cannot jump from this goto statement to its label}} + defer { // expected-note {{jump enters a defer statement}} + l1: + } + + goto l2; // expected-error {{cannot jump from this goto statement to its label}} + defer {} // expected-note {{jump bypasses defer statement}} + l2: +} + +void f3() { + x: + defer { // expected-note {{jump exits a defer statement}} + goto x; // expected-error {{cannot jump from this goto statement to its label}} + } +} + +void f4() { + defer { // expected-note {{jump exits a defer statement}} + goto y; // expected-error {{cannot jump from this goto statement to its label}} + } + y: +} + +void f5() { + defer { // expected-note {{jump enters a defer statement}} + l2: + } + goto l2; // expected-error {{cannot jump from this goto statement to its label}} +} + +void f6() { + goto b; // expected-error {{cannot jump from this goto statement to its label}} + { + defer {} // expected-note {{jump bypasses defer statement}} + b: + } + + { + defer {} // expected-note {{jump bypasses defer statement}} + b2: + } + goto b2; // expected-error {{cannot jump from this goto statement to its label}} +} + +void f7() { + defer { // expected-note {{jump bypasses defer statement}} + goto cross1; // expected-error {{cannot jump from this goto statement to its label}} + cross2: + } + defer { // expected-note {{jump exits a defer statement}} expected-note {{jump enters a defer statement}} + goto cross2; // expected-error {{cannot jump from this goto statement to its label}} + cross1: + } +} + +void f8() { + defer { + return; // expected-error {{cannot return from a defer statement}} + } + + { + defer { + return; // expected-error {{cannot return from a defer statement}} + } + } + + switch (1) { + case 1: defer { + break; // expected-error {{cannot break out of a defer statement}} + } + } + + for (;;) { + defer { + break; // expected-error {{cannot break out of a defer statement}} + } + } + + for (;;) { + defer { + continue; // expected-error {{cannot continue loop outside of enclosing defer statement}} + } + } + + switch (1) { + defer {} // expected-note {{jump bypasses defer statement}} + default: // expected-error {{cannot jump from switch statement to this case label}} + defer {} + break; + } + + switch (1) { + case 1: { + defer { // expected-note {{jump enters a defer statement}} + case 2: {} // expected-error {{cannot jump from switch statement to this case label}} + } + } + } + + switch (1) { + case 1: defer { + switch (2) { case 2: break; } + } + } + + for (;;) { + defer { for (;;) break; } + } + + for (;;) { + defer { for (;;) continue; } + } +} + +void f9() { + { + defer {} + goto l1; + } + l1: + + { + goto l2; + defer {} + } + l2: + + { + { defer {} } + goto l3; + } + l3: + + { + defer {} + { goto l4; } + } + l4: +} + +void f10(int i) { + switch (i) { + defer case 12: break; // expected-error {{cannot break out of a defer statement}} \ + expected-error {{cannot jump from switch statement to this case label}} \ + expected-note {{jump enters a defer statement}} \ + expected-note {{jump bypasses defer statement}} + + defer default: break; // expected-error {{cannot break out of a defer statement}} \ + expected-error {{cannot jump from switch statement to this case label}} \ + expected-note {{jump enters a defer statement}} + } +} diff --git a/clang/tools/libclang/CXCursor.cpp b/clang/tools/libclang/CXCursor.cpp index 3c4062410eac1..774495c3f60ce 100644 --- a/clang/tools/libclang/CXCursor.cpp +++ b/clang/tools/libclang/CXCursor.cpp @@ -224,6 +224,11 @@ CXCursor cxcursor::MakeCXCursor(const Stmt *S, const Decl *Parent, K = CXCursor_ReturnStmt; break; + // Not exposed for now because 'defer' is currently just a TS. + case Stmt::DeferStmtClass: + K = CXCursor_UnexposedStmt; + break; + case Stmt::GCCAsmStmtClass: K = CXCursor_GCCAsmStmt; break;