Opened 6 years ago
Last modified 2 weeks ago
#147 new defect
Can't initialize const member of struct
| Reported by: | mlbrooks | Owned by: | |
|---|---|---|---|
| Priority: | major | Component: | cfa-cc |
| Version: | 1.0 | Keywords: | |
| Cc: |
Description (last modified by )
[A, basic]
#ifdef HIDE_ERR
#define MAYBE_QUAL(...)
#else
#define MAYBE_QUAL(...) __VA_ARGS__
#endif
struct thing {
MAYBE_QUAL( const ) int foo;
};
void ?{}( thing & this ) {
(this.foo){ 5 };
}
int main() {
thing t;
printf("%d\n", t.foo);
}
A, expected, regardless of defines: compiles, runs, and prints 5
A, actual, no define: error: lvalue required as unary ‘&’ operand
A, actual, -DHIDE_ERR: (as expected)
When the bug occurs, CFA-cc passes bad C code to GCC; this code tries to take address of address. This behaviour occurs in spite of #166 having been fixed, though perhaps the technique of #166 can be applied in a different place to fix this issue.
Excerpt of -CFA, no define
((void)(((void)(_tmp_ctor_expr0=(*((unsigned long int **)(&(&(*_X4thisS6thingy_1)._X3fooKm_1)))
)
)
)
,(((void)((*_tmp_ctor_expr0)=((unsigned long int )5)) /* ?{} */)
,_tmp_ctor_expr0
)
)
);
Excerpt -f -CFA, -DHIDE_ERR
((void)(((void)(_tmp_ctor_expr0=( &(*_X4thisS6thingy_1)._X3foom_1
)
)
)
,(((void)((*_tmp_ctor_expr0)=((unsigned long int )5)) /* ?{} */)
,_tmp_ctor_expr0
)
)
);
By contrast, initializing a const field via the autogenerated member-wise constructor works:
[B, success corner]
struct thing {
const int foo;
};
int main() {
thing t{ 1234 };
printf("%d\n", t.foo);
}
B, actual and expected: compiles, runs, and prints 1234
We found a workaround that uses both field assignment (in place of field constructor call) and a const-cast:
[C, workaround]
struct thing {
const int foo;
};
void ?{}( thing & this ) {
* ( int * ) & ( this.foo ) = 5;
}
int main() {
thing t;
printf("%d\n", t.foo);
}
C, actual and expected: compiles, runs, and prints 5
Further variations of the basic pattern have been tried, with mixed success:
[D, mix-n-match]
#ifndef VAR_noqual
// default: include qualifiers
#define MAYBE_QUAL(...) __VA_ARGS__
#else
// variation: suppress qualifiers
#define MAYBE_QUAL(...)
#endif
#ifndef VAR_assign
// default: user's ctor calls field's ctor
#define INITIALIZE( THISFIELD, VAL ) ( THISFIELD ) { VAL }
#else
// variation: user's ctor calls field's assignment operator
#define INITIALIZE( THISFIELD, VAL ) ( THISFIELD ) = ( VAL )
#endif
#if ! defined VAR_concast1 && ! defined VAR_concast2
// default: user's field access is otherwise passthrough
#define MAYBE_CONCAST( TY, THISFIELD ) THISFIELD
#elif defined VAR_concast1 && ! defined VAR_concast2
// variation, sub 1: user's field access is via a CFA const cast
#define MAYBE_CONCAST( TY, THISFIELD ) ( TY & )( THISFIELD )
#elif ! defined VAR_concast1 && defined VAR_concast2
// variation, sub 1: user's field access is via a C const cast
#define MAYBE_CONCAST( TY, THISFIELD ) * ( TY * )( & ( THISFIELD ) )
#else
#error Bad 'concast' combination
#endif
#ifndef VAR_derefref
// default: passthrough
#define MAYBE_DEREFREF( THISFIELD ) THISFIELD
#else
// variation: take the address, then dereference it
// (has helped some cases of emitting "lvalue required" code)
#define MAYBE_DEREFREF( THISFIELD ) *&(THISFIELD)
#endif
#ifndef VAR_ctorparm
// default: user's ctor is parameterless; ctor's body hardcodes the value
#define MAYBE_CTORPARM( ... )
#define SWITCH_CTORPARM( CASE_NORMAL, CASE_VAR ) CASE_NORMAL
#else
// variation: user's ctor takes a `foo_val` param; program main hardcodes the value
#define MAYBE_CTORPARM( ... ) __VA_ARGS__
#define SWITCH_CTORPARM( CASE_NORMAL, CASE_VAR ) CASE_VAR
#endif
#ifndef VAR_with
// default: ctor body accesses the field via this-dot
#define SWITCH_WITH( CASE_NORMAL, CASE_VAR ) CASE_NORMAL
#else
// variation: ctor body accesses the field via `with`
#define SWITCH_WITH( CASE_NORMAL, CASE_VAR ) CASE_VAR
#endif
struct thing {
MAYBE_QUAL( const ) int foo;
};
void ?{}( thing & this MAYBE_CTORPARM(, int foo_val ) ) SWITCH_WITH( , with( this ) ) {
INITIALIZE( MAYBE_DEREFREF( MAYBE_CONCAST( int, SWITCH_WITH( this., ) foo ) ), SWITCH_CTORPARM( 42, foo_val ) );
}
int main() {
thing t{ MAYBE_CTORPARM( 999 ) };
printf("%d\n", t.foo);
}
D, all variation combinations, ideal: success, printing 5 or 999, as appropriate
D, expectation tolerance: as can be accepted case-by-case, rejection of some "ideal-valid" variation combinations may occur due to cfacc having to resort to conservative detection of "this appears to be a field initialization."
D, across variation combinations, actual: incomplete list follows
Some of the earlier tests are variation combinations from this general test, specifically:
- A is D with A's HIDE_ERROR being the same as D's VAR_noqual, and with the other VAR_* not defined [actual, VAR_noqual, pass; actual, none defined, fail]
- C is D with VAR_assign and VAR_concast2 defined, and the other VAR_* not defined [actual: pass]
However, in spite of the element of similarity, B is not D with VAR_ctorparm defined and the other VAR_* not defined. B (pass) uses an autogenerated constructor, while D with VAR_ctorparm defined (fail) uses a user-written constructor that tries to emulate B.
Every VAR_* option is believed to be at least a benign independent contribution, i.e. not inherently invalid. This is directly observable in all cases VAR_xxx, except VAR_concast2, defining both VAR_noqual (pass on its own) and VAR_xxx leaves a working program (VAR_noqual + VAR_xxx: pass). For VAR_concast2, its validity along with VAR_assign (as in test C) shows that it can be okay.
Even so, VAR_concast2 all alone is failing, with the same symptom as in A. No hypothesis generalizing this phenomenon is yet conceived.
VAR_assign alone: different error, from CFA resolver (before chance to generate bad code): ambiguous ?{} call, between regular and volatile; not remotely understood; have been unable to fix, with any (non)volatile combinations (these not shown)
VAR_assign + VAR_concast1: fixed, as for VAR_concast2 already discussed
VAR_derefref: different error still, also at resolver: ctor call is an invalid application
Except for VAR_noqual, each other VAR_*, on its own has some error. Except as just identified (VAR_assign and VAR_derefref), the symptom is "fails to fix the original symptom."
The switches VAR_ctorparm and VAR_with have never been seen to have an effect, i.e. these factors are independent.
Note that the -E and -CFA -XCFA,-p outputs from program D are quite readable. Looking at a -E can be easier than mentally expanding macros.
Elaborated description with recent testing outcomes and a newly found workaround. Increased priority because this issue was encountered during CS-343 assignment porting.