[squeak-dev] References to a removed and reloaded class

Levente Uzonyi leves at elte.hu
Sat Sep 22 00:53:00 UTC 2012


On Fri, 21 Sep 2012, Eliot Miranda wrote:

> 
> 
> On Fri, Sep 21, 2012 at 1:13 PM, Levente Uzonyi <leves at elte.hu> wrote:
>       I guess we're going off the track with class renaming. The problem I'd like to solve is:
>       If we remove a class and then add a class to the system with the same name, then the methods which were referring to the class before the
>       removal are still referring to the same old class. This means that two bindings will coexist in the system with the same class name, but
>       pointing to different classes.
> 
> 
> Let me get this clear.  Surely the methods should still refer to the class named X after removing and adding back a class named X right?  That can (and
> should) be accomplished by moving the binding to Undeclared on removing it and usiong the binding when declaring the filed-in class.  This is the way the
> system used to work and should continue to work.

Right, this is how it should work, but the problem is that it's not 
working that way now (and I think it didn't in the past few years).

> 
>
>       How is class renaming related to this?
>       One of the possible solutions is to move the removed class's binding to Undeclared. When you rename a class, then Class >> #rename: will check
>       if there's a binding with the new class name in Undeclared. The problem here is that Undeclared doesn't release bindings automatically (when
>       they are not referenced by any methods anymore), so bindings gone long ago are still lingering in Undeclared, therefore Class >> #rename: (and
>       also Trait
>                   #rename:) will #inform: the user about it, which breaks one of the
>
>       tests.
> 
> 
> But that's not a problem.  The set-up for these tests can (and should) prune unreferenced bindings from Undeclared, either before running or before
> complaining of there being bindings in Undeclared.

It's not the tests which are complaining, but Class >> #rename:. If you 
patch SystemDictionary >> #forgetClass:logged: to move the binding to 
Undeclared and you run SystemChangeFileTest >> #testClassRenamed then 
when the code gets to Class >> #rename: the second time, then at the line

 	(Undeclared includesKey: newName)

there will be a binding in Undeclared which points to an unreferenced, 
already removed class.

Class >> #rename: aString
 	"The new name of the receiver is the argument, aString."

 	| oldName newName |
 	(newName := aString asSymbol) = (oldName := self name)
 		ifTrue: [^ self].
 	(self environment includesKey: newName)
 		ifTrue: [^ self error: newName , ' already exists'].
 	(Undeclared includesKey: newName)
 		ifTrue: [self inform: 'There are references to, ' , aString printString , 'from Undeclared. Check them after this change.'].
 	name := newName.
 	self environment renameClass: self from: oldName

The only solution I see to the issue is to perform #removeUnreferencedKeys 
before sending #includesKey: (or we can be tricky, and do it only if we 
find that the binding is there and recheck after to see if it's really 
there or not). And yes, I think it's a problem that Undeclared is holding 
onto that reference, because every place which uses #includesKey: or 
#at:ifAbsent: might get false information.

> 
>  
>       But there's another problem with this solution:
>       There's a test which tests if a removed class's method is still referencing to the class. So replacing the value of the binding with nil is
>       not possible.
> 
> 
> But that's a bogus test.  Of course the binding's value should be set to nil.  Either the class has been removed or renamed.  The only thing that makes

So a CompiledMethod should return nil for #methodClass if its class was 
removed instead of the obsolete Class object? (see CompiledMethodTest >> 
#testMethodClass)

> sense with remove is to set it to nil.  Rename is more difficult.  It makes sense to leave it pointing to the new class if the system is running
> interactively while the user edits methods to refer to the class under the new name. Tricky.
>
>       If we don't replace the value with nil, then the binding isn't really undeclared. So there will be bindings in Undeclared with non-nil value,
>       which might have side effects in the system.
>
>       How does the solution I suggested work:
>       - when a class is removed, then its binding will be thrown away
>       - all methods referring to the class are recompiled. This means that if any such method exists, then a new binding will be created in
>       Undeclared and all methods will use that new binding.
>       The drawback is that mass class removal will be slower. Removing a single class will have no noticable difference for the user (20-40ms+the
>       recompilation time).
> 
> 
> I don't see that this is usefully different from the existing behaviour.  There seem to me to be two cases, one is renaming a class that is key to the
> system functioning, say some class in the compiler.  If the binding is set to nil then the system will break on trying to compile edited versions of the
> methods that refer to the class.  That's tough, but one has to accept it.  The work-around is to clone the class under the new name (e.g. file-out, edit,
> file back in) and move all subclasses under the new class, and then remove the old class.  The other case is where the class isn't key and the system
> keeps running.  In either case, keeping the exsting binding and setting its value to nil is the correct behaviour; it maintains system integrity w.r.t.
> bindings.

My suggested solution only applies to class removal, I don't want to touch 
class renaming.

>  
>
>       For completeness, here's a list of problems I don't want to solve now:
>       - Undeclared holding onto bindings forever
> 
> 
> non-problem.  tests that test Undeclared must prune unreferenced bindings from Undeclared.  The cost is small.  F**ing up Undeclared to make the tests run
> fast is a case of the tail wagging the dog.

It's not about the tests at all, but the system and the way Undeclared is 
being used (see above). I had no intention to make any test faster, I just 
want them to pass. :)
And I think the way Undeclared is implemented is just wrong.

>  
>       - Undeclared introducing Associations for classes instead of ReadOnlyVariableBindings
> 
> 
> Again if the binding is preserved this won't happen.

Using #at:put: will create an Association, because Undeclared is just and 
IdentityDictionary.

>  
>       - how class renaming works
> 
> 
> has it changed?

I don't think so and I don't really want to deal with it right now, Colin 
will do it in 4.5 anyway.


Levente


More information about the Squeak-dev mailing list