folke / lazy.nvim

💤 A modern plugin manager for Neovim
https://lazy.folke.io/
Apache License 2.0
14.96k stars 362 forks source link

bug: time_t is the wrong size on i686-musl #1820

Open remexre opened 1 day ago

remexre commented 1 day ago

Did you check docs and existing issues?

Neovim version (nvim -v)

v0.11.0-dev-1201+g0e2f92ed79

Operating system/version

Alpine Linux v3.14

Describe the bug

(Note: this reproduces on a wide variety of Neovim commits, and on later versions of Alpine and on Gentoo with a musl profile.)

lazy.nvim segfaults on i686-musl systems. It seems that this can be fixed with the following patch:

diff --git a/lua/lazy/stats.lua b/lua/lazy/stats.lua
index 015a2be..5195a56 100644
--- a/lua/lazy/stats.lua
+++ b/lua/lazy/stats.lua
@@ -34,51 +34,51 @@ end
 function M.cputime()
   if M.C == nil then
     pcall(function()
       ffi.cdef([[
         typedef long time_t;
         typedef int clockid_t;
         typedef struct timespec {
           time_t   tv_sec;        /* seconds */
           long     tv_nsec;       /* nanoseconds */
         } nanotime;
         int clock_gettime(clockid_t clk_id, struct timespec *tp);
       ]])
       M.C = ffi.C
     end)
   end

   local function real()
     local pnano = assert(ffi.new("nanotime[?]", 1))
     local CLOCK_PROCESS_CPUTIME_ID = jit.os == "OSX" and 12 or 2
     ffi.C.clock_gettime(CLOCK_PROCESS_CPUTIME_ID, pnano)
     return tonumber(pnano[0].tv_sec) * 1e3 + tonumber(pnano[0].tv_nsec) / 1e6
   end

   local function fallback()
     return (vim.uv.hrtime() - require("lazy")._start) / 1e6
   end

-  local ok, ret = pcall(real)
-  if ok then
-    M.cputime = real
-    M._stats.real_cputime = true
-    return ret
-  else
+  -- local ok, ret = pcall(real)
+  -- if ok then
+    -- M.cputime = real
+    -- M._stats.real_cputime = true
+    -- return ret
+  -- else
     M.cputime = fallback
     return fallback()
-  end
+  -- end
 end

 function M.stats()
   M._stats.count = 0
   M._stats.loaded = 0
   for _, plugin in pairs(require("lazy.core.config").plugins) do
     M._stats.count = M._stats.count + 1
     if plugin._.loaded then
       M._stats.loaded = M._stats.loaded + 1
     end
   end
   return M._stats
 end

 return M

The typedef long time_t; is certainly incorrect here -- long is int32_t on i686, but time_t is int64_t.

(This is insufficient to fix the segfault, but it's at least an easy thing to fix.)

FWIW, an example stack trace on segfault is:

#0  0x56a8e8fe in lj_alloc_free (msp=0xf7e98008, ptr=<optimized out>) at lj_alloc.c:1400
#1  0x56abb727 in lj_mem_free (osize=<optimized out>, p=0xf7de6940, g=0xf7e981f8)
    at /code/.deps/build/src/luajit/src/lj_gc.h:122
#2  lj_cdata_free (g=0xf7e981f8, cd=<optimized out>) at lj_cdata.c:83
#3  0x56a97077 in gc_sweep (g=g@entry=0xf7e981f8, p=0xf7e92218, lim=9, lim@entry=40) at lj_gc.c:423
#4  0x56a97d6a in gc_onestep (L=0xf7e981c8) at lj_gc.c:683
#5  0x56a9840b in lj_gc_step (L=L@entry=0xf7e981c8) at lj_gc.c:732
#6  0x56a851d7 in lj_parse_keepstr (ls=ls@entry=0xffef0ca8, str=0xf7e9f2b1 "` changed `dir`:\n- from: `\"", len=26)
    at lj_parse.c:246
#7  0x56a820bd in lex_string (tv=<optimized out>, ls=0xffef0ca8) at lj_lex.c:285
#8  lex_scan (ls=ls@entry=0xffef0ca8, tv=tv@entry=0xffef0cb0) at lj_lex.c:368
#9  0x56a82a05 in lj_lex_next (ls=ls@entry=0xffef0ca8) at lj_lex.c:460
#10 0x56a85806 in expr_binop (ls=ls@entry=0xffef0ca8, v=v@entry=0xffef02a8, limit=limit@entry=4) at lj_parse.c:2118
#11 0x56a85d7f in expr_binop (ls=ls@entry=0xffef0ca8, v=v@entry=0xffef0360, limit=limit@entry=0) at lj_parse.c:2121
#12 0x56a88678 in expr (v=0xffef0360, ls=0xffef0ca8) at lj_parse.c:2132
#13 expr_list (v=0xffef0360, ls=0xffef0ca8) at lj_parse.c:1899
#14 parse_local (ls=0xffef0ca8) at lj_parse.c:2284
#15 parse_stmt (ls=0xffef0ca8) at lj_parse.c:2676
#16 parse_chunk (ls=ls@entry=0xffef0ca8) at lj_parse.c:2713
#17 0x56a87b57 in parse_block (ls=0xffef0ca8) at lj_parse.c:2429
#18 parse_then (ls=0xffef0ca8) at lj_parse.c:2619
#19 parse_if (line=99, ls=0xffef0ca8) at lj_parse.c:2629
#20 parse_stmt (ls=0xffef0ca8) at lj_parse.c:2655
#21 parse_chunk (ls=ls@entry=0xffef0ca8) at lj_parse.c:2713
#22 0x56a87b57 in parse_block (ls=0xffef0ca8) at lj_parse.c:2429
#23 parse_then (ls=0xffef0ca8) at lj_parse.c:2619
#24 parse_if (line=95, ls=0xffef0ca8) at lj_parse.c:2629
#25 parse_stmt (ls=0xffef0ca8) at lj_parse.c:2655
#26 parse_chunk (ls=ls@entry=0xffef0ca8) at lj_parse.c:2713
#27 0x56a853a3 in parse_body (ls=ls@entry=0xffef0ca8, e=e@entry=0xffef08e0, needself=<optimized out>,
    line=line@entry=71) at lj_parse.c:1876
#28 0x56a87713 in parse_func (line=71, ls=0xffef0ca8) at lj_parse.c:2309
#29 parse_stmt (ls=0xffef0ca8) at lj_parse.c:2672
#30 parse_chunk (ls=ls@entry=0xffef0ca8) at lj_parse.c:2713
#31 0x56a88aed in lj_parse (ls=ls@entry=0xffef0ca8) at lj_parse.c:2747
#32 0x56a88be4 in cpparser (L=0xf7e981c8, dummy=0x0, ud=0xffef0ca8) at lj_load.c:50
#33 0x56a9539c in lj_vm_cpcall ()
#34 0x56a88da7 in lua_loadx (L=L@entry=0xf7e981c8, reader=reader@entry=0x56a88c90 <reader_file>,
    data=data@entry=0xffef0d6c,
    chunkname=chunkname@entry=0xf7e28afc "@/root/.local/share/nvim/lazy/lazy.nvim/lua/lazy/core/meta.lua",
    mode=mode@entry=0x0) at lj_load.c:72
#35 0x56a88ef2 in luaL_loadfilex (L=L@entry=0xf7e981c8,
    filename=filename@entry=0xf7ddbf3c "/root/.local/share/nvim/lazy/lazy.nvim/lua/lazy/core/meta.lua",
    mode=mode@entry=0x0) at lj_load.c:117
#36 0x56ac6877 in lj_cf_loadfile (L=0xf7e981c8) at lib_base.c:386
#37 0x56a95078 in lj_BC_FUNCC ()
#38 0x56acbfc4 in lj_cf_package_require (L=0xf7e981c8) at lib_package.c:453
#39 0x56a95078 in lj_BC_FUNCC ()
#40 0x56acc0b9 in lj_cf_package_require (L=0xf7e981c8) at lib_package.c:464
#41 0x56a95078 in lj_BC_FUNCC ()
#42 0x56acc0b9 in lj_cf_package_require (L=0xf7e981c8) at lib_package.c:464
#43 0x56a95078 in lj_BC_FUNCC ()
#44 0x56a80af4 in lua_pcall (L=0xf7e981c8, nargs=0, nresults=0, errfunc=-2) at lj_api.c:1151
#45 0x5683d2d5 in nlua_pcall (lstate=0xf7e981c8, nargs=0, nresults=0) at ../src/nvim/lua/executor.c:174
#46 0x56841700 in nlua_exec_file (path=0xf7e0a3a0 "/root/.config/nvim/init.lua") at ../src/nvim/lua/executor.c:1860
#47 0x56935789 in do_source (fname=0xf7e11cb0 "/root/.config/nvim/init.lua", check_other=1, is_vimrc=1, ret_sid=0x0)
    at ../src/nvim/runtime.c:2224
#48 0x5684faad in do_user_initialization () at ../src/nvim/main.c:1995
#49 0x5684ffa5 in source_startup_scripts (parmp=0xffef1604) at ../src/nvim/main.c:2108
#50 0x5684c102 in main (argc=2, argv=0xffef1764) at ../src/nvim/main.c:463

How exactly we get to gc_sweep differs, but the trace always ends in those same frames #0-#3.

Steps To Reproduce

  1. Be on an i686-musl platform. (The muslcc/i686:i686-linux-musl Docker container is sufficient.)

  2. Use the following init.lua:

    -- Bootstrap lazy.nvim
    local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
    if not (vim.uv or vim.loop).fs_stat(lazypath) then
     local lazyrepo = "https://github.com/folke/lazy.nvim.git"
     local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })
     if vim.v.shell_error ~= 0 then
       vim.api.nvim_echo({
         { "Failed to clone lazy.nvim:\n", "ErrorMsg" },
         { out, "WarningMsg" },
         { "\nPress any key to exit..." },
       }, true, {})
       vim.fn.getchar()
       os.exit(1)
     end
    end
    vim.opt.rtp:prepend(lazypath)
    
    -- Make sure to setup `mapleader` and `maplocalleader` before
    -- loading lazy.nvim so that mappings are correct.
    -- This is also a good place to setup other settings (vim.opt)
    vim.g.mapleader = " "
    vim.g.maplocalleader = "\\"
    
    -- Setup lazy.nvim
    require("lazy").setup({
    })
    
    collectgarbage()
  3. Open nvim ~/.config/nvim/init.lua (or another not-tiny file; if it doesn't repro, open another/larger file)

  4. Get a segfault

Expected Behavior

Don't segfault

Repro

vim.env.LAZY_STDPATH = ".repro"
load(vim.fn.system("curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua"))()

require("lazy.minit").repro({
  spec = {
    -- add any other plugins here
  },
})

-- not always necessary
collectgarbage()
folke commented 1 day ago

So your fix is to just disable the cpu time??

remexre commented 1 day ago

Not a fix, a "debugging patch" -- it seems that it's pointing at that block as being related to the problem. The time_t definition, I can at least verify as being ABI-incorrect, so it could be the cause of the memory corruption, but I think there's something else there too (since just changing it to long long is insufficient to fix the segfault.)