luau-lang/luau

Create a solution for `__iter` dropping the extra information passed to for loops

Hexcede opened this issue · 0 comments

Currently the __iter metamethod drops additional arguments in a for loop:

local myTable = setmetatable({}, {
    __iter = function(self, arg, ...)
        -- arg will be dropped, only self is passed
        return next, arg, ...
    end
})

-- 1 will be dropped, only myTable is passed (as self)
for _ in myTable, 1 do
    -- something
end

At first this may not appear to be an issue, however consider you intend to wrap around an arbitrary luau object, and you want to reproduce the behaviour of the original object you've wrapped 1:1 with how it behaved before. You can't do this because you have to pessimistically include an __iter metamethod for all of the values that may or may not have an __iter metamethod.

This is where the main part of my feature request ends, this communicates the problem, but, it doesn't communicate the why as much. I think this problem is niche enough that further justification is useful in understanding why accommodating for this in some way even makes sense in the first place, so, I've provided more context below about how exactly this comes up in practice (even though it is very niche) and why specifically I'd like to see this issue fixed.


This is useful in sandboxing (and in unit testing, though less so as you do have much more control in this situation and can make gaurantees) because it allows you to hook the base behaviour of an arbitrary luau value and fully reproduce the behaviour of the value regardless of what it is or where it came from. The information in the above example is lost if you pessimistically include an __iter metamethod, and, you have to since you can't be sure if the object you're targeting will contain an __iter and/or an __call and it can always include an __metatable.

Some Solutions

There are two solutions that come to mind for me.

The first solution I think would be to just pass the values from the for loop into __iter. Personally this would be my preferred solution, as it allows 1:1 reproduction of behaviour as expected. However this likely involves changing how the __iter works enough that it could be problematic.

A second alternative solution that comes to mind would be providing a way to test the presence of an __iter metamethod even when __metatable is set. This isn't as ideal because now you optimistically assume that the presence of an __iter metamethod won't change and have to proactively intercept changes to metatables you want to have coverage for, but this is arguably better than the problem being completely unsolvable, and it's a low effort fix with less edge cases to have to consider.

If this problem is so niche, why is it worth solving?

There are two main reasons that come to mind for me, firstly just that this is a usecase that luau unintentionally took away that I feel should still be considered, but, more importantly, sandboxing is a very useful tool for reverse engineering.

Consider the many pieces of obfuscated code that exist. These are present on Roblox in the wild (in non malicious contexts too) as well as in the context of exploit code. The ability to essentially peer into obfuscated code (which typically runs a small lua VM under the hood) to understand what it's doing and why is something that I have consistently found to be useful to me even if niche because it allows me to figure out what vulnerabilities exploit code is actually exploiting.

Being able to reproduce behaviours 1:1 so that potentially malicious exploit code can't detect that it's running in a sandbox and thwart your efforts to look inside it is important to be able to understand it.

Furthermore, there are just more games using code sandboxing on Roblox now than there used to be and 1:1 support with all existing luau code is pretty valuable to those games, even though they are still niche.