Bug 92695 - P1064R0 - virtual constexpr fails if object taken from array
Summary: P1064R0 - virtual constexpr fails if object taken from array
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 10.0
: P3 normal
Target Milestone: 9.3
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks: constexpr
  Show dependency treegraph
 
Reported: 2019-11-27 12:56 UTC by Toni Neubert
Modified: 2021-12-03 05:45 UTC (History)
3 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail: 10.0, 9.2.0
Last reconfirmed: 2019-11-27 00:00:00


Attachments
gcc10-pr92695.patch (989 bytes, patch)
2019-11-27 17:06 UTC, Jakub Jelinek
Details | Diff
gcc10-pr92695.patch (989 bytes, patch)
2019-11-27 17:33 UTC, Jakub Jelinek
Details | Diff
gcc10-pr92695-3.patch (635 bytes, patch)
2019-11-27 21:11 UTC, Jakub Jelinek
Details | Diff
gcc10-pr92695-2.patch (725 bytes, patch)
2019-11-27 21:12 UTC, Jakub Jelinek
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Toni Neubert 2019-11-27 12:56:41 UTC
The following code fails with: 

-> error: expression 'A::get' is not a constant expression

Compiled with `-std=c++2a` under GCC-9.2 and GCC-trunk. It works under Clang-trunk.


```
struct A {
    constexpr virtual int get() = 0;
};

struct B : A {
    constexpr int get() override {
        return 10;
    }
};

struct Foo {
    B b[1] = {};

    constexpr A * get_a() {
        // Seems to be the problem.
        return &(b[0]);
    }
};

constexpr int get() {
    Foo f;
    return f.get_a()->get();
}

constexpr auto a = get();

int main() {
    return a;
}
```

The dereferencing of the array seems to be the problem.

See online here: https://gcc.godbolt.org/z/ZSXAfb
Comment 1 Toni Neubert 2019-11-27 13:58:07 UTC
Future more, the following example also fails. Could be the same root cause but another error message appears: 

accessing value of 'f.Foo::b[0].B::<anonymous>' through a 'B' glvalue in a constant expression

Clang is just fine with it.

```
struct A {
    constexpr virtual int get() = 0;

    constexpr virtual int set(A *o) = 0;
};

struct B : A {
    constexpr int get() override {
        return 10;
    }struct A {
    constexpr virtual int get() = 0;

    constexpr virtual int set(A *o) = 0;
};

struct B : A {
    constexpr int get() override {
        return 10;
    }

    constexpr int set(A *o) override {
        a = o;
        return 20;
    }
    A *a{};
};

constexpr auto addressof = [](A &n) {
    return &n;
};

struct Foo {
    B b[1];
    A* curr_{addressof(b[0])};

    constexpr int add() {
        return curr_->set(addressof(b[0]));
    }
};


constexpr int get() {
    Foo f;
    return f.add();
}

constexpr auto a = get();

int main() {
    return a;
}

    constexpr int set(A *o) override {
        a = o;
        return 20;
    }
    A *a{};
};

constexpr auto addressof = [](A &n) {
    return &n;
};

struct Foo {
    B b[1];
    A* curr_{addressof(b[0])};

    constexpr int add() {
        return curr_->set(addressof(b[0]));
    }
};


constexpr int get() {
    Foo f;
    return f.add();
}

constexpr auto a = get();

int main() {
    return a;
}
```

See online here: https://gcc.godbolt.org/z/4ZwFAN
Comment 2 Toni Neubert 2019-11-27 14:00:04 UTC
Copy paste error. The above example should be:

```
struct A {
    constexpr virtual int get() = 0;

    constexpr virtual int set(A *o) = 0;
};

struct B : A {
    constexpr int get() override {
        return 10;
    }

    constexpr int set(A *o) override {
        a = o;
        return 20;
    }
    A *a{};
};

constexpr auto addressof = [](A &n) {
    return &n;
};

struct Foo {
    B b[1];
    A* curr_{addressof(b[0])};

    constexpr int add() {
        return curr_->set(addressof(b[0]));
    }
};


constexpr int get() {
    Foo f;
    return f.add();
}

constexpr auto a = get();

int main() {
    return a;
}
```
Comment 3 Jakub Jelinek 2019-11-27 15:41:28 UTC
Doesn't have to be an array,
struct A {
  constexpr virtual int get () = 0;
  constexpr virtual int set (A *o) = 0;
};
struct B : A {
  constexpr int get () override { return 10; }
  constexpr int set (A *o) override { a = o; return 20; }
  A *a{};
};
constexpr auto addressof = [] (A &n) { return &n; };
struct Foo {
  B b;
  A *c { addressof (b) };
  constexpr int add () { return c->set (addressof (b)); }
};
constexpr int get () { Foo f; return f.add (); }
constexpr auto a = get ();
static_assert (a == 20);
is rejected too.

I think the problem is that the r264408 testcase coverage doesn't try to dereference this arguments of the virtual methods, which is what happens in this testcase (the store to this->a in B::set(A *)).
The OBJ_TYPE_REF handling in cxx_eval_constant_expression has:
        obj = TREE_OPERAND (obj, 0);
        while (TREE_CODE (obj) == COMPONENT_REF
               && DECL_FIELD_IS_BASE (TREE_OPERAND (obj, 1)))
          obj = TREE_OPERAND (obj, 0);
but something similar isn't done in cxx_eval_indirect_ref and I think generally shouldn't be done there, it is (is it?) only the special case of virtual method's first argument.  So, I'd say this DECL_FIELD_IS_BASE handling should be done in cxx_eval_call_expression.
Comment 4 Jakub Jelinek 2019-11-27 15:56:57 UTC
To be precise, I meant something like:
--- gcc/cp/constexpr.c.jj	2019-11-27 10:03:37.916867165 +0100
+++ gcc/cp/constexpr.c	2019-11-27 16:55:17.475150697 +0100
@@ -1441,6 +1441,22 @@ cxx_bind_parameters_in_call (const const
 	    arg = adjust_temp_type (type, arg);
 	  if (!TREE_CONSTANT (arg))
 	    *non_constant_args = true;
+	  if (i == 0
+	      && DECL_VIRTUAL_P (fun))
+	    {
+	      tree addr = arg;
+	      STRIP_NOPS (addr);
+	      if (TREE_CODE (addr) == ADDR_EXPR)
+		{
+		  tree obj = TREE_OPERAND (addr, 0);
+		  while (TREE_CODE (obj) == COMPONENT_REF
+			 && DECL_FIELD_IS_BASE (TREE_OPERAND (obj, 1)))
+		    obj = TREE_OPERAND (obj, 0);
+		  if (obj != TREE_OPERAND (addr, 0))
+		    arg = build_fold_addr_expr_with_type (obj,
+							  TREE_TYPE (arg));
+		}
+	    }
 	  TREE_VEC_ELT (binds, i) = arg;
 	}
       parms = TREE_CHAIN (parms);

This fixes the testcase.  Note, there is still a weird warning:
pr92695.C:3:25: warning: inline function ‘virtual constexpr int A::set(A*)’ used but never defined
    3 |   constexpr virtual int set (A *o) = 0;
      |                         ^~~
Comment 5 Jakub Jelinek 2019-11-27 17:06:06 UTC
Created attachment 47382 [details]
gcc10-pr92695.patch

Untested fix.
Comment 6 Jakub Jelinek 2019-11-27 17:33:03 UTC
Created attachment 47383 [details]
gcc10-pr92695.patch

And here is a fix for the bogus warning.  inline or constexpr on pure virtual functions looks useless, but the standard doesn't disallow that it seems.
Comment 7 Toni Neubert 2019-11-27 20:11:47 UTC
First of all thank you very much for your extremly fast help!
I testet the patch and it did work for my second example.

But this one still fails, if we do not use the addressof function:

struct A {
	virtual int get() = 0;
};
struct B : A {
	constexpr int get() override {
		return 10;
	}
};

struct D {
	B b[2];
	A* c{&(b[0])};
};

static_assert(D{}.c->get() == 10);

Error:

main.cpp:20:28: error: non-constant condition for static assertion
   20 | static_assert(D{}.c->get() == 10);
      |               ~~~~~~~~~~~~~^~~~~
main.cpp:20:28: error: expression 'A::get' is not a constant expression
Comment 8 Jakub Jelinek 2019-11-27 21:11:12 UTC
Created attachment 47384 [details]
gcc10-pr92695-3.patch

That is another bug.
Comment 9 Jakub Jelinek 2019-11-27 21:12:26 UTC
Created attachment 47385 [details]
gcc10-pr92695-2.patch

Oops, the second patch I've attached was the same as the first one, instead of this one.
Comment 10 Jakub Jelinek 2019-11-28 08:06:41 UTC
Author: jakub
Date: Thu Nov 28 08:06:09 2019
New Revision: 278802

URL: https://gcc.gnu.org/viewcvs?rev=278802&root=gcc&view=rev
Log:
	PR c++/92695
	* decl2.c (mark_used): Don't call note_vague_linkage_fn for pure
	virtual functions, even if they are declared inline.

	* g++.dg/warn/inline3.C: New test.

Added:
    trunk/gcc/testsuite/g++.dg/warn/inline3.C
Modified:
    trunk/gcc/cp/ChangeLog
    trunk/gcc/cp/decl2.c
    trunk/gcc/testsuite/ChangeLog
Comment 11 Toni Neubert 2019-11-30 11:36:09 UTC
I tested all patches and it works as expected.

Thank you very much!
Comment 12 Toni Neubert 2019-11-30 19:20:46 UTC
Hello, I got another error using virtual classes:

```
struct A {
    virtual constexpr ~A() = default;
};

struct B : A {};

constexpr bool test() {
    B b;
    return true;
}
static_assert(test());
```

-> virtual constexpr B::~B()' used before its definition
Comment 13 Jakub Jelinek 2019-12-02 12:07:19 UTC
For #c12, I think that is because of:
5615	      /* Defer virtual destructors so that thunks get the right
5616		 linkage.  */
5617	      if (DECL_VIRTUAL_P (decl) && !at_eof)
5618		{
5619		  note_vague_linkage_fn (decl);
5620		  return true;
5621		}
which makes mark_used not to synthesize those until end of TU.
Not really sure how to fix that, shall we if constexpr synthesize them early anyway, but only register for constexpr evaluation and undo other effects of the synthesize_method?  Or handle specially during constexpr evaluation each time we see those (or just first time, by remembering the body)?
Comment 14 Jakub Jelinek 2019-12-02 21:33:37 UTC
Author: jakub
Date: Mon Dec  2 21:33:06 2019
New Revision: 278912

URL: https://gcc.gnu.org/viewcvs?rev=278912&root=gcc&view=rev
Log:
	PR c++/92695
	* constexpr.c (cxx_eval_constant_expression) <case OBJ_TYPE_REF>: Use
	STRIP_NOPS before checking for ADDR_EXPR.

	* g++.dg/cpp2a/constexpr-virtual15.C: New test.

Added:
    trunk/gcc/testsuite/g++.dg/cpp2a/constexpr-virtual15.C
Modified:
    trunk/gcc/cp/ChangeLog
    trunk/gcc/cp/constexpr.c
    trunk/gcc/testsuite/ChangeLog
Comment 15 Toni Neubert 2019-12-03 06:43:08 UTC
I really cannot help you with this. I am sorry. I don't understand the compilers source code/internals at all. I just can thank you guys for your ongoing work. I really appreciate it.
Comment 16 Jakub Jelinek 2019-12-03 08:19:36 UTC
Author: jakub
Date: Tue Dec  3 08:19:04 2019
New Revision: 278921

URL: https://gcc.gnu.org/viewcvs?rev=278921&root=gcc&view=rev
Log:
	PR c++/92695
	* constexpr.c (cxx_bind_parameters_in_call): For virtual calls,
	adjust the first argument to point to the derived object rather
	than its base.

	* g++.dg/cpp2a/constexpr-virtual14.C: New test.

Added:
    trunk/gcc/testsuite/g++.dg/cpp2a/constexpr-virtual14.C
Modified:
    trunk/gcc/cp/ChangeLog
    trunk/gcc/cp/constexpr.c
    trunk/gcc/testsuite/ChangeLog
Comment 17 Jakub Jelinek 2019-12-03 08:55:44 UTC
(In reply to Toni Neubert from comment #15)
> I really cannot help you with this. I am sorry. I don't understand the
> compilers source code/internals at all. I just can thank you guys for your
> ongoing work. I really appreciate it.

The question wasn't meant at you, but at the C++ maintainers.  From my POV, all but #c12 should be fixed now (plus Jason mentioned some issue with multiple inheritance and constexpr virtual method call evaluation).
Comment 18 Jakub Jelinek 2019-12-20 17:27:00 UTC
Author: jakub
Date: Fri Dec 20 17:26:28 2019
New Revision: 279661

URL: https://gcc.gnu.org/viewcvs?rev=279661&root=gcc&view=rev
Log:
	Backported from mainline
	2019-11-28  Jakub Jelinek  <jakub@redhat.com>

	PR c++/92695
	* decl2.c (mark_used): Don't call note_vague_linkage_fn for pure
	virtual functions, even if they are declared inline.

	* g++.dg/warn/inline3.C: New test.

Added:
    branches/gcc-9-branch/gcc/testsuite/g++.dg/warn/inline3.C
Modified:
    branches/gcc-9-branch/gcc/cp/ChangeLog
    branches/gcc-9-branch/gcc/cp/decl2.c
    branches/gcc-9-branch/gcc/testsuite/ChangeLog
Comment 19 Jakub Jelinek 2019-12-20 17:32:55 UTC
Author: jakub
Date: Fri Dec 20 17:32:23 2019
New Revision: 279663

URL: https://gcc.gnu.org/viewcvs?rev=279663&root=gcc&view=rev
Log:
	Backported from mainline
	2019-12-02  Jakub Jelinek  <jakub@redhat.com>

	PR c++/92695
	* constexpr.c (cxx_eval_constant_expression) <case OBJ_TYPE_REF>: Use
	STRIP_NOPS before checking for ADDR_EXPR.

	* g++.dg/cpp2a/constexpr-virtual15.C: New test.

Added:
    branches/gcc-9-branch/gcc/testsuite/g++.dg/cpp2a/constexpr-virtual15.C
Modified:
    branches/gcc-9-branch/gcc/cp/ChangeLog
    branches/gcc-9-branch/gcc/cp/constexpr.c
    branches/gcc-9-branch/gcc/testsuite/ChangeLog
Comment 20 Jakub Jelinek 2019-12-20 17:33:44 UTC
Author: jakub
Date: Fri Dec 20 17:33:13 2019
New Revision: 279664

URL: https://gcc.gnu.org/viewcvs?rev=279664&root=gcc&view=rev
Log:
	Backported from mainline
	2019-12-03  Jakub Jelinek  <jakub@redhat.com>

	PR c++/92695
	* constexpr.c (cxx_bind_parameters_in_call): For virtual calls,
	adjust the first argument to point to the derived object rather
	than its base.

	* g++.dg/cpp2a/constexpr-virtual14.C: New test.

Added:
    branches/gcc-9-branch/gcc/testsuite/g++.dg/cpp2a/constexpr-virtual14.C
Modified:
    branches/gcc-9-branch/gcc/cp/ChangeLog
    branches/gcc-9-branch/gcc/cp/constexpr.c
    branches/gcc-9-branch/gcc/testsuite/ChangeLog
Comment 21 Jason Merrill 2020-06-04 04:45:34 UTC
Fixed in 9.3/10.
Comment 22 Andrew Pinski 2021-08-17 06:00:54 UTC
Looks like the testcase in comment #12 was not resolved with the patches but is filed as PR 93413.