Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 79 additions & 12 deletions src/exprutil.c
Original file line number Diff line number Diff line change
Expand Up @@ -25174,6 +25174,24 @@ C mode.
} /* still_an_lvalue */


static void record_local_auto_object(
an_expr_or_stmt_traversal_block_ptr tblock)
/*
Called from examine_expr_for_auto_object when the addressing path leads to a
local variable or parameter. Normally that ends the traversal. When
tblock->look_for_temporaries is TRUE, keep going, so that a temporary on
another part of the path (e.g., the other operand of a "?" operator) is
still found and reported instead.
*/
{
if (!tblock->is_temp) {
tblock->result = TRUE;
tblock->is_temp = FALSE;
if (!tblock->look_for_temporaries) tblock->terminate = TRUE;
} /* if */
} /* record_local_auto_object */


static void examine_expr_for_auto_object(
an_expr_node_ptr expr,
an_expr_or_stmt_traversal_block_ptr tblock)
Expand Down Expand Up @@ -25217,25 +25235,45 @@ that identifies an object or a prvalue that is a pointer to an object.
from a scope enclosing a lambda), do not consider it local in this
context. */
if (var->source_corresp.enclosing_routine == curr_routine_or_null()) {
tblock->result = TRUE;
tblock->is_temp = FALSE;
tblock->terminate = TRUE;
record_local_auto_object(tblock);
} /* if */
} /* if */
} else if (node_is(expr, enk_param_ref)) {
/* An lvalue for a parameter outside an associated function
definition. An enk_param_ref for "this" won't get here because
it's not an lvalue, but the answer would be right even if it did. */
tblock->result = TRUE;
tblock->is_temp = FALSE;
tblock->terminate = TRUE;
record_local_auto_object(tblock);
} else if (node_is(expr, enk_temp_init)) {
if (!expr->variant.init.dynamic_init->static_temp) {
/* An lvalue for a nonstatic temporary. */
tblock->result = TRUE;
tblock->is_temp = TRUE;
tblock->terminate = TRUE;
} /* if */
} else if (tblock->look_for_temporaries && !is_glvalue_node(expr)) {
/* A class prvalue (e.g., a function call) on the addressing path, for
example the object expression of "f().x": a temporary is
materialized for it. */
tblock->result = TRUE;
tblock->is_temp = TRUE;
tblock->terminate = TRUE;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} else if (tblock->look_for_temporaries &&
is_operation_node(expr) &&
node_operator_is(expr, eok_indirect)) {
/* A reference bound to an array prvalue (e.g., "A{1, 2}" with "A" an
array type) is represented as "*" applied to the decayed array
temporary (under an eok_lvalue_adjust, skipped above). */
an_expr_node_ptr ptr = expr->variant.operation.operands;
if (is_operation_node(ptr) &&
node_operator_is(ptr, eok_array_to_pointer)) {
an_expr_node_ptr array = ptr->variant.operation.operands;
if (node_is(array, enk_temp_init) &&
!array->variant.init.dynamic_init->static_temp) {
tblock->result = TRUE;
tblock->is_temp = TRUE;
tblock->terminate = TRUE;
} /* if */
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} /* if */
} /* if */
} else {
/* The expression passed in is a pointer to an object. */
Expand All @@ -25254,13 +25292,14 @@ that identifies an object or a prvalue that is a pointer to an object.
} /* examine_expr_for_auto_object */


a_boolean is_glvalue_for_auto_object(an_expr_node_ptr expr,
a_boolean *is_temp)
static a_boolean glvalue_for_auto_object(an_expr_node_ptr expr,
a_boolean follow_prvalues,
a_boolean *is_temp)
/*
Return TRUE if expr is a glvalue whose underlying object is known to
be an automatic (stack-based) entity. The safe answer is FALSE. If the
underlying entity is a temporary, return *is_temp set to TRUE.
is_temp can be NULL if that information is not needed.
Do the work for is_glvalue_for_auto_object and
is_glvalue_for_auto_object_or_temp_subobject. If follow_prvalues is TRUE,
the traversal continues through class prvalues, so that a subobject of a
temporary (e.g., "S().x") is found.
*/
{
a_boolean is_auto_object = FALSE;
Expand All @@ -25271,14 +25310,42 @@ is_temp can be NULL if that information is not needed.
clear_expr_or_stmt_traversal_block(&tblock);
tblock.process_expr = examine_expr_for_auto_object;
tblock.follow_addressing_path = TRUE;
tblock.follow_class_rvalue_addressing_path = follow_prvalues;
tblock.look_for_temporaries = follow_prvalues;
traverse_expr(expr, &tblock);
is_auto_object = tblock.result;
if (is_temp != NULL) *is_temp = tblock.is_temp;
} /* if */
return is_auto_object;
} /* glvalue_for_auto_object */


a_boolean is_glvalue_for_auto_object(an_expr_node_ptr expr,
a_boolean *is_temp)
/*
Return TRUE if expr is a glvalue whose underlying object is known to
be an automatic (stack-based) entity. The safe answer is FALSE. If the
underlying entity is a temporary, return *is_temp set to TRUE.
is_temp can be NULL if that information is not needed.
*/
{
return glvalue_for_auto_object(expr, /*follow_prvalues=*/FALSE, is_temp);
} /* is_glvalue_for_auto_object */


a_boolean is_glvalue_for_auto_object_or_temp_subobject(
an_expr_node_ptr expr,
a_boolean *is_temp)
/*
Like is_glvalue_for_auto_object, but also return TRUE (with *is_temp set to
TRUE) if expr is a glvalue for a subobject of a class prvalue temporary,
e.g., "S().x".
*/
{
return glvalue_for_auto_object(expr, /*follow_prvalues=*/TRUE, is_temp);
} /* is_glvalue_for_auto_object_or_temp_subobject */


a_boolean is_prvalue_for_auto_object(an_expr_node_ptr expr,
a_boolean *is_temp)
/*
Expand Down
4 changes: 4 additions & 0 deletions src/exprutil.h
Original file line number Diff line number Diff line change
Expand Up @@ -3149,6 +3149,10 @@ extern a_boolean still_an_lvalue(a_type_ptr type_before_cast,
extern a_boolean is_glvalue_for_auto_object(an_expr_node_ptr expr,
a_boolean *is_temp);

extern a_boolean is_glvalue_for_auto_object_or_temp_subobject(
an_expr_node_ptr expr,
a_boolean *is_temp);

extern a_boolean is_prvalue_for_auto_object(an_expr_node_ptr expr,
a_boolean *is_temp);

Expand Down
1 change: 1 addition & 0 deletions src/il_walk.c
Original file line number Diff line number Diff line change
Expand Up @@ -2878,6 +2878,7 @@ default values.
tblock->complete_object_type = NULL;
tblock->call_case = FALSE;
tblock->is_temp = FALSE;
tblock->look_for_temporaries = FALSE;
#if HANDLE_VIRTUAL_BASES_IN_COMPLETE_CTOR_DTORS
tblock->orig_params = NULL;
tblock->new_params = NULL;
Expand Down
6 changes: 6 additions & 0 deletions src/il_walk.h
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,12 @@ typedef struct an_expr_or_stmt_traversal_block {
/* Fields used by is_glvalue_for_auto_object: */
a_boolean is_temp;
/* TRUE if the underlying object is a temporary. */
a_boolean look_for_temporaries;
/* TRUE if a temporary anywhere on the addressing
path (e.g., in either operand of a "?" operator,
or a class prvalue whose member is selected)
should be reported in preference to a local
variable. */
#if HANDLE_VIRTUAL_BASES_IN_COMPLETE_CTOR_DTORS
a_variable_ptr
orig_params;
Expand Down
47 changes: 37 additions & 10 deletions src/overload.c
Original file line number Diff line number Diff line change
Expand Up @@ -25425,23 +25425,39 @@ non-const to the indicated (rvalue) operand.
} /* microsoft_can_bind_ref_to_rvalue */


static void check_for_returning_reference_to_local_entity(an_operand *operand)
static void check_for_returning_reference_to_local_entity(
an_operand *operand,
a_boolean similar_lvalue_source)
/*
operand is the entity being bound to a reference in a return statement.
Issue a warning if it is a local entity.
Issue a warning if it is a local entity. Binding the returned reference to
a temporary is an error in C++26 (P2748). similar_lvalue_source is TRUE if
the original initializer was an lvalue of a type similar to the referenced
type; the reference should then bind to it directly (Core issue 2352), so
a temporary found here only gets the warning.
*/
{
a_boolean is_temp;

if (is_expression_operand(operand)) {
if (is_glvalue_for_auto_object(operand->variant.expression, &is_temp) ||
if (is_glvalue_for_auto_object_or_temp_subobject(
operand->variant.expression, &is_temp) ||
Comment thread
coderabbitai[bot] marked this conversation as resolved.
is_prvalue_for_auto_object(operand->variant.expression, &is_temp)) {
/* The expression is a glvalue or class prvalue (object) for a local
entity. Use a different message for temporaries and local
variables. */
expr_pos_warning(is_temp ? ec_return_ref_init_requires_temp :
ec_returning_ref_to_local_variable,
&operand->position);
entity (possibly a subobject of a temporary). Use a different
message for temporaries and local variables. */
if (!is_temp) {
expr_pos_warning(ec_returning_ref_to_local_variable,
&operand->position);
} else if (cpp26_mode && !similar_lvalue_source &&
!gpp_version_is(<140000) && !clangcpp_version_is(<190000)) {
/* GCC 14 and Clang 19 implement P2748. Earlier versions only issue
a warning. */
expr_pos_error(ec_return_ref_init_requires_temp, &operand->position);
} else {
expr_pos_warning(ec_return_ref_init_requires_temp,
&operand->position);
} /* if */
} /* if */
} /* if */
} /* check_for_returning_reference_to_local_entity */
Expand Down Expand Up @@ -25614,6 +25630,7 @@ the conversion.
a_boolean ref_to_const = FALSE;
a_boolean ref_to_const_volatile = FALSE;
a_boolean operand_was_rvalue, operand_is_function_lvalue;
a_boolean similar_lvalue_source;
a_boolean warn = FALSE, template_case = FALSE;
a_boolean initializing_variable =
(conv_context & CCO_INITIALIZING_VARIABLE) != 0;
Expand Down Expand Up @@ -25711,6 +25728,11 @@ the conversion.
} /* if */
base_dest_type = type_pointed_to(dest_type);
operand_was_rvalue = is_an_rvalue(source_operand);
/* An lvalue whose type is similar to the referenced type, e.g., an "int **"
lvalue for a "const int *const *const &". The reference binds directly
to it (Core issue 2352). */
similar_lvalue_source = !operand_was_rvalue &&
types_are_similar(orig_source_type, base_dest_type);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
operand_is_function_lvalue = is_a_function_designator(source_operand);
adj_base_dest_type = base_dest_type;
if (bitwise_assignment_param) {
Expand Down Expand Up @@ -26314,9 +26336,14 @@ the conversion.
source_operand,
(conv_context & CCO_STATIC_LIFETIME) != 0);
} else if ((conv_context & CCO_INITIALIZING_RETURN_VALUE) &&
(conv_context & CCO_TYPE_TRAITS_CHECK) == 0 &&
!template_case) {
/* Check for returning a reference to a local entity. */
check_for_returning_reference_to_local_entity(source_operand);
/* Check for returning a reference to a local entity. This is not done
for the invented function of the is_convertible check: P2748, which
makes binding a returned reference to a temporary ill-formed in C++26,
does not apply to it. */
check_for_returning_reference_to_local_entity(source_operand,
similar_lvalue_source);
} /* if */
if (!leave_as_object) {
/* Final step: add the reference-to to turn the glvalue or class prvalue
Expand Down
20 changes: 20 additions & 0 deletions src/pending_changes/gh-106-return-ref-to-temporary.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[GH #106]
C++26: returning a reference bound to a temporary is not allowed

A return statement that binds the returned reference to a temporary is not
allowed in C++26 (P2748). The front end only issued a warning. It is now an
error in C++26 (still a warning when emulating GCC versions before 14 or
Clang versions before 19, which do the same). For example, with --c++26:

struct S { int x; };
const int& f() { return 1; } // Now an error.
const int& g() { return S().x; } // Now an error.

Returning a reference to a member of a temporary, as in g, was previously not
diagnosed at all in the standard (non-GNU, non-Clang) modes; before C++26 it
now gets the same warning as the other cases. Returning a reference to a
local variable is still only a warning.

As P2748 specifies, this does not apply to the conversion checked by
std::is_convertible (e.g., __is_convertible_to(long, const int&) is still
true).
1 change: 0 additions & 1 deletion tests/expectations/edg_x86_64/imported.log
Original file line number Diff line number Diff line change
Expand Up @@ -856,7 +856,6 @@ clang/cpp/CXX/special/class.ctor/p1.sft.cpp:fn:-DTEST_NUMBER=1:FAIL:
clang/cpp/CXX/special/class.temporary/p1.sft.cpp:fn:-DTEST_NUMBER=1 --c++11:FAIL:
clang/cpp/CXX/special/class.temporary/p6.sft.cpp:fp:-DTEST_NUMBER=1 --c++17:FAIL:
clang/cpp/CXX/special/class.temporary/p6.sft.cpp:fp:-DTEST_NUMBER=2 --c++23:FAIL:
clang/cpp/CXX/stmt.stmt/stmt.return/p6.sft.cpp:fn:-DTEST_NUMBER=1 --c++26:FAIL:
clang/cpp/CXX/stmt.stmt/stmt.select/stmt.if/p2.sft.cpp:fn:-DTEST_NUMBER=2 --c++17 -DUNDEFINED:FAIL:
clang/cpp/CXX/stmt.stmt/stmt.select/stmt.switch/p4.sft.cpp:fn:-DTEST_NUMBER=1:FAIL:
clang/cpp/CXX/temp/temp.constr/temp.constr.order/non-template-functions.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL:
Expand Down
34 changes: 34 additions & 0 deletions tests/tests/changes/.GH106.rto/default.1.1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
fe_only -DTEST_NUMBER=1 --c++26 Test_name.c
"Test_name.c", line 11: error: returning reference to local temporary
const int& f1() { return 1; } // Temporary: diagnosed.
^

"Test_name.c", line 12: error: returning reference to local temporary
const int& f2(long l) { return l; } // Temporary from a conversion.
^

"Test_name.c", line 13: error: returning reference to local temporary
const int& f3() { return S().x; } // Member of a temporary.
^

"Test_name.c", line 14: error: returning reference to local temporary
int&& f4() { return 1; } // Rvalue reference to a temporary.
^

"Test_name.c", line 15: warning: returning reference to local variable
const int& f5() { int i = 0; return i; } // Local variable: only a warning.
^

"Test_name.c", line 17: error: returning reference to local temporary
const int& f7() { return make().x; } // Member of a call's result.
^

"Test_name.c", line 19: error: returning reference to local temporary
return b ? static_cast<int&&>(i) : S().x;
^

"Test_name.c", line 22: error: returning reference to local temporary
A&& f9() { return A{1, 2}; } // Array temporary.
^

7 errors detected in the compilation of "Test_name.c".
38 changes: 38 additions & 0 deletions tests/tests/changes/.GH106.rto/default.2.1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
fe_only -DTEST_NUMBER=2 --c++26 --gnu_version 140000 Test_name.c
"Test_name.c", line 11: error: returning reference to local temporary
const int& f1() { return 1; } // Temporary: diagnosed.
^

"Test_name.c", line 12: error: returning reference to local temporary
const int& f2(long l) { return l; } // Temporary from a conversion.
^

"Test_name.c", line 13: error: returning reference to local temporary
const int& f3() { return S().x; } // Member of a temporary.
^

"Test_name.c", line 14: error: returning reference to local temporary
int&& f4() { return 1; } // Rvalue reference to a temporary.
^

"Test_name.c", line 15: warning: returning reference to local variable
const int& f5() { int i = 0; return i; } // Local variable: only a warning.
^

"Test_name.c", line 17: error: returning reference to local temporary
const int& f7() { return make().x; } // Member of a call's result.
^

"Test_name.c", line 19: error: returning reference to local temporary
return b ? static_cast<int&&>(i) : S().x;
^

"Test_name.c", line 22: error: returning reference to local temporary
A&& f9() { return A{1, 2}; } // Array temporary.
^

"Test_name.c", line 27: warning: returning reference to local temporary
const int *const *const &f6() { return pp; }
^

7 errors detected in the compilation of "Test_name.c".
38 changes: 38 additions & 0 deletions tests/tests/changes/.GH106.rto/default.3.1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
fe_only -DTEST_NUMBER=3 --c++26 --clang_version 190000 Test_name.c
"Test_name.c", line 11: error: returning reference to local temporary
const int& f1() { return 1; } // Temporary: diagnosed.
^

"Test_name.c", line 12: error: returning reference to local temporary
const int& f2(long l) { return l; } // Temporary from a conversion.
^

"Test_name.c", line 13: error: returning reference to local temporary
const int& f3() { return S().x; } // Member of a temporary.
^

"Test_name.c", line 14: error: returning reference to local temporary
int&& f4() { return 1; } // Rvalue reference to a temporary.
^

"Test_name.c", line 15: warning: returning reference to local variable
const int& f5() { int i = 0; return i; } // Local variable: only a warning.
^

"Test_name.c", line 17: error: returning reference to local temporary
const int& f7() { return make().x; } // Member of a call's result.
^

"Test_name.c", line 19: error: returning reference to local temporary
return b ? static_cast<int&&>(i) : S().x;
^

"Test_name.c", line 22: error: returning reference to local temporary
A&& f9() { return A{1, 2}; } // Array temporary.
^

"Test_name.c", line 27: warning: returning reference to local temporary
const int *const *const &f6() { return pp; }
^

7 errors detected in the compilation of "Test_name.c".
Loading
Loading