kikito / inspect.lua

Human-readable representation of Lua tables
https://github.com/kikito/inspect.lua
MIT License
1.38k stars 196 forks source link

Using inspect on table with __metatable of the same table, causes stack overflow and gettable loop #4

Closed hadrielk closed 11 years ago

hadrielk commented 11 years ago

If you have a table 'T', with a metatable with '__metatable' = T, then inspect.lua causes either a stack overflow or loop in gettable.
That scenario may sound odd, but I've seen some C-bindings do it that way.

The first problem is Inspector:countTableAppearances() walks through all tables, and calls getmetatable() and loops in that - since getmetatable() will return the same table, it will cause a stack overflow looping forever. To fix it, change the function to have a second argument:

function Inspector:countTableAppearances(t, isMeta)

and change this:

self:countTableAppearances(getmetatable(t))

to this:

if not isMeta then self:countTableAppearances(getmetatable(t), true) end

The second problem is inspect.lua's getToStringResultSafely() function tries to get the metatable's 'tostring' member, but really the metatable is in fact the original table, because getmetatable(T) called in Inspector:putTable(t) will return what 'metatable' is, rather than the real metatable - that's the point of the 'metatable' action, to hide the real metatable from Lua code. So when getToStringResultSafely() tries to fetch the mt.tostring, it invokes the real metatable's 'index' action, since 'tostring' is not a member of T. The Lua VM detects that as a loop. So... use rawget() instead, by changing this:

local __tostring = type(mt) == 'table' and mt.__tostring

to this:

local __tostring = type(mt) == 'table' and rawget(mt, '__tostring')
kikito commented 11 years ago

Hi there,

Thanks for reporting this.

The first part was easily understandable, and I was able to fix it quickly.

I'm confused about the second part of your issue though. I'm not clear about how to set things up in order to reproduce it. From what you have written, I gather it has to do with the __metatable metamethod. I've tried setting it up in several ways, but I did not get any stack overflow. This is my last attempt:

it('can invoke the __tostring method without stack overflowing', function()      
  local x = {}     
  setmetatable(x,x)    
  x.__metatable = x    
  assert.equals(inspect(x), [[<1>{     
  __metatable = <table 1>,                                                                                                                                                        
  <metatable> = <table 1>     
}]])    
end)

Can you provide an example that triggers the stackoverflow in the second case, so I can properly test it and fix it?

In case you where runnig the specs, please note that I've replaced telescope by busted in the main branch.

Thanks a lot,

kikito commented 11 years ago

Nevermind, I found it, and used your fix on the second bug.

Thanks a lot for taking the time to report this. Those were two difficult buggers to find!

hadrielk commented 11 years ago

Sure no problem - thanks for writing the script to begin with, it helped me out!


From: Enrique García notifications@github.com To: kikito/inspect.lua inspect.lua@noreply.github.com Cc: hadrielk hadrielk@yahoo.com Sent: Sunday, January 20, 2013 6:50 PM Subject: Re: [inspect.lua] Using inspect on table with __metatable of the same table, causes stack overflow and gettable loop (#4)

Nevermind, I found it, and used your fix on the second bug. Thanks a lot for taking the time to report this. Those were two difficult buggers to find! — Reply to this email directly or view it on GitHub.