environments, error messages, and stack traces, oh my!

Changed the way processes are launched.
multishell.openTab and kernel.run now accept the current environment as a parameter.
That new process inherits a copy of the passed environment.
Reduces complexity as the calling process is not required to create a suitable env.
Stack traces have been greatly improved and now include the stack for coroutines that error.
This commit is contained in:
kepler155c@gmail.com 2020-05-11 17:25:58 -06:00
parent bd911e80e8
commit 8279c1ae12
16 changed files with 151 additions and 187 deletions

View File

@ -255,7 +255,7 @@ function page.packetSlide:eventHandler(event)
page:setFocus(page.packetGrid)
elseif event.type == 'packet_lua' then
multishell.openTab({ path = 'sys/apps/Lua.lua', args = { self.currentPacket.message }, focused = true })
multishell.openTab(_ENV, { path = 'sys/apps/Lua.lua', args = { self.currentPacket.message }, focused = true })
elseif event.type == 'prev_packet' then
local c = self.currentPacket

View File

@ -12,6 +12,7 @@ local page = UI.Page {
buttons = {
{ text = 'Activate', event = 'activate' },
{ text = 'Terminate', event = 'terminate' },
{ text = 'Inspect', event = 'inspect' },
},
},
grid = UI.ScrollingGrid {
@ -49,6 +50,12 @@ local page = UI.Page {
multishell.setFocus(t.uid)
elseif event.type == 'terminate' then
multishell.terminate(t.uid)
elseif event.type == 'inspect' then
multishell.openTab(_ENV, {
path = 'sys/apps/Lua.lua',
args = { t },
focused = true,
})
end
end
if event.type == 'quit' then

View File

@ -110,7 +110,7 @@ page = UI.Page {
self.grid:draw()
elseif event.type == 'grid_select' then
multishell.openTab({
multishell.openTab(_ENV, {
path = 'sys/apps/Lua.lua',
args = { event.selected.raw },
focused = true,

View File

@ -1,5 +1,3 @@
_G.requireInjector(_ENV)
local Event = require('opus.event')
local Util = require('opus.util')

View File

@ -31,21 +31,14 @@ local function snmpConnection(socket)
socket:write('pong')
elseif msg.type == 'script' then
local env = kernel.makeEnv()
local fn, err = load(msg.args, 'script', nil, env)
if fn then
kernel.run({
fn = fn,
env = env,
title = 'script',
})
else
_G.printError(err)
end
kernel.run(_ENV, {
chunk = msg.args,
title = 'script',
})
elseif msg.type == 'scriptEx' then
local s, m = pcall(function()
local env = kernel.makeEnv()
local env = kernel.makeEnv(_ENV)
local fn, m = load(msg.args, 'script', nil, env)
if not fn then
error(m)

View File

@ -41,12 +41,12 @@ local function telnetHost(socket, mode)
end
end
local shellThread = kernel.run({
local shellThread = kernel.run(_ENV, {
window = win,
title = mode .. ' client',
hidden = true,
fn = function()
Util.run(kernel.makeEnv(), Alt.get('shell'), table.unpack(termInfo.program))
Util.run(kernel.makeEnv(_ENV), Alt.get('shell'), table.unpack(termInfo.program))
if socket.queue then
socket:write(socket.queue)
end

View File

@ -6,16 +6,6 @@ local fs = _G.fs
local settings = _G.settings
local shell = _ENV.shell
local sandboxEnv = { }
for k,v in pairs(_ENV) do
sandboxEnv[k] = v
end
sandboxEnv.package = nil
sandboxEnv.require = nil
sandboxEnv.arg = nil
sandboxEnv._ENV = nil
sandboxEnv.shell = shell
_G.requireInjector(_ENV)
local trace = require('opus.trace')
@ -51,7 +41,7 @@ local function run(...)
local args = tokenise(...)
local command = table.remove(args, 1) or error('No such program')
local isUrl = not not command:match("^(https?:)")
local env = shell.makeEnv()
local env = shell.makeEnv(_ENV)
local path, loadFn
if isUrl then
@ -64,7 +54,7 @@ local function run(...)
local fn, err = loadFn(path, env)
if not fn then
error(err)
error(err, -1)
end
if _ENV.multishell then
@ -287,13 +277,9 @@ function shell.getRunningInfo()
return tProgramStack[#tProgramStack]
end
function shell.setEnv(name, value)
_ENV[name] = value
sandboxEnv[name] = value
end
function shell.makeEnv()
local env = setmetatable(Util.shallowCopy(sandboxEnv), { __index = _G })
-- convenience function for making a runnable env
function shell.makeEnv(env)
env = setmetatable(Util.shallowCopy(env), { __index = _G })
_G.requireInjector(env)
return env
end
@ -321,7 +307,6 @@ function shell.newTab(tabInfo, ...)
if path then
tabInfo.path = path
tabInfo.env = shell.makeEnv()
tabInfo.args = args
tabInfo.title = fs.getName(path):match('([^%.]+)')
@ -329,25 +314,21 @@ function shell.newTab(tabInfo, ...)
table.insert(tabInfo.args, 1, tabInfo.path)
tabInfo.path = 'sys/apps/shell.lua'
end
return _ENV.multishell.openTab(tabInfo)
return _ENV.multishell.openTab(_ENV, tabInfo)
end
return nil, 'No such program'
end
function shell.openTab(...)
-- needs to use multishell.launch .. so we can run with stock multishell
local tWords = tokenise( ... )
local sCommand = tWords[1]
if sCommand then
local sPath = shell.resolveProgram(sCommand)
if sPath == "sys/apps/shell.lua" then
return _ENV.multishell.launch(shell.makeEnv(), sPath, table.unpack(tWords, 2))
else
return _ENV.multishell.launch(shell.makeEnv(), "sys/apps/shell.lua", sCommand, table.unpack(tWords, 2))
end
if not _ENV.multishell then
function shell.newTab()
error('Multishell is not available')
end
end
function shell.openTab(...)
return shell.newTab({ }, ...)
end
function shell.openForegroundTab( ... )
return shell.newTab({ focused = true }, ...)
end

View File

@ -10,9 +10,8 @@ if multishell and multishell.getTabs then
local tab = kernel.getFocused()
if tab and not tab.noTerminate then
multishell.terminate(tab.uid)
multishell.openTab({
multishell.openTab(tab.env, {
path = tab.path,
env = tab.env,
args = tab.args,
focused = true,
})

View File

@ -39,7 +39,7 @@ local function systemLog()
keyboard.removeHotkey('control-d')
end
kernel.run({
kernel.run(_ENV, {
title = 'System Log',
fn = systemLog,
noTerminate = true,

View File

@ -15,7 +15,7 @@ do
end
local function startNetwork()
kernel.run({
kernel.run(_ENV, {
title = 'Net daemon',
path = 'sys/apps/netdaemon.lua',
hidden = true,

View File

@ -8,7 +8,6 @@ local kernel = _G.kernel
local keys = _G.keys
local os = _G.os
local printError = _G.printError
local shell = _ENV.shell
local window = _G.window
local parentTerm = _G.device.terminal
@ -18,7 +17,7 @@ local tabsDirty = false
local closeInd = Util.getVersion() >= 1.76 and '\215' or '*'
local multishell = { }
shell.setEnv('multishell', multishell)
_ENV.multishell = multishell
kernel.window.reposition(1, 2, w, h - 1)
@ -93,22 +92,21 @@ function multishell.getTabs()
return kernel.routines
end
function multishell.launch( tProgramEnv, sProgramPath, ... )
function multishell.launch(env, path, ...)
-- backwards compatibility
return multishell.openTab({
env = tProgramEnv,
path = sProgramPath,
return multishell.openTab(env, {
path = path,
args = { ... },
})
end
function multishell.openTab(tab)
function multishell.openTab(env, tab)
if not tab.title and tab.path then
tab.title = fs.getName(tab.path):match('([^%.]+)')
end
tab.title = tab.title or 'untitled'
tab.window = tab.window or window.create(parentTerm, 1, 2, w, h - 1, false)
tab.onExit = function(self, result, err)
tab.onExit = tab.onExit or function(self, result, err)
if not result and err and err ~= 'Terminated' or (err and err ~= 0) then
self.terminal.setBackgroundColor(colors.black)
if tonumber(err) then
@ -131,14 +129,17 @@ function multishell.openTab(tab)
end
end
local routine = kernel.run(tab)
local routine, message = kernel.run(env, tab)
if tab.focused then
multishell.setFocus(routine.uid)
else
redrawMenu()
if routine then
if tab.focused then
multishell.setFocus(routine.uid)
else
redrawMenu()
end
end
return routine.uid
return routine and routine.uid, message
end
function multishell.hideTab(tabId)
@ -316,15 +317,22 @@ kernel.hook('mouse_scroll', function(_, eventData)
end)
kernel.hook('kernel_ready', function()
overviewId = multishell.openTab({
path = config.launcher or 'sys/apps/Overview.lua',
overviewId = multishell.openTab(_ENV, {
path = 'sys/apps/shell.lua',
args = { config.launcher or 'sys/apps/Overview.lua' },
isOverview = true,
noTerminate = true,
focused = true,
title = '+',
onExit = function(_, s, m)
if not s then
kernel.halt(s, m)
end
end,
})
multishell.setTitle(overviewId, '+')
multishell.openTab({
multishell.openTab(_ENV, {
path = 'sys/apps/shell.lua',
args = { 'sys/apps/autorun.lua' },
title = 'Autorun',

View File

@ -108,6 +108,38 @@ function Routine:resume(event, ...)
end
end
function Routine:run()
self.co = self.co or coroutine.create(function()
local result, err, fn
if self.fn then
fn = self.fn
_G.setfenv(fn, self.env)
elseif self.path then
fn, err = loadfile(self.path, self.env)
elseif self.chunk then
fn, err = load(self.chunk, self.title, nil, self.env)
end
if fn then
result, err = trace(fn, table.unpack(self.args or { } ))
else
err = err or 'kernel: invalid routine'
end
pcall(self.onExit, self, result, err)
self:cleanup()
if not result then
error(err)
end
end)
table.insert(kernel.routines, self)
return self:resume()
end
-- override if any post processing is required
function Routine:onExit(status, message) -- self, status, message
if not status and message ~= 'Terminated' then
@ -134,9 +166,14 @@ function kernel.getShell()
return shell
end
kernel.makeEnv = shell.makeEnv
-- each routine inherits the parent's env
function kernel.makeEnv(env)
env = setmetatable(Util.shallowCopy(env or _ENV), { __index = _G })
_G.requireInjector(env)
return env
end
function kernel.newRoutine(args)
function kernel.newRoutine(env, args)
kernel.UID = kernel.UID + 1
local routine = setmetatable({
@ -147,52 +184,16 @@ function kernel.newRoutine(args)
}, { __index = Routine })
Util.merge(routine, args)
routine.env = args.env or shell.makeEnv()
routine.env = args.env or kernel.makeEnv(env)
routine.terminal = routine.terminal or routine.window
return routine
end
local function xprun(env, path, ...)
setmetatable(env, { __index = _G })
local fn, m = loadfile(path, env)
if fn then
return trace(fn, ...)
end
return fn, m
end
function kernel.launch(routine)
routine.co = routine.co or coroutine.create(function()
local result, err
if routine.fn then
result, err = Util.runFunction(routine.env, routine.fn, table.unpack(routine.args or { } ))
elseif routine.path then
result, err = xprun(routine.env, routine.path, table.unpack(routine.args or { } ))
else
err = 'kernel: invalid routine'
end
pcall(routine.onExit, routine, result, err)
routine:cleanup()
if not result then
error(err)
end
end)
table.insert(kernel.routines, routine)
local s, m = routine:resume()
return s and routine.uid, m
end
function kernel.run(args)
local routine = kernel.newRoutine(args)
kernel.launch(routine)
return routine
function kernel.run(env, args)
local routine = kernel.newRoutine(env, args)
local s, m = routine:run()
return s and routine, m
end
function kernel.raise(uid)
@ -314,9 +315,10 @@ local function init(...)
for _,file in ipairs(files) do
local level = file:match('(%d).%S+.lua') or 99
if tonumber(level) <= runLevel then
-- All init programs run under the original shell
local s, m = shell.run(fs.combine(dir, file))
if not s then
error(m)
error(m, -1)
end
os.sleep(0)
end
@ -331,7 +333,7 @@ local function init(...)
shell.run('sys/apps/autorun.lua')
local win = window.create(kernel.terminal, 1, 1, w, h, true)
local s, m = kernel.run({
local s, m = kernel.run(_ENV, {
title = args[1],
path = 'sys/apps/shell.lua',
args = args,
@ -349,7 +351,7 @@ local function init(...)
end
end
kernel.run({
kernel.run(_ENV, {
fn = init,
title = 'init',
args = { ... },

View File

@ -100,7 +100,7 @@ local function crypt(data, key, nonce, cntr, round)
cntr = tonumber(cntr) or 1
round = tonumber(round) or 20
local throttle = Util.throttle(function() _syslog('throttle') end)
local throttle = Util.throttle()
local out = {}
local state = initState(key, nonce, cntr)
local blockAmt = math.floor(#data/64)

View File

@ -58,6 +58,14 @@ function Routine:resume(event, ...)
else
s, m = coroutine.resume(self.co, event, ...)
end
if not s and event ~= 'terminate' then
if m and debug and debug.traceback then
local t = (debug.traceback(self.co, 1)) or ''
m = m .. '\n' .. t:match('%d\n(.+)')
end
end
if self:isDead() then
self.co = nil
self.filter = nil

View File

@ -32,95 +32,63 @@ local function traceback(x)
end
end
local function trim_traceback(target, marker)
local ttarget, tmarker = {}, {}
for line in target:gmatch("([^\n]*)\n?") do ttarget[#ttarget + 1] = line end
for line in marker:gmatch("([^\n]*)\n?") do tmarker[#tmarker + 1] = line end
local function trim_traceback(target)
local t = { }
local filters = {
"%[C%]: in function 'xpcall'",
"(...tail calls...)",
"xpcall: $",
"trace.lua:%d+:",
}
--[[
TODO : fix this trace
Anavrins - if you could take a look, it would be appreciated
-- basically i want the stack logged in a more readable
-- format and the normal code/error message to be returned
-- unsure why the traceback method concatenates the stack
-- when it can just be returned as a table - would make
-- filtering much simpler
-- the following seems to reduce the stacktrace way too
-- much - losing most of the relevant call stack
-- i have modified this a bit from the original - not sure
-- if my changes are causing the issues or not
-- Trim identical suffixes
local t_len, m_len = #ttarget, #tmarker
while t_len >= 3 and ttarget[t_len] == tmarker[m_len] do
table.remove(ttarget, t_len)
t_len, m_len = t_len - 1, m_len - 1
local function matchesFilter(line)
for _, filter in pairs(filters) do
if line:match(filter) then
return true
end
end
end
-- Trim elements from this file and xpcall invocations
while t_len >= 1 and ttarget[t_len]:find("^\tstack_trace%.lua:%d+:") or
ttarget[t_len] == "\t[C]: in function 'xpcall'" or ttarget[t_len] == " xpcall: " do
table.remove(ttarget, t_len)
t_len = t_len - 1
for line in target:gmatch("([^\n]*)\n?") do
if not matchesFilter(line) then
table.insert(t, line)
end
end
]]
ttarget[#ttarget] = nil -- remove 2 calls added by the added xpcall
ttarget[#ttarget] = nil
return ttarget
return t
end
--- Run a function with
return function (fn, ...)
-- So this is rather grim: we need to get the full traceback and current one and remove
-- the common prefix
local trace
local args = { ... }
-- xpcall in Lua 5.1 does not accept parameters
-- which is not ideal
local args = { ... }
local res = table.pack(xpcall(function()
return fn(table.unpack(args))
end, traceback))
if not res[1] then
trace = traceback("trace.lua:1:")
end
local ok, err = res[1], res[2]
if not ok and err ~= nil then
trace = trim_traceback(err, trace)
local trace = trim_traceback(err)
-- Find the position where the stack traceback actually starts
local trace_starts
for i = #trace, 1, -1 do
if trace[i] == "stack traceback:" then trace_starts = i; break end
end
_G._syslog('')
for _, line in pairs(trace) do
_G._syslog(line)
end
-- If this traceback is more than 15 elements long, keep the first 9, last 5
-- and put an ellipsis between the rest
local max = 10
if trace_starts and #trace - trace_starts > max then
local keep_starts = trace_starts + 7
for i = #trace - trace_starts - max, 0, -1 do
table.remove(trace, keep_starts + i)
err = { }
while true do
local line = table.remove(trace, 1)
if not line or line == 'stack traceback:' then
break
end
table.insert(err, line)
end
err = table.concat(err, '\n')
_G._syslog('\n' .. err .. '\n' .. 'stack traceback:')
for _, v in ipairs(trace) do
if v ~= 'stack traceback:' then
_G._syslog(v:gsub("in function", "in"))
end
table.insert(trace, keep_starts, " ...")
end
for k, line in pairs(trace) do
trace[k] = line:gsub("in function", " in")
end
return false, table.remove(trace, 1), table.concat(trace, "\n")
return ok, err
end
return table.unpack(res, 1, res.n)

View File

@ -117,7 +117,7 @@ function UI:init()
if ie.code == 'control-shift-mouse_click' then -- hack
local event = currentPage:pointToChild(x, y)
_ENV.multishell.openTab({
_ENV.multishell.openTab(_ENV, {
path = 'sys/apps/Lua.lua',
args = { event.element, self, _ENV },
focused = true })