How to make global variables "immutable" in Lua/LuaJ?

994 Views Asked by At

Description

I'm doing a LuaJ program, and here's a lib script like this:

function foo()
    print("foo");
end

I want the foo function can be invoked in other scripts directly (no require), but performs immutable in different scripts. ( Even a script overrides it, it performs as the original way in other scripts. )

For example, here's the script 1:

foo = function()
    print("bar");
end

and here is the script 2:

foo();

What's done

I have saw these two questions. They do work but not the solution to this problem.

LuaJ How to avoid overriding existing entries in Globals table

Preventing Function Overriding in Lua Table

Making global environment access-only (Lua)


I tried loading lib every time exec a script, or set local _ENV, but because there may be further callbacks from Java to Lua, it doesn't work correctly.

I now handle it by create a Globals and load lib script every time load a script in Java like this:

    public static void main(String[] args) {
        loadAndCallViaDifferentEnv(libPath, script1);
        loadAndCallViaDifferentEnv(libPath, script2);
    }

    static void loadAndCallViaDifferentEnv(String libPath, String scriptPath) {
        Globals globals = JsePlatform.standardGlobals();
        globals.loadfile(libPath).call();
        globals.loadfile(scriptPath).call();
    }

It works well, but costs much. Is there a better way?

2

There are 2 best solutions below

0
On BEST ANSWER

I assume you want to protect three functions from overwriting: foo1, foo2 and print

-- define foo1 and foo2 inside protected table instead of as usual globals
local protected = {}

function protected.foo1()  
   print("foo1");
end

function protected.foo2()
   print("foo2");
end

-- if a function to be protected already exists, remove it from globals:
protected.print = print
print = nil

-- Now set the metatable on globals
setmetatable(_G, {
   __index = protected,
   __newindex =
      function(t, k, v)
         if not protected[k] then
            rawset(t, k, v)
         end
      end
})

Now you can invoke foo1, foo2 and print from other modules without require, but you can not overwrite them:

-- the script 1:
foo1 = function()
   print("bar");
end
foo1();   -- original protected.foo1() is invoked
-- the script 2:
foo1();   -- original protected.foo1() is invoked
0
On

I did it slightly differently by "hiding" a special field. This makes the table immutabily not require a new metatable for each one

-- Imm{hi='immutable'}: make a table immutable.
-- A performant immutable table implementation. We actually hide the table
-- inside rawget(f, '#frozen'), but nobody needs to know that!

local Imm = setmetatable({
  __name='Imm', __fmt=mty.tblFmt,
  __newindex=function() error('invalid operation on Imm', 2) end,
  __index=function(v, k) return rawget(v, '#frozen')[k] end,
  __pairs=function(v)    return pairs(rawget(v, '#frozen')) end,
  __ipairs=function(v)   return ipairs(rawget(v, '#frozen')) end,
  __len=function(v)      return #rawget(v, '#frozen') end,
}, {__name='ImmTy', __call=function(ty_, t)
  return setmetatable({['#frozen']=t}, ty_) end
})

Soon to be available in https://github.com/civboot/civlua/blob/main/ds/ds.lua