Tuesday, March 04, 2008

Unexpected behavior

Do the following and see what happens:

  1. Create a subclass of nil called Test.
  2. Implement doesNotUnderstand: as ^self.
  3. Evaluate Test new ifTrue: [4] ifFalse: [5].
Did it work? Why, exactly?

6 comments:

Reinout Heeck said...

Heh, something funny was bound to happen. Seasoned Smalltalkers know that the system cheats at the level of some special selectors, #ifTrue:ifFalse: is one of those.

If you put the code into a method of Test the behaviour will stay the same, then if you use the inspector to drill down into the methodDictionary of the class you can see the byte code of your method which will explain it all ;-)

Andres said...

Let's not forget that mustBeBoolean is answered by the object via DNU before jump false executes.

Andres.

Reinout Heeck said...

#mustBeBoolean is only sent by the simulator (e.g. when stepping with the debugger).

In my compiled method I see no send of it, and in my virgin 7.6 image the only sender of #mustBeBoolean is Context>>jump:if:.

(I could post the byte codes here, but I don't want to spoil this exercise for others ;-)

Reinout Heeck said...

I spoke too soon: I went over the VM sources and just learnt that the JIT generates an isBoolean check for the conditional branch byte codes, so it is natural that #mustBeBoolean is not visible in the byte code.

Andres said...

Technically, I do not feel the VM is cheating... jumpFalse could be implemented (in Smalltalk) as

false == thisContext topOfStack

and in that case, we would still have the same behavior without hitting DNU in Test.

Andres.

Mike Klein said...

Not quite... the DNU is interacting weirdly with the upcalled mustBeBoolean... try this code:

Smalltalk.Core defineClass: #Test
superclass: nil
indexedType: #none
private: false
instanceVariableNames: ' '
classInstanceVariableNames: ''
imports: ''
category: 'Kernel-Classes'.

Core.Test compile: 'doesNotUnderstand: message ^false '.
answerOne := Core.Test new ifTrue: [4 ] ifFalse: [5] .
Core.Test compile: 'mustBeBoolean ^false '.
answerTwo := Core.Test new ifTrue: [4 ] ifFalse: [5] .

answerOne -> answerTwo