diff --git a/analyze_structure.lua b/analyze_structure.lua index 56075dd..65cc1cb 100644 --- a/analyze_structure.lua +++ b/analyze_structure.lua @@ -39,10 +39,10 @@ local LEAF_VALUE_TYPES = { ['boolean'] = true, } -local SHORT_STRING_MAX_LEN = 7 -local MINIMUM_NUMBER_OF_SET_ELEMENTS = 2 - -local RECURSIVE_TOSTRING_TIMEOUT = 10 +local SHORT_STRING_MAX_LEN = 7 -- Range: [0, ∞[ +local MINIMUM_NUMBER_OF_SET_ELEMENTS = 2 -- Range: [1, ∞[ +local ALLOWED_HOLE_SIZE_IN_SEQUENCE = 1 -- Range: [0, ∞[. Set to 0, to completely disallow holes in sequences. +local RECURSIVE_TOSTRING_TIMEOUT = 10 -- Range: [1, ∞[. High values may result in crashes on specially-crafted input. -------------------------------------------------------------------------------- @@ -98,6 +98,8 @@ local function largest_number_index (t) end local function nr_elements_in_table (t) + -- Determines the total number of elements in the table. + assert(type(t) == 'table') -- local k, count = nil, -1 @@ -108,16 +110,30 @@ local function nr_elements_in_table (t) end local function nr_elements_in_seq (t) + -- Determines the number of elements in the sequence part of the table. + -- Allows holes of size `ALLOWED_HOLE_SIZE_IN_SEQUENCE`, before stopping. + -- This function works even when the given table's metamethods throws errors. + -- + -- Returns: + -- * Number: number of elements + -- * Boolean: whether the table has holes. + assert(type(t) == 'table') - if getmetatable(t) and getmetatable(t).__index then - return 0, false -- FIXME: Temporary stopgap for when __index throws an - -- error. I think we need to clone the numbers part of the table, to - -- fix this. + -- We don't want to crash if the __index metamethod throws an error, so we + -- copy the pairs with number keys into a fresh table, which we then operate + -- on lower down. + if debug.getmetatable(t) and debug.getmetatable(t).__index then + local t_prime = {} + for k, v in pairs(t) do + if type(k) == 'number' then t_prime[k] = v end + end + t = t_prime end + -- Now we run though the table, from 1 and up. local i, last_elem_i, nr_elems, has_holes = 0, 0, 0, false - while i <= last_elem_i + 2 do + while i <= last_elem_i + 1 + ALLOWED_HOLE_SIZE_IN_SEQUENCE do i = i + 1 if t[i] ~= nil then last_elem_i, nr_elems = i, nr_elems + 1 diff --git a/test/test_analyze_structure.lua b/test/test_analyze_structure.lua index 361e107..27f5764 100644 --- a/test/test_analyze_structure.lua +++ b/test/test_analyze_structure.lua @@ -229,6 +229,13 @@ SUITE:addTest('Can count elements, even though metatable.__index throws errors', assert_equal(2, info[input].seq_elems) end) +SUITE:addTest('Can count elements, even though metatable.__index throws errors and __metatable returns bogus', function () + local input = setmetatable({ 'hi', 'hello' }, {__index = function (_, k) error('Undefined access on key: '..k) end, __metatable = {}}) + local info = analyze_structure(input, math.huge) + + assert_equal(2, info[input].seq_elems) +end) + -------------------------------------------------------------------------------- -- Uniform structure