Simple flaw in lua source code causing a huge problem about locals

I am saying this here as its not a important bug at all and this bug is very interesting.

now lets get to point, there is a flaw in lua C source code that makes locals get adjusted after the values get written as bytecode
image
here is the code that causes this (this is lua source code not luau, but I am pretty sure it is very similar so can be fixed with this context)
now to fix this problem the adjustlocvars function should be called before the part where the expressions are read (before the if statement)

local a = function()
   print(a)
end

here is bytecode of this code with the flawed function (site: luac.nl)


as you can see the variable “a” is treated like a global (which what lua does when there is no local or upvalue found) where in normal languages this should be caught as a upvalue

this is not a important bug but would be good if its fixed

1 Like

I’m a little confused.

local a = function()

is in a scope above the function, meaning it is not an upvalue to that function. Where is the bug?

I will give you a example
local a = function()

end
is same as

local function a()

end

right?
but in the second example the local is adjusted before the function body is read so if you use the name “a” in the second example it will be a upvalue

this is the bug in the first example it is not a upvalue

there are examples like

local a = part.Touched:Connect(function(p)
a:Disconnect() --error
end)


this is the problem, as you can see the compiler writes a “GETUPVAL” for “a”

but if you do this with
local a = function()

end

this wont work
and I want to say in languages like python you can do this with lambdas with no problem and it will be a upvalue

this is just a simple example on what the bug is
if you want to fix this bug without changing anything

local a; --declare local before function
a = function() --assign local
  print(a) --will write a GETUPVAL
end

but this doesnt look good or anything and this proves my point, why not just fix it in the source code?

No, it is not the same. This is because of the lexical scoping property of functions, the fact function is a constructor - not variable declaration.

local function a()
print(a)
end

interpreter goes “alright, its a function constructor, which means we’re making a function value, let me index the code, which has a inside it, and now that I’ve done that i’ll assign the new function value to a new variable a” – in that order. When you run the function, its index already has a in it, thus its an upvalue.

going

local a = function(...)
print(a)
end

interpreter goes “alright, a is being declared in this top scope, and it looks like to a function constructor, which just returns a function value, so i’ll index this function, and return/assign it back to a.” thus, a is in the scope above when the function is indexed, meaning its a global.

the moment you do local a is the moment you’ve made your variable in memory, where the that isn’t the case with local function a() because functions have their own constructor, like lua tables have their own constructor, thus construction comes first.

if this was not the case, then we couldn’t have anonymous functions (functions without variables, aka local function(...) end.

I will give you points though because the original Lua manual incorrectly calls this “syntatic sugar”, as if to imply they’re the same, despite disproving itself in the later paragraph by talking about how function declarations are a constructor. You could call this an oversight, but it serves an actual functionality that is distinct.

As for the below:

local a = part.Touched:Connect(function(p)
a:Disconnect() --error
end)

“create a, assign it to… RBXScriptSignal… which is whats returned from Connect, which indexs the function value given to its parameter, all before a is assigned to, even though a exists, its just blank.” thus you cannot disconnect. I admit this one is an annoying symptom of it and I don’t really care to defend it.

to add on, also, if we didn’t do it like this, then you can’t really do local a: any because assignment would have to occur to initialize the variable

yeah but I read the source code so I am superior


if you can read it a little here you can see that it actually creates a function and assigns it as a local nothing else

but at this part it adjusts the local before the body function which makes it usable in the function scope

now you said the reason I cant use the local inside the function it was getting assigned to is because of scope, it might be (I still think its a flaw) as languages like python lets you do this why cant lua too? I dont want to declare a local before assigning it to a function

a = lambda: a
print(a()) #prints <function <lambda> at memory address>

python example

you might be right, it makes sense why this might be made like this but I am defending that it might be a flaw or should be changed as other languages are doing this, fixing this is simple as changing the calls line like 3 lines above

the way compilers in bytecode languages like lua store locals is more different than you think

it doesnt store any very important data it just stores the register the local is, then when you use GETUPVAL or anything else it just returns the value at the register where the local would be.

So if the local statement didnt error the local register would never be blank

im typing something else but by blank I just meant nil, which is a special value in lua. variables only exist in lua when they have a value but this behavior is what I’m gonna explain rq

python does not have variables, they have names for you. they are a reference that points to a value. when you go local a in lua, you are making an actual dynamic variable, its just simply nil , which is basically the same as None is python minus needing to assign it (this happens automatically, which we’ll get to later). this doesn’t mean literally nothing, it means “not something” – but I’m gonna assume you know that. This is all part of a design though, because all variables in Lua are automatically global, where in Python, they’re local.

when you do local, you declare its position in the scope immediately, but not its activation, which is what allows the function constructor to access it as a global, because it constructs its scope as well, and it makes adjustlocalvars really important where it sits, because it isn’t where its position is in the scope, its making it available in its scope.

if (testnext(ls, '='))
nexps = explist1(ls, &e);
else {
e.k = VVOID;
nexps = 0;
}

this evaluates the expressions defined, assigning either VVOID to the expression’s value when nothing is produced (to be handled).

lets assume though we’re funny and changed some source code, where adjustlocalvars happens before this expression evaluation:

  1. the local variables would be marked as active before their initialization expressions are evaluated, which means they are not set to nil nor the correct value. this could lead to issues where those variables are in scope and potentially accessible despite not being evaluated. this gets problematic especially if their initialization involves other variables whose order of intilization matters. look at this bundle of mess for example:
local function createFunction()
    local a = function() print(a) end
    return a
end

a = createFunction()
a()
  1. you can crash the entire VM (being dramatic) by initializing a variable with an erroring/nonsensical expression that wasn’t caught during initialization, like in a pcall(), and get weird results.
  2. referencing the variable before its initialized can be bad especially if you have other code that needs to yield or simply require the variable to exist. you get weird results if I do like repeat task.wait() until a when a does in fact exist but not as nil or an actual value. unsure what would even happen there.
  3. debugging is gonna start to break down when you get into the nitty gritty of it because variables will active in the scopes as if they’re initialized when they’re not.
  4. garbage collection. lua has automatic garbage collection and you may cause unwanted behavior if the initialization comes after the creation of the variable within the scope, considering the primary factor that garbage collection looks for is if a variable is being used or not nil.
  5. multi-processing in luau can get messy real quick if you can’t even rely on the regular VM’s consistency with variables like that.

that makes sense, I am marking it as solution thanks!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.