Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: 306ec0c999
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 257 lines (220 sloc) 10.567 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
-------------------------------------------------------------------------------
-- Copyright (c) 2011 Sierra Wireless and others.
-- All rights reserved. This program and the accompanying materials
-- are made available under the terms of the Eclipse Public License v1.0
-- which accompanies this distribution, and is available at
-- http://www.eclipse.org/legal/epl-v10.html
--
-- Contributors:
-- Sierra Wireless - initial API and implementation
-------------------------------------------------------------------------------

-- This module defines a single "class" which allows to dump arbitrary values by gathering as munch data as
-- possible on it for debugging purposes.
--
-- Recursions are avoided by "flatten" all tables in the structure and referencing them by their ID.
-- The dump format is documented in Confluence Wiki:
-- https://confluence.anyware-tech.com/display/ECLIPSE/Lua+IDE+Internship#LuaIDEInternship-Datadumpformat
--
-- It has two major parts :
-- dump : Result data structure: keys are numbers which are IDs for all dumped tables.
-- Values are table structures.
-- tables : Table dictionary, used to register all dumped tables, keys are tables,
-- values are their ID. Functions upvalues are also stored here in the same way.
--
-- The dump itself is done by type functions, one for each Lua type. For example a complete dump of the VM is done with:
-- local introspection = require"debugintrospection"
-- local dump = introspection:new()
-- dump.dump.root = dump:table(_G) -- so dump.root will be a number which point to the result of _G introspection
--
-- WARNING: do never keep any direct reference to internal fields (dump or tables): a dump object is not re-dumped to
-- avoid making huge data structures (and potentially overflow l2b)

-- Utility function (from ReadyAgent utils, copied here to avoid this dependency for a single function)
local function isregulararray(T)
    local n = 1
    for k, v in pairs(T) do
        if rawget(T, n) == nil then return false end
        n = n + 1
    end
    return true
end

local dump_pool = {}
dump_pool.__index = dump_pool

local all_dumps = setmetatable({ }, { __mode = "k" }) -- register all dumps to avoid to re-dump them (see above warning)

--- Creates a new dump pool with specified options
-- @param dump_locales (boolean) whether local values are dumped
-- @param dump_upvalues (boolean) whether function upvalues are dumped
-- @param dump_metatables (boolean) whether metatables (for tables and userdata) are dumped
-- @param dump_stacks (boolean) whether thread stacks are dumped
-- @param dump_fenv (boolean) whether function environments are dumped
function dump_pool:new(dump_locales, dump_upvalues, dump_metatables, dump_stacks, dump_fenv, keep_reference)
    local dump = setmetatable({
        current_id = 1,
        tables = { },
        dump = { },
        -- set switches, force a boolean value because nil would mess with __index metamethod
        dump_locales = dump_locales and true or false,
        dump_upvalues = dump_upvalues and true or false,
        dump_metatables = dump_metatables and true or false,
        dump_stacks = dump_stacks and true or false,
        dump_fenv = dump_fenv and true or false,
        keep_reference = keep_reference and true or false,
    }, self)
    all_dumps[dump] = true
    return dump
end

function dump_pool:_next_id()
    local id = self.current_id
    self.current_id = id + 1
    return id
end

function dump_pool:_register_new(value)
    local id = self.current_id
    self.current_id = id + 1
    self.tables[value] = id
    return id
end

--- Utility function to factorize all metatable handling
function dump_pool:_metatable(value, result, depth)
    --TODO: add support for __pairs and __ipairs ?
    if self.dump_metatables then
        local mt = getmetatable(value)
        if mt then
            result.metatable = self[type(mt)](self, mt, depth-1)
            if mt.__len then result.length = #value end
        end
    end
    return result
end

--- Adds a field into destination table, if both key and value has been successfully dumped
function dump_pool:_field(dest, key, value, depth)
    local dkey, dvalue = self[type(key)](self, key, depth-1), self[type(value)](self, value, depth-1)
    if dkey and dvalue then dest[#dest + 1] = { dkey, dvalue } end
end

--- Functions used to extract debug informations from different data types.
-- each function takes the value to debug as parameter and returns its
-- debugging structure (or an id, for tables), modifying the pool if needed.

function dump_pool:table(value, depth)
    depth = depth or math.huge
    if depth < 0 then return nil end
    
    if all_dumps[value] then return nil end
    local id = self.tables[value]
    if not id then
        -- this is a new table: register it
        id = self:_register_new(value)
        local t = { type = "table", repr = tostring(value), ref = self.keep_reference and value or nil }
        --Detect "arrays" (tables with 1..n keys). Empty tables are really tables
        t.array = (not not next(value)) and isregulararray(value)
        
        -- For arrays, make sure that keys are given in 1..n order
        for k,v in (t.array and ipairs or pairs)(value) do
            self:_field(t, k, v, depth)
        end
        
        -- The registered length refers to # result because if actual element count
        -- can be known with dumped values
        t.length = #value
        self:_metatable(value, t, depth)
        self.dump[id] = t
    end
    return id
end

function dump_pool:userdata(value, depth)
    depth = depth or math.huge
    if depth < 0 then return nil end
    
    return self:_metatable(value, { type = "userdata", repr = tostring(value), ref = self.keep_reference and value or nil }, depth)
end

function dump_pool:thread(value, depth)
    depth = depth or math.huge
    if depth < 0 then return nil end
    
    local result = { type = "thread", repr = tostring(value), status = coroutine.status(value), ref = self.keep_reference and value or nil }
    local stack = self.tables[value]
    if self.dump_stacks and not stack then
        stack = self:_register_new(value)
        local stack_table = { type="special" }
        
        for i=1, math.huge do
            if not debug.getinfo(value, i, "f") then break end
            -- _filed is not used here because i is not a function and there is no risk to get a nil from number or function
            stack_table[#stack_table+1] = { self:number(i, depth - 1), self["function"](self, i, depth - 1, value) }
        end
        
        stack_table.repr = tostring(#stack_table).." levels"
        self.dump[stack] = stack_table
    end
    result.stack = stack
    return result
end

dump_pool["function"] = function(self, value, depth, thread) -- function is a keyword...
    depth = depth or math.huge
    if depth < 0 then return nil end
    
    local info = thread and debug.getinfo(thread, value, "nSfl") or debug.getinfo(value, "nSfl")
    local func = info.func -- in case of value is a stack index
    local result = { type = "function", ref = self.keep_reference and func or nil }
    result.kind = info.what
    
    if info.name and #info.name > 0 then result.repr = "function: "..info.name -- put natural name, if available
    elseif func then result.repr = tostring(func) -- raw tostring otherwise
    else result.repr = "<tail call>" end -- nothing is available for tail calls
    
    if not func then return result end -- there is no more info to gather for tail calls
    
    if info.what ~= "C" then
        --TODO: do something if function is not defined in a file
        if info.source:sub(1,1) == "@" then
            result.file = info.source:sub(2)
        end
        result.line_from = info.linedefined
        result.line_to = info.lastlinedefined
        if info.currentline >= 0 then
            result.line_current = info.currentline
        end
    end
    
    -- Dump function env (if different from _G)
    local env = getfenv(func)
    if self.dump_fenv and env ~= getfenv(0) then
        result.environment = self:table(env, depth - 1)
    end
    
    -- Dump function upvalues (if any), trated as a table (recursion is handled in the same way)
    local upvalues = self.tables[func]
    if self.dump_upvalues and not upvalues and func and debug.getupvalue(func, 1) then
        -- Register upvalues table into result
        local ups_table = { type="special" }
        upvalues = self:_register_new(func)
        
        for i=1, math.huge do
            local name, val = debug.getupvalue(func, i)
            if not name then break end
            self:_field(ups_table, name, val, depth)
        end
        
        ups_table.repr = tostring(#ups_table)
        self.dump[upvalues] = ups_table
    end
    result.upvalues = upvalues
    
    -- Dump function locales (only for running function, recursion not handled)
    if self.dump_locales and type(value) == "number" then
        local getlocal = thread and function(...) return debug.getlocal(thread, ...) end or debug.getlocal
        if getlocal(value, 1) then
            local locales = { type="special" }
            local locales_id = self:_next_id()
            
            for i=1, math.huge do
                local name, val = getlocal(value, i)
                if not name then break
                elseif name:sub(1,1) ~= "(" and val ~= self then -- internal values are ignored
                    self:_field(locales, name, val, depth)
                end
            end
            
            locales.repr = tostring(#locales)
            self.dump[locales_id] = locales
            result.locales = locales_id
        end
    end
    return result
end

function dump_pool:string(value, depth)
    depth = depth or math.huge
    if depth < 0 then return nil end
    
    -- make the string printable (%q pattern keeps real newlines and adds quotes)
    return { type = "string", repr = string.format("%q", value):gsub("\\\n", "\\n"), length = #value,
             ref = self.keep_reference and value or nil }
end

-- default debug function for other types
setmetatable(dump_pool, {
    __index = function(cls, vtype)
        return function(self, value, depth)
            return (depth == nil or depth >= 0) and { repr = tostring(value), type=vtype, ref = self.keep_reference and value or nil } or nil
        end
    end
})

return dump_pool
Something went wrong with that request. Please try again.