|
105 | 105 | //! |
106 | 106 | //! ## Inspecting Call Stack |
107 | 107 | //! |
108 | | -//! Use `getInfo()`, `getArg()`, and `getLocal()` to examine the call stack at any time: |
| 108 | +//! Use `getInfo()`, `getArg()`, `getLocal()`, and `getUpvalue()` to examine the call stack at any time: |
109 | 109 | //! ```zig |
110 | 110 | //! const debug = lua.debug(); |
111 | 111 | //! const depth = debug.stackDepth(); |
|
129 | 129 | //! std.log.info("Local '{}' = {}", .{ l.name, l.value }); |
130 | 130 | //! } |
131 | 131 | //! const name = debug.setLocal(0, 1, i32, 42); // Set first local to 42 |
| 132 | +//! |
| 133 | +//! // Get and modify function upvalues |
| 134 | +//! const func = try lua.globals().get("myFunction", Lua.Function); |
| 135 | +//! defer func.?.deinit(); |
| 136 | +//! const upval1 = debug.getUpvalue(func.?, 1, i32); // Get first upvalue |
| 137 | +//! if (upval1) |uv| { |
| 138 | +//! std.log.info("Upvalue '{s}' = {}", .{ uv.name, uv.value }); |
| 139 | +//! } |
| 140 | +//! const upvalue_name = debug.setUpvalue(func.?, 1, i32, 99); // Set first upvalue to 99 |
132 | 141 | //! ``` |
133 | 142 | //! |
134 | 143 | //! ## Single-Step Debugging |
@@ -589,6 +598,110 @@ pub fn setLocal(self: Self, level: i32, n: i32, comptime T: type, value: T) ?[:0 |
589 | 598 | return name_ptr.?; |
590 | 599 | } |
591 | 600 |
|
| 601 | +/// Get upvalue at a specific position from a function. |
| 602 | +/// |
| 603 | +/// Retrieves the value of upvalue `n` from the specified function. |
| 604 | +/// Upvalues are numbered starting from 1 (first upvalue). |
| 605 | +/// |
| 606 | +/// IMPORTANT: Requires debug level 2 for Lua code compilation to get upvalue names. |
| 607 | +/// With debug level 0-1, upvalue names will be empty strings but values are still accessible. |
| 608 | +/// Use `Compiler.Opts{ .dbg_level = 2 }` when compiling Lua source for full debug information. |
| 609 | +/// |
| 610 | +/// Returns a struct containing both the upvalue name and value, or null if: |
| 611 | +/// - The function reference is invalid |
| 612 | +/// - The upvalue number is out of range |
| 613 | +/// - The function is a native/C function (C functions have unnamed upvalues) |
| 614 | +/// |
| 615 | +/// Example: |
| 616 | +/// ```zig |
| 617 | +/// const func = try lua.globals().get("myFunction", Lua.Function); |
| 618 | +/// defer func.?.deinit(); |
| 619 | +/// |
| 620 | +/// const debug = lua.debug(); |
| 621 | +/// const upval1 = debug.getUpvalue(func.?, 1, i32); // Get first upvalue as i32 |
| 622 | +/// if (upval1) |uv| { |
| 623 | +/// std.log.info("Upvalue '{}' = {}", .{ uv.name, uv.value }); |
| 624 | +/// } |
| 625 | +/// ``` |
| 626 | +/// |
| 627 | +/// Parameters: |
| 628 | +/// - `func`: Function reference to inspect |
| 629 | +/// - `n`: Upvalue number (1-based indexing) |
| 630 | +/// - `T`: Type to convert the upvalue to |
| 631 | +/// |
| 632 | +/// Returns: Struct with name and converted value, or null if not available |
| 633 | +pub fn getUpvalue(self: Self, func: Lua.Function, n: i32, comptime T: type) ?struct { name: [:0]const u8, value: T } { |
| 634 | + // Push function onto the stack to get its index |
| 635 | + stack.push(func.ref.lua, func.ref); |
| 636 | + defer self.state.pop(1); |
| 637 | + |
| 638 | + const name_ptr = self.state.getUpvalue(-1, n); |
| 639 | + if (name_ptr == null) { |
| 640 | + return null; // Upvalue not available |
| 641 | + } |
| 642 | + |
| 643 | + // Get the value from the top of the stack and pop it |
| 644 | + defer self.state.pop(1); |
| 645 | + |
| 646 | + const value = stack.toValue(func.ref.lua, T, -1) orelse return null; |
| 647 | + const name = name_ptr.?; |
| 648 | + |
| 649 | + return .{ .name = name, .value = value }; |
| 650 | +} |
| 651 | + |
| 652 | +/// Set upvalue at a specific position in a function. |
| 653 | +/// |
| 654 | +/// Sets the value of upvalue `n` in the specified function. |
| 655 | +/// Upvalues are numbered starting from 1 (first upvalue). |
| 656 | +/// |
| 657 | +/// IMPORTANT: Requires debug level 2 for Lua code compilation to get upvalue names. |
| 658 | +/// With debug level 0-1, upvalue names will be empty strings but values can still be set. |
| 659 | +/// Use `Compiler.Opts{ .dbg_level = 2 }` when compiling Lua source for full debug information. |
| 660 | +/// |
| 661 | +/// Returns the name of the upvalue that was set, or null if: |
| 662 | +/// - The function reference is invalid |
| 663 | +/// - The upvalue number is out of range |
| 664 | +/// - The function is a native/C function (C functions have unnamed upvalues) |
| 665 | +/// |
| 666 | +/// Example: |
| 667 | +/// ```zig |
| 668 | +/// const func = try lua.globals().get("myFunction", Lua.Function); |
| 669 | +/// defer func.?.deinit(); |
| 670 | +/// |
| 671 | +/// const debug = lua.debug(); |
| 672 | +/// const name = debug.setUpvalue(func.?, 1, i32, 42); // Set first upvalue to 42 |
| 673 | +/// if (name) |upvalue_name| { |
| 674 | +/// std.log.info("Set upvalue '{s}' = 42", .{upvalue_name}); |
| 675 | +/// } |
| 676 | +/// ``` |
| 677 | +/// |
| 678 | +/// Parameters: |
| 679 | +/// - `func`: Function reference to modify |
| 680 | +/// - `n`: Upvalue number (1-based indexing) |
| 681 | +/// - `T`: Type of the value to set |
| 682 | +/// - `value`: The value to set the upvalue to |
| 683 | +/// |
| 684 | +/// Returns: Name of the upvalue that was set, or null if not available |
| 685 | +pub fn setUpvalue(self: Self, func: Lua.Function, n: i32, comptime T: type, value: T) ?[:0]const u8 { |
| 686 | + // Push function onto the stack first to get its index |
| 687 | + stack.push(func.ref.lua, func.ref); |
| 688 | + |
| 689 | + // Push the value onto the stack (lua_setupvalue expects value on top) |
| 690 | + stack.push(func.ref.lua, value); |
| 691 | + |
| 692 | + const name_ptr = self.state.setUpvalue(-2, n); // Function is now at -2, value at -1 |
| 693 | + |
| 694 | + // Pop the function from stack (setUpvalue pops the value automatically) |
| 695 | + self.state.pop(1); |
| 696 | + |
| 697 | + if (name_ptr == null) { |
| 698 | + // setUpvalue pops the value even on failure, so we don't need to pop it |
| 699 | + return null; // Upvalue not available |
| 700 | + } |
| 701 | + |
| 702 | + return name_ptr.?; |
| 703 | +} |
| 704 | + |
592 | 705 | // Tests for debug functionality |
593 | 706 | const expect = std.testing.expect; |
594 | 707 | const expectEqual = std.testing.expectEqual; |
@@ -1012,3 +1125,165 @@ test "getLocal and setLocal in debug breakpoint" { |
1012 | 1125 |
|
1013 | 1126 | try expect(LocalVariableTester.breakpoint_hit); |
1014 | 1127 | } |
| 1128 | + |
| 1129 | +test "getUpvalue and setUpvalue in debug breakpoint" { |
| 1130 | + const lua = try Lua.init(&std.testing.allocator); |
| 1131 | + defer lua.deinit(); |
| 1132 | + |
| 1133 | + // Callback to test getUpvalue when breakpoint is hit |
| 1134 | + const UpvalueTester = struct { |
| 1135 | + var breakpoint_hit: bool = false; |
| 1136 | + |
| 1137 | + pub fn debugbreak(self: *@This(), debug: *Self, ar: Info) void { |
| 1138 | + _ = self; |
| 1139 | + _ = ar; |
| 1140 | + if (!breakpoint_hit) { |
| 1141 | + breakpoint_hit = true; |
| 1142 | + |
| 1143 | + // Get the current function and test its upvalues |
| 1144 | + const info = debug.getInfo(0, .{ .source = true, .name = true }); |
| 1145 | + std.debug.assert(info != null); |
| 1146 | + std.debug.assert(std.mem.eql(u8, info.?.name.?, "innerFunction")); |
| 1147 | + |
| 1148 | + // Get function on stack like in conformance test |
| 1149 | + var c_debug: State.Debug = undefined; |
| 1150 | + const get_result = debug.state.getInfo(0, "f", &c_debug); |
| 1151 | + std.debug.assert(get_result != 0); |
| 1152 | + // Function is now on top of stack at index -1 |
| 1153 | + |
| 1154 | + // Test getting first upvalue directly from stack function (like conformance test) |
| 1155 | + const upvalue_name = debug.state.getUpvalue(-1, 1); |
| 1156 | + std.debug.assert(upvalue_name != null); |
| 1157 | + std.debug.assert(std.mem.eql(u8, upvalue_name.?, "outer_var")); |
| 1158 | + // Value is now on top of stack |
| 1159 | + const upvalue_val = debug.state.toIntegerX(-1); |
| 1160 | + std.debug.assert(upvalue_val.? == 5); |
| 1161 | + debug.state.pop(1); // Pop upvalue |
| 1162 | + |
| 1163 | + // Test setting the upvalue to a new value |
| 1164 | + debug.state.pushInteger(999); |
| 1165 | + const set_name = debug.state.setUpvalue(-2, 1); // -2 because we pushed a value, function is at -2 |
| 1166 | + std.debug.assert(set_name != null); |
| 1167 | + std.debug.assert(std.mem.eql(u8, set_name.?, "outer_var")); |
| 1168 | + |
| 1169 | + // Verify the upvalue was actually changed |
| 1170 | + const upvalue_name2 = debug.state.getUpvalue(-1, 1); |
| 1171 | + std.debug.assert(upvalue_name2 != null); |
| 1172 | + const upvalue_val2 = debug.state.toIntegerX(-1); |
| 1173 | + std.debug.assert(upvalue_val2.? == 999); |
| 1174 | + debug.state.pop(1); // Pop upvalue |
| 1175 | + |
| 1176 | + // Test getting non-existent upvalue (should be null) |
| 1177 | + const upval_invalid = debug.state.getUpvalue(-1, 10); |
| 1178 | + std.debug.assert(upval_invalid == null); |
| 1179 | + |
| 1180 | + debug.state.pop(1); // Pop function from stack |
| 1181 | + } |
| 1182 | + } |
| 1183 | + }; |
| 1184 | + |
| 1185 | + var tester = UpvalueTester{}; |
| 1186 | + lua.setCallbacks(&tester); |
| 1187 | + |
| 1188 | + // Create a function with upvalues like in the conformance test |
| 1189 | + const code = |
| 1190 | + \\local outer_var = 5 |
| 1191 | + \\function innerFunction() |
| 1192 | + \\ return outer_var * 2 -- Breakpoint will be set on this line |
| 1193 | + \\end |
| 1194 | + ; |
| 1195 | + |
| 1196 | + // Compile with debug level 2 for upvalue names and breakpoint support |
| 1197 | + const Compiler = @import("Compiler.zig"); |
| 1198 | + var options = Compiler.Opts{}; |
| 1199 | + options.dbg_level = 2; |
| 1200 | + options.opt_level = 0; // Don't optimize away upvalues |
| 1201 | + |
| 1202 | + const result = try Compiler.compile(code, options); |
| 1203 | + defer result.deinit(); |
| 1204 | + switch (result) { |
| 1205 | + .ok => |bytecode| { |
| 1206 | + _ = try lua.exec(bytecode, void); |
| 1207 | + }, |
| 1208 | + .err => |message| { |
| 1209 | + std.debug.print("Compile error: {s}\n", .{message}); |
| 1210 | + return error.CompileError; |
| 1211 | + }, |
| 1212 | + } |
| 1213 | + |
| 1214 | + const func = try lua.globals().get("innerFunction", Lua.Function); |
| 1215 | + defer func.?.deinit(); |
| 1216 | + |
| 1217 | + // Try line 3 (function start is line 2, line 3 is return) |
| 1218 | + const actual_line = try func.?.setBreakpoint(3, true); |
| 1219 | + try expectEqual(actual_line, 3); |
| 1220 | + |
| 1221 | + const call_result = try func.?.call(.{}, i32); |
| 1222 | + try expectEqual(call_result.ok.?, 1998); // 999 * 2 (modified upvalue) |
| 1223 | + |
| 1224 | + try expect(UpvalueTester.breakpoint_hit); |
| 1225 | +} |
| 1226 | + |
| 1227 | +test "high-level getUpvalue and setUpvalue API" { |
| 1228 | + const lua = try Lua.init(&std.testing.allocator); |
| 1229 | + defer lua.deinit(); |
| 1230 | + |
| 1231 | + // Create a function with upvalues |
| 1232 | + const code = |
| 1233 | + \\local shared_value = 42 |
| 1234 | + \\function closure() |
| 1235 | + \\ return shared_value |
| 1236 | + \\end |
| 1237 | + ; |
| 1238 | + |
| 1239 | + // Compile with debug level 2 for upvalue names |
| 1240 | + const Compiler = @import("Compiler.zig"); |
| 1241 | + var options = Compiler.Opts{}; |
| 1242 | + options.dbg_level = 2; |
| 1243 | + options.opt_level = 0; // Don't optimize away upvalues |
| 1244 | + |
| 1245 | + const result = try Compiler.compile(code, options); |
| 1246 | + defer result.deinit(); |
| 1247 | + switch (result) { |
| 1248 | + .ok => |bytecode| { |
| 1249 | + _ = try lua.exec(bytecode, void); |
| 1250 | + }, |
| 1251 | + .err => |message| { |
| 1252 | + std.debug.print("Compile error: {s}\n", .{message}); |
| 1253 | + return error.CompileError; |
| 1254 | + }, |
| 1255 | + } |
| 1256 | + |
| 1257 | + const func = try lua.globals().get("closure", Lua.Function); |
| 1258 | + defer func.?.deinit(); |
| 1259 | + |
| 1260 | + const debug = lua.debug(); |
| 1261 | + |
| 1262 | + // Test getting upvalue using high-level API |
| 1263 | + const upval1 = debug.getUpvalue(func.?, 1, i32); |
| 1264 | + try expect(upval1 != null); |
| 1265 | + try expectEqual(upval1.?.value, 42); |
| 1266 | + try expect(std.mem.eql(u8, upval1.?.name, "shared_value")); |
| 1267 | + |
| 1268 | + // Test setting upvalue using high-level API |
| 1269 | + const set_name = debug.setUpvalue(func.?, 1, i32, 100); |
| 1270 | + try expect(set_name != null); |
| 1271 | + try expect(std.mem.eql(u8, set_name.?, "shared_value")); |
| 1272 | + |
| 1273 | + // Verify the upvalue was changed |
| 1274 | + const upval2 = debug.getUpvalue(func.?, 1, i32); |
| 1275 | + try expect(upval2 != null); |
| 1276 | + try expectEqual(upval2.?.value, 100); |
| 1277 | + |
| 1278 | + // Test the function returns the modified upvalue |
| 1279 | + const call_result = try func.?.call(.{}, i32); |
| 1280 | + try expectEqual(call_result.ok.?, 100); |
| 1281 | + |
| 1282 | + // Test getting non-existent upvalue |
| 1283 | + const upval_invalid = debug.getUpvalue(func.?, 10, i32); |
| 1284 | + try expect(upval_invalid == null); |
| 1285 | + |
| 1286 | + // Test setting non-existent upvalue |
| 1287 | + const set_invalid = debug.setUpvalue(func.?, 10, i32, 42); |
| 1288 | + try expect(set_invalid == null); |
| 1289 | +} |
0 commit comments