From 311502deb058f2d506f83fba0507f1b4d9bce04a Mon Sep 17 00:00:00 2001 From: "d.belincev" Date: Tue, 20 Jan 2026 17:07:52 +0300 Subject: [PATCH 1/3] fix: add validation and conversion types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit При сравнении числа с булево, выдавало ошибку т.к. tonumber не мог привести его к числу. Добавил проверки на типы, преобразование булева в число и обратно. Исключил арифметические операции с булево и сравнения с строковым типом. --- jsonpath.lua | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/jsonpath.lua b/jsonpath.lua index 07bf2c9..b3d1b35 100755 --- a/jsonpath.lua +++ b/jsonpath.lua @@ -297,9 +297,18 @@ local function eval_ast(ast, obj) -- null must never be equal to other boolean, invert op1 return not op1 else + if type(op2) == 'string' then + return {} + end + if type(op2) == 'number' then + return op2 ~= 0 + end return (op2 and true or false) end elseif type(op1) == 'number' then + if type(op2) == 'boolean' then + return op2 and 1 or 0 + end return tonumber(op2) elseif type(op1) == 'cdata' and tostring(ffi.typeof(op1)) == 'ctype' then return tonumber(op2) @@ -314,6 +323,10 @@ local function eval_ast(ast, obj) return op1 and true or false end + local function is_bool(val) + return type(val) == 'boolean' + end + -- Helper helper: evaluate variable expression inside abstract syntax tree local function eval_var(expr, obj) if obj == nil then @@ -408,14 +421,29 @@ local function eval_ast(ast, obj) return nil, err end if operator == '+' then + if is_bool(op1) or is_bool(op2) then + return nil, "Prohibited arithmetic op on bool" + end op1 = tonumber(op1) + tonumber(op2) elseif operator == '-' then + if is_bool(op1) or is_bool(op2) then + return nil, "Prohibited arithmetic op on bool" + end op1 = tonumber(op1) - tonumber(op2) elseif operator == '*' then + if is_bool(op1) or is_bool(op2) then + return nil, "Prohibited arithmetic op on bool" + end op1 = tonumber(op1) * tonumber(op2) elseif operator == '/' then + if is_bool(op1) or is_bool(op2) then + return nil, "Prohibited arithmetic op on bool" + end op1 = tonumber(op1) / tonumber(op2) elseif operator == '%' then + if is_bool(op1) or is_bool(op2) then + return nil, "Prohibited arithmetic op on bool" + end op1 = tonumber(op1) % tonumber(op2) elseif operator:upper() == 'AND' or operator == '&&' then op1 = notempty(op1) and notempty(op2) @@ -426,22 +454,22 @@ local function eval_ast(ast, obj) elseif operator == '<>' or operator == '!=' then op1 = op1 ~= match_type(op1, op2) elseif operator == '>' then - if is_null(op1) then + if is_null(op1) or is_bool(op1) then return false end op1 = op1 > match_type(op1, op2) elseif operator == '>=' then - if is_null(op1) then + if is_null(op1) or is_bool(op1) then return false end op1 = op1 >= match_type(op1, op2) elseif operator == '<' then - if is_null(op1) then + if is_null(op1) or is_bool(op1) then return false end op1 = op1 < match_type(op1, op2) elseif operator == '<=' then - if is_null(op1) then + if is_null(op1) or is_bool(op1) then return false end op1 = op1 <= match_type(op1, op2) From 0cb6c491910f3a158050f108a0e8966f3898702e Mon Sep 17 00:00:00 2001 From: "d.belincev" Date: Wed, 21 Jan 2026 16:44:12 +0300 Subject: [PATCH 2/3] tests --- test/test.lua | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/test/test.lua b/test/test.lua index ff565de..d392ec8 100755 --- a/test/test.lua +++ b/test/test.lua @@ -946,6 +946,112 @@ testQuery = { lu.assertNil(err) lu.assertItemsEquals(result, { array[2], array[3] }) end, + + testFilterIntBoolComparison = function () + local array = { + { id = 1, value = 0 }, + { id = 2, value = 1 }, + { id = 3, value = 2 }, + } + local result, err = jp.query(array, '$[?(@.value==true)]') + lu.assertNil(err) + lu.assertItemsEquals(result, { array[2] }) + + local result, err = jp.query(array, '$[?(@.value>true)]') + lu.assertNil(err) + lu.assertItemsEquals(result, { array[3] }) + + local result, err = jp.query(array, '$[?(@.value>=true)]') + lu.assertNil(err) + lu.assertItemsEquals(result, { array[2], array[3] }) + + local result, err = jp.query(array, '$[?(@.value1)]') + lu.assertNil(err) + lu.assertItemsEquals(result, {}) + + local result, err = jp.query(array, '$[?(@.value>=1)]') + lu.assertNil(err) + lu.assertItemsEquals(result, {}) + + local result, err = jp.query(array, '$[?(@.value<1)]') + lu.assertNil(err) + lu.assertItemsEquals(result, {}) + + local result, err = jp.query(array, '$[?(@.value<=1)]') + lu.assertNil(err) + lu.assertItemsEquals(result, {}) + end, + + testFilterBoolStrComparison = function () + local array = { + { id = 1, value = true }, + { id = 2, value = false }, + } + local result, err = jp.query(array, '$[?(@.value=="1")]') + lu.assertNil(err) + lu.assertItemsEquals(result, {}) + + local result, err = jp.query(array, '$[?(@.value>"1")]') + lu.assertNil(err) + lu.assertItemsEquals(result, {}) + + local result, err = jp.query(array, '$[?(@.value>="1")]') + lu.assertNil(err) + lu.assertItemsEquals(result, {}) + + local result, err = jp.query(array, '$[?(@.value<"1")]') + lu.assertNil(err) + lu.assertItemsEquals(result, {}) + + local result, err = jp.query(array, '$[?(@.value<="1")]') + lu.assertNil(err) + lu.assertItemsEquals(result, {}) + end, + + testFilterArithmeticOpOnBool = function () + local array = { + { id = 1, value = 0 }, + { id = 1, value = 1 }, + { id = 2, value = 2 }, + } + local result, err = jp.query(array, '$[?(@.value==true+1)]') + lu.assertNil(err) + lu.assertItemsEquals(result, {}) + + local result, err = jp.query(array, '$[?(@.value==true*1)]') + lu.assertNil(err) + lu.assertItemsEquals(result, {}) + + local result, err = jp.query(array, '$[?(@.value==true/1)]') + lu.assertNil(err) + lu.assertItemsEquals(result, {}) + + local result, err = jp.query(array, '$[?(@.value==true%1)]') + lu.assertNil(err) + lu.assertItemsEquals(result, {}) + + local result, err = jp.query(array, '$[?(@.value<>false+1)]') + lu.assertNil(err) + lu.assertItemsEquals(result, {}) + end, } From 34b3a33f45bde81f568ce648693a3386aec5b36a Mon Sep 17 00:00:00 2001 From: "d.belincev" Date: Fri, 23 Jan 2026 14:36:53 +0300 Subject: [PATCH 3/3] fix: arihmetic op, redesign comporation --- jsonpath.lua | 119 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 77 insertions(+), 42 deletions(-) diff --git a/jsonpath.lua b/jsonpath.lua index b3d1b35..ab526f7 100755 --- a/jsonpath.lua +++ b/jsonpath.lua @@ -291,31 +291,46 @@ end)() local function eval_ast(ast, obj) -- Helper helper: match type of second operand to type of first operand - local function match_type(op1, op2) + local function match_cmp_type(op1, op2, compare) if type(op1) == 'boolean' then if is_null(op2) then - -- null must never be equal to other boolean, invert op1 - return not op1 + return not op1, nil else - if type(op2) == 'string' then - return {} + if type(op2) == 'string' then + return nil, "cannot compare boolean with string" end if type(op2) == 'number' then - return op2 ~= 0 + if compare then + return nil, "cannot compare boolean with number" + end + return op2 ~= 0, nil + end + if type(op2) == 'boolean' then + return op2, nil end - return (op2 and true or false) + return (op2 and true or false), nil end elseif type(op1) == 'number' then if type(op2) == 'boolean' then - return op2 and 1 or 0 + return op2 and 1 or 0, nil + end + if type(op2) == 'string' then + local num = tonumber(op2) + if num == nil then + return nil, "cannot compare number with non-numeric string" + end + return num, nil end - return tonumber(op2) + return tonumber(op2), nil elseif type(op1) == 'cdata' and tostring(ffi.typeof(op1)) == 'ctype' then - return tonumber(op2) + return tonumber(op2), nil elseif is_null(op1) then - return op2 + if compare then + return nil, "cannot compare null with other values" + end + return op2, nil end - return tostring(op2 or '') + return tostring(op2 or ''), nil end -- Helper helper: convert operand to boolean @@ -323,8 +338,10 @@ local function eval_ast(ast, obj) return op1 and true or false end - local function is_bool(val) - return type(val) == 'boolean' + local function is_str_or_int(val) + return type(val) == 'string' or + type(val) == 'number' or + (type(val) == 'cdata' and tostring(ffi.typeof(val)) == 'ctype') end -- Helper helper: evaluate variable expression inside abstract syntax tree @@ -421,28 +438,34 @@ local function eval_ast(ast, obj) return nil, err end if operator == '+' then - if is_bool(op1) or is_bool(op2) then - return nil, "Prohibited arithmetic op on bool" + if is_str_or_int(op1) and is_str_or_int(op2) then + op1 = tonumber(op1) + tonumber(op2) + else + return nil, "Only operations on strings and numbers are allowed." end - op1 = tonumber(op1) + tonumber(op2) elseif operator == '-' then - if is_bool(op1) or is_bool(op2) then - return nil, "Prohibited arithmetic op on bool" + if is_str_or_int(op1) and is_str_or_int(op2) then + op1 = tonumber(op1) - tonumber(op2) + else + return nil, "Only operations on strings and numbers are allowed." end - op1 = tonumber(op1) - tonumber(op2) elseif operator == '*' then - if is_bool(op1) or is_bool(op2) then - return nil, "Prohibited arithmetic op on bool" + if is_str_or_int(op1) and is_str_or_int(op2) then + op1 = tonumber(op1) * tonumber(op2) + else + return nil, "Only operations on strings and numbers are allowed." end - op1 = tonumber(op1) * tonumber(op2) elseif operator == '/' then - if is_bool(op1) or is_bool(op2) then - return nil, "Prohibited arithmetic op on bool" + if is_str_or_int(op1) and is_str_or_int(op2) then + op1 = tonumber(op1) / tonumber(op2) + else + return nil, "Only operations on strings and numbers are allowed." end - op1 = tonumber(op1) / tonumber(op2) elseif operator == '%' then - if is_bool(op1) or is_bool(op2) then - return nil, "Prohibited arithmetic op on bool" + if is_str_or_int(op1) and is_str_or_int(op2) then + op1 = tonumber(op1) % tonumber(op2) + else + return nil, "Only operations on strings and numbers are allowed." end op1 = tonumber(op1) % tonumber(op2) elseif operator:upper() == 'AND' or operator == '&&' then @@ -450,29 +473,41 @@ local function eval_ast(ast, obj) elseif operator:upper() == 'OR' or operator == '||' then op1 = notempty(op1) or notempty(op2) elseif operator == '=' or operator == '==' then - op1 = op1 == match_type(op1, op2) + local op2, err = match_cmp_type(op1, op2, false) + if err then + return nil, err + end + op1 = op1 == op2 elseif operator == '<>' or operator == '!=' then - op1 = op1 ~= match_type(op1, op2) + local op2, err = match_cmp_type(op1, op2, false) + if err then + return nil, err + end + op1 = op1 ~= op2 elseif operator == '>' then - if is_null(op1) or is_bool(op1) then - return false + local op2, err = match_cmp_type(op1, op2, true) + if err then + return nil, err end - op1 = op1 > match_type(op1, op2) + op1 = op1 > op2 elseif operator == '>=' then - if is_null(op1) or is_bool(op1) then - return false + local op2, err = match_cmp_type(op1, op2, true) + if err then + return nil, err end - op1 = op1 >= match_type(op1, op2) + op1 = op1 >= op2 elseif operator == '<' then - if is_null(op1) or is_bool(op1) then - return false + local op2, err = match_cmp_type(op1, op2, true) + if err then + return nil, err end - op1 = op1 < match_type(op1, op2) + op1 = op1 < op2 elseif operator == '<=' then - if is_null(op1) or is_bool(op1) then - return false + local op2, err = match_cmp_type(op1, op2, true) + if err then + return nil, err end - op1 = op1 <= match_type(op1, op2) + op1 = op1 <= op2 else return nil, 'unknown expression operator "' .. operator .. '"' end