]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
[Test] Fix Lua 5.4 and cffi-lua compatibility issues
authorVsevolod Stakhov <vsevolod@rspamd.com>
Fri, 5 Dec 2025 20:43:20 +0000 (20:43 +0000)
committerVsevolod Stakhov <vsevolod@rspamd.com>
Fri, 5 Dec 2025 20:43:20 +0000 (20:43 +0000)
- Fix rspamd_memspn to handle empty character set without crash
- Add unpack compatibility shim for Lua 5.2+ (table.unpack)
- Replace deprecated table.maxn with # operator
- Fix cffi-lua strict type checking (char* vs unsigned char*)
- Add helpers for cdata-to-number conversion (64-bit integers)
- Add proper NULL pointer detection for cffi-lua
- Fix lua_resume to use coroutine threads in Lua 5.4
- Update test expectations for Lua 5.4 tostring(float) behavior

src/libutil/str_util.c
test/lua/telescope.lua
test/lua/unit/regxep.lua
test/lua/unit/selectors.lua
test/lua/unit/smtp_addr.lua
test/lua/unit/url.lua
test/lua/unit/utf.lua
test/rspamd_lua_pcall_vs_resume_test.c

index eaaeaccfe060c49a0e3646a6d1dbbf0724684edb..8ddd4f4a3ab6102dc29d94b12ee0bee3a2508be3 100644 (file)
@@ -2963,7 +2963,13 @@ gsize rspamd_memspn(const char *s, const char *e, gsize len)
        gsize byteset[32 / sizeof(gsize)];
        const char *p = s, *end = s + len;
 
+       if (!e[0]) {
+               /* Empty set - nothing matches */
+               return 0;
+       }
+
        if (!e[1]) {
+               /* Single character optimization */
                for (; p < end && *p == *e; p++);
                return p - s;
        }
index e2d9e65fd9fd5757382c458b36f5e5decdc651e5..29d9f36c49b938cc083d559b35f5341acf3a36c0 100644 (file)
@@ -36,6 +36,9 @@ local compat_env = require 'compat_env'
 local getfenv = _G.getfenv or compat_env.getfenv
 local setfenv = _G.setfenv or compat_env.setfenv
 
+-- Lua 5.2+ compatibility: unpack was moved to table.unpack
+local unpack = _G.unpack or table.unpack
+
 
 local _VERSION = "0.6.0"
 
index a27e7b3639c13f7ae59ae995ba099ea797c79fdb..25bf09c42ba6158e314cb67c3d9a63490546a4f1 100644 (file)
@@ -1,6 +1,6 @@
 context("Regexp unit tests", function()
   local re = require("rspamd_regexp")
-  
+
   test("Regexp creation", function()
     assert_not_nil(re.create_cached('/test$/m'))
     assert_not_nil(re.create_cached('^test$', 'm'))
@@ -21,36 +21,36 @@ context("Regexp unit tests", function()
       -- Cyrillic utf8 letter
       {'/\\S<[-\\w\\.]+\\@[-\\w\\.]+>/r', 'some<example@exаmple.com>', false},
     }
-    
+
     for _,c in ipairs(cases) do
       local r = re.create_cached(c[1])
       assert_not_nil(r, "cannot parse " .. c[1])
       local res = r:match(c[2])
-      
+
       assert_equal(res, c[3], string.format("'%s' doesn't match with '%s'",
         c[2], c[1]))
     end
   end)
-  
+
   test("Regexp capture", function()
     local cases = {
-      {'Body=(\\S+)(?: Fuz1=(\\S+))?(?: Fuz2=(\\S+))?', 
-        'mc-filter4 1120; Body=1 Fuz1=2 Fuz2=3', 
+      {'Body=(\\S+)(?: Fuz1=(\\S+))?(?: Fuz2=(\\S+))?',
+        'mc-filter4 1120; Body=1 Fuz1=2 Fuz2=3',
         {'Body=1 Fuz1=2 Fuz2=3', '1', '2', '3'}},
-      {'Body=(\\S+)(?: Fuz1=(\\S+))?(?: Fuz2=(\\S+))?', 
+      {'Body=(\\S+)(?: Fuz1=(\\S+))?(?: Fuz2=(\\S+))?',
       'mc-filter4 1120; Body=1 Fuz1=2', {'Body=1 Fuz1=2', '1', '2'}},
-      {'Body=(\\S+)(?: Fuz1=(\\S+))?(?: Fuz2=(\\S+))?', 
-      'mc-filter4 1120; Body=1 Fuz1=2 mc-filter4 1120; Body=1 Fuz1=2 Fuz2=3', 
+      {'Body=(\\S+)(?: Fuz1=(\\S+))?(?: Fuz2=(\\S+))?',
+      'mc-filter4 1120; Body=1 Fuz1=2 mc-filter4 1120; Body=1 Fuz1=2 Fuz2=3',
       {'Body=1 Fuz1=2', '1', '2'}, {'Body=1 Fuz1=2 Fuz2=3', '1', '2', '3'}},
     }
     for _,c in ipairs(cases) do
       local r = re.create_cached(c[1])
       assert_not_nil(r, "cannot parse " .. c[1])
       local res = r:search(c[2], false, true)
-      
+
       assert_not_nil(res, "cannot find pattern")
-      
-      for k = 3, table.maxn(c) do
+
+      for k = 3, #c do
         for n,m in ipairs(c[k]) do
           assert_equal(res[k - 2][n], c[k][n], string.format("'%s' doesn't match with '%s'",
             c[k][n], res[k - 2][n]))
@@ -58,7 +58,7 @@ context("Regexp unit tests", function()
       end
     end
   end)
-  
+
   test("Regexp split", function()
     local cases = {
       {'\\s', 'one', {'one'}}, -- one arg
@@ -68,23 +68,23 @@ context("Regexp unit tests", function()
       {'\\s', '  one   two  ', {'one', 'two'}}, -- multiple delimiters
       {'\\s', '  one   ', {'one'}}, -- multiple delimiters
       {'[:,]', ',,,:::one,two,,', {'one', 'two'}}, -- multiple delimiters
-      {'[\\|\\s]', '16265 | 1.1.1.0/22 | TR | ripencc | 2014-02-28', 
+      {'[\\|\\s]', '16265 | 1.1.1.0/22 | TR | ripencc | 2014-02-28',
         {'16265', '1.1.1.0/22', 'TR', 'ripencc', '2014-02-28'}}, -- practical
       {'|', '16265 | 1.1.1.0/22 | TR | ripencc | 2014-02-28', {}} -- bad re
     }
-  
+
     for _,c in ipairs(cases) do
       local r = re.create_cached(c[1])
       assert_not_nil(r, "cannot parse " .. c[1])
-      
+
       local res = r:split(c[2])
       assert_not_nil(res, "cannot split " .. c[2])
-      
+
       for i,r in ipairs(c[3]) do
         assert_equal(res[i], r)
       end
     end
   end)
-  
+
   end
 )
\ No newline at end of file
index 6362e5cdea5ce3e0aadb6bc6209742c272ebe3ab..1266a90521e4ccb1a7bc9351c8b322d9e049642a 100644 (file)
@@ -230,11 +230,11 @@ context("Selectors test", function()
 
     ["pool_var double"] = {
                 selector = [[pool_var("int_var", 'double')]],
-                expect = {"1"}},
+                expect = {"1.0"}},
 
     ["time"] = {
                 selector = "time",
-                expect = {"1537364211"}},
+                expect = {"1537364211.0"}},
 
 --    ["request_header"] = {
 --                selector = "request_header(hdr1)",
index afccd0a750743ab1a65500dfd524283961400bd3..c4ac167747fda110dff0da35eb854b8d50ead6b4 100644 (file)
@@ -86,11 +86,18 @@ context("SMTP address check functions", function()
     '<a@example.com><>',
   }
 
+  -- Helper to check if a cdata pointer is NULL (cffi-lua compatibility)
+  local function is_null(ptr)
+    if ptr == nil then return true end
+    -- For cffi-lua, check if pointer equals NULL
+    return ptr == ffi.new('void*')
+  end
+
   fun.each(function(case)
     test("Parse invalid smtp addr: " .. case, function()
       local st = ffi.C.rspamd_email_address_from_smtp(case, #case)
 
-      assert_nil(st, "should not be able to parse " .. case)
+      assert_true(is_null(st), "should not be able to parse " .. case)
     end)
   end, cases_invalid)
 
index d5c3ad42dfd4ae9ba25d4cd8b2a94354ad0b386e..83ee0fc476eda5d16bbc6ad93e5f1ec1ae649b8c 100644 (file)
@@ -215,13 +215,22 @@ context("URL check functions", function()
     { "///foo", "/foo" },
   }
 
+  -- Helper to convert cdata numbers to Lua numbers (cffi-lua compatibility)
+  local function cdata_to_number(v)
+    -- Try tonumber first (works in LuaJIT and for small values)
+    local n = tonumber(v)
+    if n then return n end
+    -- For 64-bit integers in cffi-lua, convert via string
+    return tonumber(tostring(v):match("^(%d+)"))
+  end
+
   for i, v in ipairs(cases) do
     test(string.format("Normalize paths '%s'", v[1]), function()
-      local buf = ffi.new("uint8_t[?]", #v[1])
+      local buf = ffi.new("char[?]", #v[1])
       local sizbuf = ffi.new("size_t[1]")
       ffi.copy(buf, v[1], #v[1])
       ffi.C.rspamd_normalize_path_inplace(buf, #v[1], sizbuf)
-      local res = ffi.string(buf, tonumber(sizbuf[0]))
+      local res = ffi.string(buf, cdata_to_number(sizbuf[0]))
       assert_equal(v[2], res, 'expected ' .. v[2] .. ' but got ' .. res .. ' in path ' .. v[1])
     end)
   end
index a4ea451b567c24ab3bdcacf009e88750a5bc6574..dbf493e3b99ceede85febc6d57a4e1171416aff8 100644 (file)
@@ -11,8 +11,8 @@ context("UTF8 check functions", function()
     void rspamd_fast_utf8_library_init (unsigned flags);
     void ottery_rand_bytes(void *buf, size_t n);
     double rspamd_get_ticks(int allow);
-    size_t rspamd_fast_utf8_validate (const unsigned char *data, size_t len);
-    size_t rspamd_fast_utf8_validate_ref (const unsigned char *data, size_t len);
+    size_t rspamd_fast_utf8_validate (const char *data, size_t len);
+    size_t rspamd_fast_utf8_validate_ref (const char *data, size_t len);
     char * rspamd_str_make_utf_valid (const char *src, size_t slen, size_t *dstlen, void *);
   ]]
 
@@ -78,6 +78,15 @@ context("UTF8 check functions", function()
     end)
   end
 
+  -- Helper to convert cdata numbers to Lua numbers (cffi-lua compatibility)
+  local function cdata_to_number(v)
+    -- Try tonumber first (works in LuaJIT and for small values)
+    local n = tonumber(v)
+    if n then return n end
+    -- For 64-bit integers in cffi-lua, convert via string
+    return tonumber(tostring(v):match("^(%d+)"))
+  end
+
   -- Enable sse and avx2
   ffi.C.rspamd_fast_utf8_library_init(3)
   local valid_cases = {
@@ -93,7 +102,7 @@ context("UTF8 check functions", function()
       ffi.copy(buf, c)
 
       local ret = ffi.C.rspamd_fast_utf8_validate(buf, #c)
-      assert_equal(ret, 0)
+      assert_equal(cdata_to_number(ret), 0)
     end)
   end
   local invalid_cases = {
@@ -119,7 +128,7 @@ context("UTF8 check functions", function()
       ffi.copy(buf, c)
 
       local ret = ffi.C.rspamd_fast_utf8_validate(buf, #c)
-      assert_not_equal(ret, 0)
+      assert_not_equal(cdata_to_number(ret), 0)
     end)
   end
 
index 82de9bb8ba23ec928e771a91405bcbf6d3322959..b7930f9b06433d9d630e3452ba4d8522152d13e9 100644 (file)
@@ -53,15 +53,19 @@ test_resume(lua_State *L, int function_call)
        t1 = rspamd_get_virtual_ticks();
 
        for (i = 0; i < N; i++) {
-               lua_rawgeti(L, LUA_REGISTRYINDEX, function_call);
+               /* In Lua 5.4, lua_resume must be called on a coroutine, not the main thread */
+               lua_State *coro = lua_newthread(L);
+               lua_rawgeti(coro, LUA_REGISTRYINDEX, function_call);
 #if LUA_VERSION_NUM < 502
-               lua_resume(L, 0);
+               lua_resume(coro, 0);
 #elif LUA_VERSION_NUM >= 504
-               lua_resume(L, NULL, 0, NULL);
+               int nres;
+               lua_resume(coro, NULL, 0, &nres);
 #else
-               lua_resume(L, NULL, 0);
+               lua_resume(coro, NULL, 0);
 #endif
-               lua_pop(L, 1);
+               lua_pop(coro, 1);
+               lua_pop(L, 1); /* pop the coroutine from main state */
        }
 
        t2 = rspamd_get_virtual_ticks();
@@ -85,7 +89,8 @@ test_resume_get_thread(int function_call)
 #if LUA_VERSION_NUM < 502
                lua_resume(ent->lua_state, 0);
 #elif LUA_VERSION_NUM >= 504
-               lua_resume(ent->lua_state, NULL, 0, NULL);
+               int nres;
+               lua_resume(ent->lua_state, NULL, 0, &nres);
 #else
                lua_resume(ent->lua_state, NULL, 0);
 #endif
@@ -115,7 +120,8 @@ test_resume_get_new_thread(int function_call)
 #if LUA_VERSION_NUM < 502
                lua_resume(ent->lua_state, 0);
 #elif LUA_VERSION_NUM >= 504
-               lua_resume(ent->lua_state, NULL, 0, NULL);
+               int nres;
+               lua_resume(ent->lua_state, NULL, 0, &nres);
 #else
                lua_resume(ent->lua_state, NULL, 0);
 #endif