Bug 110619 - Dangling pointer returned from constexpr function converts in nullptr
Summary: Dangling pointer returned from constexpr function converts in nullptr
Status: UNCONFIRMED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 13.1.0
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: accepts-invalid, diagnostic, rejects-valid
Depends on:
Blocks: constexpr
  Show dependency treegraph
 
Reported: 2023-07-10 18:39 UTC by Fedor Chelnokov
Modified: 2023-08-07 09:12 UTC (History)
5 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Fedor Chelnokov 2023-07-10 18:39:36 UTC
The following function

constexpr auto f() {
    int i = 0;
    return &i;
};

returns dangling pointer on stack variable, but it is not nullptr. So next assertion passes in Clang and MSVC:

static_assert( f() != nullptr );


But in GCC the assertion fails. Online demo: https://gcc.godbolt.org/z/6E4rE963n
Comment 1 Andrew Pinski 2023-07-10 18:43:30 UTC
I would have expected this to be undefined ...
So the static_assert could work or not.
Comment 2 Andrew Pinski 2023-07-10 18:44:16 UTC
>but it is not nullptr.

Or is it just undefined so it could be considered a nullptr ...
Comment 3 Fedor Chelnokov 2023-07-10 19:04:40 UTC
I think according to https://eel.is/c++draft/basic.stc#general-4 the function shall return an "invalid pointer valued". And nullptr is not considered such.

And if one modifies the function slightly (see auto p appear):

constexpr auto g() {
    int i = 0;
    auto p = &i;
    return p;
};

Then static_assert passes: https://gcc.godbolt.org/z/5shcxfcxG
Comment 4 Jiang An 2023-07-12 01:51:20 UTC
(In reply to Andrew Pinski from comment #1)
> I would have expected this to be undefined ...
> So the static_assert could work or not.

If this were undefined (not true IIUC), static_assert would be required not to work because the condition expression is not a constant expression ([expr.const]/5.8).

We should keep in mind that all kinds of core UB that may occur within (tentative) constant evaluation (except for violation of [[assume]], currently) are not totally undefined, since they must cause constant evaluation failure. CWG issue should be submitted if such detection is unimplementable.
Comment 5 Patrick Palka 2023-07-13 19:02:58 UTC
IIUC Nathaniel's patch at https://gcc.gnu.org/pipermail/gcc-patches/2023-July/623377.html will fix this.
Comment 6 GCC Commits 2023-07-26 01:45:35 UTC
The trunk branch has been updated by Jason Merrill <jason@gcc.gnu.org>:

https://gcc.gnu.org/g:b8266af71c19a0bd7db4d08c8d2ee3c33214508c

commit r14-2772-gb8266af71c19a0bd7db4d08c8d2ee3c33214508c
Author: Nathaniel Shead <nathanieloshead@gmail.com>
Date:   Sun Jul 23 01:14:37 2023 +1000

    c++: Prevent dangling pointers from becoming nullptr in constexpr [PR110619]
    
    Currently, when typeck discovers that a return statement will refer to a
    local variable it rewrites to return a null pointer. This causes the
    error messages for using the return value in a constant expression to be
    unhelpful, especially for reference return values, and is also a visible
    change to otherwise valid code (as in the linked PR).
    
    The transformation is nonetheless important, however, both as a safety
    guard against attackers being able to gain a handle to other data on the
    stack, and to prevent duplicate warnings from later null-dereference
    warning passes.
    
    As such, this patch just delays the transformation until cp_genericize,
    after constexpr function definitions have been generated.
    
            PR c++/110619
    
    gcc/cp/ChangeLog:
    
            * cp-gimplify.cc (cp_genericize_r): Transform RETURN_EXPRs to
            not return dangling pointers.
            * cp-tree.h (RETURN_EXPR_LOCAL_ADDR_P): New flag.
            (check_return_expr): Add a new parameter.
            * semantics.cc (finish_return_stmt): Set flag on RETURN_EXPR
            when referring to dangling pointer.
            * typeck.cc (check_return_expr): Disable transformation of
            dangling pointers, instead pass this information to caller.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/cpp1y/constexpr-110619.C: New test.
    
    Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
Comment 7 Peter Cordes 2023-08-07 05:04:43 UTC
(In reply to Andrew Pinski from comment #2)
> >but it is not nullptr.
> 
> Or is it just undefined so it could be considered a nullptr ...


Implementation-defined behaviour, according to answers on https://stackoverflow.com/questions/76843246/why-does-the-address-of-an-out-of-scope-variable-equal-zero-with-constexpr


https://eel.is/c++draft/basic.compound#def:value,invalid_pointer

https://eel.is/c++draft/basic.stc.general#4

>  Indirection through an invalid pointer value and passing an invalid pointer value to a deallocation function have undefined behavior.
> **Any other use of an invalid pointer value has implementation-defined behavior.**

So this wasn't a bug, but the new behaviour is also allowed.

This commit could be reverted or kept, depending on maintainability and/or quality-of-life for users of GCC.  Having it pick the other implementation-defined behaviour from clang (GCC's previous behaviour) is maybe a *good* thing, to help programmers catch dependence on an invalid pointer being either null or non-null if they try their code with both compilers.
Comment 8 Fedor Chelnokov 2023-08-07 07:41:44 UTC
Please note that GCC 13 also accepts invalid program (because dangling pointers were converted in nullptr):

constexpr auto f(int a) {
    return &a;
}

constexpr auto g(int b) {
    return &b;
}

static_assert(f(1) <= g(2));

This program must be rejected because of relational comparison of unrelated pointers as Clang and MSVC do, online demo: https://gcc.godbolt.org/z/q5z3Gvehe
And your patch fixes it as well, thanks.
Comment 9 Jonathan Wakely 2023-08-07 09:12:57 UTC
(In reply to Fedor Chelnokov from comment #8)
> This program must be rejected because of relational comparison of unrelated
> pointers

I disagree. If GCC were to define the behaviour of returning invalid pointers during constant evaluation as converting them to null pointers, then those wouldn't be unrelated pointers. They'd be null pointers. And the comparison would be well defined.

But I don't think that conversion was ever intended as the defined behaviour, just an unintended consequence. If it was intended, it should have been documented at https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Implementation.html

The fact that we _don't_ do any such conversion should also be documented there, of course. We should say that other uses of invalid pointers do not modify the pointer values, so copying them preserves the invalid value (without trapping, except on Itanium?) and pointer comparisons and pointer arithmetic are undefined because they don't point to an object.