Opened 6 months ago

#305 new defect

Rvalue-to-reference promotion circumvents RAII

Reported by: mlbrooks Owned by:
Priority: major Component: cfa-cc
Version: 1.0 Keywords: rvalue reference raii
Cc:

Description

For background, present-state rvalue-to-reference promotion allows this case:

int foo() {
    return 42;
}
int & x = foo();
// ... call whatever else
printf("%d\n", x); // 42

Ongoing work to rule out certain cases may consider ruling out this case too.

Perhaps surprisingly, present-state handling does not consider this arrangment to be an instant memory error. The returned value's lifetime is not limited to the call expression; the value lives in a hoised temporary with lifetime roughly block scope.

As it is, objects like this returned 42, should they have RAII, miss an RAII callback step. A demo follows.

First, we need an object that logs its RAII calls (included for reproduction, can skip on reading):

struct Obj { 
    int idCopied;
    int idNotCopied;
};

int nextIdCopied = 1;
int nextIdNotCopied = 1;

ptrdiff_t getRelativeAddr( void * addr ) {
    size_t addrVal = (size_t) addr;
    static size_t refAddr = 0;
    if (refAddr == 0 ) refAddr = addrVal;
    return refAddr - addrVal;
}

void ?{}( Obj & this ) {
    this.idCopied = nextIdCopied;
    this.idNotCopied = nextIdNotCopied;
    nextIdCopied += 1;
    nextIdNotCopied += 1;
    printf("ctor dflt @%+6zd, $%d, #%d\n", getRelativeAddr(&this), this.idCopied, this.idNotCopied);
}

void ?{}( Obj & this, Obj src ) {
    this.idCopied = src.idCopied;
    this.idNotCopied = nextIdNotCopied;
    nextIdNotCopied += 1;
    printf("ctor copy @%+6zd, $%d, #%d:=#%d\n", getRelativeAddr(&this), this.idCopied, this.idNotCopied, src.idNotCopied);
}

void ^?{}( Obj & this ) {
    printf("dtor      @%+6zd, $%d, #%d\n", getRelativeAddr(&this), this.idCopied, this.idNotCopied);
}

void observe( Obj & this ) {
    printf("observe   @%+6zd, $%d, #%d\n", getRelativeAddr(&this), this.idCopied, this.idNotCopied);
}

Obj bar() {
    return (Obj){};
}

Now the real test harness:

printf("==== Case 1, baseline\n");
{
    Obj o1 = bar();
    observe(o1);
}
printf("==== Case 2, SUT\n");
{
    Obj & o2 = bar();
    observe(o2);
}

Output key:
@_ is an address offset from an arbitrary point
$_ is a value carried in an object and kept by all its copies
#_ is like a version number, different for each ctor-defined copy

Actual: Runs with output

==== Case 1, baseline
ctor dflt @    +0, $1, #1
ctor copy @    +8, $1, #2:=#1
dtor      @    +0, $1, #1
ctor copy @  -136, $1, #3:=#2
dtor      @  -144, $1, #2
observe   @  -136, $1, #3
dtor      @  -136, $1, #3
==== Case 2, SUT
ctor dflt @    +0, $2, #4
ctor copy @    +8, $2, #5:=#4
dtor      @    +0, $2, #4
observe   @  -144, $2, #5
dtor      @  -144, $2, #5

Expected: Runs with case-1 output as-is, case-2 shows same variable/RAII pattern as case-1; OR compile error that returned values cannot be saved in reference variables

Further background:

All copy ctors have the anomaly that the src is just bits at a meaningless address (not even shown in output),
for which no lifecycle-maintence occurs. Work is in progress to fix it (change copy ctor signature to use const reference for src argument).

Baseline has the known anomaly that #2 shows up at a different address (dtor @-144) than where it was created (ctor @+8). It's not desired, but a fix is not coming soon (requires custom ABI or using the Box Pass in more cases). "Expected" outcome statement assumes present-state on this matter. The good being illustrated is that observe, a user's function, sees an address (#3 @-136) that got a lifecycle maintenance call (copy ctor #3 @-136).
The anomaly is contained to phenomena seen by the RAII routines, meaning it's just a limitation on what kinds of RAII can be used along with returning by value.

Illustration of the issue with rvalue-to-reference promotion:

SUT has an anomaly that observe, a user's function, sees an address (#5 @-144) never before "made right" by lifecycle maintenance. So, an object that contains references into itself, which normally can be returned by value (provided that its RAII accommodates the baseline anomaly), presents garbage if the returned value is stored in a reference.

Change History (0)

Note: See TracTickets for help on using tickets.