Changes between Initial Version and Version 1 of Ticket #147


Ignore:
Timestamp:
Nov 12, 2025, 5:51:21 PM (2 weeks ago)
Author:
mlbrooks
Comment:

Elaborated description with recent testing outcomes and a newly found workaround. Increased priority because this issue was encountered during CS-343 assignment porting.

Legend:

Unmodified
Added
Removed
Modified
  • Ticket #147

    • Property Priority minormajor
  • Ticket #147 – Description

    initial v1  
    1 Declare struct thingy with field foo.  In the "broken" version, foo is const.  Declare a constructor for thingy that initializes foo from constant 5.  In the "broken" version, CFA-cc passes bad C code to GCC; this code tries to take address of address.
     1[A, basic]
     2{{{
     3#ifdef HIDE_ERR
     4    #define MAYBE_QUAL(...)
     5#else
     6    #define MAYBE_QUAL(...) __VA_ARGS__
     7#endif
    28
    3 cat scratch.cfa
    4 {{{
    5 struct thingy {
    6   #if broken
    7   const
    8   #endif
    9     size_t foo;
     9struct thing {
     10    MAYBE_QUAL( const ) int foo;
    1011};
    11 void ?{}(thingy &this) with(this) {
    12   foo{ 5 };
     12void ?{}( thing & this ) {
     13    (this.foo){ 5 };
     14}
     15int main() {
     16    thing t;
     17    printf("%d\n", t.foo);
    1318}
    1419}}}
    1520
    16 driver/cfa scratch.cfa -Dbroken
    17 error: lvalue required as unary ‘&’ operand
     21A, expected, regardless of defines:  compiles, runs, and prints 5
    1822
    19 driver/cfa scratch.cfa
    20 (works)
     23A, actual, no define:  `error: lvalue required as unary ‘&’ operand`
    2124
    22 driver/cfa scratch.cfa -Dbroken -CFA
     25A, actual, -DHIDE_ERR:  (as expected)
     26
     27When 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.
     28
     29Excerpt of `-CFA`, no define
    2330{{{
    2431((void)(((void)(_tmp_ctor_expr0=(*((unsigned long int **)(&(&(*_X4thisS6thingy_1)._X3fooKm_1)))
     
    3340}}}
    3441
    35 driver/cfa scratch.cfa -CFA
     42Excerpt -f `-CFA`, -DHIDE_ERR
    3643{{{
    3744((void)(((void)(_tmp_ctor_expr0=(                           &(*_X4thisS6thingy_1)._X3foom_1
     
    4552);
    4653}}}
     54
     55By contrast, initializing a const field via the autogenerated member-wise constructor works:
     56
     57[B, success corner]
     58{{{
     59struct thing {
     60    const int foo;
     61};
     62int main() {
     63    thing t{ 1234 };
     64    printf("%d\n", t.foo);
     65}
     66}}}
     67
     68B, actual and expected:   compiles, runs, and prints 1234
     69
     70We found a workaround that uses both field assignment (in place of field constructor call) and a const-cast:
     71
     72[C, workaround]
     73{{{
     74struct thing {
     75    const int foo;
     76};
     77void ?{}( thing & this ) {
     78    * ( int * ) & ( this.foo ) = 5;
     79}
     80int main() {
     81    thing t;
     82    printf("%d\n", t.foo);
     83}
     84}}}
     85
     86C, actual and expected:   compiles, runs, and prints 5
     87
     88Further variations of the basic pattern have been tried, with mixed success:
     89[D, mix-n-match]
     90{{{
     91#ifndef VAR_noqual
     92    // default: include qualifiers
     93    #define MAYBE_QUAL(...) __VA_ARGS__
     94#else
     95    // variation: suppress qualifiers
     96    #define MAYBE_QUAL(...)
     97#endif
     98
     99#ifndef VAR_assign
     100    // default: user's ctor calls field's ctor
     101    #define INITIALIZE( THISFIELD, VAL ) ( THISFIELD ) { VAL }
     102#else
     103    // variation: user's ctor calls field's assignment operator
     104    #define INITIALIZE( THISFIELD, VAL ) ( THISFIELD ) = ( VAL )
     105#endif
     106
     107#if ! defined VAR_concast1  &&  ! defined VAR_concast2
     108    // default: user's field access is otherwise passthrough
     109    #define MAYBE_CONCAST( TY, THISFIELD ) THISFIELD
     110#elif defined VAR_concast1  &&  ! defined VAR_concast2
     111    // variation, sub 1: user's field access is via a CFA const cast
     112    #define MAYBE_CONCAST( TY, THISFIELD ) ( TY & )( THISFIELD )
     113#elif ! defined VAR_concast1  &&  defined VAR_concast2
     114    // variation, sub 1: user's field access is via a C const cast
     115    #define MAYBE_CONCAST( TY, THISFIELD ) * ( TY * )( & ( THISFIELD ) )
     116#else
     117    #error Bad 'concast' combination
     118#endif
     119
     120#ifndef VAR_derefref
     121    // default: passthrough
     122    #define MAYBE_DEREFREF( THISFIELD ) THISFIELD
     123#else
     124    // variation: take the address, then dereference it
     125    // (has helped some cases of emitting "lvalue required" code)
     126    #define MAYBE_DEREFREF( THISFIELD ) *&(THISFIELD)
     127#endif
     128
     129#ifndef VAR_ctorparm
     130    // default: user's ctor is parameterless; ctor's body hardcodes the value
     131    #define MAYBE_CTORPARM( ... )
     132    #define SWITCH_CTORPARM( CASE_NORMAL, CASE_VAR ) CASE_NORMAL
     133#else
     134    // variation: user's ctor takes a `foo_val` param; program main hardcodes the value
     135    #define MAYBE_CTORPARM( ... ) __VA_ARGS__
     136    #define SWITCH_CTORPARM( CASE_NORMAL, CASE_VAR ) CASE_VAR
     137#endif
     138
     139#ifndef VAR_with
     140    // default: ctor body accesses the field via this-dot
     141    #define SWITCH_WITH( CASE_NORMAL, CASE_VAR ) CASE_NORMAL
     142#else
     143    // variation: ctor body accesses the field via `with`
     144    #define SWITCH_WITH( CASE_NORMAL, CASE_VAR ) CASE_VAR
     145#endif
     146
     147struct thing {
     148    MAYBE_QUAL( const ) int foo;
     149};
     150void ?{}( thing & this  MAYBE_CTORPARM(, int foo_val ) ) SWITCH_WITH( , with( this ) ) {
     151    INITIALIZE( MAYBE_DEREFREF( MAYBE_CONCAST( int, SWITCH_WITH( this., ) foo ) ), SWITCH_CTORPARM( 42, foo_val ) );
     152}
     153int main() {
     154    thing t{ MAYBE_CTORPARM( 999 ) };
     155    printf("%d\n", t.foo);
     156}
     157}}}
     158
     159D, all variation combinations, ideal: success, printing 5 or 999, as appropriate
     160
     161D, 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."
     162
     163D, across variation combinations, actual: incomplete list follows
     164
     165Some of the earlier tests are variation combinations from this general test, specifically:
     166- 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]
     167- C is D with VAR_assign and VAR_concast2 defined, and the other VAR_* not defined [actual: pass]
     168
     169However, 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.
     170
     171Every 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.
     172
     173Even so, VAR_concast2 all alone is failing, with the same symptom as in A.  No hypothesis generalizing this phenomenon is yet conceived.
     174
     175VAR_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)
     176
     177VAR_assign + VAR_concast1: fixed, as for VAR_concast2 already discussed
     178
     179VAR_derefref: different error still, also at resolver: ctor call is an invalid application
     180
     181Except 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."
     182
     183The switches VAR_ctorparm and VAR_with have never been seen to have an effect, i.e. these factors are independent.
     184
     185Note 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.