starwing / lua-protobuf

A Lua module to work with Google protobuf
MIT License
1.75k stars 387 forks source link

使用 unknown_import 加载带有 import 语句的 proto 时报错 #167

Closed najoast closed 2 years ago

najoast commented 2 years ago

重现方法

  1. 创建 test.proto 文件,内容为空即可。

    touch test.proto
  2. 创建 test2.lua,把以下代码拷入:

    
    local protoc = require "protoc"
    local p = protoc.new()
    p.include_imports = true
    p.unknown_import = function(self, name)
    return self:loadfile("test.proto")
    end

p:load [[ syntax = "proto3"; import "not_exist.proto"; ]]


3. 运行 `test2.lua`

lua test2.lua


### 报错信息

lua: ./protoc.lua:1169: bad argument #2 to 'encode' (table expected at field 'file', got boolean) stack traceback: [C]: in function 'pb.encode' ./protoc.lua:1169: in function 'protoc.compile' ./protoc.lua:1180: in function 'protoc.load' test2.lua:11: in main chunk [C]: in ?


### 原因简单分析
下面这段代码内的 on_import 的传参 info 是个 true
```lua
local function do_compile(self, f, ...)
   if self.include_imports then
      local old = self.on_import
      local infos = {}
      function self.on_import(info)
         insert_tab(infos, info)
      end

self:compilefile 最终会调用 pb.encode('.google.protobuf.FileDescriptorSet', set), 这个 set 就是上面的 infos,里面有个 true, 就导致在 encode 时报错了

function Parser:compilefile(fn)
   if self == Parser then self = Parser.new() end
   local set = do_compile(self, self.parsefile, self, fn)
   return pb.encode('.google.protobuf.FileDescriptorSet', set)
end

function Parser:loadfile(fn)
   if self == Parser then self = Parser.new() end
   local ret, pos = pb.load(self:compilefile(fn))
   if ret then return ret, pos end
   error("load failed at offset "..pos)
end

为什么有个 true

Parser:parse(src, name) 里,为了防止循环 load,在开始加载前赋了个 true,加载完成后再把加载结果放进去。


function Parser:parse(src, name)
   local loaded = self.loaded[name]
   if loaded then
      if loaded == true then
         error("loop loaded: "..name)
      end
      return loaded
   end

   self.loaded[name] = true
   -- 省略
   self.loaded[name] = name ~= "<input>" and info or nil
   return ctx:resolve(lex, info)
end

可能就是各种 callback 导致状态没同步好。

starwing commented 2 years ago

检查过了,你函数用错了,loadifle的意思是“将这个文件的内容载入pb的内存数据库”,你这里的需求是“编译期间载入一个新的文件继续编译”,所以你应该用“parsefile”函数,下面的代码就没有报错了:

local protoc = require "protoc"
local p = protoc.new()
p.include_imports = true
p.unknown_import = function(self, name)
   return self:parsefile("test.proto")
end

p:load [[
syntax = "proto3";
import "not_exist.proto";
]]