Opened 4 months ago
#272 new defect
Compiler crash when referencing generic member of generic struct
Reported by: | mlbrooks | Owned by: | |
---|---|---|---|
Priority: | major | Component: | cfa-cc |
Version: | 1.0 | Keywords: | |
Cc: |
Description
Possible overlap with #166, but the specific failure cases and symptom here are different. Some of the test-case driving presented here might help drive a similar breadth of cases for working #166.
Headline case:
forall( E & ) struct inner { E * item; }; forall( E & ) struct outer { inner(E) wrapped; }; forall(E &) void f( outer(E) & out, E * elem ) { out.wrapped.item = elem; }
ACUTAL: Assertion failure in Type::genericSubstitution()
during new-to-old AST conversion.
EXPECTED: Compiles successfully. If run with the harness below, prints -1.000000 3.140000 1.414000
.
The breadth of cases that follows illustrates when the issue does (not) happen. I don't expect you to read the preprocessor acrobatics, but I hope you can understand the -E output of each concrete case. The headline case above is a small formatting edit of the -E output of the no-defines case, whose explicit coordinates are -DRUNCASE=1 -DTYPECASE_INNER_GENER -DTYPECASE_OUTER_GENER -DTYPECASE_WRAPPER_TRAD
.
demo.cfa:
#if 1 < ((defined TYPECASE_INNER_GENER) + (defined TYPECASE_INNER_CONCR)) #error multiple TYPECASE_INNER_X #endif #if 1 < ((defined TYPECASE_OUTER_GENER) + (defined TYPECASE_OUTER_CONCR)) #error multiple TYPECASE_OUTER_X #endif #if 1 < ((defined TYPECASE_WRAPPER_TRAD) + (defined TYPECASE_WRAPPER_PLN9)) #error multiple TYPECASE_WRAPPER_X #endif #if (!defined TYPECASE_INNER_CONCR) && (!defined TYPECASE_INNER_GENER) #define TYPECASE_INNER_GENER #endif #if (!defined TYPECASE_OUTER_CONCR) && (!defined TYPECASE_OUTER_GENER) #define TYPECASE_OUTER_GENER #endif #if (!defined TYPECASE_WRAPPER_PLN9) && (!defined TYPECASE_WRAPPER_TRAD) #define TYPECASE_WRAPPER_TRAD #endif #define DEFTY_INNER_FRAG(T) struct inner { T * item; }; #if defined TYPECASE_INNER_GENER #define DEFTY_INNER forall( E & ) DEFTY_INNER_FRAG(E) #define TY_INNER(T) inner(T) #else #define DEFTY_INNER DEFTY_INNER_FRAG(float) #define TY_INNER(T) inner #endif #if defined TYPECASE_WRAPPER_TRAD #define DEFFLD(ty) ty wrapped; #define FLD(obj) obj.wrapped #else #define DEFFLD(ty) inline ty; #define FLD(obj) obj #endif #define DEFTY_OUTER_FRAG(T) struct outer { DEFFLD(TY_INNER(T)) }; #if defined TYPECASE_OUTER_GENER #define DEFTY_OUTER forall( E & ) DEFTY_OUTER_FRAG(E) #define TY_OUTER(T) outer(T) #define DEFFUNC(...) forall(E &) { __VA_ARGS__ } #else #define DEFTY_OUTER DEFTY_OUTER_FRAG(float) #define TY_OUTER(T) outer #define DEFFUNC(...) __VA_ARGS__ #endif #define GENTY_OUTER TY_OUTER(E) #if (defined TYPECASE_INNER_GENER) && (defined TYPECASE_OUTER_GENER) #define ELEM E #else #define ELEM float #endif DEFTY_INNER DEFTY_OUTER #if RUNCASE==4 || RUNCASE==5 || RUNCASE==6 || RUNCASE==7 DEFFUNC ( void rval_helper( ELEM * ) { // this helper is a nop // SUT action to be done as follow-up step } void lval_helper( ELEM * & tgt, ELEM * val ) { // this helper does the SUT action tgt = val; } void mockStep( ELEM * & tgt, ELEM * val ) { } ) // follow-up step for no-op variations // applies workaround B (assumes it succeeds) // (cases that use the mock action only illustrate wheteher the compiler claims success) #define MOCK_SUT_ACTION ((TY_INNER(ELEM)&)(FLD(out))).item = elem; #endif DEFFUNC ( void f( GENTY_OUTER & out, ELEM * elem ) { #if RUNCASE==1 // headline, maximally broken FLD(out).item = elem; #elif RUNCASE==2 // workaround A, successful sometimes (*&(FLD(out))).item = elem; #elif RUNCASE==3 // workaround B, successful always ((TY_INNER(ELEM)&)(FLD(out))).item = elem; #elif RUNCASE==4 // variation as top-level expr, not broken FLD(out).item; MOCK_SUT_ACTION #elif RUNCASE==5 // variation as simple r-value, not broken rval_helper( FLD(out).item ); MOCK_SUT_ACTION #elif RUNCASE==6 // variation as simple l-value, blocked by #166 lval_helper( FLD(out).item, elem ); #elif RUNCASE==7 // variation as hacked l-value, maximally broken // here, *& is a #166-workaround // looks like workaround A: coincidence? lval_helper( *& FLD(out).item, elem ); #else #error bad RUNCASE #endif } ) // Gives a demonstration of "compiler does right thing," as more than "compiler claims success." // Except for runcases 4,5. // While the innermost datum being a pointer adds complexity at the harness level, it removes a // confounding factor from the core repro: the failure happens with `forall(E&)`. Switching the // core datum to a non-pointer would make these declarations `forall(E)`, which would add boxing // to the polymorphic calls. int main() { TY_OUTER(float) obj; // `obj` content test point: because "proper" access via fields is SUT, // circumvent that for the test harness float * & objContTP = * (float **) & obj; // state 1, via TP: obj points to oldbox float oldbox = -1.0; objContTP = &oldbox; printf("%f ", *objContTP); // state 2, via SUT: obj points to newbox float newbox = 3.14; f(obj, &newbox); printf("%f ", *objContTP); // mutation in state 2: show state 2 has the pointing expected newbox = 1.414; printf("%f\n", *objContTP); }
run.sh:
dotest() { # (set -x; $cfa demo.cfa $RUNCASE $TYPECASE_INNER $TYPECASE_OUTER $TYPECASE_WRAPPER -E ) (set -x; $cfa demo.cfa $RUNCASE $TYPECASE_INNER $TYPECASE_OUTER $TYPECASE_WRAPPER && ./a.out) } # Failures that are success-adjacent, and successes that are failure-adjacent, are driven here. for RUNCASE in '-DRUNCASE=1' do for TYPECASE_INNER in '-DTYPECASE_INNER_GENER' '-DTYPECASE_INNER_CONCR' do for TYPECASE_OUTER in '-DTYPECASE_OUTER_GENER' '-DTYPECASE_OUTER_CONCR' do for TYPECASE_WRAPPER in '-DTYPECASE_WRAPPER_TRAD' '-DTYPECASE_WRAPPER_PLN9' do dotest done done done done for RUNCASE in '-DRUNCASE=2' '-DRUNCASE=3' do for TYPECASE_INNER in '-DTYPECASE_INNER_GENER' do for TYPECASE_OUTER in '-DTYPECASE_OUTER_GENER' do for TYPECASE_WRAPPER in '-DTYPECASE_WRAPPER_TRAD' '-DTYPECASE_WRAPPER_PLN9' do dotest done done done done for RUNCASE in '-DRUNCASE=4' '-DRUNCASE=5' '-DRUNCASE=6' '-DRUNCASE=7' do for TYPECASE_INNER in '-DTYPECASE_INNER_GENER' do for TYPECASE_OUTER in '-DTYPECASE_OUTER_GENER' do for TYPECASE_WRAPPER in '-DTYPECASE_WRAPPER_TRAD' do dotest done done done done # All combinations are driven here # "All combinations pass" is a fine condition for statisfaction. # for RUNCASE in '-DRUNCASE=1' '-DRUNCASE=2' '-DRUNCASE=3' '-DRUNCASE=4' '-DRUNCASE=5' '-DRUNCASE=6' '-DRUNCASE=7' # do # for TYPECASE_INNER in '-DTYPECASE_INNER_GENER' '-DTYPECASE_INNER_CONCR' # do # for TYPECASE_OUTER in '-DTYPECASE_OUTER_GENER' '-DTYPECASE_OUTER_CONCR' # do # for TYPECASE_WRAPPER in '-DTYPECASE_WRAPPER_TRAD' '-DTYPECASE_WRAPPER_PLN9' # do # dotest # done # done # done # done
Invoking ./run.sh
gives
ACUTAL: mix of crashes, as in headline-actual, and successful runs, as in headline-expected, according to the case summary following, except with RUNCASE=6 being an occurrence of #166
EXPECTED: always a successful run, as in headline-expected, except with RUNCASE=6 being either a successful run or an occurrence of #166
Summary of the cases and actual outcomes (to be read along with the -E dump):
- RUNCASE=1 is the vanilla version. Running it across varied generic/concrete options for TYPECASE_INNER and TYPECASE_OUTER shows that it only happens when both levels are generic. Running it across varied traditional/plan-9 options for TYPECASE_WRAPPER shows that it is unaffected by using a plan-9 wrapping of the inner within the outer.
- RUNCASE=2 (add
*&
) and RUNCASE=3 (add a cast to the desired reference type) are possible workarounds. Adding*&
works for a traditional wrapper but not for a plan-9 wrapper. Adding a cast always works. - The variations for RUNCASE in 4..7 show that no magic coming from the assignment operator is at issue, rather, that seeking a _reference_ to the contained element seems to be the problem (as in 6 and 7 being broken), while mere mention of the contained element works (as in 4 and 5 succeeding).