based on the technical specification n3589[1] deferred statements execute at the end of the scope they're added on, similar to the cleanup attribute, and they have much of the same constraints as statement expressions, with added limitation that no local jump shall jump over a defer statement, nor shall return, break, and continue, jump out of one
1: https://open-std.org/JTC1/SC22/WG14/www/docs/n3589.pdf gcc/c-family/ChangeLog: * c-common.cc: Add defer keyword. * c-common.h (enum rid): Add RID_DEFER. * c-cppbuiltin.cc (c_cpp_builtins): Set __STDC_DEFER_TS25755__. gcc/c/ChangeLog: * c-decl.cc (struct c_spot_bindings): Add defer_blocks. (struct c_scope): Add has_defer_block flag. (set_spot_bindings): Clear defer_blocks. (c_binding_adjust_jump_barrier): Adjust for defer. (c_bindings_start_stmt_expr): Rename to c_bindings_start_jump_barrier. (c_bindings_start_jump_barrier): Adjust for defer. (c_bindings_end_stmt_expr): Rename to c_bindings_end_jump_barrier. (c_bindings_end_jump_barrier): Adjust for defer. (lookup_label_for_goto): Check defer contraints. (check_earlier_gotos): Likewise. (c_release_switch_bindings): Likewise. (c_check_switch_jump_warnings): Likewise. * c-parser.cc (c_parser_defer_statement): New function. (c_parser_statement_after_labels): Check defer contraints and parse defer. * c-tree.h (IN_DEFER_STMT): Add variant. (c_bindings_start_stmt_expr): Rename to c_bindings_start_jump_barrier. (c_bindings_end_stmt_expr): Rename to c_bindings_end_jump_barrier. (c_bindings_start_jump_barrier): New function. (c_bindings_end_jump_barrier): New function. (c_begin_defer_block): New function. (c_end_defer_block): New function. * c-typeck.cc (c_finish_return): Adjust. (c_finish_bc_stmt): Adjust. (c_begin_stmt_expr): Likewise. (c_finish_stmt_expr): Likewise. (c_begin_defer_block): New function. (c_end_defer_block): New function. gcc/testsuite/ChangeLog: * gcc.dg/defer-1.c: New test. * gcc.dg/defer-2.c: New test. * gcc.dg/defer-3.c: New test. Signed-off-by: Anna (navi) Figueiredo Gomes <n...@vlhl.dev> --- gcc/c-family/c-common.cc | 1 + gcc/c-family/c-common.h | 10 +- gcc/c-family/c-cppbuiltin.cc | 3 + gcc/c/c-decl.cc | 87 +++++++++++-- gcc/c/c-parser.cc | 39 ++++++ gcc/c/c-tree.h | 8 +- gcc/c/c-typeck.cc | 46 +++++-- gcc/testsuite/gcc.dg/defer-1.c | 224 +++++++++++++++++++++++++++++++++ gcc/testsuite/gcc.dg/defer-2.c | 83 ++++++++++++ gcc/testsuite/gcc.dg/defer-3.c | 15 +++ 10 files changed, 490 insertions(+), 26 deletions(-) create mode 100644 gcc/testsuite/gcc.dg/defer-1.c create mode 100644 gcc/testsuite/gcc.dg/defer-2.c create mode 100644 gcc/testsuite/gcc.dg/defer-3.c diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc index e7dd4602ac1..e265b8eaf60 100644 --- a/gcc/c-family/c-common.cc +++ b/gcc/c-family/c-common.cc @@ -518,6 +518,7 @@ const struct c_common_resword c_common_reswords[] = { "continue", RID_CONTINUE, 0 }, { "decltype", RID_DECLTYPE, D_CXXONLY | D_CXX11 | D_CXXWARN }, { "default", RID_DEFAULT, 0 }, + { "defer", RID_DEFER, D_C2Y | D_CONLY }, { "delete", RID_DELETE, D_CXXONLY | D_CXXWARN }, { "do", RID_DO, 0 }, { "double", RID_DOUBLE, 0 }, diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h index 5c0fddd5221..a808c73cd5b 100644 --- a/gcc/c-family/c-common.h +++ b/gcc/c-family/c-common.h @@ -97,11 +97,11 @@ enum rid RID_IMAGINARY, /* C */ - RID_INT, RID_CHAR, RID_FLOAT, RID_DOUBLE, RID_VOID, - RID_ENUM, RID_STRUCT, RID_UNION, RID_IF, RID_ELSE, - RID_WHILE, RID_DO, RID_FOR, RID_SWITCH, RID_CASE, - RID_DEFAULT, RID_BREAK, RID_CONTINUE, RID_RETURN, RID_GOTO, - RID_SIZEOF, RID_BITINT, + RID_INT, RID_CHAR, RID_FLOAT, RID_DOUBLE, RID_VOID, + RID_ENUM, RID_STRUCT, RID_UNION, RID_IF, RID_ELSE, + RID_WHILE, RID_DO, RID_FOR, RID_SWITCH, RID_CASE, + RID_DEFAULT, RID_DEFER, RID_BREAK, RID_CONTINUE, RID_RETURN, + RID_GOTO, RID_SIZEOF, RID_BITINT, /* C extensions */ RID_ASM, RID_TYPEOF, RID_TYPEOF_UNQUAL, RID_ALIGNOF, RID_ATTRIBUTE, diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc index 4aea9028863..493a167f95d 100644 --- a/gcc/c-family/c-cppbuiltin.cc +++ b/gcc/c-family/c-cppbuiltin.cc @@ -1596,6 +1596,9 @@ c_cpp_builtins (cpp_reader *pfile) if (flag_iso) cpp_define (pfile, "__STRICT_ANSI__"); + if (flag_isoc2y) + builtin_define_with_int_value ("__STDC_DEFER_TS25755__", 1); + if (!flag_signed_char) cpp_define (pfile, "__CHAR_UNSIGNED__"); diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc index 56ca3078226..7f0a7ef7d72 100644 --- a/gcc/c/c-decl.cc +++ b/gcc/c/c-decl.cc @@ -397,7 +397,7 @@ struct GTY(()) c_spot_bindings { of the label or goto. This lets us look at older or newer bindings in the scope, as appropriate. */ struct c_binding *bindings_in_scope; - struct c_jump_barrier stmt_exprs; + struct c_jump_barrier stmt_exprs, defer_blocks; }; /* This structure is used to keep track of bindings seen when a goto @@ -533,6 +533,10 @@ struct GTY((chain_next ("%h.outer"))) c_scope { decl_jump_unsafe would return true for any of the bindings. This is used to avoid looping over all the bindings unnecessarily. */ BOOL_BITFIELD has_jump_unsafe_decl : 1; + + /* True if this scope has any deferred statement. This is used to + * check invalid goto jumps. */ + BOOL_BITFIELD has_defer_block : 1; }; /* The scope currently in effect. */ @@ -986,6 +990,8 @@ set_spot_bindings (struct c_spot_bindings *p, bool defining) } p->stmt_exprs.count = 0; p->stmt_exprs.left = false; + p->defer_blocks.count = 0; + p->defer_blocks.left = false; } /* Update spot bindings P as we pop out of SCOPE. Return true if we @@ -1605,9 +1611,12 @@ finish_underspecified_init (tree name, unsigned int prev_state) /* Adjust the bindings for the start of a statement expression. */ static void -c_binding_adjust_jump_barrier (struct c_spot_bindings *binding, bool start) +c_binding_adjust_jump_barrier (struct c_spot_bindings *binding, + bool defer, bool start) { - struct c_jump_barrier *barrier = &binding->stmt_exprs; + struct c_jump_barrier *barrier = defer + ? &binding->defer_blocks + : &binding->stmt_exprs; barrier->count += start ? 1 : -1; if (barrier->count < 0) { @@ -1617,10 +1626,13 @@ c_binding_adjust_jump_barrier (struct c_spot_bindings *binding, bool start) } void -c_bindings_start_stmt_expr (struct c_spot_bindings* switch_bindings) +c_bindings_start_jump_barrier (bool defer, struct c_spot_bindings* switch_bindings) { struct c_scope *scope; + if (defer) + current_scope->has_defer_block = true; + for (scope = current_scope; scope != NULL; scope = scope->outer) { struct c_binding *b; @@ -1638,20 +1650,24 @@ c_bindings_start_stmt_expr (struct c_spot_bindings* switch_bindings) continue; label_vars = b->u.label; - c_binding_adjust_jump_barrier (&label_vars->label_bindings, true); + c_binding_adjust_jump_barrier (&label_vars->label_bindings, defer, true); FOR_EACH_VEC_SAFE_ELT (label_vars->gotos, ix, g) - c_binding_adjust_jump_barrier (&g->goto_bindings, true); + c_binding_adjust_jump_barrier (&g->goto_bindings, defer, true); } } if (switch_bindings != NULL) - c_binding_adjust_jump_barrier (switch_bindings, true); + { + if (defer) + switch_bindings->scope->has_defer_block = true; + c_binding_adjust_jump_barrier (switch_bindings, defer, true); + } } /* Adjust the bindings for the end of a statement expression. */ void -c_bindings_end_stmt_expr (struct c_spot_bindings *switch_bindings) +c_bindings_end_jump_barrier (bool defer, struct c_spot_bindings *switch_bindings) { struct c_scope *scope; @@ -1671,9 +1687,9 @@ c_bindings_end_stmt_expr (struct c_spot_bindings *switch_bindings) if (TREE_CODE (b->decl) != LABEL_DECL) continue; label_vars = b->u.label; - c_binding_adjust_jump_barrier (&label_vars->label_bindings, false); + c_binding_adjust_jump_barrier (&label_vars->label_bindings, defer, false); FOR_EACH_VEC_SAFE_ELT (label_vars->gotos, ix, g) - c_binding_adjust_jump_barrier (&g->goto_bindings, false); + c_binding_adjust_jump_barrier (&g->goto_bindings, defer, false); } } @@ -1681,6 +1697,7 @@ c_bindings_end_stmt_expr (struct c_spot_bindings *switch_bindings) { c_binding_adjust_jump_barrier (switch_bindings, defer, false); gcc_assert (switch_bindings->stmt_exprs.count >= 0); + gcc_assert (switch_bindings->defer_blocks.count >= 0); } } @@ -4158,6 +4175,19 @@ lookup_label_for_goto (location_t loc, tree name) inform (DECL_SOURCE_LOCATION (label), "label %qD defined here", label); } + if (label_vars->label_bindings.defer_blocks.left) + { + auto_diagnostic_group d; + error_at (loc, "jump out of defer block"); + inform (DECL_SOURCE_LOCATION (label), "label %qD defined here", label); + } + else if (label_vars->label_bindings.scope->has_defer_block) + { + auto_diagnostic_group d; + error_at (loc, "jump over defer block"); + inform (DECL_SOURCE_LOCATION (label), "label %qD defined here", label); + } + return label; } @@ -4244,10 +4274,29 @@ check_earlier_gotos (tree label, struct c_label_vars* label_vars) if (g->goto_bindings.stmt_exprs.count > 0) { - auto_diagnostic_group d; error_at (g->loc, "jump into statement expression"); inform (DECL_SOURCE_LOCATION (label), "label %qD defined here", - label); + label); + } + + if (g->goto_bindings.defer_blocks.count > 0) + { + error_at (g->loc, "jump into defer block"); + inform (DECL_SOURCE_LOCATION (label), "label %qD defined here", + label); + } + else if (g->goto_bindings.defer_blocks.left) + { + error_at (g->loc, "jump out of defer block"); + inform (DECL_SOURCE_LOCATION (label), "label %qD defined here", + label); + } + else if (label_vars->label_bindings.scope->has_defer_block + || g->goto_bindings.scope->has_defer_block) + { + error_at (g->loc, "jump over defer block"); + inform (DECL_SOURCE_LOCATION (label), "label %qD defined here", + label); } } @@ -4334,6 +4383,7 @@ void c_release_switch_bindings (struct c_spot_bindings *bindings) { gcc_assert (bindings->stmt_exprs.count == 0 && !bindings->stmt_exprs.left); + gcc_assert (bindings->defer_blocks.count == 0 && !bindings->defer_blocks.left); XDELETE (bindings); } @@ -4405,6 +4455,19 @@ c_check_switch_jump_warnings (struct c_spot_bindings *switch_bindings, inform (switch_loc, "switch starts here"); } + if (switch_bindings->defer_blocks.count > 0) + { + saw_error = true; + error_at (case_loc, "switch jumps into defer block"); + inform (switch_loc, "switch starts here"); + } + else if (switch_bindings->scope->has_defer_block) + { + saw_error = true; + error_at (case_loc, "switch jumps over defer block"); + inform (switch_loc, "switch starts here"); + } + return saw_error; } diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc index 1907293bc68..ffdfa9a6f26 100644 --- a/gcc/c/c-parser.cc +++ b/gcc/c/c-parser.cc @@ -1765,6 +1765,7 @@ static void c_parser_statement_after_labels (c_parser *, bool *, tree, static tree c_parser_c99_block_statement (c_parser *, bool *, location_t * = NULL); static void c_parser_if_statement (c_parser *, bool *, vec<tree> *); +static void c_parser_defer_statement (c_parser *parser, bool *if_p, tree before_labels); static void c_parser_switch_statement (c_parser *, bool *, tree); static void c_parser_while_statement (c_parser *, bool, unsigned short, bool, bool *, tree); @@ -8230,6 +8231,9 @@ c_parser_statement_after_labels (c_parser *parser, bool *if_p, case RID_FOR: c_parser_for_statement (parser, false, 0, false, if_p, before_labels); break; + case RID_DEFER: + c_parser_defer_statement (parser, if_p, before_labels); + break; case RID_GOTO: c_parser_consume_token (parser); if (c_parser_next_token_is (parser, CPP_NAME)) @@ -8756,6 +8760,41 @@ c_parser_if_statement (c_parser *parser, bool *if_p, vec<tree> *chain) c_parser_maybe_reclassify_token (parser); } +/* Parse a defer statement (ISO/DIS TS 25755 6.4). + + defer-statement: + defer deferred-block */ + +static void +c_parser_defer_statement (c_parser *parser, bool *if_p, tree before_labels) +{ + location_t loc = c_parser_peek_token (parser)->location; + unsigned char save_in_statement = in_statement; + tree deferred; + + gcc_assert (c_parser_next_token_is_keyword (parser, RID_DEFER)); + c_parser_consume_token (parser); + + if (c_parser_next_token_is (parser, CPP_SEMICOLON)) + { + c_end_defer_block (loc, c_begin_defer_block ()); + add_stmt (build_empty_stmt (loc)); + c_parser_consume_token (parser); + warning_at (loc, OPT_Wempty_body, + "suggest braces around empty body in %<defer%> statement"); + return; + } + + deferred = c_begin_defer_block (); + + in_statement = IN_DEFER_STMT; + c_parser_statement_after_labels (parser, if_p, before_labels); + in_statement = save_in_statement; + + deferred = c_end_defer_block (loc, deferred); + push_cleanup(deferred, deferred, false); +} + /* Parse a switch statement (C90 6.6.4, C99 6.8.4, C11 6.8.4). switch-statement: diff --git a/gcc/c/c-tree.h b/gcc/c/c-tree.h index bb0b113754e..7ab9d009e76 100644 --- a/gcc/c/c-tree.h +++ b/gcc/c/c-tree.h @@ -643,6 +643,7 @@ extern struct obstack parser_obstack; #define IN_OMP_FOR 8 #define IN_OBJC_FOREACH 16 #define IN_NAMED_STMT 32 +#define IN_DEFER_STMT 64 extern unsigned char in_statement; extern bool switch_statement_break_seen_p; @@ -654,8 +655,8 @@ extern void finish_underspecified_init (tree, unsigned int); extern void push_scope (void); extern tree pop_scope (void); extern void c_mark_decl_jump_unsafe_in_current_scope (); -extern void c_bindings_start_stmt_expr (struct c_spot_bindings *); -extern void c_bindings_end_stmt_expr (struct c_spot_bindings *); +extern void c_bindings_start_jump_barrier (bool, struct c_spot_bindings *); +extern void c_bindings_end_jump_barrier (bool, struct c_spot_bindings *); extern void record_inline_static (location_t, tree, tree, enum c_inline_static_type); @@ -869,6 +870,9 @@ extern tree c_end_compound_stmt (location_t, tree, bool); extern void c_finish_if_stmt (location_t, tree, tree, tree); extern void c_finish_loop (location_t, location_t, tree, location_t, tree, tree, tree, tree, bool); + +extern tree c_begin_defer_block (void); +extern tree c_end_defer_block (location_t loc, tree body); extern tree c_begin_stmt_expr (void); extern tree c_finish_stmt_expr (location_t, tree); extern tree c_process_expr_stmt (location_t, tree); diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc index f26184e5603..7a939a823e1 100644 --- a/gcc/c/c-typeck.cc +++ b/gcc/c/c-typeck.cc @@ -12871,6 +12871,12 @@ c_finish_return (location_t loc, tree retval, tree origtype, bool musttail_p) warning_at (xloc, 0, "function declared %<noreturn%> has a %<return%> statement"); + if (in_statement & IN_DEFER_STMT) + { + error_at (loc, "return from defer block"); + return NULL_TREE; + } + set_musttail_on_return (retval, xloc, musttail_p); if (retval) @@ -13252,6 +13258,9 @@ c_finish_bc_stmt (location_t loc, tree label, bool is_break, tree name) case IN_OMP_FOR: error_at (loc, "break statement used with OpenMP for loop"); return NULL_TREE; + case IN_DEFER_STMT: + error_at (loc, "break statement jumps out of defer block"); + return NULL_TREE; case IN_ITERATION_STMT: case IN_OBJC_FOREACH: break; @@ -13269,6 +13278,9 @@ c_finish_bc_stmt (location_t loc, tree label, bool is_break, tree name) case IN_OMP_BLOCK: error_at (loc, "invalid exit from OpenMP structured block"); return NULL_TREE; + case IN_DEFER_STMT: + error_at (loc, "continue statement jumps out of defer block"); + return NULL_TREE; case IN_ITERATION_STMT: case IN_OMP_FOR: case IN_OBJC_FOREACH: @@ -13414,9 +13426,9 @@ c_begin_stmt_expr (void) keep_next_level (); ret = c_begin_compound_stmt (true); - c_bindings_start_stmt_expr (c_switch_stack == NULL - ? NULL - : c_switch_stack->bindings); + c_bindings_start_jump_barrier (false, c_switch_stack == NULL + ? NULL + : c_switch_stack->bindings); /* Mark the current statement list as belonging to a statement list. */ STATEMENT_LIST_STMT_EXPR (ret) = 1; @@ -13435,9 +13447,9 @@ c_finish_stmt_expr (location_t loc, tree body) body = c_end_compound_stmt (loc, body, true); - c_bindings_end_stmt_expr (c_switch_stack == NULL - ? NULL - : c_switch_stack->bindings); + c_bindings_end_jump_barrier (false, c_switch_stack == NULL + ? NULL + : c_switch_stack->bindings); /* Locate the last statement in BODY. See c_end_compound_stmt about always returning a BIND_EXPR. */ @@ -13532,7 +13544,27 @@ c_finish_stmt_expr (location_t loc, tree body) return t; } } - + +tree +c_begin_defer_block (void) +{ + c_bindings_start_jump_barrier (true, c_switch_stack == NULL + ? NULL + : c_switch_stack->bindings); + + return c_begin_compound_stmt (true); +} + +tree +c_end_defer_block (location_t loc, tree body) +{ + c_bindings_end_jump_barrier (true, c_switch_stack == NULL + ? NULL + : c_switch_stack->bindings); + + return c_end_compound_stmt (loc, body, true); +} + /* Begin and end compound statements. This is as simple as pushing and popping new statement lists from the tree. */ diff --git a/gcc/testsuite/gcc.dg/defer-1.c b/gcc/testsuite/gcc.dg/defer-1.c new file mode 100644 index 00000000000..1cbf44d8d00 --- /dev/null +++ b/gcc/testsuite/gcc.dg/defer-1.c @@ -0,0 +1,224 @@ +/* { dg-do run } */ +/* { dg-options "-std=c2y" } */ + +#include <setjmp.h> + +#define assert(x) if (!(x)) __builtin_abort () + +#if __STDC_DEFER_TS25755__ != 1 +# error "missing defer flag" +#endif + +int a () +{ + int r = 5; + return r; + + defer r *= 2; +} + +int b () +{ + int r = 0; + goto b; + { + defer r += 2; + } +b: + r += 5; + return r; +} + +int c () +{ + int r = 0; + { + defer r += 2; + goto b; + } +b: + r += 5; + return r; +} + +int d () +{ + int r = 0; + goto b; + { +b: + defer r += 2; + } + r += 5; + return r; +} + +int e () +{ + int r = 0; + { + goto b; + defer r += 2; + } +b:; + r += 5; + return r; +} + +int f () +{ + int r = 4; + int *p = &r; + defer *p = 5; + return *p; +} + +int g () +{ + int r = 1; + { + defer r += 2; + if (true) + defer r *= 2; + r += 10; + } + return r; +} + +int h () +{ + int r = 1; + { + defer r += 2; + for (int i = 0; i < 5; i++) + defer r *= 2; + } + return r; +} + +int i () +{ + int r = 0; + { + defer { + defer r *= 4; + r *= 2; + defer { + r += 3; + } + } + defer r += 1; + } + + return r; +} + +int j () +{ + int r = 0; + { + defer if (0) r = 5; + } + return r; +} + +int global = 0; + +int k () +{ + global = 5; + defer global = 10; + return global; +} + +int l () +{ + int r = 5; + int j = 2; + + { + defer { + int j = 10; + r *= j; + } + } + + return r; +} + +int m () +{ + int r = 0; + + void clear(int *value) { + *value = 10; + } + + { + [[gnu::cleanup(clear)]] int j = 1; + defer r = j * 2; + } + return r; +} + +int n () +{ + jmp_buf env; + int r = 0; + + if (setjmp(env) != 0) + return r; + + defer r = 1; + + { + defer r = 2; + r++; + longjmp(env, 1); + } +} + +int o () +{ + int r = 0; + + int foo() { + r = 2; + defer r *= 2; + return r; + } + + return foo() + r; +} + +int p (int *arg) +{ + *arg = 2; + defer *arg *= 2; + return *arg; +} + + int +main () +{ + int arg; + + assert (a () == 5); + assert (b () == 5); + assert (c () == 7); + assert (d () == 7); + assert (e () == 5); + assert (f () == 4); + assert (g () == 14); + assert (h () == 34); + assert (i () == 20); + assert (j () == 0); + assert (k () == 5 && global == 10); + assert (l () == 50); + assert (m () == 2); + assert (n () == 1); + assert (o () == 6); + assert (p (&arg) == 2 && arg == 4); + + return 0; +} diff --git a/gcc/testsuite/gcc.dg/defer-2.c b/gcc/testsuite/gcc.dg/defer-2.c new file mode 100644 index 00000000000..0b5f14b6571 --- /dev/null +++ b/gcc/testsuite/gcc.dg/defer-2.c @@ -0,0 +1,83 @@ +/* { dg-do compile } */ +/* { dg-options "-std=c2y" } */ + +void a () +{ + goto b; /* { dg-error "jump over defer block" } */ + defer; +b: +} + +void b () { + defer { + goto b; /* { dg-error "jump out of defer block" } */ + } +b: +} + +int c () { + defer { + return 5; /* { dg-error "return from defer block" } */ + } + return 1; +} + +void d () { + defer { +b: + } + goto b; /* { dg-error "jump out of defer block" } */ +} + +void e () { + goto b; /* { dg-error "jump over defer block" } */ + { + defer; +b: + } +} + +void f () { + switch (1) { + defer; + default: /* { dg-error "switch jumps over defer block" } */ + defer; + break; + } +} + +void g () { + switch (1) { + default: + defer { + break; /* { dg-error "break statement jumps out of defer block" } */ + } + } + + for (;;) { + defer { + break; /* { dg-error "break statement jumps out of defer block" } */ + } + } + + for (;;) { + defer { + continue; /* { dg-error "continue statement jumps out of defer block" } */ + } + } +} + +int h () +{ + int r = 1; + { +b: + r++; + defer r *= 2; + if (r < 10) + goto b; /* { dg-error "jump over defer block" } */ + } + + return r; +} + diff --git a/gcc/testsuite/gcc.dg/defer-3.c b/gcc/testsuite/gcc.dg/defer-3.c new file mode 100644 index 00000000000..30d57a82883 --- /dev/null +++ b/gcc/testsuite/gcc.dg/defer-3.c @@ -0,0 +1,15 @@ +/* { dg-do run } */ +/* { dg-options "-std=c2y" } */ + +extern void exit(int); +extern void abort(void); + +int main(void) { + { + defer { + exit(0); + } + } + abort(); + return 1; +} -- 2.49.1