From 2ad49dfd0c1cca74bab0321680701169726dd255 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sun, 28 Feb 2016 12:05:55 +0100 Subject: [PATCH 1/4] Fix some broken documentation in gears.debug Signed-off-by: Uli Schlachter --- lib/gears/debug.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gears/debug.lua b/lib/gears/debug.lua index ec27ed5d..8bf5f5c1 100644 --- a/lib/gears/debug.lua +++ b/lib/gears/debug.lua @@ -62,13 +62,13 @@ function debug.dump(data, tag, depth) print(debug.dump_return(data, tag, depth)) end --- Print an warning message +--- Print an warning message -- @tparam string message The warning message to print function debug.print_warning(message) io.stderr:write(os.date("%Y-%m-%d %T W: ") .. tostring(message) .. "\n") end --- Print an error message +--- Print an error message -- @tparam string message The error message to print function debug.print_error(message) io.stderr:write(os.date("%Y-%m-%d %T E: ") .. tostring(message) .. "\n") From 01f11003d6207dc153daf7dc7203a8c9090e3cf1 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sun, 28 Feb 2016 12:07:02 +0100 Subject: [PATCH 2/4] Add gears.protected_call This library is a wrapper around pcall() / xpcall() that prints an error message via gears.debug.print_error() in case of errors. Signed-off-by: Uli Schlachter --- lib/gears/init.lua | 1 + lib/gears/protected_call.lua | 58 ++++++++++++++++++++++++++++++ spec/gears/protected_call_spec.lua | 43 ++++++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 lib/gears/protected_call.lua create mode 100644 spec/gears/protected_call_spec.lua diff --git a/lib/gears/init.lua b/lib/gears/init.lua index 6f5198d4..4f5e83f6 100644 --- a/lib/gears/init.lua +++ b/lib/gears/init.lua @@ -17,6 +17,7 @@ return cache = require("gears.cache"); matrix = require("gears.matrix"); shape = require("gears.shape"); + protected_call = require("gears.protected_call"); } -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/lib/gears/protected_call.lua b/lib/gears/protected_call.lua new file mode 100644 index 00000000..41f9dfa7 --- /dev/null +++ b/lib/gears/protected_call.lua @@ -0,0 +1,58 @@ +--------------------------------------------------------------------------- +-- @author Uli Schlachter +-- @copyright 2016 Uli Schlachter +-- @release @AWESOME_VERSION@ +-- @module gears.protected_call +--------------------------------------------------------------------------- + +local gdebug = require("gears.debug") +local tostring = tostring +local traceback = debug.traceback +local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1) +local xpcall = xpcall + +local protected_call = {} + +local function error_handler(err) + gdebug.print_error(traceback("Error during a protected call: " .. tostring(err))) +end + +local function handle_result(success, ...) + if success then + return ... + end +end + +local do_pcall +if _VERSION <= "Lua 5.1" then + -- Lua 5.1 doesn't support arguments in xpcall :-( + do_pcall = function(func, ...) + local args = { ... } + return handle_result(xpcall(function() + return func(unpack(args)) + end, error_handler)) + end +else + do_pcall = function(func, ...) + return handle_result(xpcall(func, error_handler, ...)) + end +end + +--- Call a function in protected mode and handle error-reporting. +-- If the function call succeeds, all results of the function are returned. +-- Otherwise, an error message is printed and nothing is returned. +-- @tparam function func The function to call +-- @param ... Arguments to the function +-- @return The result of the given function, or nothing if an error occurred. +function protected_call.call(func, ...) + return do_pcall(func, ...) +end + +local pcall_mt = {} +function pcall_mt:__call(...) + return do_pcall(...) +end + +return setmetatable(protected_call, pcall_mt) + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/spec/gears/protected_call_spec.lua b/spec/gears/protected_call_spec.lua new file mode 100644 index 00000000..602f9bd9 --- /dev/null +++ b/spec/gears/protected_call_spec.lua @@ -0,0 +1,43 @@ +--------------------------------------------------------------------------- +-- @author Uli Schlachter +-- @copyright 2016 Uli Schlachter +--------------------------------------------------------------------------- + +local gdebug = require("gears.debug") +local protected_call = require("gears.protected_call") + +describe("gears.protected_call", function() + -- Stop the error reporting during tests + local orig_print_error = gdebug.print_error + local errors + before_each(function() + errors = {} + gdebug.print_error = function(err) + table.insert(errors, err) + end + end) + after_each(function() + gdebug.print_error = orig_print_error + end) + + it("Call with arguments and result", function() + local called = false + local function f(...) + called = true + assert.is_same({ ... }, { 1, "second" }) + return "first", 2 + end + local results = { protected_call(f, 1, "second") } + assert.is_true(called) + assert.is_same({ "first", 2 }, results) + assert.is_same(errors, {}) + end) + + it("Call with error", function() + assert.is_same({}, { protected_call(error, "I was called") }) + assert.is_same(#errors, 1) + assert.is_truthy(string.find(errors[1], "I was called")) + end) +end) + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 From 7bf2f17a8a6781159e189e64934a11031ba8797d Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sun, 28 Feb 2016 12:11:36 +0100 Subject: [PATCH 3/4] Make gears.widget.{fit,layout}_widget more robust This commit makes these methods invoke the method on a widget in a protected context. Thanks to this, e.g. the wibox and other widgets are protected from errors in a child widget. Additionally, fit_widget() now assumes 0 if a widget's :fit() method didn't provide a number. Signed-off-by: Uli Schlachter --- lib/wibox/widget/base.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/wibox/widget/base.lua b/lib/wibox/widget/base.lua index c528ad1d..d347a43c 100644 --- a/lib/wibox/widget/base.lua +++ b/lib/wibox/widget/base.lua @@ -8,6 +8,7 @@ local object = require("gears.object") local cache = require("gears.cache") local matrix = require("gears.matrix") +local protected_call = require("gears.protected_call") local util = require("awful.util") local setmetatable = setmetatable local pairs = pairs @@ -144,7 +145,7 @@ local widget_dependencies = setmetatable({}, { __mode = "kv" }) local function get_cache(widget, kind) if not widget._widget_caches[kind] then widget._widget_caches[kind] = cache.new(function(...) - return widget[kind](widget, ...) + return protected_call(widget[kind], widget, ...) end) end return widget._widget_caches[kind] @@ -222,9 +223,9 @@ function base.fit_widget(parent, context, widget, width, height) end end - -- Apply forced size - w = widget._forced_width or w - h = widget._forced_height or h + -- Apply forced size and handle nil's + w = widget._forced_width or w or 0 + h = widget._forced_height or h or 0 -- Also sanitize the output. w = math.max(0, math.min(w, width)) From 8560c6d2af9c6d8747b0f0df587ad6bb97633921 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sun, 28 Feb 2016 12:28:55 +0100 Subject: [PATCH 4/4] Use gears.protected_call where applicable Signed-off-by: Uli Schlachter --- lib/awful/menu.lua | 10 +++------- lib/awful/spawn.lua | 17 +++++------------ lib/beautiful/init.lua | 10 +++------- lib/gears/timer.lua | 19 +++++-------------- lib/wibox/hierarchy.lua | 12 +++--------- 5 files changed, 19 insertions(+), 49 deletions(-) diff --git a/lib/awful/menu.lua b/lib/awful/menu.lua index 2e29179d..8fdd51cb 100644 --- a/lib/awful/menu.lua +++ b/lib/awful/menu.lua @@ -20,13 +20,13 @@ local beautiful = require("beautiful") local dpi = require("beautiful").xresources.apply_dpi local object = require("gears.object") local surface = require("gears.surface") +local protected_call = require("gears.protected_call") local cairo = require("lgi").cairo local setmetatable = setmetatable local tonumber = tonumber local string = string local ipairs = ipairs local pairs = pairs -local pcall = pcall local print = print local table = table local type = type @@ -374,12 +374,8 @@ function menu:add(args, index) local theme = load_theme(args.theme or {}, self.theme) args.theme = theme args.new = args.new or menu.entry - local success, item = pcall(args.new, self, args) - if not success then - print("Error while creating menu entry: " .. item) - return - end - if not item.widget then + local item = protected_call(args.new, self, args) + if (not item) or (not item.widget) then print("Error while checking menu entry: no property widget found.") return end diff --git a/lib/awful/spawn.lua b/lib/awful/spawn.lua index 395f666a..b25ca803 100644 --- a/lib/awful/spawn.lua +++ b/lib/awful/spawn.lua @@ -24,6 +24,7 @@ local lgi = require("lgi") local Gio = lgi.Gio local GLib = lgi.GLib local util = require("awful.util") +local protected_call = require("gears.protected_call") local spawn = {} @@ -205,10 +206,7 @@ function spawn.read_lines(input_stream, line_callback, done_callback, close) stream:close() end if done_callback then - xpcall(done_callback, function(err) - print(debug.traceback("Error while calling done_callback:" - .. tostring(err), 2)) - end) + protected_call(done_callback) end end local start_read, finish_read @@ -226,14 +224,9 @@ function spawn.read_lines(input_stream, line_callback, done_callback, close) done() else -- Read a line - xpcall(function() - -- This needs tostring() for older lgi versions which returned - -- "GLib.Bytes" instead of Lua strings (I guess) - line_callback(tostring(line)) - end, function(err) - print(debug.traceback("Error while calling line_callback: " - .. tostring(err), 2)) - end) + -- This needs tostring() for older lgi versions which returned + -- "GLib.Bytes" instead of Lua strings (I guess) + protected_call(line_callback, tostring(line)) -- Read the next line start_read() diff --git a/lib/beautiful/init.lua b/lib/beautiful/init.lua index 7725f310..d7f95f2f 100644 --- a/lib/beautiful/init.lua +++ b/lib/beautiful/init.lua @@ -18,6 +18,7 @@ local lgi = require("lgi") local Pango = lgi.Pango local PangoCairo = lgi.PangoCairo local gears_debug = require("gears.debug") +local protected_call = require("gears.protected_call") local xresources = require("beautiful.xresources") @@ -109,7 +110,6 @@ end -- containing all the theme values. function beautiful.init(config) if config then - local success local homedir = os.getenv("HOME") -- If config is the path to the theme file, @@ -118,16 +118,12 @@ function beautiful.init(config) if type(config) == 'string' then -- Expand the '~' $HOME shortcut config = config:gsub("^~/", homedir .. "/") - success, theme = xpcall(function() return dofile(config) end, - debug.traceback) + theme = protected_call(dofile, config) elseif type(config) == 'table' then - success = true theme = config end - if not success then - return gears_debug.print_error("beautiful: error loading theme file " .. theme) - elseif theme then + if theme then -- expand '~' if homedir then for k, v in pairs(theme) do diff --git a/lib/gears/timer.lua b/lib/gears/timer.lua index 1c50cefe..7c320ef2 100644 --- a/lib/gears/timer.lua +++ b/lib/gears/timer.lua @@ -17,6 +17,7 @@ local traceback = debug.traceback local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1) local glib = require("lgi").GLib local object = require("gears.object") +local protected_call = require("gears.protected_call") --- Timer objects. This type of object is useful when triggering events repeatedly. -- The timer will emit the "timeout" signal every N seconds, N being the timeout @@ -43,11 +44,7 @@ function timer:start() return end self.data.source_id = glib.timeout_add(glib.PRIORITY_DEFAULT, self.data.timeout * 1000, function() - xpcall(function() - self:emit_signal("timeout") - end, function(err) - print(debug.traceback("Error during executing timeout handler: "..tostring(err))) - end) + protected_call(self.emit_signal, self, "timeout") return true end) self:emit_signal("start") @@ -126,10 +123,8 @@ end function timer.start_new(timeout, callback) local t = timer.new({ timeout = timeout }) t:connect_signal("timeout", function() - local success, cont = xpcall(callback, function(err) - print(debug.traceback("Error during executing timeout handler: "..tostring(err), 2)) - end) - if not success or not cont then + local cont = protected_call(callback) + if not cont then t:stop() end end) @@ -160,11 +155,7 @@ end local delayed_calls = {} capi.awesome.connect_signal("refresh", function() for _, callback in ipairs(delayed_calls) do - xpcall(function() - callback[1](unpack(callback, 2)) - end, function(err) - print(debug.traceback("Error during delayed call: "..tostring(err), 2)) - end) + protected_call(unpack(callback)) end delayed_calls = {} end) diff --git a/lib/wibox/hierarchy.lua b/lib/wibox/hierarchy.lua index 502d912f..5e6abc47 100644 --- a/lib/wibox/hierarchy.lua +++ b/lib/wibox/hierarchy.lua @@ -10,6 +10,7 @@ --------------------------------------------------------------------------- local matrix = require("gears.matrix") +local protected_call = require("gears.protected_call") local cairo = require("lgi").cairo local base = require("wibox.widget.base") local no_parent = base.no_parent_I_know_what_I_am_doing @@ -274,17 +275,10 @@ function hierarchy:draw(context, cr) local opacity = widget.opacity local function call(func, extra_arg1, extra_arg2) if not func then return end - local function error_function(err) - print(debug.traceback("Error while drawing widget: " .. tostring(err), 2)) - end if not extra_arg2 then - xpcall(function() - func(widget, context, cr, self:get_size()) - end, error_function) + protected_call(func, widget, context, cr, self:get_size()) else - xpcall(function() - func(widget, context, extra_arg1, extra_arg2, cr, self:get_size()) - end, error_function) + protected_call(func, widget, context, extra_arg1, extra_arg2, cr, self:get_size()) end end