Opened 3 years 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).