autopsy of a nasty environment bug

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

autopsy of a nasty environment bug

Nicolas Cellier

    "Create a foreign environment importing all from Smalltalk globals"
    foreign := Environment withName: #Foreign.
    foreign exportSelf.
    foreign import: Smalltalk globals.

    "Bind a new global"
    fooKey := #ClassVarScopeFoo.
    fooValue := Smalltalk globals at: fooKey put: Object basicNew.
    fooBinding := Smalltalk globals bindingOf: fooKey.
    self assert: (foreign bindingOf: fooKey) == fooBinding.

    "Unbind the global: it is moved to, and shared by both undeclared"
    Smalltalk globals removeKey: fooKey.
    self assert: (Smalltalk globals undeclared associationAt: fooKey) == fooBinding.
    self assert: (foreign undeclared associationAt: fooKey) == fooBinding.

    "Create a class var: the undeclared binding is moved to classPool.
    This is questionable, it was a global with potentially larger/different scope."
    parent := Object
        subclass: #ClassVarScopeParent
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'Dummy-Tests-Class'.
    child := parent
        subclass: #ClassVarScopeChild
        instanceVariableNames: ''
        classVariableNames: 'ClassVarScopeFoo'
        poolDictionaries: ''
        category: 'Dummy-Tests-Class'.
    self assert: (child classPool associationAt: fooKey) == fooBinding.
   
    "The global is no more in Smalltalk globals undeclared"
    self assert: (Smalltalk globals undeclared includesKey: fooKey) not.
    "But it is still in foreign undeclared"
    self assert: (foreign undeclared associationAt: fooKey) == fooBinding.
   
    "Rebind a new global"
    fooValue := Smalltalk globals at: fooKey put: Object basicNew.
    globalFooBinding := Smalltalk globals bindingOf: fooKey.
   
    "The binding has been removed from foreign undeclared"
    self assert: (foreign undeclared includesKey: fooKey) not.
    self assert: (foreign bindingOf: fooKey) == globalFooBinding.
   
    "But because #showBinding: did use a becomeForward: the class pool and global bindings are now surprisingly fused.
    That explains that a foreign environment importing Smalltalk globals is enough to make ClassVarScopeTest fail"
    self assert: globalFooBinding == fooBinding.
    self assert: (child classPool associationAt: fooKey) == globalFooBinding.
   
    "save our souls"
    classes := { child. parent }.
    child := parent := nil.
    classes do: [ :each |
        each
            removeFromChanges;
            removeFromSystemUnlogged ].
    classes := nil.
    Smalltalk globals removeKey: fooKey.
    foreign destroy.


Reply | Threaded
Open this post in threaded view
|

Re: autopsy of a nasty environment bug

Nicolas Cellier
Some lessons from this snippet:
- if several undeclared share the same binding, then we should rebind all at once, not just some
- the undeclared shall better point weakly to the bindings, letting them being garbaged collected if possible
- what do we expect when we have two unrelated bindings #Foo->nil in envA undeclared and envB undeclared, then import envA into envB (envB import: envA)?
  I would expect (envB undeclared associationAt: #Foo) becomeForward: (envA undeclared associationAt: #Foo)...
- moving a binding from undeclared to some classPool/sharedPool (PoolDictionary) is completely ignoring the variable scope by now, which may lead to erroneous re-binding.

I don't see obvious solution for the last point (testing that all references to the binding are reachable by the defining class scope thru methodDictionary and subclasses methodDictionary?).
For PoolDictionary, it's even more tough (going thru all classes sharing this poolDictionary).

I didn't see any attempt at handling the case when both environment A & B declare a variable Foo, and environment C imports both A and B...
This is another problem but may lead to other requirements on undeclared handling.

2016-10-25 10:30 GMT+02:00 Nicolas Cellier <[hidden email]>:

    "Create a foreign environment importing all from Smalltalk globals"
    foreign := Environment withName: #Foreign.
    foreign exportSelf.
    foreign import: Smalltalk globals.

    "Bind a new global"
    fooKey := #ClassVarScopeFoo.
    fooValue := Smalltalk globals at: fooKey put: Object basicNew.
    fooBinding := Smalltalk globals bindingOf: fooKey.
    self assert: (foreign bindingOf: fooKey) == fooBinding.

    "Unbind the global: it is moved to, and shared by both undeclared"
    Smalltalk globals removeKey: fooKey.
    self assert: (Smalltalk globals undeclared associationAt: fooKey) == fooBinding.
    self assert: (foreign undeclared associationAt: fooKey) == fooBinding.

    "Create a class var: the undeclared binding is moved to classPool.
    This is questionable, it was a global with potentially larger/different scope."
    parent := Object
        subclass: #ClassVarScopeParent
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'Dummy-Tests-Class'.
    child := parent
        subclass: #ClassVarScopeChild
        instanceVariableNames: ''
        classVariableNames: 'ClassVarScopeFoo'
        poolDictionaries: ''
        category: 'Dummy-Tests-Class'.
    self assert: (child classPool associationAt: fooKey) == fooBinding.
   
    "The global is no more in Smalltalk globals undeclared"
    self assert: (Smalltalk globals undeclared includesKey: fooKey) not.
    "But it is still in foreign undeclared"
    self assert: (foreign undeclared associationAt: fooKey) == fooBinding.
   
    "Rebind a new global"
    fooValue := Smalltalk globals at: fooKey put: Object basicNew.
    globalFooBinding := Smalltalk globals bindingOf: fooKey.
   
    "The binding has been removed from foreign undeclared"
    self assert: (foreign undeclared includesKey: fooKey) not.
    self assert: (foreign bindingOf: fooKey) == globalFooBinding.
   
    "But because #showBinding: did use a becomeForward: the class pool and global bindings are now surprisingly fused.
    That explains that a foreign environment importing Smalltalk globals is enough to make ClassVarScopeTest fail"
    self assert: globalFooBinding == fooBinding.
    self assert: (child classPool associationAt: fooKey) == globalFooBinding.
   
    "save our souls"
    classes := { child. parent }.
    child := parent := nil.
    classes do: [ :each |
        each
            removeFromChanges;
            removeFromSystemUnlogged ].
    classes := nil.
    Smalltalk globals removeKey: fooKey.
    foreign destroy.