Why is this printing anything?

setfenv(function()end,setmetatable({},{__index=print}))()

It’s printing
table: 1CF65F98 script table: 1CF65F98 script

blob.png

edit: bug???

maybe that’s why? XD

but why is __index being invoked

I never use meta tables, so i can’t answer that unfortunately.

Seems like setfenv is doing something in the background that has to access the “script” key in the environment of the script twice after setting it.

setfenv(
	function()
		(function(f)return f(f)end)(function(f)
			if tick()-t>1 then
				return
			end
			print("a")
			return f(f)
		end)
	end,
	setmetatable({tick=tick,t=tick(),print=print},{__index=print})
)()

why is it invoking __index so much
table: 1A213648 script table: 1A213648 script table: 1A213648 script table: 1A213648 script table: 1A213648 script table: 1A213648 script table: 1A213648 script a table: 1A213648 script table: 1A213648 script table: 1A213648 script ...

it’s causing stack overflows that would otherwise not occur

local c,tick,error=tick(),tick,error
setfenv(1,setmetatable({},{
	__index=function(t,k)
		--print(t,k)
		return rawget(t,k)
	end})
);
(function(f)
	return f(f)
end)(function(f)
	if tick()-c>1 then
		error("lag")
	end 
	return f(f)
end)

this should error “lag” but it doesn’t

More weirdness:

setfenv(function() end, setmetatable({ script = 'a' }, { __index = print }))()

No output. Setting script = script also bypasses not having any output.

That’s because it is stack overflowing. Just having the overhead of one tick call per function call isn’t enough to make it not stack overflow in one second. Removing the bit where you setfenv would still cause a stack overflow. Simply adding another tick call (maybe, but a wait would definitely do it) should be enough to change that error.

Wait… it errors “script” when I add another tick?

Hold up, what!?

I’m confused as to why rawget is erroring “script”.

local function test()
	setfenv(1,env);
	local a
	local a = a
	local a = a
	local a = a
end

Analyzed bytecode:

Instructions : 9 {
         OP=GETGLOBAL A=0 Bx=0 (Line=43)
         OP=LOADK A=1 Bx=1 (Line=43)
         OP=GETUPVAL A=2 B=0 C=0 (Line=43)
         OP=CALL A=0 B=3 C=1 (Line=43)
         OP=LOADNIL A=0 B=0 C=0 (Line=44)
         OP=MOVE A=1 B=0 C=0 (Line=45)
         OP=MOVE A=2 B=1 C=0 (Line=46)
         OP=MOVE A=3 B=2 C=0 (Line=47)
         OP=RETURN A=0 B=1 C=0 (Line=48)
     }

Times __index(env,“script”) got called (line + supposed instruction):

Line 44: none (start of function?)
Line 44: LOADNIL
Line 45: MOVE
Line 46: MOVE
Line 47: MOVE
Line 48: RETURN

Apparently this __index thing fires for every instruction and when a function starts.
(In my example everything after the CALL for setfenv(0,env) as the env wasn’t set yet)

No idea why they do that, as it doesn’t seem to be used…

I don’t think this should be giving me a stack overflow
local c=tick();(function(f)return f(f)end)(function(f)if tick()-c>1 then error("Finished")end return f(f)end)

It should, unless it takes more than a second to overflow the stack.
A stack overflow after 16384 function calls, which would take way less than a second.
(I thought you could only have 200 stacks, but I just tested it: 2^14 stacks, aka 16384)
(well in studio, at least, I assume it’s the same online)

1 Like

I would think it should if I just wrote f(f) instead of return f(f) because tail calls.

There are no tailcalls in ROBLOX

This only happens in identities 2 and 3, so my guess about it being related to security rings true. Random guess: Each instruction needs to be associated with a script in order for the interpreter to agree to execute it.

“So why/how does my code still run when script is undefined?” The interpreter apparently falls back to a slower verification method which doesn’t require a script reference. In practice, this means that instructions are processed roughly twice as slow as they normally would be with script-based verification.

Code run in higher identities doesn’t seem to go through the same verification process. Practically, that means that Lua code executed in the command bar or in a plugin is 3-4 times faster than the same code executed in a normal script.

Everything I just wrote is a guess based off of running benchmarks in a few different places. Here’s code to test with

--compare results running from the command bar vs a script
local forceFallbackCheck = true --slower fallback verification
--~2x slower when =true and running in identities 2/3
--doesn't affect other identities
setfenv(
	function()
		local t0 = tick()
		for i = 1, 1e5 do --do random stuff
			local somevar = i - 1
			local function someclosure() end
			someclosure()
			local someindex = ({})[1]
		end
		print('elapsed:', tick() - t0)
	end,
	setmetatable({}, {
		__index = {
			print = print;
			tick = tick;
			script = forceFallbackCheck and nil or script
		};
	})
)()

Would be cool if a roblox engineer could shed some light on this.

2 Likes

It only running in identities 2 and 3 might give us a possible explanation:
The difference between 2 and 3 is whether the script’s LinkedSource is a ROBLOX script.
Those run at identity 3 instead of identity 2.
(or game:SetPlaceId() is used to set the place to a roblox place)

I tested with returning a LinkedSource script: still identity 2, so not sure if the __index matters.
I assume you also tested this thing in identity 3, as it also happens there indeed, very weird.