Roblox should call tostring on objects before displaying errors in the console

A recentish change made it possible to error with tables and inspect them with pcall. However, currently if you error with a table and it’s not caught by a pcall, a rather useless message is displayed:

image

Roblox should attempt to call tostring on error values before they are displayed in the console. This way, humans can hopefully get a better explanation than just “error occurred”. (Note: error should not call tostring, but instead it should be Roblox’s error reporter code when it receives an object. error should still be able to throw objects.)

As of right now, your code needs to know whether it is going to be called inside of pcall or not to decide if it should deliver an error for humans (a string) or an error for computers (an object). It would be great if we could always error with objects that have a __tostring metamethod and have no worry that if this error makes its way to a human (no pcall used), the error text will be meaningful.

Issue related to this feature request: https://github.com/evaera/roblox-lua-promise/issues/38

22 Likes

Unfortunately, this wouldn’t be secure. Anything that called __tostring would be running it within a privileged thread, having the same problem as __gc.

One solution could be to accept __tostring when it is a string. Obviously this isn’t as flexible, but at least you’d be able to provide a bit more context.

Another unconventional solution could be to accept __tostring when it is a BindableFunction. Bindables are how Roblox passed values between security identities in a safe manner. Because the bound function is called with its original identity, there are no problems with it being invoked by a privileged thread. Implementing it might look like this:

local StatusError = {}
StatusError.__tostring = Instance.new("BindableFunction")
function StatusError.__tostring:OnInvoke()
	return string.format("%d: %s", self.Code, self.Message)
end

local function NewStatusError(code, message)
	return setmetatable({
		Code = code,
		Message = message,
	}, StatusError)
end

local status = NewStatusError(404, "Not Found")

-- Implementation of tostring might do something like this:
local ts = getmetatable(status).__tostring
if typeof(ts) == "Instance" and ts.ClassName == "BindableFunction" then
	print(ts:Invoke(status))
end

Not as bad as it might seem, I’d say. The only problem is that BindableFunctions are allowed to yield, which may not play well with how tostring is implemented.

3 Likes