Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adjust file metatables even in compat53.module mode #67

Merged
merged 2 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions compat53/file_mt.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
local lua_version = _VERSION:sub(-3)

local M = {}

local unpack = lua_version == "5.1" and unpack or table.unpack

local function addasterisk(fmt)
if type(fmt) == "string" and fmt:sub(1, 1) ~= "*" then
return "*"..fmt
else
return fmt
end
end

function M.update_file_meta(file_meta, is_luajit52)

-- make '*' optional for file:read and file:lines

local file_lines = file_meta.__index.lines
file_meta.__index.lines = function(self, ...)
local n = select('#', ...)
for i = 1, n do
local a = select(i, ...)
local b = addasterisk(a)
-- as an optimization we only allocate a table for the
-- modified format arguments when we have a '*' somewhere
if a ~= b then
local args = { ... }
args[i] = b
for j = i+1, n do
args[j] = addasterisk(args[j])
end
return file_lines(self, unpack(args, 1, n))
end
end
return file_lines(self, ...)
end

local file_read = file_meta.__index.read
file_meta.__index.read = function(self, ...)
local n = select('#', ...)
for i = 1, n do
local a = select(i, ...)
local b = addasterisk(a)
-- as an optimization we only allocate a table for the
-- modified format arguments when we have a '*' somewhere
if a ~= b then
local args = { ... }
args[i] = b
for j = i+1, n do
args[j] = addasterisk(args[j])
end
return file_read(self, unpack(args, 1, n))
end
end
return file_read(self, ...)
end

if not is_luajit52 then
local file_write = file_meta.__index.write
file_meta.__index.write = function(self, ...)
local ret, err = file_write(self, ...)
if ret then
return self
end
return ret, err
end
end
end

return M
62 changes: 7 additions & 55 deletions compat53/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,57 +17,15 @@ if lua_version < "5.3" then
local file_meta = gmt(io.stdout)


-- make '*' optional for file:read and file:lines
if type(file_meta) == "table" and type(file_meta.__index) == "table" then

local function addasterisk(fmt)
if type(fmt) == "string" and fmt:sub(1, 1) ~= "*" then
return "*"..fmt
else
return fmt
end
end

local file_lines = file_meta.__index.lines
file_meta.__index.lines = function(self, ...)
local n = select('#', ...)
for i = 1, n do
local a = select(i, ...)
local b = addasterisk(a)
-- as an optimization we only allocate a table for the
-- modified format arguments when we have a '*' somewhere
if a ~= b then
local args = { ... }
args[i] = b
for j = i+1, n do
args[j] = addasterisk(args[j])
end
return file_lines(self, unpack(args, 1, n))
end
end
return file_lines(self, ...)
end
-- detect LuaJIT (including LUAJIT_ENABLE_LUA52COMPAT compilation flag)
local is_luajit = (string.dump(function() end) or ""):sub(1, 3) == "\027LJ"
local is_luajit52 = is_luajit and
#setmetatable({}, { __len = function() return 1 end }) == 1

local file_read = file_meta.__index.read
file_meta.__index.read = function(self, ...)
local n = select('#', ...)
for i = 1, n do
local a = select(i, ...)
local b = addasterisk(a)
-- as an optimization we only allocate a table for the
-- modified format arguments when we have a '*' somewhere
if a ~= b then
local args = { ... }
args[i] = b
for j = i+1, n do
args[j] = addasterisk(args[j])
end
return file_read(self, unpack(args, 1, n))
end
end
return file_read(self, ...)
end

if type(file_meta) == "table" and type(file_meta.__index) == "table" then
local file_mt = require("compat53.file_mt")
file_mt.update_file_meta(file_meta, is_luajit52)
end -- got a valid metatable for file objects


Expand All @@ -85,12 +43,6 @@ if lua_version < "5.3" then
local io_type = io.type


-- detect LuaJIT (including LUAJIT_ENABLE_LUA52COMPAT compilation flag)
local is_luajit = (string.dump(function() end) or ""):sub(1, 3) == "\027LJ"
local is_luajit52 = is_luajit and
#setmetatable({}, { __len = function() return 1 end }) == 1


-- make package.searchers available as an alias for package.loaders
local p_index = { searchers = package.loaders }
setmetatable(package, {
Expand Down
66 changes: 65 additions & 1 deletion compat53/module.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ if lua_version < "5.3" then
debug, io, math, package, string, table
local io_lines = io.lines
local io_read = io.read
local io_open = io.open
local io_popen = io.popen
local io_tmpfile = io.tmpfile
local unpack = lua_version == "5.1" and unpack or table.unpack
local debug_setmetatable = type(debug) == "table" and debug.setmetatable

-- create module table
M = {}
Expand Down Expand Up @@ -450,7 +454,6 @@ if lua_version < "5.3" then
if type(debug) == "table" then
local debug_setfenv = debug.setfenv
local debug_getfenv = debug.getfenv
local debug_setmetatable = debug.setmetatable

M.debug = setmetatable({}, { __index = debug })

Expand Down Expand Up @@ -822,6 +825,67 @@ if lua_version < "5.3" then
end
end -- not luajit

if is_luajit then
local compat_file_meta = {}
local compat_file_meta_loaded = false

local function load_compat_file_meta(file_meta)
-- fill compat_file_meta with original entries
for k, v in pairs(file_meta) do
compat_file_meta[k] = v
end
compat_file_meta.__index = {}
for k, v in pairs(file_meta.__index) do
compat_file_meta.__index[k] = v
end

-- update it with compatibility functions
local file_mt = require("compat53.file_mt")
file_mt.update_file_meta(compat_file_meta, is_luajit52)

compat_file_meta_loaded = true
end

function M.io.open(...)
local fd, err = io_open(...)
if fd and debug_setmetatable then
if not compat_file_meta_loaded then
local file_meta = gmt(fd)
load_compat_file_meta(file_meta)
end
debug_setmetatable(fd, compat_file_meta)
end

return fd, err
end

function M.io.popen(...)
local fd, err = io_popen(...)
if fd and debug_setmetatable then
if not compat_file_meta_loaded then
local file_meta = gmt(fd)
load_compat_file_meta(file_meta)
end
debug_setmetatable(fd, compat_file_meta)
end

return fd, err
end

function M.io.tmpfile(...)
local fd, err = io_tmpfile(...)
if fd and debug_setmetatable then
if not compat_file_meta_loaded then
local file_meta = gmt(fd)
load_compat_file_meta(file_meta)
end
debug_setmetatable(fd, compat_file_meta)
end

return fd, err
end
end

end -- lua 5.1

-- further write should be forwarded to _G
Expand Down
2 changes: 1 addition & 1 deletion liolib.c
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ static int l_checkmode (const char *mode) {
typedef luaL_Stream LStream;


#define tolstream(L) ((LStream *)luaL_checkudata(L, 1, LUA_FILEHANDLE))
#define tolstream(L) (luaL_checktype(L, 1, LUA_TUSERDATA), (LStream *)lua_touserdata(L, 1))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This introduces a security issue: now file methods can be passed an incorrect userdata object and incur C undefined behaviour.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will look into reverting this line because I don't think it ended up being used in practice anyway. We're only really using the io.popen function from this file right now.

I tried using more of this module to implement Lua 5.3-like file:write() behavior in compat53.module mode, but file handles returned by compat-enabled modules would be incompatible with io functions from non-compat-enabled caller code.

In the end, I managed to solve my problem without having to jump this hoop because apparently LuaJIT doesn't such a check, so I can make my modified metatable for files in compat53.module there. As of now, file:write() returns the file handle using compat53.module in all VMs except PUC-Rio Lua 5.1.


#define isclosed(p) ((p)->closef == NULL)

Expand Down
1 change: 1 addition & 0 deletions rockspecs/compat53-scm-0.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ build = {
modules = {
["compat53.init"] = "compat53/init.lua",
["compat53.module"] = "compat53/module.lua",
["compat53.file_mt"] = "compat53/file_mt.lua",
["compat53.utf8"] = "lutf8lib.c",
["compat53.table"] = "ltablib.c",
["compat53.string"] = "lstrlib.c",
Expand Down
23 changes: 15 additions & 8 deletions tests/test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ end
local V = _VERSION:gsub("^.*(%d+)%.(%d+)$", "%1%2")
if jit then V = "jit" end

local is_puclua51 = (_VERSION == "Lua 5.1" and not jit)

local mode = "global"
if arg[1] == "module" then
mode = "module"
Expand Down Expand Up @@ -575,7 +577,7 @@ ___''
do
print("io.write()", io.type(io.write("hello world\n")))
local f = assert(io.tmpfile())
print("file:write()", io.type(f:write("hello world\n")))
print("io.tmpfile => file:write()", io.type(f:write("hello world\n")))
f:close()
end

Expand All @@ -588,13 +590,18 @@ do
io.input("data.txt")
print("io.read()", io.read("n", "number", "l", "a"))
io.input(io.stdin)
if mode ~= "module" then
local f = assert(io.open("data.txt", "r"))
print("file:read()", f:read("*n", "*number", "*l", "*a"))
f:close()
f = assert(io.open("data.txt", "r"))
print("file:read()", f:read("n", "number", "l", "a"))
f:close()
if not is_puclua51 then
local f = assert(io.open("data.txt", "r"))
print("file:read()", f:read("*n", "*number", "*l", "*a"))
f:close()
f = assert(io.open("data.txt", "r"))
print("file:read()", f:read("n", "number", "l", "a"))
f:close()
os.remove("data.txt")

local g = assert(io.open("data.txt", "w"))
print("io.open => file:write()", type(g:write("hello")))
g:close()
end
os.remove("data.txt")
end
Expand Down
Loading