diff --git a/build.zig b/build.zig index e4eabc91..cb635cba 100644 --- a/build.zig +++ b/build.zig @@ -28,6 +28,13 @@ pub const ApiCheck = enum { off, }; +pub const ApiCheckType = enum { + /// Apicheck with Zig stack traces for supported Lua verions. + stack_trace, + /// Default Lua api checks. + basic, +}; + pub fn build(b: *Build) void { // Remove the default install and uninstall steps b.top_level_steps = .{}; @@ -42,7 +49,9 @@ pub fn build(b: *Build) void { const luau_use_4_vector = b.option(bool, "luau_use_4_vector", "Build Luau to use 4-vectors instead of the default 3-vector.") orelse false; const lua_user_h = b.option(Build.LazyPath, "lua_user_h", "Lazy path to user supplied c header file") orelse null; const additional_system_headers = b.option([]Build.LazyPath, "additional_system_headers", "Slice of Lazy paths to additional system headers to include when building Lua") orelse null; - const api_check = b.option(ApiCheck, "apicheck", "Enable parameter checks in the Lua API") orelse .debug; + const api_check = b.option(ApiCheck, "apicheck", "Enable parameter check assertions in the Lua API") orelse .debug; + // TODO: if the stack traces go well and don't error, maybe remove this. Why would someone not want the stack trace? + const api_check_type = b.option(ApiCheckType, "apicheck_type", "Whether apicheck should show stack traces or only abort") orelse .stack_trace; if (lang == .luau and shared) { std.debug.panic("Luau does not support compiling or loading shared modules", .{}); @@ -108,7 +117,7 @@ pub fn build(b: *Build) void { t.mod.linkSystemLibrary(system_library_name, .{ .preferred_link_mode = link_mode }); } else if (b.lazyDependency(@tagName(lang), .{})) |upstream| { const lib = switch (lang) { - .luajit => luajit_setup.configure(b, target, optimize, upstream, shared), + .luajit => luajit_setup.configure(b, target, optimize, upstream, shared, api_check), .luau => luau_setup.configure(b, target, optimize, upstream, luau_use_4_vector), else => lua_setup.configure(b, target, optimize, upstream, .{ .lang = lang, @@ -116,6 +125,7 @@ pub fn build(b: *Build) void { .library_name = library_name, .lua_user_h = lua_user_h, .api_check = api_check, + .api_check_type = api_check_type, }), }; diff --git a/build/include/assert.h b/build/include/assert.h new file mode 100644 index 00000000..3c7a1922 --- /dev/null +++ b/build/include/assert.h @@ -0,0 +1,6 @@ +// This will be included when is included when LUA_USE_APICHECK is +// defined. Undefines any assert that may have already been defined, and then +// defines assert to be the zlua_assert function that shows a stack trace. +#undef assert +extern void zlua_assert(int e); +#define assert(e) zlua_assert((e) ? 1 : 0) diff --git a/build/lua.zig b/build/lua.zig index 9b5f0c3d..92bbfb2c 100644 --- a/build/lua.zig +++ b/build/lua.zig @@ -3,11 +3,13 @@ const std = @import("std"); const Build = std.Build; const Step = std.Build.Step; -const applyPatchToFile = @import("utils.zig").applyPatchToFile; +const utils = @import("utils.zig"); +const applyPatchToFile = utils.applyPatchToFile; const build = @import("../build.zig"); const ApiCheck = build.ApiCheck; +const ApiCheckType = build.ApiCheckType; const Language = build.Language; pub const Options = struct { @@ -16,6 +18,7 @@ pub const Options = struct { library_name: []const u8, lua_user_h: ?Build.LazyPath, api_check: ApiCheck, + api_check_type: ApiCheckType, }; pub fn configure( @@ -56,6 +59,8 @@ pub fn configure( const user_header = "user.h"; + const apicheck_enabled = opts.api_check == .on or (opts.api_check == .debug and optimize == .debug); + const flags = [_][]const u8{ // Standard version used in Lua Makefile "-std=gnu99", @@ -69,12 +74,12 @@ pub fn configure( }, // Enable api check - if (opts.api_check == .on or (opts.api_check == .debug and optimize == .debug)) "-DLUA_USE_APICHECK" else "", + if (apicheck_enabled) "-DLUA_USE_APICHECK" else "", // Build as DLL for windows if shared if (target.result.os.tag == .windows and shared) "-DLUA_BUILD_AS_DLL" else "", - if (lua_user_h) |_| b.fmt("-DLUA_USER_H=\"{s}\"", .{user_header}) else "", + if (lua_user_h != null) b.fmt("-DLUA_USER_H=\"{s}\"", .{user_header}) else "", if (target.result.cpu.arch == .wasm32) "-flto" else "", }; @@ -110,6 +115,9 @@ pub fn configure( library.installHeader(upstream.path("src/lauxlib.h"), "lauxlib.h"); library.installHeader(upstream.path("src/luaconf.h"), "luaconf.h"); + // This adds build/include/assert.h to the include path so Lua finds it before the system . + if (apicheck_enabled and opts.api_check_type == .stack_trace) library.root_module.addIncludePath(b.path("build/include")); + if (lua_user_h) |user_h| { library.root_module.addIncludePath(user_h.dirname()); library.installHeader(user_h, user_header); diff --git a/build/luajit.zig b/build/luajit.zig index cd0ee1ec..9a97b49e 100644 --- a/build/luajit.zig +++ b/build/luajit.zig @@ -3,9 +3,18 @@ const std = @import("std"); const Build = std.Build; const Step = std.Build.Step; +const ApiCheck = @import("../build.zig").ApiCheck; + const applyPatchToFile = @import("utils.zig").applyPatchToFile; -pub fn configure(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, upstream: *Build.Dependency, shared: bool) *Step.Compile { +pub fn configure( + b: *Build, + target: Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + upstream: *Build.Dependency, + shared: bool, + api_check: ApiCheck, +) *Step.Compile { // TODO: extract this to the main build function because it is shared between all specialized build functions const lib = b.createModule(.{ @@ -227,12 +236,19 @@ pub fn configure(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin. library.root_module.addIncludePath(recdef_header.dirname()); library.root_module.addIncludePath(folddef_header.dirname()); + const flags = [_][]const u8{ + // Enable api check + // TODO: can we support zig stack traces for LuaJIT too? + if (api_check == .on or (api_check == .debug and optimize == .debug)) "-DLUA_USE_APICHECK" else "", + }; + lib.addCSourceFiles(.{ .root = .{ .dependency = .{ .dependency = upstream, .sub_path = "", } }, .files = &luajit_vm, + .flags = &flags, }); lib.sanitize_c = .off; diff --git a/src/lib.zig b/src/lib.zig index 1b45fc99..f1162d86 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -111,6 +111,12 @@ extern "c" fn zig_registerAssertionHandler() void; /// This function is defined in luau.cpp and ensures Zig uses the correct free when compiling luau code extern "c" fn zig_luau_free(ptr: *anyopaque) void; +/// Called from C for Lua function parameter check assertions when LUA_USE_APICHECK is defined. +/// Essentially, this is used in place of C's assert so stack traces are shown. +export fn zlua_assert(ok: bool) void { + std.debug.assert(ok); +} + const Allocator = std.mem.Allocator; // Types @@ -731,12 +737,8 @@ pub const Lua = opaque { } else if (nsize == 0) { return null; } else { - const builtin = @import("builtin"); // FIXME: remove when zig-0.15 is released and 0.14 can be dropped // ptr is null, allocate a new block of memory - const new_ptr = (if (builtin.zig_version.major == 0 and builtin.zig_version.minor < 15) - allocator_ptr.alignedAlloc(u8, alignment, nsize) - else - allocator_ptr.alignedAlloc(u8, .fromByteUnits(alignment), nsize)) catch return null; + const new_ptr = allocator_ptr.alignedAlloc(u8, .fromByteUnits(alignment), nsize) catch return null; return new_ptr.ptr; } }