Lua: Avoiding Stack Overflows With Metamethods

Today I want to talk about what I’ve seen as a common problem in Lua programs: causing crashes and eating up way too much memory due to mistakes in custom metamethods. For the examples I will be using the neat Twine library by Kurt Jarvi. However, I am not making this choice to single him out personally; I can’t count how many times I have made the mistake I am going to describe. I am only using his library as an example because it is fresh on my mind.

The Example Use-Case

Twine lets you create strings that have some additional functionality that what you get from Lua’s standard library of string functions. One of the neat aspects of Twine is how is handles accessing the elements of a string as if it were an array. Here is the relevant example from the README (slightly edited for readability and whitespace):

a = twine('Twine is nifty.')

print(a[2])    --> 'w'

-- NOTE: May not work in 5.1, but proved to work in at least 5.1.4+
print(#a)    --> 15

-- Equivalent of Lua string.find(a,'nifty') or JavaScript a.indexOf('nifty')
print(a['nifty'])    --> 10

I think it’s cool how the library supports indexing numbers and strings for indices and the semantic difference it gives each. The library accomplishes this by implementing a custom __index() metamethod. But the original implementation had a common bug that caused Lua to run out of memory.

The Problem

Note: For the rest of this article I will assume the reader is familiar with Lua’s concepts of metatables and metamethods. If not then there is good information in the official manual.

Keep in mind that Twine stores the string data in a property named value. With that said, let’s look at the original definition for __index():

local tmeta = {

    -- ...

__index = function(self,index)
    local ret = index
    if type(index) == 'number' then
        ret = string.sub(self.value,index,index)
    elseif type(index) == 'string' then
        ret = string.find(self.value,index)
    end
    return ret
end,

__tostring = function(self,arg)
    return self.value
end,

    -- ...

}

__index() looks at the type of its index argument to determine what it should return. The concept is both simple and slick. But it has a coding error, one which I’ve shot myself in the foot with many times.

Here is the problem: __index() access self.value. That will trigger the __index() metamethod on self, which in this case means it calls the same __index(). That tries to access self.value, which invokes the same __index(), which accesses self.value…. This leads to an unending chain of nested calls which eventually overflows the stack. I kept the __tostring() metamethod in the code above because it triggers the same problem.

A Solution

What we want to do is access self.value without triggering any metamethods. The standard Lua function getraw() exists for this very purpose. For example:

self.index            -- Calls __index(self, index)
rawget(self, "index") -- Returns self.index, avoiding all metamethods

The important take-away is this: inside of metamethods you should carefully consider using rawget() and rawset() to avoid creating an un-ending loop of metamethod calls. I blew up the stack a number of times in my Lua programs until this practice became a habit, heh. So try to keep it in mind whenever you’re writing a metatable to add custom metamethods to a table in your software.

Advertisements

Add Your Thoughts

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s