Niki
Niki

Reputation: 95

Browsing "Senders" in Finder also returns code that doesn't contain the message I'm searching for in Pharo?

Opened a fresh Pharo 5 image, opened Finder and searched for the + selector. Then selected the + and clicked Senders. Got about 5000 results, most of them seem fine, however upon closer inspection some results seem to missing any reference of my message send.

Not a few I might add, about 1700 of them seem to be erroneous and contain no reference of + what so ever. What do you think is the problem? Here's one example:

Example

Upvotes: 4

Views: 95

Answers (2)

Leandro Caniglia
Leandro Caniglia

Reputation: 14858

Just to clarify @Peter's answer a little bit:

The iteration

  1 to: 10 do: [:each | dictionary at: each put: each]

sends + 1 to the block variable each every time it iterates the block.

We do not see + 1 in the source code, but it actually happens behind the scenes. The reason why such a hidden send is found is that Smalltalk does not analyze the source code but the CompiledMethod and does that by looking at the literal frame and, as Peter says, also at the bytecodes.

For instance, by compiling and inspecting the method

m
  1 to: 10 do: [:i | i foo]

we can see that the intermediate representation Ir tab reads:

 1. label: 1
 2. pushLiteral: 1
 3. popIntoTemp: #i
 4. goto: 2

 5. label: 2
 6. pushTemp: #i
 7. pushLiteral: 10
 8. send: #'<='
 9. if: false goto: 4 else: 3

10. label: 3
11. pushTemp: #i
12. send: #foo
13. popTop
14. pushTemp: #i
15. pushLiteral: 1
16. send: #+                       "Here we sum 1 to i"
17. popIntoTemp: #i
18. goto: 2

19. label: 4
20. returnReceiver

Lines 15 and 16 represent the + 1 that actually takes place. Note that the symbol #+ is not in the literal frame (Raw tab of the inspector). It is not in the AST either because + is absent from the source code


Why this happens?

There are some special messages, #to:do: being one of them, that get inlined when your code sends them. Inlining a method means including its bytecodes in the sender rather than actually sending them.

For example, if your code reads

self foo.
1 to: 10 do: [:i | i foo].
self bar

your method has 4 sends: foo, to:do:, foo (again) and bar. However, the compiler will produce only 3 sends foo, foo and bar and will include the bytecodes of to:do: in place.

And given that to:do: needs to sum 1 to the block argument i, the send +, which is actually sent, is detected as occurring in your method (because it fact, it does).

However, if you rewrite the method as

m
  | block |
  block := [:i | i foo].
  self foo.
  1 to: 10 do: block
  self bar

the compiler will refuse to inline to:do: and will send it as a regular message. As a result your method will not be a sender of + any longer.


If to:do: is not sent, why my method is a sender of it?

This is a subtler question. As we have seen, when to:do: is inlined, it is not sent. How then it is possible that my method is recognized as a sender of to:do:?

Well, the reason is that the compiler will anyway add the symbol #to:do: to the literal frame of your method. And it will do that just for allowing your method to be found as a sender of to:do:. It doesn't matter whether your method actually sends to:do: or not, the inlining technique is just an optimization. At a higher level of abstraction we all want to see that our method is a "sender" of to:do:, hence the "trick" of adding it to the literal frame.

Upvotes: 4

Peter Uhnak
Peter Uhnak

Reputation: 10217

Update

The search goes both through the textual source code and the bytecode; so it shows you the method, because it found #+ in the bytecode, but the browser is too limited to show you both text and bytes, so it shows you only the text (even though the match was done on bytecode)

As for bytecode itself, Pharo (re)compiles a method every time you save it; so for example when you save the following method

Something>>loop
    1 to: 10 do: [ :each | ]

the system will compile it, and when you inspect the method and look at the bytecode, you will see this

enter image description here

You can write the bytecode by hand too I believe.


Original answer

Because for some special selectors it also looks through the bytecode, which you can see when you inspect the method itself.

enter image description here

This can be very easily discovered in less than a minute by looking through the code of Pharo itself (once you are more comfortable with Pharo):

  • #+ senders gives you the list of senders of a selector,
  • so you look at the implementation of senders and go through the available call chain (you can select selector with mouse or doubleclick and ctrl+n to show you the senders browser)
    • sendersallSendersOf:thoroughWhichSelectorsReferTo:
  • and there you see

.

thoroughWhichSelectorsReferTo: literal
    "Answer a set of selectors whose methods access the argument as a 
    literal. Dives into the compact literal notation, making it slow but 
    thorough "
    | selectors special byte |
    "for speed we check the special selectors here once per class"
    special := Smalltalk
        hasSpecialSelector: literal
        ifTrueSetByte: [ :value | byte := value ].
    selectors := OrderedCollection new.
    self selectorsAndMethodsDo: [ :sel :method |
            ((method refersToLiteral: literal) or: [special and: [method scanFor: byte]]) ifTrue: [selectors add: sel]].
    ^ selectors

Upvotes: 6

Related Questions