Problem with a function that uses debug.info

Edit: The roblox given error message actually tells the wrong function name even if I’m not using debug.info, but I get the correct function name in studio (I made it error in studio as well by setting the variable to be nil). However, the situation where the code works correctly in studio and errors in live game seems to only happen when I do use debug.info.

  1. What do you want to achieve?

I made a class system and for debugging purposes I made a function local getClassName in the modulescript called Class. The class constructor Class.new calls getClassName and stores the result in a table.

Here's the code of the modulescript Class.
local Class = {}

local metadata = {}
local classes = {}
local classNamesForClasses = {}

local function getClassName()
	local classModuleFullName = debug.info(3, "s")
	return string.reverse(string.split(string.reverse(classModuleFullName), ".")[1])
end

function Class.new(classData, parentClass)
	assert(typeof(classData) == "table", "classData needs to be a table.")
	--assert(typeof(isAbstract) == "boolean", "isAbstract needs to be a boolean.")
	assert(typeof(parentClass) == "table" or typeof(parentClass) == "nil", "parentClass needs to be either a table (class) or nil")
	local objectProperties = classData.objectProperties
	assert(objectProperties ~= nil, "no object properties")
	local staticProperties = classData.staticProperties
	assert(staticProperties ~= nil, "no static properties")
	
	for propertyName, property in pairs(objectProperties) do
		assert(property.get ~= nil or property.set ~= nil, "Property cannot be both unreadable and unwritable.")
		-- Actually, there's probably no problem in this if the method is a static method.
		--assert(classData[propertyName] == nil, "Having a method and an object property with the same name is not allowed.")
	end
	for propertyName, property in pairs(staticProperties) do
		assert(property.get ~= nil or property.set ~= nil, "Property cannot be both unreadable and unwritable.")
		assert(classData[propertyName] == nil, "Having a method and a static property with the same name is not allowed.")
	end
	
	local self = {}
	metadata[self] = {
		classData = classData,
		--isAbstract = isAbstract,
		parentClass = parentClass
	}
	classes[classData] = self
	classNamesForClasses[self] = getClassName()
	return setmetatable(self, Class)
end

local function tryGetMethod(class, key)
	local currentClass = class
	repeat
		local classMetadata = metadata[currentClass]
		local classData = classMetadata.classData
		--assert(classData.staticProperties[key] == nil, "This is a property, not a method.")
		if classData.staticProperties[key] ~= nil then
			-- this is a property
			return nil
		end
		-- Now it is known that this is not a property.
		local method = classData[key]
		--assert(method ~= nil, "Method does not exist.")
		if typeof(method) == "function" then
			return method
		else
			-- either this is a field (private field because this system does not support public field (also doesn't support private properties))
			-- or this is nil (nothing)
			currentClass = classMetadata.parentClass
		end
	until currentClass == nil
	return nil
end

function Class:__index(key)
	-- Object properties may be needed by the Object module, but static properties are only needed by this module,
	-- and this module can access them without indexing the class object itself.
	if key == "objectProperties" then
		return metadata[self].classData.objectProperties
	elseif key == "staticProperties" then
		error("no access")
	end
	
	local currentClass = self
	repeat
		local classData = metadata[currentClass].classData
		local staticProperty = classData.staticProperties[key]
		if staticProperty ~= nil then
			assert(staticProperty.get ~= nil, "This property cannot be read.")
			return staticProperty.get()
		end
		local method = tryGetMethod(currentClass, key)
		if method ~= nil then
			return method
		end
		currentClass = Class.getParentClass(currentClass)
	until currentClass == nil
	error("No property or method with this name exists.")
end

function Class:__newindex(key, value)
	assert(key ~= "objectProperties" and key ~= "staticProperties", "The tables 'objectProperties' and 'staticProperties' cannot be changed to new ones.")
	local currentClass = self
	repeat
		local classData = metadata[currentClass].classData
		local staticProperty = classData.staticProperties[key]
		if staticProperty ~= nil then
			assert(staticProperty.set ~= nil, "This property cannot be set.")
			staticProperty.set(metadata[currentClass].classData, value)
			return
		end
		currentClass = Class.getParentClass(currentClass)
	until currentClass == nil
	error("This property does not exist.")
end

function Class.hasMethod(class, key)
	return tryGetMethod(class, key) ~= nil
end

-- should only be used by the Object class, not by any other scripts or module scripts
function Class.getMethod(class, methodName)
	local method = tryGetMethod(class, methodName)
	assert(method ~= nil, "This object method does not exist")
	return method
end

-- should only be used by the Object class, not by any other scripts or module scripts
function Class.getParentClass(class)
	return metadata[class].parentClass
end

--[[
function Class.isAbstract(class)
	return metadata[class].isAbstract
end
--]]

function Class.getClass(classData)
	local class = classes[classData]
	assert(class ~= nil, "There is no class corresponding to this classData.")
	return class
end

-- can be used for calling parent class getter function in child class setter
function Class.getGetter(class, propertyName, isObjectProperty)
	local classData = metadata[class].classData
	local property = (isObjectProperty and classData.objectProperties or classData.staticProperties)[propertyName]
	assert(property ~= nil, "Property doesn't exist.")
	assert(property.get ~= nil, "Property is writeonly")
	return property.get
end

-- can be used for calling parent class setter function in child class setter
function Class.getSetter(class, propertyName, isObjectProperty)
	local classData = metadata[class].classData
	local property = (isObjectProperty and classData.objectProperties or classData.staticProperties)[propertyName]
	assert(property ~= nil, "Property doesn't exist.")
	assert(property.set ~= nil, "Property is readonly")
	return property.set
end

function Class.getClassName(class)
	return classNamesForClasses[class]
end

return Class
  1. What is the issue?

When I’m testing in studio, there’s no issue. However, when I publish the place and play it, there’s an error on both the client and the server.

  1. What solutions have you tried so far? Did you look for solutions on the Developer Hub?

In order to minimize possible effects of my other code, I made a new (server)script and a new modulescript for investigating this.
image

DebugInfoProblemModule:

local DebugInfoProblemModule = {}

local function problemFunction()
	local scriptFullName = debug.info(3, "s")
	local returnValue = string.reverse(scriptFullName)
	return returnValue
end

function DebugInfoProblemModule.problemFunctionCaller()
	local scriptFullNameReverse = problemFunction()
end

return DebugInfoProblemModule

DebugInfoProblemScript

local debugInfoProblemFolder = script.Parent
local DebugInfoProblemModule = require(debugInfoProblemFolder.DebugInfoProblemModule)

DebugInfoProblemModule.problemFunctionCaller()

The error message (server) in the developer console now looks like this.

As I said, I do not get this error in studio, only when playing in the published place. Another interesting thing is that the error message says Line 5 - function problemFunctionCaller, although line 5 is in the local function problemFunction.

So that describes the initial problem. Now, when I edited the code, I noticed some surprising things. Here’s the module with an addition.

local DebugInfoProblemModule = {}

local function problemFunction()
	local scriptFullName = debug.info(3, "s")
	if scriptFullName == nil then
		error("scriptFullName == nil")
	end
	local returnValue = string.reverse(scriptFullName)
	return returnValue
end

function DebugInfoProblemModule.problemFunctionCaller()
	local scriptFullNameReverse = problemFunction()
end

return DebugInfoProblemModule

And here’s the error message that again says problemFunctionCaller.

Now, if I add either tostring(tostring(scriptFullName)), print(tostring(scriptFullName)) or debug.info(1, "n"), there’s no error anymore. In the first and the third case, output is empty. Below is the output in the second case.

The odd thing here is the lack of error. Here’s the code for the first case tostring(tostring(scriptFullName)):

local DebugInfoProblemModule = {}

local function problemFunction()
	local scriptFullName = debug.info(3, "s")
	tostring(tostring(scriptFullName))
	if scriptFullName == nil then
		error("scriptFullName == nil")
	end
	local returnValue = string.reverse(scriptFullName)
	return returnValue
end

function DebugInfoProblemModule.problemFunctionCaller()
	local scriptFullNameReverse = problemFunction()
end

return DebugInfoProblemModule

I made the other two additions to the same location in the code (only one of these three additions was included at a time). There are also other alternative additions that have the same effect (no error).

Also, when adding just tostring(scriptFullName) or print(scriptFullName) (no nested function calls) I get my own error (scriptFullName == nil).

Based on my knowledge of the functions I used in the three additions, none of these additions should change the value of the variable scriptFullName.

I’m wondering if something in my own code is causing these odd things or if debug.info does something I’m not aware of that causes this.

I’m not very sure on this one. Maybe debug.info does not work at all in-game?

The function problemFunction (and the function getClassName) gives the intended return value with certain kinds of additions to the code inside the function (three of which I mentioned in the original post, although I didn’t mention that it gives the intended return value with them).

However, I have no idea how any of these additions can affect the behavior of problemFunction, because I’m not setting the value of any variable in those lines of code and I’m pretty sure that tostring and print shouldn’t affect the values of variables either.

If debug.info wouldn’t work at all, I would not get the intended result. So it seems like it returns correct values in live games as well, but it seems to also do something else that it doesn’t do in studio. As mentioned in the original post, not only does something strange seem to happen to the variable scriptFullName, but the string.reverse-related error message given by roblox contains the name of the wrong function. Apparently the latter happens in a live game also when I don’t use debug.info, although the error message in studio (now I get error in studio as well because I set the variable to be nil) gives the name of the correct function. However, the situation where the code works correctly in studio and errors in live game seems to only happen when I do use debug.info instead of setting the variable to something arbitrary.