Initial version - 1.2.0.60
EN: 1.2.0.60 CN: 1.2.0.61 JP: 1.2.0.63 KR: 1.2.0.67
This commit is contained in:
@@ -0,0 +1,913 @@
|
||||
local debuggee = {}
|
||||
local socket = require("socket.core")
|
||||
local json
|
||||
local handlers = {}
|
||||
local sock
|
||||
local directorySeperator = package.config:sub(1, 1)
|
||||
local sourceBasePath = "."
|
||||
local storedVariables = {}
|
||||
local nextVarRef = 1
|
||||
local baseDepth, breaker, sendEvent
|
||||
local dumpCommunication = false
|
||||
local ignoreFirstFrameInC = false
|
||||
local debugTargetCo, redirectedPrintFunction, onError, addUserdataVar
|
||||
local defaultOnError = function(e)
|
||||
print("****************************************************")
|
||||
print(e)
|
||||
print("****************************************************")
|
||||
end
|
||||
local function valueToString(value, depth)
|
||||
local str = ""
|
||||
depth = depth or 0
|
||||
local t = type(value)
|
||||
if t == "table" then
|
||||
str = str .. "{\n"
|
||||
for k, v in pairs(value) do
|
||||
str = str .. string.rep(" ", depth + 1) .. "[" .. valueToString(k) .. "]" .. " = " .. valueToString(v, depth + 1) .. ",\n"
|
||||
end
|
||||
str = str .. string.rep(" ", depth) .. "}"
|
||||
elseif t == "string" then
|
||||
str = str .. "\"" .. tostring(value) .. "\""
|
||||
else
|
||||
str = str .. tostring(value)
|
||||
end
|
||||
return str
|
||||
end
|
||||
local sethook = debug.sethook
|
||||
debug.sethook = nil
|
||||
local cocreate = coroutine.create
|
||||
function coroutine.create(f)
|
||||
local c = cocreate(f)
|
||||
debuggee.addCoroutine(c)
|
||||
return c
|
||||
end
|
||||
local debug_getinfo = function(depth, what)
|
||||
if debugTargetCo then
|
||||
return debug.getinfo(debugTargetCo, depth, what)
|
||||
else
|
||||
return debug.getinfo(depth + 1, what)
|
||||
end
|
||||
end
|
||||
local debug_getlocal = function(depth, i)
|
||||
if debugTargetCo then
|
||||
return debug.getlocal(debugTargetCo, depth, i)
|
||||
else
|
||||
return debug.getlocal(depth + 1, i)
|
||||
end
|
||||
end
|
||||
local DO_TEST = false
|
||||
local getMatchCount = function(a, b)
|
||||
local n = math.min(#a, #b)
|
||||
for i = 0, n - 1 do
|
||||
if a[#a - i] == b[#b - i] then
|
||||
else
|
||||
return i
|
||||
end
|
||||
end
|
||||
return n
|
||||
end
|
||||
if DO_TEST then
|
||||
assert(getMatchCount({
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
}, {
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
}) == 3)
|
||||
assert(getMatchCount({"b", "c"}, {
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
}) == 2)
|
||||
assert(getMatchCount({
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
}, {"b", "c"}) == 2)
|
||||
assert(getMatchCount({}, {
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
}) == 0)
|
||||
assert(getMatchCount({
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
}, {}) == 0)
|
||||
assert(getMatchCount({
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
}, {
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
"d"
|
||||
}) == 0)
|
||||
end
|
||||
local splitChunkName = function(s)
|
||||
if string.sub(s, 1, 1) == "@" then
|
||||
s = string.sub(s, 2)
|
||||
end
|
||||
local a = {}
|
||||
for word in string.gmatch(s, "[^/\\]+") do
|
||||
a[#a + 1] = string.lower(word)
|
||||
end
|
||||
return a
|
||||
end
|
||||
if DO_TEST then
|
||||
local a = splitChunkName("@.\\vscode-debuggee.lua")
|
||||
assert(#a == 2)
|
||||
assert(a[1] == ".")
|
||||
assert(a[2] == "vscode-debuggee.lua")
|
||||
local a = splitChunkName("@C:\\dev\\VSCodeLuaDebug\\debuggee/lua\\socket.lua")
|
||||
assert(#a == 6)
|
||||
assert(a[1] == "c:")
|
||||
assert(a[2] == "dev")
|
||||
assert(a[3] == "vscodeluadebug")
|
||||
assert(a[4] == "debuggee")
|
||||
assert(a[5] == "lua")
|
||||
assert(a[6] == "socket.lua")
|
||||
local a = splitChunkName("@main.lua")
|
||||
assert(#a == 1)
|
||||
assert(a[1] == "main.lua")
|
||||
end
|
||||
local Path = {}
|
||||
function Path.isAbsolute(a)
|
||||
local firstChar = string.sub(a, 1, 1)
|
||||
if firstChar == "/" or firstChar == "\\" then
|
||||
return true
|
||||
end
|
||||
if string.match(a, "^%a%:[/\\]") then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
local np_pat1, np_pat2 = ("[^SEP:]+SEP%.%.SEP?"):gsub("SEP", directorySeperator), ("SEP+%.?SEP"):gsub("SEP", directorySeperator)
|
||||
function Path.normpath(path)
|
||||
path = path:gsub("[/\\]", directorySeperator)
|
||||
if directorySeperator == "\\" then
|
||||
local unc = ("SEPSEP"):gsub("SEP", directorySeperator)
|
||||
if path:match("^" .. unc) then
|
||||
return unc .. Path.normpath(path:sub(3))
|
||||
end
|
||||
end
|
||||
local k
|
||||
repeat
|
||||
path, k = path:gsub(np_pat2, directorySeperator)
|
||||
until k == 0
|
||||
repeat
|
||||
path, k = path:gsub(np_pat1, "", 1)
|
||||
until k == 0
|
||||
if path == "" then
|
||||
path = "."
|
||||
end
|
||||
return path
|
||||
end
|
||||
function Path.concat(a, b)
|
||||
local lastChar = string.sub(a, #a, #a)
|
||||
if lastChar ~= "/" and lastChar ~= "\\" then
|
||||
a = a .. directorySeperator
|
||||
end
|
||||
if string.match(b, "^%.%\\") or string.match(b, "^%.%/") then
|
||||
b = string.sub(b, 3)
|
||||
end
|
||||
return a .. b
|
||||
end
|
||||
function Path.toAbsolute(base, sub)
|
||||
if Path.isAbsolute(sub) then
|
||||
return Path.normpath(sub)
|
||||
else
|
||||
return Path.normpath(Path.concat(base, sub))
|
||||
end
|
||||
end
|
||||
if DO_TEST then
|
||||
assert(Path.isAbsolute("c:\\asdf\\afsd"))
|
||||
assert(Path.isAbsolute("c:/asdf/afsd"))
|
||||
if directorySeperator == "\\" then
|
||||
assert(Path.toAbsolute("c:\\asdf", "fdsf") == "c:\\asdf\\fdsf")
|
||||
assert(Path.toAbsolute("c:\\asdf", ".\\fdsf") == "c:\\asdf\\fdsf")
|
||||
assert(Path.toAbsolute("c:\\asdf", "..\\fdsf") == "c:\\fdsf")
|
||||
assert(Path.toAbsolute("c:\\asdf", "c:\\fdsf") == "c:\\fdsf")
|
||||
assert(Path.toAbsolute("c:/asdf", "../fdsf") == "c:\\fdsf")
|
||||
assert(Path.toAbsolute("\\\\HOST\\asdf", "..\\fdsf") == "\\\\HOST\\fdsf")
|
||||
elseif directorySeperator == "/" then
|
||||
assert(Path.toAbsolute("/usr/bin/asdf", "fdsf") == "/usr/bin/asdf/fdsf")
|
||||
assert(Path.toAbsolute("/usr/bin/asdf", "./fdsf") == "/usr/bin/asdf/fdsf")
|
||||
assert(Path.toAbsolute("/usr/bin/asdf", "../fdsf") == "/usr/bin/fdsf")
|
||||
assert(Path.toAbsolute("/usr/bin/asdf", "/usr/bin/fdsf") == "/usr/bin/fdsf")
|
||||
assert(Path.toAbsolute("\\usr\\bin\\asdf", "..\\fdsf") == "/usr/bin/fdsf")
|
||||
end
|
||||
end
|
||||
local coroutineSet = {}
|
||||
setmetatable(coroutineSet, {__mode = "v"})
|
||||
local sendFully = function(str)
|
||||
local first = 1
|
||||
while first <= #str do
|
||||
local sent = sock:send(str, first)
|
||||
if sent and 0 < sent then
|
||||
first = first + sent
|
||||
else
|
||||
error("sock:send() returned < 0")
|
||||
end
|
||||
end
|
||||
end
|
||||
local logToDebugConsole = function(output, category)
|
||||
local dumpMsg = {
|
||||
event = "output",
|
||||
type = "event",
|
||||
body = {
|
||||
category = category or "console",
|
||||
output = output
|
||||
}
|
||||
}
|
||||
local dumpBody = json.encode(dumpMsg)
|
||||
sendFully("#" .. #dumpBody .. "\n" .. dumpBody)
|
||||
end
|
||||
local createHaltBreaker = function()
|
||||
local loadedChunkNameMap = {}
|
||||
for chunkname, _ in pairs(debug.getchunknames()) do
|
||||
loadedChunkNameMap[chunkname] = splitChunkName(chunkname)
|
||||
end
|
||||
local findMostSimilarChunkName = function(path)
|
||||
local splitedReqPath = splitChunkName(path)
|
||||
local maxMatchCount = 0
|
||||
local foundChunkName
|
||||
for chunkName, splitted in pairs(loadedChunkNameMap) do
|
||||
local count = getMatchCount(splitedReqPath, splitted)
|
||||
if maxMatchCount < count then
|
||||
maxMatchCount = count
|
||||
foundChunkName = chunkName
|
||||
end
|
||||
end
|
||||
return foundChunkName
|
||||
end
|
||||
local lineBreakCallback
|
||||
local updateCoroutineHook = function(c)
|
||||
if lineBreakCallback then
|
||||
sethook(c, lineBreakCallback, "l")
|
||||
else
|
||||
sethook(c)
|
||||
end
|
||||
end
|
||||
local sethalt = function(cname, ln)
|
||||
for i = ln, ln + 10 do
|
||||
if debug.sethalt(cname, i) then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
return {
|
||||
setBreakpoints = function(path, lines)
|
||||
local foundChunkName = findMostSimilarChunkName(path)
|
||||
local verifiedLines = {}
|
||||
if foundChunkName then
|
||||
debug.clearhalt(foundChunkName)
|
||||
for _, ln in ipairs(lines) do
|
||||
verifiedLines[ln] = sethalt(foundChunkName, ln)
|
||||
end
|
||||
end
|
||||
return verifiedLines
|
||||
end,
|
||||
setLineBreak = function(callback)
|
||||
if callback then
|
||||
sethook(callback, "l")
|
||||
else
|
||||
sethook()
|
||||
end
|
||||
lineBreakCallback = callback
|
||||
for cid, c in pairs(coroutineSet) do
|
||||
updateCoroutineHook(c)
|
||||
end
|
||||
end,
|
||||
coroutineAdded = function(c)
|
||||
updateCoroutineHook(c)
|
||||
end,
|
||||
stackOffset = {
|
||||
enterDebugLoop = 6,
|
||||
halt = 6,
|
||||
step = 4,
|
||||
stepDebugLoop = 6
|
||||
}
|
||||
}
|
||||
end
|
||||
local createPureBreaker = function()
|
||||
local lineBreakCallback
|
||||
local breakpointsPerPath = {}
|
||||
local chunknameToPathCache = {}
|
||||
local chunkNameToPath = function(chunkname)
|
||||
local cached = chunknameToPathCache[chunkname]
|
||||
if cached then
|
||||
return cached
|
||||
end
|
||||
local splitedReqPath = splitChunkName(chunkname)
|
||||
local maxMatchCount = 0
|
||||
local foundPath
|
||||
for path, _ in pairs(breakpointsPerPath) do
|
||||
local splitted = splitChunkName(path)
|
||||
local count = getMatchCount(splitedReqPath, splitted)
|
||||
if maxMatchCount < count then
|
||||
maxMatchCount = count
|
||||
foundPath = path
|
||||
end
|
||||
end
|
||||
if foundPath then
|
||||
chunknameToPathCache[chunkname] = foundPath
|
||||
end
|
||||
return foundPath
|
||||
end
|
||||
local entered = false
|
||||
local hookfunc = function()
|
||||
if entered then
|
||||
return false
|
||||
end
|
||||
entered = true
|
||||
if lineBreakCallback then
|
||||
lineBreakCallback()
|
||||
end
|
||||
local info = debug_getinfo(2, "Sl")
|
||||
if info then
|
||||
local path = chunkNameToPath(info.source)
|
||||
path = path and string.lower(path)
|
||||
local bpSet = breakpointsPerPath[path]
|
||||
if bpSet and bpSet[info.currentline] then
|
||||
_G.__halt__()
|
||||
end
|
||||
end
|
||||
entered = false
|
||||
end
|
||||
sethook(hookfunc, "l")
|
||||
return {
|
||||
setBreakpoints = function(path, lines)
|
||||
local t = {}
|
||||
local verifiedLines = {}
|
||||
for _, ln in ipairs(lines) do
|
||||
t[ln] = true
|
||||
verifiedLines[ln] = ln
|
||||
end
|
||||
path = path and string.lower(path)
|
||||
breakpointsPerPath[path] = t
|
||||
return verifiedLines
|
||||
end,
|
||||
setLineBreak = function(callback)
|
||||
lineBreakCallback = callback
|
||||
end,
|
||||
coroutineAdded = function(c)
|
||||
sethook(c, hookfunc, "l")
|
||||
end,
|
||||
stackOffset = {
|
||||
enterDebugLoop = 6,
|
||||
halt = 7,
|
||||
step = 4,
|
||||
stepDebugLoop = 7
|
||||
}
|
||||
}
|
||||
end
|
||||
local sendMessage = function(msg)
|
||||
local body = json.encode(msg)
|
||||
if dumpCommunication then
|
||||
logToDebugConsole("[SENDING] " .. valueToString(msg))
|
||||
end
|
||||
sendFully("#" .. #body .. "\n" .. body)
|
||||
end
|
||||
local recvMessage = function()
|
||||
local header = sock:receive("*l")
|
||||
if header == nil then
|
||||
return nil
|
||||
end
|
||||
if string.sub(header, 1, 1) ~= "#" then
|
||||
error("헤더 이상함:" .. header)
|
||||
end
|
||||
local bodySize = tonumber(header:sub(2))
|
||||
local body = sock:receive(bodySize)
|
||||
return json.decode(body)
|
||||
end
|
||||
local debugLoop = function()
|
||||
storedVariables = {}
|
||||
nextVarRef = 1
|
||||
while true do
|
||||
local msg = recvMessage()
|
||||
if msg then
|
||||
if dumpCommunication then
|
||||
logToDebugConsole("[RECEIVED] " .. valueToString(msg), "stderr")
|
||||
end
|
||||
local fn = handlers[msg.command]
|
||||
if not fn then
|
||||
goto lbl_38
|
||||
end
|
||||
do
|
||||
local rv = fn(msg)
|
||||
if rv == "CONTINUE" then
|
||||
break
|
||||
end
|
||||
end
|
||||
goto lbl_38
|
||||
else
|
||||
if redirectedPrintFunction then
|
||||
_G.print = redirectedPrintFunction
|
||||
end
|
||||
break
|
||||
end
|
||||
::lbl_38::
|
||||
end
|
||||
storedVariables = {}
|
||||
nextVarRef = 1
|
||||
end
|
||||
local sockArray = {}
|
||||
function debuggee.start(jsonLib, config)
|
||||
json = jsonLib
|
||||
assert(jsonLib)
|
||||
config = config or {}
|
||||
local connectTimeout = config.connectTimeout or 5.0
|
||||
local controllerHost = config.controllerHost or "localhost"
|
||||
local controllerPort = config.controllerPort or 56789
|
||||
onError = config.onError or defaultOnError
|
||||
addUserdataVar = config.addUserdataVar or function()
|
||||
return
|
||||
end
|
||||
local redirectPrint = config.redirectPrint or false
|
||||
dumpCommunication = config.dumpCommunication or false
|
||||
ignoreFirstFrameInC = config.ignoreFirstFrameInC or false
|
||||
if not config.luaStyleLog then
|
||||
function valueToString(value)
|
||||
return json.encode(value)
|
||||
end
|
||||
end
|
||||
local breakerType
|
||||
if debug.sethalt then
|
||||
breaker = createHaltBreaker()
|
||||
breakerType = "halt"
|
||||
else
|
||||
breaker = createPureBreaker()
|
||||
breakerType = "pure"
|
||||
end
|
||||
local err
|
||||
sock, err = socket.tcp()
|
||||
if not sock then
|
||||
error(err)
|
||||
end
|
||||
sockArray = {sock}
|
||||
if sock.settimeout then
|
||||
sock:settimeout(connectTimeout)
|
||||
end
|
||||
local res, err = sock:connect(controllerHost, tostring(controllerPort))
|
||||
if not res then
|
||||
sock:close()
|
||||
sock = nil
|
||||
return false, breakerType
|
||||
end
|
||||
if sock.settimeout then
|
||||
sock:settimeout()
|
||||
end
|
||||
sock:setoption("tcp-nodelay", true)
|
||||
local initMessage = recvMessage()
|
||||
assert(initMessage and initMessage.command == "welcome")
|
||||
sourceBasePath = initMessage.sourceBasePath
|
||||
directorySeperator = initMessage.directorySeperator
|
||||
if redirectPrint then
|
||||
redirectedPrintFunction = _G.print
|
||||
function _G.print(...)
|
||||
local t = {
|
||||
n = select("#", ...),
|
||||
...
|
||||
}
|
||||
for i = 1, #t do
|
||||
t[i] = tostring(t[i])
|
||||
end
|
||||
sendEvent("output", {
|
||||
category = "stdout",
|
||||
output = table.concat(t, "\t") .. "\n"
|
||||
})
|
||||
end
|
||||
end
|
||||
debugLoop()
|
||||
return true, breakerType
|
||||
end
|
||||
function debuggee.poll()
|
||||
if not sock then
|
||||
return
|
||||
end
|
||||
while true do
|
||||
local r, w, e = socket.select(sockArray, nil, 0)
|
||||
if e == "timeout" then
|
||||
break
|
||||
end
|
||||
local msg = recvMessage()
|
||||
if msg then
|
||||
if dumpCommunication then
|
||||
logToDebugConsole("[POLL-RECEIVED] " .. valueToString(msg), "stderr")
|
||||
end
|
||||
if msg.command == "pause" then
|
||||
debuggee.enterDebugLoop(1)
|
||||
return
|
||||
end
|
||||
local fn = handlers[msg.command]
|
||||
if fn then
|
||||
local rv = fn(msg)
|
||||
else
|
||||
end
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
local getCoroutineId = function(c)
|
||||
local threadIdHex = string.sub(tostring(c), 9)
|
||||
return tonumber(threadIdHex, 16)
|
||||
end
|
||||
function debuggee.addCoroutine(c)
|
||||
local cid = getCoroutineId(c)
|
||||
coroutineSet[cid] = c
|
||||
breaker.coroutineAdded(c)
|
||||
end
|
||||
local sendSuccess = function(req, body)
|
||||
sendMessage({
|
||||
command = req.command,
|
||||
success = true,
|
||||
request_seq = req.seq,
|
||||
type = "response",
|
||||
body = body
|
||||
})
|
||||
end
|
||||
local sendFailure = function(req, msg)
|
||||
sendMessage({
|
||||
command = req.command,
|
||||
success = false,
|
||||
request_seq = req.seq,
|
||||
type = "response",
|
||||
message = msg
|
||||
})
|
||||
end
|
||||
function sendEvent(eventName, body)
|
||||
sendMessage({
|
||||
event = eventName,
|
||||
type = "event",
|
||||
body = body
|
||||
})
|
||||
end
|
||||
local currentThreadId = function()
|
||||
return 0
|
||||
end
|
||||
local startDebugLoop = function()
|
||||
sendEvent("stopped", {
|
||||
reason = "breakpoint",
|
||||
threadId = currentThreadId(),
|
||||
allThreadsStopped = true
|
||||
})
|
||||
local status, err = pcall(debugLoop)
|
||||
if not status then
|
||||
onError(err)
|
||||
end
|
||||
end
|
||||
function _G.__halt__()
|
||||
baseDepth = breaker.stackOffset.halt
|
||||
startDebugLoop()
|
||||
end
|
||||
function debuggee.enterDebugLoop(depthOrCo, what)
|
||||
if sock == nil then
|
||||
return false
|
||||
end
|
||||
if what then
|
||||
sendEvent("output", {category = "stderr", output = what})
|
||||
end
|
||||
if type(depthOrCo) == "thread" then
|
||||
baseDepth = 0
|
||||
debugTargetCo = depthOrCo
|
||||
elseif type(depthOrCo) == "table" then
|
||||
baseDepth = depthOrCo.depth or 0
|
||||
debugTargetCo = depthOrCo.co
|
||||
else
|
||||
baseDepth = (depthOrCo or 0) + breaker.stackOffset.enterDebugLoop
|
||||
debugTargetCo = nil
|
||||
end
|
||||
startDebugLoop()
|
||||
return true
|
||||
end
|
||||
function debuggee.print(category, ...)
|
||||
if sock == nil then
|
||||
return false
|
||||
end
|
||||
local t = {
|
||||
...
|
||||
}
|
||||
for i = 1, #t do
|
||||
t[i] = tostring(t[i])
|
||||
end
|
||||
local categoryVscodeConsole = "stdout"
|
||||
if category == "warning" then
|
||||
categoryVscodeConsole = "console"
|
||||
elseif category == "error" then
|
||||
categoryVscodeConsole = "stderr"
|
||||
elseif category == "log" then
|
||||
categoryVscodeConsole = "stdout"
|
||||
end
|
||||
sendEvent("output", {
|
||||
category = categoryVscodeConsole,
|
||||
output = table.concat(t, "\t") .. "\n"
|
||||
})
|
||||
end
|
||||
function handlers.setBreakpoints(req)
|
||||
local bpLines = {}
|
||||
for _, bp in ipairs(req.arguments.breakpoints) do
|
||||
bpLines[#bpLines + 1] = bp.line
|
||||
end
|
||||
local verifiedLines = breaker.setBreakpoints(req.arguments.source.path, bpLines)
|
||||
local breakpoints = {}
|
||||
for i, ln in ipairs(bpLines) do
|
||||
breakpoints[i] = {
|
||||
verified = verifiedLines[ln] ~= nil,
|
||||
line = verifiedLines[ln]
|
||||
}
|
||||
end
|
||||
sendSuccess(req, {breakpoints = breakpoints})
|
||||
end
|
||||
function handlers.configurationDone(req)
|
||||
sendSuccess(req, {})
|
||||
return "CONTINUE"
|
||||
end
|
||||
function handlers.threads(req)
|
||||
local c = coroutine.running()
|
||||
local mainThread = {
|
||||
id = currentThreadId(),
|
||||
name = c and tostring(c) or "main"
|
||||
}
|
||||
sendSuccess(req, {
|
||||
threads = {mainThread}
|
||||
})
|
||||
end
|
||||
function handlers.stackTrace(req)
|
||||
assert(req.arguments.threadId == 0)
|
||||
local stackFrames = {}
|
||||
local firstFrame = (req.arguments.startFrame or 0) + baseDepth
|
||||
local lastFrame = req.arguments.levels and req.arguments.levels ~= 0 and firstFrame + req.arguments.levels - 1 or 9999
|
||||
if ignoreFirstFrameInC then
|
||||
local info = debug_getinfo(firstFrame, "lnS")
|
||||
if info and info.what == "C" then
|
||||
firstFrame = firstFrame + 1
|
||||
end
|
||||
end
|
||||
for i = firstFrame, lastFrame do
|
||||
local info = debug_getinfo(i, "lnS")
|
||||
if info == nil then
|
||||
break
|
||||
end
|
||||
local src = info.source
|
||||
if string.sub(src, 1, 1) == "@" then
|
||||
src = string.sub(src, 2)
|
||||
end
|
||||
local name
|
||||
if info.name then
|
||||
name = info.name .. " (" .. (info.namewhat or "?") .. ")"
|
||||
else
|
||||
name = "?"
|
||||
end
|
||||
local sframe = {
|
||||
name = name,
|
||||
source = {
|
||||
name = nil,
|
||||
path = Path.toAbsolute(sourceBasePath, src)
|
||||
},
|
||||
column = 1,
|
||||
line = info.currentline or 1,
|
||||
id = i
|
||||
}
|
||||
stackFrames[#stackFrames + 1] = sframe
|
||||
end
|
||||
sendSuccess(req, {stackFrames = stackFrames})
|
||||
end
|
||||
local scopeTypes = {
|
||||
Locals = 1,
|
||||
Upvalues = 2,
|
||||
Globals = 3
|
||||
}
|
||||
function handlers.scopes(req)
|
||||
local depth = req.arguments.frameId
|
||||
local scopes = {}
|
||||
local addScope = function(name)
|
||||
scopes[#scopes + 1] = {
|
||||
name = name,
|
||||
expensive = false,
|
||||
variablesReference = depth * 1000000 + scopeTypes[name]
|
||||
}
|
||||
end
|
||||
addScope("Locals")
|
||||
addScope("Upvalues")
|
||||
addScope("Globals")
|
||||
sendSuccess(req, {scopes = scopes})
|
||||
end
|
||||
local registerVar = function(varNameCount, name_, value, noQuote)
|
||||
local ty = type(value)
|
||||
local name
|
||||
if type(name_) == "number" then
|
||||
name = "[" .. name_ .. "]"
|
||||
else
|
||||
name = tostring(name_)
|
||||
end
|
||||
if varNameCount[name] then
|
||||
varNameCount[name] = varNameCount[name] + 1
|
||||
name = name .. " (" .. varNameCount[name] .. ")"
|
||||
else
|
||||
varNameCount[name] = 1
|
||||
end
|
||||
local item = {name = name, type = ty}
|
||||
if ty == "string" and not noQuote then
|
||||
item.value = "\"" .. value .. "\""
|
||||
else
|
||||
item.value = tostring(value)
|
||||
end
|
||||
if ty == "table" or ty == "function" or ty == "userdata" then
|
||||
storedVariables[nextVarRef] = value
|
||||
item.variablesReference = nextVarRef
|
||||
nextVarRef = nextVarRef + 1
|
||||
else
|
||||
item.variablesReference = -1
|
||||
end
|
||||
return item
|
||||
end
|
||||
function handlers.variables(req)
|
||||
local varRef = req.arguments.variablesReference
|
||||
local variables = {}
|
||||
local varNameCount = {}
|
||||
local addVar = function(name, value, noQuote)
|
||||
variables[#variables + 1] = registerVar(varNameCount, name, value, noQuote)
|
||||
end
|
||||
if 1000000 <= varRef then
|
||||
local depth = math.floor(varRef / 1000000)
|
||||
local scopeType = varRef % 1000000
|
||||
if scopeType == scopeTypes.Locals then
|
||||
for i = 1, 9999 do
|
||||
local name, value = debug_getlocal(depth, i)
|
||||
if name == nil then
|
||||
break
|
||||
end
|
||||
addVar(name, value, nil)
|
||||
end
|
||||
elseif scopeType == scopeTypes.Upvalues then
|
||||
local info = debug_getinfo(depth, "f")
|
||||
if info and info.func then
|
||||
for i = 1, 9999 do
|
||||
local name, value = debug.getupvalue(info.func, i)
|
||||
if name == nil then
|
||||
break
|
||||
end
|
||||
addVar(name, value, nil)
|
||||
end
|
||||
end
|
||||
elseif scopeType == scopeTypes.Globals then
|
||||
for name, value in pairs(_G) do
|
||||
addVar(name, value)
|
||||
end
|
||||
table.sort(variables, function(a, b)
|
||||
return a.name < b.name
|
||||
end)
|
||||
end
|
||||
else
|
||||
local var = storedVariables[varRef]
|
||||
if type(var) == "table" then
|
||||
for k, v in pairs(var) do
|
||||
addVar(k, v)
|
||||
end
|
||||
table.sort(variables, function(a, b)
|
||||
local aNum, aMatched = string.gsub(a.name, "^%[(%d+)%]$", "%1")
|
||||
local bNum, bMatched = string.gsub(b.name, "^%[(%d+)%]$", "%1")
|
||||
if aMatched == 1 and bMatched == 1 then
|
||||
return tonumber(aNum) < tonumber(bNum)
|
||||
elseif aMatched == bMatched then
|
||||
return a.name < b.name
|
||||
else
|
||||
return aMatched < bMatched
|
||||
end
|
||||
end)
|
||||
elseif type(var) == "function" then
|
||||
local info = debug.getinfo(var, "S")
|
||||
addVar("(source)", tostring(info.short_src), true)
|
||||
addVar("(line)", info.linedefined)
|
||||
for i = 1, 9999 do
|
||||
local name, value = debug.getupvalue(var, i)
|
||||
if name == nil then
|
||||
break
|
||||
end
|
||||
addVar(name, value)
|
||||
end
|
||||
elseif type(var) == "userdata" then
|
||||
addUserdataVar(var, addVar)
|
||||
end
|
||||
local mt = getmetatable(var)
|
||||
if mt then
|
||||
addVar("(metatable)", mt)
|
||||
end
|
||||
end
|
||||
sendSuccess(req, {variables = variables})
|
||||
end
|
||||
function handlers.continue(req)
|
||||
sendSuccess(req, {})
|
||||
return "CONTINUE"
|
||||
end
|
||||
local stackHeight = function()
|
||||
for i = 1, 9999999 do
|
||||
if debug_getinfo(i, "") == nil then
|
||||
return i
|
||||
end
|
||||
end
|
||||
end
|
||||
local stepTargetHeight
|
||||
local step = function()
|
||||
if stepTargetHeight == nil or stackHeight() <= stepTargetHeight then
|
||||
breaker.setLineBreak(nil)
|
||||
baseDepth = breaker.stackOffset.stepDebugLoop
|
||||
startDebugLoop()
|
||||
end
|
||||
end
|
||||
function handlers.next(req)
|
||||
stepTargetHeight = stackHeight() - breaker.stackOffset.step
|
||||
breaker.setLineBreak(step)
|
||||
sendSuccess(req, {})
|
||||
return "CONTINUE"
|
||||
end
|
||||
function handlers.stepIn(req)
|
||||
stepTargetHeight = nil
|
||||
breaker.setLineBreak(step)
|
||||
sendSuccess(req, {})
|
||||
return "CONTINUE"
|
||||
end
|
||||
function handlers.stepOut(req)
|
||||
stepTargetHeight = stackHeight() - (breaker.stackOffset.step + 1)
|
||||
breaker.setLineBreak(step)
|
||||
sendSuccess(req, {})
|
||||
return "CONTINUE"
|
||||
end
|
||||
function handlers.evaluate(req)
|
||||
local sourceCode = req.arguments.expression
|
||||
if string.sub(sourceCode, 1, 1) == "!" then
|
||||
sourceCode = string.sub(sourceCode, 2)
|
||||
else
|
||||
sourceCode = "return (" .. sourceCode .. ")"
|
||||
end
|
||||
local depth = req.arguments.frameId
|
||||
local tempG = {}
|
||||
local declared = {}
|
||||
local set = function(k, v)
|
||||
tempG[k] = v
|
||||
declared[k] = true
|
||||
end
|
||||
for name, value in pairs(_G) do
|
||||
set(name, value)
|
||||
end
|
||||
if depth then
|
||||
local info = debug_getinfo(depth, "f")
|
||||
if info and info.func then
|
||||
for i = 1, 9999 do
|
||||
local name, value = debug.getupvalue(info.func, i)
|
||||
if name == nil then
|
||||
break
|
||||
end
|
||||
set(name, value)
|
||||
end
|
||||
end
|
||||
for i = 1, 9999 do
|
||||
local name, value = debug_getlocal(depth, i)
|
||||
if name == nil then
|
||||
goto lbl_80
|
||||
end
|
||||
set(name, value)
|
||||
end
|
||||
else
|
||||
end
|
||||
::lbl_80::
|
||||
local mt = {
|
||||
__newindex = function()
|
||||
error("assignment not allowed", 2)
|
||||
end,
|
||||
__index = function(t, k)
|
||||
if not declared[k] then
|
||||
error("not declared", 2)
|
||||
end
|
||||
end
|
||||
}
|
||||
setmetatable(tempG, mt)
|
||||
local fn, err = (loadstring or load)(sourceCode, "X", nil, tempG)
|
||||
if fn == nil then
|
||||
sendFailure(req, string.gsub(err, "^%[string %\"X%\"%]%:%d+%: ", ""))
|
||||
return
|
||||
end
|
||||
if setfenv ~= nil then
|
||||
setfenv(fn, tempG)
|
||||
end
|
||||
local success, aux = pcall(fn)
|
||||
if not success then
|
||||
aux = aux or ""
|
||||
sendFailure(req, string.gsub(aux, "^%[string %\"X%\"%]%:%d+%: ", ""))
|
||||
return
|
||||
end
|
||||
local varNameCount = {}
|
||||
local item = registerVar(varNameCount, "", aux)
|
||||
sendSuccess(req, {
|
||||
result = item.value,
|
||||
type = item.type,
|
||||
variablesReference = item.variablesReference
|
||||
})
|
||||
end
|
||||
return debuggee
|
||||
Reference in New Issue
Block a user