пятница, 15 апреля 2016 г.

Halt next object call

ObjectVirus allows very simple implementation of object centric debugging. We can infect given object by virus which will halt any message send.
Let's create new GhostBehaviour for this:

GHGhostBehaviour subclass: #GHObjectCallHalt
      instanceVariableNames: ''
      classVariableNames: ''
      package: 'Ghost-ObjectCallHalt'

GHGhostBehaviour>>send: aMessage to: anObject
       | method breakpoint |
       anObject recoverFromVirus.
       self halt.
       ^aMessage sendTo: anObject

First we disable interception behaviour to not halt any following messages (to get halt once logic). And then we halt message processing. It will open debugger on any message send. And to access target method "step into" button should be pressed few times in the debugger.
To activate such behaviour on objects we can create suitable method:

Object>>haltOnNextCall
       | virus |
       virus := GHObjectVirus behaviour: GHObjectCallHalt new.
       virus infect: self

And now we can play with this:

      point := 2@3.
      point haltOnNextCall.
      0@0 distanceTo: point

It will open debugger:


Of course we don't want to step into target method manually. We want to see target method just in the debugger. To achieve this we can set up breakpoint on target method and run it instead of halt message processing:

GHGhostBehaviour >>send: aMessage to: anObject
       | method breakpoint |
       anObject recoverFromVirus.
       method := anObject class lookupSelector: aMessage selector.

       breakpoint := Breakpoint new
             node: method ast;
             once.
       breakpoint link condition: [ :receiver | receiver == anObject ] arguments: #(#object).
       breakpoint install.

       ^aMessage sendTo: anObject

And with this change we will see what we want:


Debugger shows Point>>x method because #distanceTo: asks #x and #y of given point to compute distance:


Last thing which is not nice is that we see all message interception logic on the debugger stack view. Fortunately Pharo stack model allows context manipulation and we can really remove unneeded stack elements from execution context:

GHGhostBehaviour >>send: aMessage to: anObject
       | method breakpoint |
       anObject recoverFromVirus.
       method := anObject class lookupSelector: aMessage selector.

       breakpoint := Breakpoint new
             node: method ast;
             once.
       breakpoint link condition: [ :receiver | receiver == anObject ] arguments: #(#object).
       breakpoint install.

       self activateTargetMethodOf: aMessage for: anObject

GHGhostBehaviour >>activateTargetMethodOf: aMessage for: anObject
       | targetContext sender objectClass |
       sender := thisContext.
       [ sender selector == #cannotInterpret: ] whileFalse: [ sender := sender sender ].
       thisContext terminateTo: sender sender.
       "We should not use direct message to object because we propaply already install breakpoint to it"
       objectClass := GHMetaMessages extractClassOf: anObject.
       targetContext := thisContext sender
             activateMethod: (objectClass lookupSelector: aMessage selector)
             withArgs: aMessage arguments
             receiver: anObject
             class: objectClass.

       targetContext jump

It covers our tracks of "halt-virus" infection:


At the end we can extend Pharo debugger with new button "Run to next self send" which will resume process execution and halt on next call to current receiver:

ResumeDebugAction subclass: #GHRunToNextSelfCallDebugAction
       instanceVariableNames: ''
       classVariableNames: ''
       package: 'Ghost-ObjectCallHalt'

GHRunToNextSelfCallDebugAction >>defaultLabel

       ^ 'Run to next self call'

GHRunToNextSelfCallDebugAction >>id

       ^ #runToNextObjectCall

GHRunToNextSelfCallDebugAction class >>gtStackDebuggingActionFor: aDebugger
       <gtStackDebuggingAction>

       ^ (self forDebugger: aDebugger)
             icon: GLMUIThemeExtraIcons glamorousPlay

GHRunToNextSelfCallDebugAction >>executeAction

       self currentContext receiver haltOnNextCall.

       ^super executeAction.

And with these debugger will be looked like:


And if you press new button debugger will be opened on #y method of current point:


Code is hosted in Ghost repository. You can use it in your development process:

Metacello new
  baseline: 'Ghost';
  repository: 'github://dionisiydk/Ghost';
  load: 'ObjectCallHalt'

Комментариев нет:

Отправить комментарий