Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[basic.life], [class.cdtor] Referring to a member of a virtual base outside lifetime #204

Copy link
Copy link
Closed
@ecatmur

Description

@ecatmur
Issue body actions

[basic.life] paragraphs 6-7 prohibit the pointer-to-virtual-base and reference-to-virtual-base conversions respectively for out-of-lifetime class objects; however they do not bar the equally dangerous formation of a glvalue to a member of a virtual base class:

struct B { int i; };
struct D : virtual B {};
union U { int i; D d; } u = {.i = 0};
int& r = u.d.i;

(Note that this does not involve formation of an lvalue to the B subobject of u.d, which would be undefined behavior under [basic.life] paragraph 7; instead, [expr.ref] works on the basis that members of a base class are members of the derived class; see e.g. paragraph 7 of that clause.)

Indeed, since B is here a standard-layout type, u.d is pointer-interconvertible with u.d.i [class.mem] and so we can use a cast to bypass the prohibition:

B& b = reinterpret_cast<B&>(u.d.i);

[class.cdtor] paragraph 1 tries to prohibit referencing members of virtual bases, along with virtual bases themselves (a class with a virtual base has a nontrivial constructor) but fails because it does not cover access to objects of class type with nontrivial constructor whose lifetime was ended other than by calling a nontrivial destructor (e.g. via storage reuse):

struct B { int i; };
struct D : virtual B {};
union U { int i; D d; } u = {.d = {}};
D& d = u.d;
u.i = 0;
int& r = d.i;

Per issue 1530, the prohibitions in [class.cdtor] paragraphs 1 and 3 are overbroad and inconsistent with [basic.life]; we want to permit address computation on bases and members as long as virtual base layouts are not involved:

struct X { float f; ~X() {} };
union U { int i; X x; } u = {.x = {}};
u.i = 0;
&u.x.f;  // OK per [basic.life]/7, but
u.x = {};
u.x.~X();  // invoking the destructor of x makes this...
u.x.i = 0;
&u.x.f;  // ...UB per [class.cdtor]/1

Indeed, [class.cdtor] should only concern itself with objects under construction and destruction, and not those outside lifetime whose construction and/or destruction is not under way: [basic.life]/6 [basic.life]/7.

(See also issue 1517.)

Suggested resolution:

  1. Change [basic.life] paragraph 6 as follows:

... The program has undefined behavior if:

  • the pointer is used as the operand of a delete-expression, or
  • the pointer is used to access a non-static data member or call a non-static member function of the object, or
  • the pointer is implicitly converted ([conv.ptr]) to a pointer to a virtual base class, or
  • the pointer is used as the operand of a static_cast ([expr.static.cast]), except when the conversion is to pointer to cv void, or to pointer to cv void and subsequently to pointer to cv char, cv unsigned char, or cv std::byte ([cstddef.syn]), or
  • the pointer is used as the operand of a dynamic_cast ([expr.dynamic.cast]).
    [Note: referencing a non-static data member or calling a non-static member function is performed via a glvalue formed by indirecting the pointer; see below. --end note]
    ...
    void B::mutate() {
    new (this) D2; // reuses storage --- ends the lifetime of *this
    f(); // undefined behavior (see below)
    ... = this; // OK, this points to valid memory
    }

void g() {
void* p = std::malloc(sizeof(D1) + sizeof(D2));
B* pb = new (p) D1;
pb->mutate();
*pb; // OK, pb points to valid memory
void* q = pb; // OK, pb points to valid memory
pb->f(); // undefined behavior (see below): lifetime of *pb has ended
}
-- end example]

Drafting note: the removed bullet point is unnecessary and misleading, since class member access expressions involving pointer syntax are transformed to (*(E1)).E2 [expr.ref]. This means the calls to B::f() in the example need to refer to the following paragraph.

  1. Change [basic.life] paragraph 7 as follows:

[...] The program has undefined behavior if:

  • the glvalue is used to access the object, or
  • the glvalue is used to call a non-static member function of the object, or
  • the glvalue is bound to a reference to a virtual base class ([dcl.init.ref]), or
  • the gvalue is used to refer to a non-static data member of a virtual base class, or
  • the glvalue is used as the operand of a dynamic_cast ([expr.dynamic.cast]) or as the operand of typeid.
    [Example:
    struct W { int j; };
    struct X : public virtual W { };
    struct Y {
    int* p;
    X x;
    Y() : p(&x.j) { // undefined, x is not yet constructed
    }
    };
    -- end example]

Drafting note: alternatively, the inserted bullet point could be formalized, viz:

  • the glvalue is used as the object of a class member access expression whose id-expression refers to a non-static data member of a virtual base class, or
  1. Strike [class.cdtor] paragraph 1 entirely, including examples, and replace with:

[Note: [basic.life] describes how a pointer or glvalue to an object that is outside its lifetime and is not under construction or destruction can be used. --end note]

Drafting note: the wording "to an object that is outside its lifetime" is a bit careless; [basic.life] paragraphs 6 and 7 are more exact but I don't feel it's necessary to replicate that wording here.

  1. Change [class.cdtor] paragraph 3 as follows:

To explicitly or implicitly convert a pointer (a glvalue) referring to an object of class X that is under construction or destruction to a pointer (reference) to a direct or indirect virtual base class B of X, or to use said pointer (glvalue) to form a glvalue to a non-static data member of the B subobject of obj, or to call a non-static member function of said subobject, the construction of X and the construction of all of its direct or indirect bases that directly or indirectly derive fromfor which B is a direct or indirect virtual base shall have started and the destruction of these classes shall not have completed, otherwise the conversion, member access or member function call results in undefined behavior. To form a pointer to (or access the value of) a direct non-static member of an object obj, the construction of obj shall have started and its destruction shall not have completed, otherwise the computation of the pointer value (or accessing the member value) results in undefined behavior.

Drafting note: several of these changes are taken from issue 1517 as regarding the application of the resolution of issue 597 to [class.cdtor] paragraph 3.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      Morty Proxy This is a proxified and sanitized view of the page, visit original site.