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; +}