233 lines
5.6 KiB
Lua
233 lines
5.6 KiB
Lua
--[[
|
|
This module creates a crash object that can be sent to Backtrace.
|
|
For information about what are the acceptable fields, see document:
|
|
https://api.backtrace.io/#tag/submit-crash
|
|
]]
|
|
|
|
local CorePackages = game:GetService("CorePackages")
|
|
local HttpService = game:GetService("HttpService")
|
|
|
|
local Cryo = require(CorePackages.Packages.Cryo)
|
|
local t = require(CorePackages.Packages.t)
|
|
|
|
local ProcessErrorStack = require(script.Parent.ProcessErrorStack)
|
|
|
|
local DEFAULT_THREAD_NAME = "default"
|
|
|
|
local IBacktraceStack = t.strictInterface({
|
|
guessed_frame = t.optional(t.boolean),
|
|
funcName = t.optional(t.string),
|
|
address = t.optional(t.string),
|
|
line = t.optional(t.string),
|
|
column = t.optional(t.string),
|
|
sourceCode = t.optional(t.string),
|
|
library = t.optional(t.string),
|
|
debug_identifier = t.optional(t.string),
|
|
faulted = t.optional(t.boolean),
|
|
registers = t.optional(t.map(t.string, t.some(t.string, t.number))),
|
|
})
|
|
|
|
local IBacktraceThread = t.strictInterface({
|
|
name = t.optional(t.string),
|
|
fault = t.optional(t.boolean),
|
|
stack = t.optional(t.array(IBacktraceStack)),
|
|
})
|
|
|
|
local IArch = t.strictInterface({
|
|
name = t.string,
|
|
registers = t.map(t.string, t.string),
|
|
})
|
|
|
|
local ISourceCode = t.strictInterface({
|
|
text = t.optional(t.string),
|
|
startLine = t.optional(t.number),
|
|
startColumn = t.optional(t.number),
|
|
startPos = t.optional(t.number),
|
|
path = t.optional(t.string),
|
|
tabWidth = t.optional(t.number),
|
|
})
|
|
|
|
local IPerm = t.strictInterface({
|
|
read = t.boolean,
|
|
write = t.boolean,
|
|
exec = t.boolean,
|
|
})
|
|
|
|
local IMemory = t.strictInterface({
|
|
start = t.string,
|
|
size = t.optional(t.number),
|
|
data = t.optional(t.string),
|
|
perms = t.optional(IPerm),
|
|
})
|
|
|
|
local IModule = t.strictInterface({
|
|
start = t.string,
|
|
size = t.number,
|
|
code_file = t.optional(t.string),
|
|
version = t.optional(t.string),
|
|
debug_file = t.optional(t.string),
|
|
debug_identifier = t.optional(t.string),
|
|
debug_file_exists = t.optional(t.boolean),
|
|
})
|
|
|
|
local IAttributes = t.optional(t.map(t.string, t.some(t.string, t.number, t.boolean)))
|
|
|
|
local IAnnotation = function(annotation)
|
|
local function checkTypeRecursive(value)
|
|
if type(value) == "table" then
|
|
for key, subValue in pairs(value) do
|
|
local valid, error = checkTypeRecursive(subValue)
|
|
if not valid then
|
|
return false, string.format("error when checking key: %s - %s", key, error)
|
|
end
|
|
end
|
|
return true
|
|
else
|
|
local type = t.some(t.string, t.number, t.boolean)
|
|
return type(value)
|
|
end
|
|
end
|
|
|
|
return checkTypeRecursive(annotation)
|
|
end
|
|
|
|
local IAnnotations = t.optional(t.map(t.string, IAnnotation))
|
|
|
|
local IBacktraceReport = t.intersection(
|
|
t.strictInterface({
|
|
-- Must haves
|
|
uuid = t.string,
|
|
timestamp = t.number,
|
|
lang = t.string,
|
|
langVersion = t.string,
|
|
agent = t.string,
|
|
agentVersion = t.string,
|
|
threads = t.map(t.string, IBacktraceThread),
|
|
mainThread = t.string,
|
|
|
|
-- Optionals
|
|
attributes = IAttributes,
|
|
annotations = IAnnotations,
|
|
symbolication = t.optional(t.literal("minidump")),
|
|
entryThread = t.optional(t.string),
|
|
arch = t.optional(IArch),
|
|
fingerprint = t.optional(t.string),
|
|
classifiers = t.optional(t.array(t.string)),
|
|
sourceCode = t.optional(t.map(t.string, ISourceCode)),
|
|
memory = t.optional(t.array(IMemory)),
|
|
modules = t.optional(t.array(IModule)),
|
|
}),
|
|
function(report)
|
|
local hasRegisters = false
|
|
|
|
local threads = report.threads
|
|
for _, thread in pairs(threads) do
|
|
local stacks = thread.stack
|
|
if stacks ~= nil then
|
|
for _, stack in ipairs(stacks) do
|
|
if stack.registers ~= nil then
|
|
hasRegisters = true
|
|
break
|
|
end
|
|
end
|
|
end
|
|
if hasRegisters then
|
|
break
|
|
end
|
|
end
|
|
|
|
if hasRegisters and report.arch == nil then
|
|
return false, "arch must exist if you want to have registers in the stack"
|
|
else
|
|
return true
|
|
end
|
|
end
|
|
)
|
|
|
|
local BacktraceReport = {
|
|
IAttributes = IAttributes,
|
|
IAnnotations = IAnnotations,
|
|
}
|
|
BacktraceReport.__index = BacktraceReport
|
|
|
|
function BacktraceReport:validate()
|
|
return IBacktraceReport(self)
|
|
end
|
|
|
|
-- Return a basic report that has all the required fields
|
|
function BacktraceReport.new()
|
|
local self = {
|
|
uuid = HttpService:GenerateGUID(false):lower(),
|
|
timestamp = os.time(),
|
|
lang = "lua",
|
|
langVersion = "Roblox",
|
|
agent = "backtrace-Lua",
|
|
agentVersion = "0.1.0",
|
|
threads = {},
|
|
mainThread = DEFAULT_THREAD_NAME,
|
|
}
|
|
|
|
setmetatable(self, BacktraceReport)
|
|
|
|
return self
|
|
end
|
|
|
|
function BacktraceReport:addAttributes(newAttributes)
|
|
if type(newAttributes) ~= "table" then
|
|
warn("Cannot add attributes of type: ", type(newAttributes))
|
|
return
|
|
end
|
|
|
|
local attributes = self.attributes or {}
|
|
|
|
attributes = Cryo.Dictionary.join(attributes, newAttributes)
|
|
|
|
self.attributes = attributes
|
|
end
|
|
|
|
function BacktraceReport:addAnnotations(newAnnotations)
|
|
if type(newAnnotations) ~= "table" then
|
|
warn("Cannot add annotations of type: ", type(newAnnotations))
|
|
return
|
|
end
|
|
|
|
local annotations = self.annotations or {}
|
|
|
|
annotations = Cryo.Dictionary.join(annotations, newAnnotations)
|
|
|
|
self.annotations = annotations
|
|
end
|
|
|
|
function BacktraceReport:addStackToThread(stack, threadName)
|
|
local threads = self.threads
|
|
|
|
threads = Cryo.Dictionary.join(threads, {
|
|
[threadName] = {
|
|
name = threadName,
|
|
stack = stack,
|
|
}
|
|
})
|
|
|
|
self.threads = threads
|
|
end
|
|
|
|
function BacktraceReport:addStackToMainThread(stack)
|
|
self:addStackToThread(stack, self.mainThread)
|
|
end
|
|
|
|
function BacktraceReport.fromMessageAndStack(errorMessage, errorStack)
|
|
local report = BacktraceReport.new()
|
|
|
|
report:addAttributes({
|
|
["error.message"] = errorMessage,
|
|
})
|
|
|
|
local stack, sourceCode = ProcessErrorStack(errorStack)
|
|
report:addStackToMainThread(stack)
|
|
report.sourceCode = sourceCode
|
|
|
|
return report
|
|
end
|
|
|
|
return BacktraceReport
|