Skip to content

Commit a82719f

Browse files
committed
Fix temp var destruction in static initializers
For static vars: static x as ClassUdt = ClassUdt( 123 ) we produce code like this: static inited as integer if inited = FALSE then inited = TRUE x.constructor( 123 ) end if i.e. the initialization code is wrapped in an If block so the variable is only initialized once. This did not take temp vars in the initializer expression into account; they were destroyed by the astBuildBranch() in hWrapInStaticFlag(), resulting in code like this: static inited as integer dim temp as ... condition = (inited = FALSE) temp.destructor() if condition then inited = TRUE temp.constructor(...) x.constructor( temp ) end if This is obviously broken since the temp var is destroyed too early, before even being initialized. To fix this, I adjusted the astBuildBranch() call to no longer call the dtors, and instead, astDtorListFlush() is now done manually on the initialization code path. Furthermore, cVarDecl() needs to use LINKs and a single astAdd() instead of multiple astAdd()s for adding the DECL node and the initialization code. Besides initializer expressions, there can also be array bounds expressions with temp vars in a static var decl (in case of static dynamic arrays), this also works properly now. (hWrapInStaticFlag() is also used for the Redim calls used to initialize the static dynamic arrays) (cherry picked from commit a673e44)
1 parent ae0624e commit a82719f

File tree

3 files changed

+120
-15
lines changed

3 files changed

+120
-15
lines changed

changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Version 1.02.1
1818
- Using the -asm att|intel option for non-x86[_64] targets now triggers an error
1919
- C backend: -masm=... will now only be passed to gcc for x86[_64] targets
2020
- Screen didn't return an error code if it failed
21+
- Bad code generated for temporary variable destruction in static variable initialization: temporary strings or UDTs with destructors could be destroyed before even being initialized
2122

2223

2324
Version 1.02.0

src/compiler/parser-decl-var.bas

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -920,19 +920,47 @@ private function hWrapInStaticFlag( byval code as ASTNODE ptr ) as ASTNODE ptr
920920
symbSetIsImplicit( flag )
921921
t = astNewDECL( flag, TRUE )
922922

923+
'' Since this will wrap the initialization code in an If block, we have
924+
'' to take special care of temp vars from the initialization
925+
'' expression(s). Their dtors should no longer be called at the end of
926+
'' the whole vardecl statement, but only on the initialization code
927+
'' path.
928+
''
929+
'' Expressions parsed by cVarDecl():
930+
'' * var initializers
931+
'' * array bounds (exprTB) for Redim
932+
''
933+
'' These can all have temp vars, but luckily they all belong to the
934+
'' initialization step, which means that we don't have to use dtor list
935+
'' scopes/cookies for precise control, but can safely assume that the
936+
'' *whole* dtor list represents these and only these expressions.
937+
''
938+
'' This is somewhat similar to astNewIIF()'s handling of true/false code
939+
'' paths, except that here the expression(s) aren't split up into
940+
'' multiple code paths, but rather "moved" into another code path as a
941+
'' whole. (that's another reason why it shouldn't be necessary to use
942+
'' dtor list scopes/cookies here)
943+
923944
'' if flag = 0 then
945+
'' Building this branch without calling temp var dtors, because those
946+
'' belong to the <code>, not to this branch condition. Luckily this
947+
'' condition expression is trivial and won't have temp vars itself.
924948
label = symbAddLabel( NULL )
925949
t = astNewLINK( t, _
926950
astBuildBranch( _
927951
astNewBOP( AST_OP_EQ, astNewVAR( flag ), astNewCONSTi( 0 ) ), _
928-
label, FALSE ) )
952+
label, FALSE, TRUE ) )
929953

930954
'' flag = 1
931955
t = astNewLINK( t, astBuildVarAssign( flag, 1 ) )
932956

933957
'' <code>
934958
t = astNewLINK( t, code )
935959

960+
'' Destroy currently registered temp vars (should be only those from the
961+
'' <code>) on this code path only
962+
t = astNewLINK( t, astDtorListFlush( ) )
963+
936964
'' end if
937965
function = astNewLINK( t, astNewLABEL( label ) )
938966
end function
@@ -1579,22 +1607,25 @@ function cVarDecl _
15791607
''
15801608
if( sym <> NULL ) then
15811609
if( token <> FB_TK_EXTERN ) then
1610+
'' Build a LINK'ed tree of all code that should
1611+
'' be astAdd'ed here in place of the vardecl.
1612+
'' (early astAdd's would cause problems with temp var dtors)
1613+
dim t as ASTNODE ptr
1614+
15821615
'' not declared already?
15831616
if( is_declared = FALSE ) then
15841617
dim as FBSYMBOL ptr desc = NULL
1585-
dim as ASTNODE ptr var_decl = NULL
15861618

15871619
'' Don't init if it's a temp FOR var, it will
15881620
'' have the start condition put into it.
1589-
var_decl = astNewDECL( sym, _
1590-
((initree = NULL) and (not is_fordecl)) )
1621+
t = astNewDECL( sym, ((initree = NULL) and (not is_fordecl)) )
15911622

15921623
'' add the descriptor too, if any
15931624
desc = symbGetArrayDescriptor( sym )
15941625
if( desc <> NULL ) then
15951626
'' Note: descriptor may not have an initree here, in case it's COMMON
15961627
'' FIXME: should probably not add DECL nodes for COMMONs/SHAREDs in the first place (not done for EXTERNs either)
1597-
var_decl = astNewLINK( var_decl, astNewDECL( desc, (symbGetTypeIniTree( desc ) = NULL) ) )
1628+
t = astNewLINK( t, astNewDECL( desc, (symbGetTypeIniTree( desc ) = NULL) ) )
15981629
end if
15991630

16001631
'' handle arrays (must be done after adding the decl node)
@@ -1606,7 +1637,7 @@ function cVarDecl _
16061637

16071638
'' flush the decl node, safe to do here as it's a non-static
16081639
'' local var (and because the decl must be flushed first)
1609-
var_decl = hFlushDecl( var_decl )
1640+
t = hFlushDecl( t )
16101641

16111642
'' bydesc array params have no descriptor
16121643
if( desc <> NULL ) then
@@ -1615,7 +1646,7 @@ function cVarDecl _
16151646
'' even if unscoped, more so than to the array's own initializer.
16161647
'' * This must be LINK'ed to avoid astAdd() which would destroy temp vars from the
16171648
'' array initializer too early.
1618-
var_decl = astNewLINK( var_decl, astTypeIniFlush( desc, symbGetTypeIniTree( desc ), FALSE, AST_OPOPT_ISINI ) )
1649+
t = astNewLINK( t, astTypeIniFlush( desc, symbGetTypeIniTree( desc ), FALSE, AST_OPOPT_ISINI ) )
16191650
symbSetTypeIniTree( desc, NULL )
16201651
end if
16211652
end if
@@ -1626,23 +1657,22 @@ function cVarDecl _
16261657

16271658
if( fbLangOptIsSet( FB_LANG_OPT_SCOPE ) ) then
16281659
'' flush the init tree (must be done after adding the decl node)
1629-
astAdd( hFlushInitializer( sym, var_decl, initree, has_dtor ) )
1660+
t = hFlushInitializer( sym, t, initree, has_dtor )
16301661
'' unscoped
16311662
else
16321663
'' flush the init tree (must be done after adding the decl node)
1633-
astAddUnscoped( hFlushInitializer( sym, var_decl, initree, has_dtor ) )
1664+
astAddUnscoped( hFlushInitializer( sym, t, initree, has_dtor ) )
1665+
t = NULL
16341666

16351667
'' initializer as assignment?
16361668
if( assign_initree ) then
1637-
dim as ASTNODE ptr assign_vardecl = any
1638-
16391669
'' clear it before it's initialized?
16401670
if( symbGetVarHasDtor( sym ) ) then
1641-
astAdd( astBuildVarDtorCall( sym, TRUE ) )
1671+
t = astNewLINK( t, astBuildVarDtorCall( sym, TRUE ) )
16421672
end if
16431673

16441674
'' use the initializer as an assignment
1645-
astAdd( astTypeIniFlush( sym, assign_initree, FALSE, AST_OPOPT_ISINI ) )
1675+
t = astNewLINK( t, astTypeIniFlush( sym, assign_initree, FALSE, AST_OPOPT_ISINI ) )
16461676
end if
16471677
end if
16481678
end if
@@ -1664,9 +1694,10 @@ function cVarDecl _
16641694
redimcall = hWrapInStaticFlag( redimcall )
16651695
end if
16661696

1667-
astAdd( redimcall )
1668-
redimcall = NULL
1697+
t = astNewLINK( t, redimcall )
16691698
end if
1699+
1700+
astAdd( t )
16701701
end if
16711702
end if
16721703

tests/structs/temp-var-dtors.bas

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3222,6 +3222,29 @@ namespace tempUdtArg
32223222
function = type( x.i )
32233223
end function
32243224

3225+
type MyStaticVar
3226+
i as integer
3227+
declare constructor( byref as ClassUdt )
3228+
end type
3229+
3230+
constructor MyStaticVar( byref rhs as ClassUdt )
3231+
end constructor
3232+
3233+
sub staticVarInitExpr( )
3234+
'' Static var initialization is wrapped in an If block that
3235+
'' ensures that the static var is only initialized during the
3236+
'' first call. This must handle temp vars in the initialization
3237+
'' code properly.
3238+
static x1 as MyStaticVar = MyStaticVar( ClassUdt( 123 ) )
3239+
end sub
3240+
3241+
sub staticRedimBoundsExpr( )
3242+
'' Static dynamic arrays with non-constant array bounds
3243+
'' expressions, that will be initialized with a Redim call,
3244+
'' are also affected
3245+
static array(0 to fInteger( ClassUdt( 1 ) )) as integer
3246+
end sub
3247+
32253248
sub test cdecl( )
32263249
begin( )
32273250
select case( fInteger( ClassUdt( 111 ) ) )
@@ -3284,6 +3307,16 @@ namespace tempUdtArg
32843307
next
32853308
CU_ASSERT( count = 5 )
32863309
check( 1, 0, 1 )
3310+
3311+
begin( )
3312+
staticVarInitExpr( ) '' 1st call (init done here)
3313+
staticVarInitExpr( ) '' 2nd call (no init anymore)
3314+
check( 1, 0, 1 )
3315+
3316+
begin( )
3317+
staticRedimBoundsExpr( ) '' 1st call (init done here)
3318+
staticRedimBoundsExpr( ) '' 2nd call (no init anymore)
3319+
check( 1, 0, 1 )
32873320
end sub
32883321
end namespace
32893322

@@ -3310,6 +3343,36 @@ namespace tempStringArg
33103343
function = type( valint( s ) )
33113344
end function
33123345

3346+
type MyStaticVar
3347+
i as integer
3348+
declare constructor( byref as string )
3349+
end type
3350+
3351+
constructor MyStaticVar( byref rhs as string )
3352+
end constructor
3353+
3354+
sub staticVarInitExpr( )
3355+
'' Static var initialization is wrapped in an If block that
3356+
'' ensures that the static var is only initialized during the
3357+
'' first call. This must handle temp vars in the initialization
3358+
'' code properly.
3359+
static x1 as MyStaticVar = MyStaticVar( "abc" )
3360+
3361+
'' This was fairly reliable in triggering a crash, so it's probably a good test
3362+
static x2(0 to ...) as MyStaticVar => { _
3363+
"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", _
3364+
"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", _
3365+
"1", "2", "3", "4", "5", "6", "7", "8", "9", "10" _
3366+
}
3367+
end sub
3368+
3369+
sub staticRedimBoundsExpr( )
3370+
'' Static dynamic arrays with non-constant array bounds
3371+
'' expressions, that will be initialized with a Redim call,
3372+
'' are also affected
3373+
static array(0 to fInteger( "1" )) as integer
3374+
end sub
3375+
33133376
sub test cdecl( )
33143377
select case( fInteger( "111" ) )
33153378
case 111
@@ -3362,6 +3425,16 @@ namespace tempStringArg
33623425
next
33633426
CU_ASSERT( count = 5 )
33643427
end scope
3428+
3429+
'' Static initialization tests with temp strings: the code here
3430+
'' shouldn't crash (other than that we can't test much, since
3431+
'' we can't count calls to fb_StrDelete() etc. - but we have
3432+
'' the tempUdtArg tests for that anyways)
3433+
staticVarInitExpr( ) '' 1st call (init done here)
3434+
staticVarInitExpr( ) '' 2nd call (no init anymore)
3435+
3436+
staticRedimBoundsExpr( ) '' 1st call (init done here)
3437+
staticRedimBoundsExpr( ) '' 2nd call (no init anymore)
33653438
end sub
33663439
end namespace
33673440

0 commit comments

Comments
 (0)