From 6c3f3f4d5c8b21dc5b0605ae8b57e3f2e5941b14 Mon Sep 17 00:00:00 2001 From: Ehab Maali <107338078+71iq@users.noreply.github.com> Date: Sat, 7 Nov 2026 23:15:45 +0200 Subject: [PATCH] Evaluate GNU C typeof operands of variably modified type [GH #516] The operand of a C typeof operator must be evaluated when it has a variably modified type, but it was treated as unevaluated, so its side effects were lost. When the IL is lowered, keep the operand (scanned as an unevaluated operand, as for sizeof) and evaluate it once: in a block-scope declaration, before the declarators; in an expression, where the cast, compound literal, __builtin_va_arg, or sizeof of a variable-length array type that uses the type is evaluated. A use of a typedef does not repeat the evaluation, and nothing is evaluated in a parameter declaration. The dimension expressions of the resulting type are evaluated wherever that type is used, so the operand is evaluated only if it has side effects of its own and those dimensions have none. A source-generating back end reproduces the typeof, so nothing is added for it. The operand is evaluated once per declaration, not once per declarator, as in GCC 17 (GCC PR c/124576); earlier GCC versions are not emulated in that respect. Add regression tests. gnu/c/pr114831-2, gnu/c/vla-15 and gnu/c/vla-16 now pass. --- src/expr.c | 137 ++++++++++++++++++ src/il_alloc.c | 1 + src/il_def.h | 6 + src/il_display.c | 3 + .../gh-516-typeof-evaluation.txt | 17 +++ .../edg_x86_32_cp/regressions.log | 1 + tests/expectations/edg_x86_64/imported.log | 3 - .../edg_x86_64_cp/regressions.log | 1 + .../.gh-516-params.rto/default.1.1.txt | 1 + .../.gh-516-params.rto/default.1.2.txt | 1 + .../.gh-516-single.rto/default.1.1.txt | 1 + .../.gh-516-single.rto/default.1.2.txt | 1 + .../regressions/.gh-516.rto/default.1.1.txt | 1 + .../regressions/.gh-516.rto/default.1.2.txt | 1 + tests/tests/regressions/gh-516-params.sft.c | 12 ++ tests/tests/regressions/gh-516-single.sft.c | 22 +++ tests/tests/regressions/gh-516.sft.c | 71 +++++++++ 17 files changed, 277 insertions(+), 3 deletions(-) create mode 100644 src/pending_changes/gh-516-typeof-evaluation.txt create mode 100644 tests/tests/regressions/.gh-516-params.rto/default.1.1.txt create mode 100644 tests/tests/regressions/.gh-516-params.rto/default.1.2.txt create mode 100644 tests/tests/regressions/.gh-516-single.rto/default.1.1.txt create mode 100644 tests/tests/regressions/.gh-516-single.rto/default.1.2.txt create mode 100644 tests/tests/regressions/.gh-516.rto/default.1.1.txt create mode 100644 tests/tests/regressions/.gh-516.rto/default.1.2.txt create mode 100644 tests/tests/regressions/gh-516-params.sft.c create mode 100644 tests/tests/regressions/gh-516-single.sft.c create mode 100644 tests/tests/regressions/gh-516.sft.c diff --git a/src/expr.c b/src/expr.c index 8986d35f0c..6921065bc9 100644 --- a/src/expr.c +++ b/src/expr.c @@ -67,6 +67,10 @@ static void scan_compound_literal(a_type_ptr *p_literal_type, an_init_component_ptr rescan_icp, an_operand *result, a_local_expr_options_set local_options); +#if DO_IL_LOWERING +static void evaluate_pending_typeof_operands(a_type_ptr tp, + an_operand *result); +#endif /* DO_IL_LOWERING */ static void scan_expr_full(an_operand *result, an_operand *bound_function_selector, int prec_level, @@ -4951,6 +4955,9 @@ been scanned: builtin_func represents the reference to the builtin function an_expr_node_ptr node; a_type_ptr type, type_to_cast_to = NULL; a_boolean err = FALSE; +#if DO_IL_LOWERING + a_type_ptr scanned_type; +#endif /* DO_IL_LOWERING */ db_enter(4, "scan_va_arg_operator"); /* va_arg not possible in preprocessing expressions. */ @@ -4989,6 +4996,9 @@ been scanned: builtin_func represents the reference to the builtin function /* Scan the type. */ type_position = pos_curr_token; type_name(&type); +#if DO_IL_LOWERING + scanned_type = type; +#endif /* DO_IL_LOWERING */ if (is_function_type(type) || is_array_type(type) || is_any_reference_type(type)) { @@ -5037,6 +5047,9 @@ been scanned: builtin_func represents the reference to the builtin function } /* if */ make_expression_operand(va_arg_node, result); } /* if */ +#if DO_IL_LOWERING + evaluate_pending_typeof_operands(scanned_type, result); +#endif /* DO_IL_LOWERING */ rule_out_expr_kinds(ROEK_CONSTANT, result); if (builtin_func == NULL) { #if EXTRA_SOURCE_POSITIONS_IN_IL @@ -15300,6 +15313,11 @@ scanned sizeof or __datasizeof expression, and return the result in *result &operand, result); operand_was_used = !is_type; nonconstant_case = TRUE; +#if DO_IL_LOWERING + if (is_type) { + evaluate_pending_typeof_operands(orig_sizeof_type, result); + } /* if */ +#endif /* DO_IL_LOWERING */ } /* if */ #if MICROSOFT_EXTENSIONS_ALLOWED } else if (cppcli_enabled && !template_case && @@ -19147,6 +19165,76 @@ called from outside of the expression-processing routines. } /* scan_type_returning_type_trait_operator */ +#if DO_IL_LOWERING +static an_expr_node_ptr take_typeof_operands(a_type_ptr tp) +/* +tp is a variably modified type used in an expression (e.g., as the type of a +cast). Return the operands of any C typeof operators in tp whose evaluation +is still pending (see typeof_operand_pending), combined with comma operators, +or NULL if there are none, and mark them as no longer pending. A typedef in +tp stops the search: a use of a typedef does not repeat the evaluation of the +typeof operands in its definition. +*/ +{ + an_expr_node_ptr inits = NULL; + + while (is_variably_modified_type(tp)) { + if (tp->kind == (a_type_kind)tk_typeref) { + if (typeref_is_typedef(tp)) break; + if (tp->variant.typeref.typeof_operand_pending) { + an_expr_node_ptr expr = decltype_arg(tp); + check_assertion(expr != NULL); + tp->variant.typeref.typeof_operand_pending = FALSE; + if (expr->is_lvalue) expr = add_address_of_to_node(expr); + inits = inits == NULL ? expr : make_comma_node(inits, expr); + } /* if */ + tp = tp->variant.typeref.type; + } else if (tp->kind == (a_type_kind)tk_pointer) { + tp = tp->variant.pointer.type; + } else if (tp->kind == (a_type_kind)tk_array) { + tp = tp->variant.array.element_type; + } else if (tp->kind == (a_type_kind)tk_routine) { + tp = tp->variant.routine.return_type; + } else { + break; + } /* if */ + } /* while */ + return inits; +} /* take_typeof_operands */ + + +static void evaluate_pending_typeof_operands(a_type_ptr tp, + an_operand *result) +/* +tp is a type used by an expression whose value is described by *result: +The type of a cast, a compound literal, a __builtin_va_arg, or a sizeof +whose operand has a variable-length array type. If tp contains C typeof +operators whose operands are still to be evaluated (see +typeof_operand_pending), change *result so that those operands are evaluated +first. An lvalue result remains an lvalue. +*/ +{ + if (C_mode() && !is_error_operand(result) && + is_variably_modified_type(tp)) { + an_expr_node_ptr type_inits = take_typeof_operands(tp); + if (type_inits != NULL) { + an_expr_node_ptr node = make_node_from_operand(result); + if (node->is_lvalue) { + /* Use "*(inits, &result)" to keep the lvalue. */ + node = add_indirection_to_node( + make_comma_node(type_inits, add_address_of_to_node(node))); + make_glvalue_expression_operand(node, result); + } else { + make_expression_operand(make_comma_node(type_inits, node), result); + } /* if */ + /* Preserve the execution point before conditional folding. */ + rule_out_expr_kinds(ROEK_INTEGRAL_CONSTANT, result); + } /* if */ + } /* if */ +} /* evaluate_pending_typeof_operands */ +#endif /* DO_IL_LOWERING */ + + a_type_ptr scan_typeof_operator(a_rescan_control_block *rcblock, ARG_UNUSED a_decl_pos_block *decl_pos_block) /* @@ -19193,6 +19281,10 @@ the expression-processing routines. an_expr_node_ptr saved_decltype_rescan_operand = NULL; a_boolean is_unqual_variant = curr_token == tok_typeof_unqual; +#if DO_IL_LOWERING + unsigned long saved_error_count = + diagnostic_counters.total.errors; +#endif /* DO_IL_LOWERING */ parens_optional = gpp_mode && gnu_version >= 30400; /* Note that, unlike e.g. sizeof, typeof can appear directly in a declarative @@ -19382,6 +19474,45 @@ the expression-processing routines. prep_generic_operand(&operand); } /* if */ expr = make_node_from_operand(&operand); +#if DO_IL_LOWERING + if (C_mode() && vla_enabled && rcblock == NULL && + is_variably_modified_type(result) && + diagnostic_counters.total.errors == saved_error_count && + node_has_side_effects(expr, (a_boolean *)NULL) && + !type_has_side_effects(result)) { + /* The operand has a variably modified type, so it is evaluated + (C23 6.7.3.6). As for sizeof, the expression scanned above (as + an unevaluated operand) is kept and evaluated at run time. This + is needed only when the IL is lowered: a back end that generates + source (e.g., the C++-generating back end) reproduces the typeof + itself, and the compiler of that source evaluates its operand. + The dimension expressions of the resulting type are evaluated + wherever that type is used, so the operand is evaluated only if + it has side effects of its own and those dimensions have none + (e.g., "typeof(++n, (int (*)[n])p)"); otherwise the evaluation + of a dimension expression would be repeated. */ + if (saved_expr_stack != NULL) { + /* Expression contexts defer evaluation to VLA lowering. */ + typeof_type->variant.typeref.typeof_operand_pending = TRUE; + } else if (depth_stmt_stack >= 0 && + !scope_is(&scope_stack_top(), sck_func_prototype)) { + /* A declaration in a block evaluates the type specifier once, + before any declarator bounds or initializers, even if the + declaration has several declarators. */ + a_statement_ptr stmt = add_statement_at_stmt_pos( + stmk_expr, + &operand.position, + /*compiler_generated=*/TRUE); + stmt->expr = expr; + set_expr_result_not_used(expr); + } else { + /* A parameter declaration (or a declaration outside of any + function, which is an error for a variably modified type). + No statement can be added here, so the operand is not + evaluated. */ + } /* if */ + } /* if */ +#endif /* DO_IL_LOWERING */ if (!dependent_arg) { /* Check for cases where the result type is not dependent but the expression is instantiation-dependent. */ @@ -30287,6 +30418,9 @@ indication in *rcblock). rescan_icp is not freed. expression (C99 says the lifetime extends to the end of the block). */ dip->has_temporary_lifetime = FALSE; make_glvalue_expression_operand(expr, result); +#if DO_IL_LOWERING + evaluate_pending_typeof_operands(literal_type, result); +#endif /* DO_IL_LOWERING */ } /* if */ record_cast_position_in_rescan_info(result, (an_expr_node_ptr)NULL, @@ -30473,6 +30607,9 @@ just an expression in parentheses. Return the scanned expression in &type_position, type_cast_to); } /* if */ +#if DO_IL_LOWERING + if (!err) evaluate_pending_typeof_operands(type_cast_to, result); +#endif /* DO_IL_LOWERING */ } /* if */ set_operand_position(result, &start_position, &end_position, &start_position); diff --git a/src/il_alloc.c b/src/il_alloc.c index 0d590dfa02..94024114dd 100644 --- a/src/il_alloc.c +++ b/src/il_alloc.c @@ -2157,6 +2157,7 @@ to default values. pte->variant.typeref.explicit_memory_attribute_made_implicit = FALSE; #endif /* NEAR_AND_FAR_ALLOWED */ pte->variant.typeref.has_variably_modified_type = FALSE; + pte->variant.typeref.typeof_operand_pending = FALSE; #if LOWER_VARIABLE_LENGTH_ARRAYS pte->variant.typeref.is_lowered_variably_modified_type = FALSE; #endif /* LOWER_VARIABLE_LENGTH_ARRAYS */ diff --git a/src/il_def.h b/src/il_def.h index 43470cfc6b..e02f9b3f38 100644 --- a/src/il_def.h +++ b/src/il_def.h @@ -10774,6 +10774,12 @@ typedef struct a_type { has_variably_modified_type:1; /* The type referred to is a variably modified type, i.e., is or contains a VLA type. */ + a_bit_field + typeof_operand_pending:1; + /* The typeof operand (in C) has a variably modified + type, so it must be evaluated where this type is + used in an expression (e.g., in a cast). Set to + FALSE once that evaluation has been emitted. */ #if LOWER_VARIABLE_LENGTH_ARRAYS a_bit_field is_lowered_variably_modified_type:1; diff --git a/src/il_display.c b/src/il_display.c index 9145ed239d..f5bc605151 100644 --- a/src/il_display.c +++ b/src/il_display.c @@ -2738,6 +2738,9 @@ Display the indicated type entry. if (ptr->variant.typeref.has_variably_modified_type) { disp_boolean("has_variably_modified_type", TRUE); } /* if */ + if (ptr->variant.typeref.typeof_operand_pending) { + disp_boolean("typeof_operand_pending", TRUE); + } /* if */ #if BACK_END_IS_CP_GEN_BE if (ptr->variant.typeref.surrounding_name_linkage_state != (a_name_linkage_kind)nlk_none) { diff --git a/src/pending_changes/gh-516-typeof-evaluation.txt b/src/pending_changes/gh-516-typeof-evaluation.txt new file mode 100644 index 0000000000..50600e43ae --- /dev/null +++ b/src/pending_changes/gh-516-typeof-evaluation.txt @@ -0,0 +1,17 @@ +[GH #516] +GNU C: typeof operands of variably modified type were not evaluated + +When the operand of a GNU C typeof (or C23 typeof) operator has a variably +modified type, the operand must be evaluated. The front end treated it as +unevaluated, so its side effects were lost. The operand is now evaluated +once where the declaration, cast, compound literal, va_arg, or sizeof of a +variable-length array type appears. As in GCC 17, it is evaluated once per +declaration rather than once per declarator, and it is not evaluated again +when a typedef for the type is used. If the type's array bounds also have +side effects, side effects in the operand outside those bounds can still be +lost. For example, with --gnu_version: + + int n = 1, a[8]; + void f(void) { + typeof (++n, (int (*)[n])a) p, q; // n is now 2. + } diff --git a/tests/expectations/edg_x86_32_cp/regressions.log b/tests/expectations/edg_x86_32_cp/regressions.log index abd2f80e25..8bc97c1c7c 100644 --- a/tests/expectations/edg_x86_32_cp/regressions.log +++ b/tests/expectations/edg_x86_32_cp/regressions.log @@ -3,6 +3,7 @@ gh-309-diag.sft.cpp:fp:-DTEST_NUMBER=1 --g++:REQUIREMENTS NOT MET:Diagnostic pos gh-309-diag.sft.cpp:fp:-DTEST_NUMBER=2 --g++ --no_exceptions:REQUIREMENTS NOT MET:Diagnostic position of implicit destructor calls at scope exit gh-309.sft.cpp:fp:-DTEST_NUMBER=1 --g++:REQUIREMENTS NOT MET: gh-309.sft.cpp:fp:-DTEST_NUMBER=2 --g++ --no_exceptions:REQUIREMENTS NOT MET: +gh-516.sft.c:rp:-DTEST_NUMBER=1:REQUIREMENTS NOT MET:GNU C typeof operand of variably modified type is evaluated immediate-pragma-lost.sft.cpp:cppbe:-DTEST_NUMBER=1:PASS: pragma-il-class-no-lowering.sft.cpp:fp:-DTEST_NUMBER=1:REQUIREMENTS NOT MET: pragma-il-class.sft.cpp:fp:-DTEST_NUMBER=1:REQUIREMENTS NOT MET: diff --git a/tests/expectations/edg_x86_64/imported.log b/tests/expectations/edg_x86_64/imported.log index 12ba47df3f..a575fade7c 100644 --- a/tests/expectations/edg_x86_64/imported.log +++ b/tests/expectations/edg_x86_64/imported.log @@ -3413,7 +3413,6 @@ gnu/c/pr113262.sft.c:fn:-DTEST_NUMBER=1:FAIL: gnu/c/pr113492.sft.c:fp:-DTEST_NUMBER=1 --c23:FAIL: gnu/c/pr113596.sft.c:rp:-DTEST_NUMBER=1:FAIL: gnu/c/pr114090.sft.c:rp:-DTEST_NUMBER=1:FAIL: -gnu/c/pr114831-2.sft.c:rp:-DTEST_NUMBER=1 --c23:FAIL: gnu/c/pr115109.sft.c:fp:-DTEST_NUMBER=1 --c23:FAIL: gnu/c/pr115426.sft.c:fp:-DTEST_NUMBER=1 --c11 --strict_gnu:FAIL: gnu/c/pr116034.sft.c:rp:-DTEST_NUMBER=1:FAIL(COMPILE): @@ -6095,8 +6094,6 @@ gnu/c/visibility-a.sft.c:s:-DTEST_NUMBER=1:SKIPPED: gnu/c/visibility-c.sft.c:s:-DTEST_NUMBER=1:SKIPPED: gnu/c/visibility-d.sft.c:s:-DTEST_NUMBER=1:SKIPPED: gnu/c/vla-13.sft.c:rp:-DTEST_NUMBER=1 --c99:FAIL: -gnu/c/vla-15.sft.c:rp:-DTEST_NUMBER=1 --c99 --strict_gnu:FAIL: -gnu/c/vla-16.sft.c:rp:-DTEST_NUMBER=1 --c99 --strict_gnu:FAIL: gnu/c/vla-18.sft.c:fn:-DTEST_NUMBER=1 --c99:FAIL: gnu/c/vla-2.sft.c:fp:-DTEST_NUMBER=1 --c99 --strict_gnu:FAIL: gnu/c/vla-21.sft.c:fp:-DTEST_NUMBER=1 --c99:FAIL: diff --git a/tests/expectations/edg_x86_64_cp/regressions.log b/tests/expectations/edg_x86_64_cp/regressions.log index abd2f80e25..8bc97c1c7c 100644 --- a/tests/expectations/edg_x86_64_cp/regressions.log +++ b/tests/expectations/edg_x86_64_cp/regressions.log @@ -3,6 +3,7 @@ gh-309-diag.sft.cpp:fp:-DTEST_NUMBER=1 --g++:REQUIREMENTS NOT MET:Diagnostic pos gh-309-diag.sft.cpp:fp:-DTEST_NUMBER=2 --g++ --no_exceptions:REQUIREMENTS NOT MET:Diagnostic position of implicit destructor calls at scope exit gh-309.sft.cpp:fp:-DTEST_NUMBER=1 --g++:REQUIREMENTS NOT MET: gh-309.sft.cpp:fp:-DTEST_NUMBER=2 --g++ --no_exceptions:REQUIREMENTS NOT MET: +gh-516.sft.c:rp:-DTEST_NUMBER=1:REQUIREMENTS NOT MET:GNU C typeof operand of variably modified type is evaluated immediate-pragma-lost.sft.cpp:cppbe:-DTEST_NUMBER=1:PASS: pragma-il-class-no-lowering.sft.cpp:fp:-DTEST_NUMBER=1:REQUIREMENTS NOT MET: pragma-il-class.sft.cpp:fp:-DTEST_NUMBER=1:REQUIREMENTS NOT MET: diff --git a/tests/tests/regressions/.gh-516-params.rto/default.1.1.txt b/tests/tests/regressions/.gh-516-params.rto/default.1.1.txt new file mode 100644 index 0000000000..5349d2a14c --- /dev/null +++ b/tests/tests/regressions/.gh-516-params.rto/default.1.1.txt @@ -0,0 +1 @@ +eccp -DTEST_NUMBER=1 --c --c11 --no_strict_gnu --gnu_version=130300 Test_name.c diff --git a/tests/tests/regressions/.gh-516-params.rto/default.1.2.txt b/tests/tests/regressions/.gh-516-params.rto/default.1.2.txt new file mode 100644 index 0000000000..ff2642c6d5 --- /dev/null +++ b/tests/tests/regressions/.gh-516-params.rto/default.1.2.txt @@ -0,0 +1 @@ +./a.out diff --git a/tests/tests/regressions/.gh-516-single.rto/default.1.1.txt b/tests/tests/regressions/.gh-516-single.rto/default.1.1.txt new file mode 100644 index 0000000000..5349d2a14c --- /dev/null +++ b/tests/tests/regressions/.gh-516-single.rto/default.1.1.txt @@ -0,0 +1 @@ +eccp -DTEST_NUMBER=1 --c --c11 --no_strict_gnu --gnu_version=130300 Test_name.c diff --git a/tests/tests/regressions/.gh-516-single.rto/default.1.2.txt b/tests/tests/regressions/.gh-516-single.rto/default.1.2.txt new file mode 100644 index 0000000000..ff2642c6d5 --- /dev/null +++ b/tests/tests/regressions/.gh-516-single.rto/default.1.2.txt @@ -0,0 +1 @@ +./a.out diff --git a/tests/tests/regressions/.gh-516.rto/default.1.1.txt b/tests/tests/regressions/.gh-516.rto/default.1.1.txt new file mode 100644 index 0000000000..ce85e782b6 --- /dev/null +++ b/tests/tests/regressions/.gh-516.rto/default.1.1.txt @@ -0,0 +1 @@ +eccp -DTEST_NUMBER=1 --c --c11 --no_strict_gnu --gnu_version=170000 Test_name.c diff --git a/tests/tests/regressions/.gh-516.rto/default.1.2.txt b/tests/tests/regressions/.gh-516.rto/default.1.2.txt new file mode 100644 index 0000000000..ff2642c6d5 --- /dev/null +++ b/tests/tests/regressions/.gh-516.rto/default.1.2.txt @@ -0,0 +1 @@ +./a.out diff --git a/tests/tests/regressions/gh-516-params.sft.c b/tests/tests/regressions/gh-516-params.sft.c new file mode 100644 index 0000000000..085bdb8a73 --- /dev/null +++ b/tests/tests/regressions/gh-516-params.sft.c @@ -0,0 +1,12 @@ +//type:rp +//options_all:--c --c11 --no_strict_gnu --gnu_version=130300 +//remark:GNU C typeof operand in a block-scope prototype is not evaluated +// A typeof operand of variably modified type in a block-scope function +// prototype is not evaluated, and must not be inserted as a statement. +int a[8]; +int main(void) { + int m = 2; + void local_proto(typeof (++m, (int (*)[m])a) p); + void local_proto2(int n, typeof (++m, (int (*)[m])a) *p, int q[m]); + return m != 2; +} diff --git a/tests/tests/regressions/gh-516-single.sft.c b/tests/tests/regressions/gh-516-single.sft.c new file mode 100644 index 0000000000..0884970d82 --- /dev/null +++ b/tests/tests/regressions/gh-516-single.sft.c @@ -0,0 +1,22 @@ +//type:rp +//options_all:--c --c11 --no_strict_gnu --gnu_version=130300 +//remark:GNU C typeof operand of variably modified type is evaluated once +// Each typeof operand of variably modified type is evaluated exactly once +// (with any back end): declarations with one declarator, a typedef, and a +// cast. +int main(void) { + int k = 1, a[8], fails = 0; + typeof (++k, (int (*)[k])a) p; + (void)sizeof(p); + fails += k != 2; + typedef typeof (++k, (int (*)[k])a) T; + T t; + (void)sizeof(t); + fails += k != 3; + (void)(typeof (++k, (int (*)[k])a))a; + fails += k != 4; + typeof ((({ ++k; 0; }), (int (*)[k])a)) s; + (void)sizeof(s); + fails += k != 5; + return fails; +} diff --git a/tests/tests/regressions/gh-516.sft.c b/tests/tests/regressions/gh-516.sft.c new file mode 100644 index 0000000000..b9463bf29b --- /dev/null +++ b/tests/tests/regressions/gh-516.sft.c @@ -0,0 +1,71 @@ +//type:rp +//require:DO_IL_LOWERING 1 +//options_all:--c --c11 --no_strict_gnu --gnu_version=170000 +//remark:GNU C typeof operand of variably modified type is evaluated + +// With a source-generating back end, the typeof is reproduced and the run-time +// behavior is that of the compiler of the generated code. GCC versions before +// 17 evaluate the operand once per declarator (GCC PR c/124576), so a recent +// version is emulated here. + +int k, a[8], fails; +#define T(e) typeof (e, (int (*)[k])a) +#define EXPECT(v) (fails += k != (v)) + +static int va_arg_case(int x, ...) { + __builtin_va_list ap; + __builtin_va_start(ap, x); + void *p = __builtin_va_arg(ap, T(++k)); + __builtin_va_end(ap); + return p == a; +} + +int main(void) { + int n = 2; + k = 1; + T(++k) p; + (void)sizeof(p); + EXPECT(2); + // The type specifier is evaluated once per declaration, not once per + // declarator. + k = 1; + T(++k) q, r; + (void)sizeof(q); (void)sizeof(r); + EXPECT(2); + // A use of a typedef does not repeat the evaluation. + k = 1; + typedef T(++k) TT; + EXPECT(2); + TT t; + (void)sizeof(t); + EXPECT(2); + // An operand that is not variably modified is not evaluated. + k = 1; + typeof (++k, (int *)a) ordinary; + (void)sizeof(ordinary); + EXPECT(1); + k = 1; + (void)(T(++k))a; + EXPECT(2); + // A statement expression nested in the operand is evaluated once too. + k = 1; + typeof ((({ ++k; 0; }), (int (*)[k])a)) s, u; + (void)sizeof(s); (void)sizeof(u); + EXPECT(2); + // Compound literals, used as an rvalue and as an lvalue. + k = 1; + (void)(T(++k)){0}; + EXPECT(2); + k = 1; + void *addr = &(T(++k)){0}; + (void)addr; + EXPECT(2); + k = 1; + fails += !va_arg_case(0, (int (*)[2])a); + EXPECT(2); + // sizeof applied to a variable-length array type evaluates its operand. + k = 1; + (void)sizeof(T(++k)[n]); + EXPECT(2); + return fails; +}