| author | |
| committer | |
| log | 69399fbb82ea74fce4fb6bbfec5ab2cbfa435c1a |
| tree | de5962dc4d073de59e317cdd76546d1b92b8cfee |
| parent | a0a40c2e7e177bed7032ddd3d271016127ae9205 |
- fixup eh pointer decoding3 files changed, 95 insertions(+), 53 deletions(-)
lib/std/dwarf.zig+61-28| ... | @@ -1494,18 +1494,34 @@ pub const DwarfInfo = struct { | ... | @@ -1494,18 +1494,34 @@ pub const DwarfInfo = struct { |
| 1494 | } | 1494 | } |
| 1495 | 1495 | ||
| 1496 | const id_len = @as(u8, if (is_64) 8 else 4); | 1496 | const id_len = @as(u8, if (is_64) 8 else 4); |
| 1497 | const entry_bytes = eh_frame[stream.pos..][0..length - id_len]; | 1497 | const entry_bytes = eh_frame[stream.pos..][0 .. length - id_len]; |
| 1498 | const id = try reader.readInt(u32, di.endian); | 1498 | const id = try reader.readInt(u32, di.endian); |
| 1499 | 1499 | ||
| 1500 | // TODO: Get section_offset here (pass in from headers) | 1500 | // TODO: Get section_offset here (pass in from headers) |
| 1501 | 1501 | ||
| 1502 | if (id == 0) { | 1502 | if (id == 0) { |
| 1503 | const cie = try CommonInformationEntry.parse(entry_bytes, @ptrToInt(eh_frame.ptr), 0, length_offset, @sizeOf(usize), di.endian); | 1503 | const cie = try CommonInformationEntry.parse( |
| 1504 | entry_bytes, | ||
| 1505 | @ptrToInt(eh_frame.ptr), | ||
| 1506 | 0, | ||
| 1507 | true, | ||
| 1508 | length_offset, | ||
| 1509 | @sizeOf(usize), | ||
| 1510 | di.endian, | ||
| 1511 | ); | ||
| 1504 | try di.cie_map.put(allocator, length_offset, cie); | 1512 | try di.cie_map.put(allocator, length_offset, cie); |
| 1505 | } else { | 1513 | } else { |
| 1506 | const cie_offset = stream.pos - 4 - id; | 1514 | const cie_offset = stream.pos - 4 - id; |
| 1507 | const cie = di.cie_map.get(cie_offset) orelse return badDwarf(); | 1515 | const cie = di.cie_map.get(cie_offset) orelse return badDwarf(); |
| 1508 | const fde = try FrameDescriptionEntry.parse(entry_bytes, @ptrToInt(eh_frame.ptr), 0, cie, @sizeOf(usize), di.endian); | 1516 | const fde = try FrameDescriptionEntry.parse( |
| 1517 | entry_bytes, | ||
| 1518 | @ptrToInt(eh_frame.ptr), | ||
| 1519 | 0, | ||
| 1520 | true, | ||
| 1521 | cie, | ||
| 1522 | @sizeOf(usize), | ||
| 1523 | di.endian, | ||
| 1524 | ); | ||
| 1509 | try di.fde_list.append(allocator, fde); | 1525 | try di.fde_list.append(allocator, fde); |
| 1510 | } | 1526 | } |
| 1511 | } | 1527 | } |
| ... | @@ -1557,6 +1573,11 @@ const EhPointerContext = struct { | ... | @@ -1557,6 +1573,11 @@ const EhPointerContext = struct { |
| 1557 | // The address of the pointer field itself | 1573 | // The address of the pointer field itself |
| 1558 | pc_rel_base: u64, | 1574 | pc_rel_base: u64, |
| 1559 | 1575 | ||
| 1576 | // Whether or not to follow indirect pointers. This should only be | ||
| 1577 | // used when decoding pointers at runtime using the current process's | ||
| 1578 | // debug info. | ||
| 1579 | follow_indirect: bool, | ||
| 1580 | |||
| 1560 | // These relative addressing modes are only used in specific cases, and | 1581 | // These relative addressing modes are only used in specific cases, and |
| 1561 | // might not be available / required in all parsing contexts | 1582 | // might not be available / required in all parsing contexts |
| 1562 | data_rel_base: ?u64 = null, | 1583 | data_rel_base: ?u64 = null, |
| ... | @@ -1570,7 +1591,7 @@ fn readEhPointer(reader: anytype, enc: u8, addr_size_bytes: u8, ctx: EhPointerCo | ... | @@ -1570,7 +1591,7 @@ fn readEhPointer(reader: anytype, enc: u8, addr_size_bytes: u8, ctx: EhPointerCo |
| 1570 | const value: union(enum) { | 1591 | const value: union(enum) { |
| 1571 | signed: i64, | 1592 | signed: i64, |
| 1572 | unsigned: u64, | 1593 | unsigned: u64, |
| 1573 | } = switch (enc & 0x0f) { | 1594 | } = switch (enc & EH.PE.type_mask) { |
| 1574 | EH.PE.absptr => .{ | 1595 | EH.PE.absptr => .{ |
| 1575 | .unsigned = switch (addr_size_bytes) { | 1596 | .unsigned = switch (addr_size_bytes) { |
| 1576 | 2 => try reader.readInt(u16, endian), | 1597 | 2 => try reader.readInt(u16, endian), |
| ... | @@ -1590,33 +1611,31 @@ fn readEhPointer(reader: anytype, enc: u8, addr_size_bytes: u8, ctx: EhPointerCo | ... | @@ -1590,33 +1611,31 @@ fn readEhPointer(reader: anytype, enc: u8, addr_size_bytes: u8, ctx: EhPointerCo |
| 1590 | else => return badDwarf(), | 1611 | else => return badDwarf(), |
| 1591 | }; | 1612 | }; |
| 1592 | 1613 | ||
| 1593 | const relative_to = enc & 0xf0; | 1614 | var base = switch (enc & EH.PE.rel_mask) { |
| 1594 | var base = switch (relative_to) { | ||
| 1595 | EH.PE.pcrel => ctx.pc_rel_base, | 1615 | EH.PE.pcrel => ctx.pc_rel_base, |
| 1596 | EH.PE.textrel => ctx.text_rel_base orelse return error.PointerBaseNotSpecified, | 1616 | EH.PE.textrel => ctx.text_rel_base orelse return error.PointerBaseNotSpecified, |
| 1597 | EH.PE.datarel => ctx.data_rel_base orelse return error.PointerBaseNotSpecified, | 1617 | EH.PE.datarel => ctx.data_rel_base orelse return error.PointerBaseNotSpecified, |
| 1598 | EH.PE.funcrel => ctx.function_rel_base orelse return error.PointerBaseNotSpecified, | 1618 | EH.PE.funcrel => ctx.function_rel_base orelse return error.PointerBaseNotSpecified, |
| 1599 | EH.PE.indirect => { | ||
| 1600 | switch (addr_size_bytes) { | ||
| 1601 | 2 => return @intToPtr(*const u16, value.unsigned).*, | ||
| 1602 | 4 => return @intToPtr(*const u32, value.unsigned).*, | ||
| 1603 | 8 => return @intToPtr(*const u64, value.unsigned).*, | ||
| 1604 | else => return error.UnsupportedAddrSize, | ||
| 1605 | } | ||
| 1606 | }, | ||
| 1607 | else => null, | 1619 | else => null, |
| 1608 | }; | 1620 | }; |
| 1609 | 1621 | ||
| 1610 | if (base) |b| { | 1622 | const ptr = if (base) |b| switch (value) { |
| 1611 | return switch (value) { | 1623 | .signed => |s| @intCast(u64, s + @intCast(i64, b)), |
| 1612 | .signed => |s| @intCast(u64, s + @intCast(i64, b)), | 1624 | .unsigned => |u| u + b, |
| 1613 | .unsigned => |u| u + b, | 1625 | } else switch (value) { |
| 1626 | .signed => |s| @intCast(u64, s), | ||
| 1627 | .unsigned => |u| u, | ||
| 1628 | }; | ||
| 1629 | |||
| 1630 | if ((enc & EH.PE.indirect) > 0 and ctx.follow_indirect) { | ||
| 1631 | return switch (addr_size_bytes) { | ||
| 1632 | 2 => return @intToPtr(*const u16, ptr).*, | ||
| 1633 | 4 => return @intToPtr(*const u32, ptr).*, | ||
| 1634 | 8 => return @intToPtr(*const u64, ptr).*, | ||
| 1635 | else => return error.UnsupportedAddrSize, | ||
| 1614 | }; | 1636 | }; |
| 1615 | } else { | 1637 | } else { |
| 1616 | return switch (value) { | 1638 | return ptr; |
| 1617 | .signed => |s| @intCast(u64, s), | ||
| 1618 | .unsigned => |u| u, | ||
| 1619 | }; | ||
| 1620 | } | 1639 | } |
| 1621 | } | 1640 | } |
| 1622 | 1641 | ||
| ... | @@ -1668,6 +1687,7 @@ pub const CommonInformationEntry = struct { | ... | @@ -1668,6 +1687,7 @@ pub const CommonInformationEntry = struct { |
| 1668 | cie_bytes: []const u8, | 1687 | cie_bytes: []const u8, |
| 1669 | section_base: u64, | 1688 | section_base: u64, |
| 1670 | section_offset: u64, | 1689 | section_offset: u64, |
| 1690 | is_runtime: bool, | ||
| 1671 | length_offset: u64, | 1691 | length_offset: u64, |
| 1672 | addr_size_bytes: u8, | 1692 | addr_size_bytes: u8, |
| 1673 | endian: std.builtin.Endian, | 1693 | endian: std.builtin.Endian, |
| ... | @@ -1735,7 +1755,10 @@ pub const CommonInformationEntry = struct { | ... | @@ -1735,7 +1755,10 @@ pub const CommonInformationEntry = struct { |
| 1735 | reader, | 1755 | reader, |
| 1736 | personality_enc.?, | 1756 | personality_enc.?, |
| 1737 | addr_size_bytes, | 1757 | addr_size_bytes, |
| 1738 | .{ .pc_rel_base = @ptrToInt(&cie_bytes[stream.pos]) - section_base + section_offset }, | 1758 | .{ |
| 1759 | .pc_rel_base = @ptrToInt(&cie_bytes[stream.pos]) - section_base + section_offset, | ||
| 1760 | .follow_indirect = is_runtime, | ||
| 1761 | }, | ||
| 1739 | endian, | 1762 | endian, |
| 1740 | ); | 1763 | ); |
| 1741 | }, | 1764 | }, |
| ... | @@ -1785,6 +1808,7 @@ pub const FrameDescriptionEntry = struct { | ... | @@ -1785,6 +1808,7 @@ pub const FrameDescriptionEntry = struct { |
| 1785 | fde_bytes: []const u8, | 1808 | fde_bytes: []const u8, |
| 1786 | section_base: u64, | 1809 | section_base: u64, |
| 1787 | section_offset: u64, | 1810 | section_offset: u64, |
| 1811 | is_runtime: bool, | ||
| 1788 | cie: CommonInformationEntry, | 1812 | cie: CommonInformationEntry, |
| 1789 | addr_size_bytes: u8, | 1813 | addr_size_bytes: u8, |
| 1790 | endian: std.builtin.Endian, | 1814 | endian: std.builtin.Endian, |
| ... | @@ -1798,15 +1822,21 @@ pub const FrameDescriptionEntry = struct { | ... | @@ -1798,15 +1822,21 @@ pub const FrameDescriptionEntry = struct { |
| 1798 | reader, | 1822 | reader, |
| 1799 | cie.fde_pointer_enc, | 1823 | cie.fde_pointer_enc, |
| 1800 | addr_size_bytes, | 1824 | addr_size_bytes, |
| 1801 | .{ .pc_rel_base = @ptrToInt(&fde_bytes[stream.pos]) - section_base + section_offset }, | 1825 | .{ |
| 1826 | .pc_rel_base = @ptrToInt(&fde_bytes[stream.pos]) - section_base + section_offset, | ||
| 1827 | .follow_indirect = is_runtime, | ||
| 1828 | }, | ||
| 1802 | endian, | 1829 | endian, |
| 1803 | ) orelse return badDwarf(); | 1830 | ) orelse return badDwarf(); |
| 1804 | 1831 | ||
| 1805 | const pc_range = try readEhPointer( | 1832 | const pc_range = try readEhPointer( |
| 1806 | reader, | 1833 | reader, |
| 1807 | cie.fde_pointer_enc & 0x0f, | 1834 | cie.fde_pointer_enc, |
| 1808 | addr_size_bytes, | 1835 | addr_size_bytes, |
| 1809 | .{ .pc_rel_base = @ptrToInt(&fde_bytes[stream.pos]) - section_base + section_offset }, | 1836 | .{ |
| 1837 | .pc_rel_base = 0, | ||
| 1838 | .follow_indirect = false, | ||
| 1839 | }, | ||
| 1810 | endian, | 1840 | endian, |
| 1811 | ) orelse return badDwarf(); | 1841 | ) orelse return badDwarf(); |
| 1812 | 1842 | ||
| ... | @@ -1819,9 +1849,12 @@ pub const FrameDescriptionEntry = struct { | ... | @@ -1819,9 +1849,12 @@ pub const FrameDescriptionEntry = struct { |
| 1819 | const lsda_pointer = if (cie.lsda_pointer_enc != EH.PE.omit) | 1849 | const lsda_pointer = if (cie.lsda_pointer_enc != EH.PE.omit) |
| 1820 | try readEhPointer( | 1850 | try readEhPointer( |
| 1821 | reader, | 1851 | reader, |
| 1822 | cie.lsda_pointer_enc & 0x0f, | 1852 | cie.lsda_pointer_enc, |
| 1823 | addr_size_bytes, | 1853 | addr_size_bytes, |
| 1824 | .{ .pc_rel_base = @ptrToInt(&fde_bytes[stream.pos]) }, | 1854 | .{ |
| 1855 | .pc_rel_base = @ptrToInt(&fde_bytes[stream.pos]) - section_base + section_offset, | ||
| 1856 | .follow_indirect = is_runtime, | ||
| 1857 | }, | ||
| 1825 | endian, | 1858 | endian, |
| 1826 | ) | 1859 | ) |
| 1827 | else | 1860 | else |
lib/std/dwarf/EH.zig+6| ... | @@ -1,6 +1,10 @@ | ... | @@ -1,6 +1,10 @@ |
| 1 | pub const PE = struct { | 1 | pub const PE = struct { |
| 2 | pub const absptr = 0x00; | 2 | pub const absptr = 0x00; |
| 3 | 3 | ||
| 4 | pub const size_mask = 0x7; | ||
| 5 | pub const sign_mask = 0x8; | ||
| 6 | pub const type_mask = size_mask | sign_mask; | ||
| 7 | |||
| 4 | pub const uleb128 = 0x01; | 8 | pub const uleb128 = 0x01; |
| 5 | pub const udata2 = 0x02; | 9 | pub const udata2 = 0x02; |
| 6 | pub const udata4 = 0x03; | 10 | pub const udata4 = 0x03; |
| ... | @@ -10,11 +14,13 @@ pub const PE = struct { | ... | @@ -10,11 +14,13 @@ pub const PE = struct { |
| 10 | pub const sdata4 = 0x0B; | 14 | pub const sdata4 = 0x0B; |
| 11 | pub const sdata8 = 0x0C; | 15 | pub const sdata8 = 0x0C; |
| 12 | 16 | ||
| 17 | pub const rel_mask = 0x70; | ||
| 13 | pub const pcrel = 0x10; | 18 | pub const pcrel = 0x10; |
| 14 | pub const textrel = 0x20; | 19 | pub const textrel = 0x20; |
| 15 | pub const datarel = 0x30; | 20 | pub const datarel = 0x30; |
| 16 | pub const funcrel = 0x40; | 21 | pub const funcrel = 0x40; |
| 17 | pub const aligned = 0x50; | 22 | pub const aligned = 0x50; |
| 23 | |||
| 18 | pub const indirect = 0x80; | 24 | pub const indirect = 0x80; |
| 19 | 25 | ||
| 20 | pub const omit = 0xff; | 26 | pub const omit = 0xff; |
lib/std/dwarf/call_frame.zig+28-25| ... | @@ -216,16 +216,35 @@ pub const Instruction = union(Opcode) { | ... | @@ -216,16 +216,35 @@ pub const Instruction = union(Opcode) { |
| 216 | } | 216 | } |
| 217 | }; | 217 | }; |
| 218 | 218 | ||
| 219 | /// See section 6.4.1 of the DWARF5 specification | 219 | /// This is a virtual machine that runs DWARF call frame instructions. |
| 220 | /// See section 6.4.1 of the DWARF5 specification. | ||
| 220 | pub const VirtualMachine = struct { | 221 | pub const VirtualMachine = struct { |
| 222 | |||
| 221 | const RegisterRule = union(enum) { | 223 | const RegisterRule = union(enum) { |
| 224 | // The spec says that the default rule for each column is the undefined rule. | ||
| 225 | // However, it also allows ABI / compiler authors to specify alternate defaults, so | ||
| 226 | // there is a distinction made here. | ||
| 227 | default: void, | ||
| 228 | |||
| 222 | undefined: void, | 229 | undefined: void, |
| 223 | same_value: void, | 230 | same_value: void, |
| 231 | |||
| 232 | // offset(N) | ||
| 224 | offset: i64, | 233 | offset: i64, |
| 234 | |||
| 235 | // val_offset(N) | ||
| 225 | val_offset: i64, | 236 | val_offset: i64, |
| 237 | |||
| 238 | // register(R) | ||
| 226 | register: u8, | 239 | register: u8, |
| 240 | |||
| 241 | // expression(E) | ||
| 227 | expression: []const u8, | 242 | expression: []const u8, |
| 243 | |||
| 244 | // val_expression(E) | ||
| 228 | val_expression: []const u8, | 245 | val_expression: []const u8, |
| 246 | |||
| 247 | // Augmenter-defined rule | ||
| 229 | architectural: void, | 248 | architectural: void, |
| 230 | }; | 249 | }; |
| 231 | 250 | ||
| ... | @@ -248,7 +267,7 @@ pub const VirtualMachine = struct { | ... | @@ -248,7 +267,7 @@ pub const VirtualMachine = struct { |
| 248 | pub const Column = struct { | 267 | pub const Column = struct { |
| 249 | /// Register can only null in the case of the CFA column | 268 | /// Register can only null in the case of the CFA column |
| 250 | register: ?u8 = null, | 269 | register: ?u8 = null, |
| 251 | rule: RegisterRule = .{ .undefined = {} }, | 270 | rule: RegisterRule = .{ .default = {} }, |
| 252 | }; | 271 | }; |
| 253 | 272 | ||
| 254 | const ColumnRange = struct { | 273 | const ColumnRange = struct { |
| ... | @@ -264,13 +283,6 @@ pub const VirtualMachine = struct { | ... | @@ -264,13 +283,6 @@ pub const VirtualMachine = struct { |
| 264 | /// The result of executing the CIE's initial_instructions | 283 | /// The result of executing the CIE's initial_instructions |
| 265 | cie_row: ?Row = null, | 284 | cie_row: ?Row = null, |
| 266 | 285 | ||
| 267 | pub fn reset(self: *VirtualMachine) void { | ||
| 268 | self.stack.clearRetainingCapacity(); | ||
| 269 | self.columns.clearRetainingCapacity(); | ||
| 270 | self.current_row = .{}; | ||
| 271 | self.cie_row = null; | ||
| 272 | } | ||
| 273 | |||
| 274 | pub fn deinit(self: *VirtualMachine, allocator: std.mem.Allocator) void { | 286 | pub fn deinit(self: *VirtualMachine, allocator: std.mem.Allocator) void { |
| 275 | self.stack.deinit(allocator); | 287 | self.stack.deinit(allocator); |
| 276 | self.columns.deinit(allocator); | 288 | self.columns.deinit(allocator); |
| ... | @@ -357,7 +369,7 @@ pub const VirtualMachine = struct { | ... | @@ -357,7 +369,7 @@ pub const VirtualMachine = struct { |
| 357 | 369 | ||
| 358 | /// Executes a single instruction. | 370 | /// Executes a single instruction. |
| 359 | /// If this instruction is from the CIE, `is_initial` should be set. | 371 | /// If this instruction is from the CIE, `is_initial` should be set. |
| 360 | /// Returns the value of `current_row` before executing this instruction | 372 | /// Returns the value of `current_row` before executing this instruction. |
| 361 | pub fn step( | 373 | pub fn step( |
| 362 | self: *VirtualMachine, | 374 | self: *VirtualMachine, |
| 363 | allocator: std.mem.Allocator, | 375 | allocator: std.mem.Allocator, |
| ... | @@ -367,13 +379,16 @@ pub const VirtualMachine = struct { | ... | @@ -367,13 +379,16 @@ pub const VirtualMachine = struct { |
| 367 | ) !Row { | 379 | ) !Row { |
| 368 | // CIE instructions must be run before FDE instructions | 380 | // CIE instructions must be run before FDE instructions |
| 369 | assert(!is_initial or self.cie_row == null); | 381 | assert(!is_initial or self.cie_row == null); |
| 370 | if (!is_initial and self.cie_row == null) self.cie_row = self.current_row; | 382 | if (!is_initial and self.cie_row == null) { |
| 383 | self.cie_row = self.current_row; | ||
| 384 | self.current_row.copy_on_write = true; | ||
| 385 | } | ||
| 371 | 386 | ||
| 372 | const prev_row = self.current_row; | 387 | const prev_row = self.current_row; |
| 373 | switch (instruction) { | 388 | switch (instruction) { |
| 374 | .set_loc => |i| { | 389 | .set_loc => |i| { |
| 375 | if (i.operands.address <= self.current_row.offset) return error.InvalidOperation; | 390 | if (i.operands.address <= self.current_row.offset) return error.InvalidOperation; |
| 376 | // TODO: Check cie.segment_selector_size != for DWARFV4 | 391 | // TODO: Check cie.segment_selector_size != 0 for DWARFV4 |
| 377 | self.current_row.offset = i.operands.address; | 392 | self.current_row.offset = i.operands.address; |
| 378 | }, | 393 | }, |
| 379 | inline .advance_loc, | 394 | inline .advance_loc, |
| ... | @@ -392,11 +407,6 @@ pub const VirtualMachine = struct { | ... | @@ -392,11 +407,6 @@ pub const VirtualMachine = struct { |
| 392 | const column = try self.getOrAddColumn(allocator, i.operands.register); | 407 | const column = try self.getOrAddColumn(allocator, i.operands.register); |
| 393 | column.rule = .{ .offset = @intCast(i64, i.operands.offset) * cie.data_alignment_factor }; | 408 | column.rule = .{ .offset = @intCast(i64, i.operands.offset) * cie.data_alignment_factor }; |
| 394 | }, | 409 | }, |
| 395 | // .offset_extended_sf => |i| { | ||
| 396 | // try self.resolveCopyOnWrite(allocator); | ||
| 397 | // const column = try self.getOrAddColumn(allocator, i.operands.register); | ||
| 398 | // column.rule = .{ .offset = i.operands.offset * cie.data_alignment_factor }; | ||
| 399 | // }, | ||
| 400 | inline .restore, | 410 | inline .restore, |
| 401 | .restore_extended, | 411 | .restore_extended, |
| 402 | => |i| { | 412 | => |i| { |
| ... | @@ -405,7 +415,7 @@ pub const VirtualMachine = struct { | ... | @@ -405,7 +415,7 @@ pub const VirtualMachine = struct { |
| 405 | const column = try self.getOrAddColumn(allocator, i.operands.register); | 415 | const column = try self.getOrAddColumn(allocator, i.operands.register); |
| 406 | column.rule = for (self.rowColumns(cie_row)) |cie_column| { | 416 | column.rule = for (self.rowColumns(cie_row)) |cie_column| { |
| 407 | if (cie_column.register == i.operands.register) break cie_column.rule; | 417 | if (cie_column.register == i.operands.register) break cie_column.rule; |
| 408 | } else .{ .undefined = {} }; | 418 | } else .{ .default = {} }; |
| 409 | } else return error.InvalidOperation; | 419 | } else return error.InvalidOperation; |
| 410 | }, | 420 | }, |
| 411 | .nop => {}, | 421 | .nop => {}, |
| ... | @@ -427,13 +437,6 @@ pub const VirtualMachine = struct { | ... | @@ -427,13 +437,6 @@ pub const VirtualMachine = struct { |
| 427 | .remember_state => { | 437 | .remember_state => { |
| 428 | try self.stack.append(allocator, self.current_row.columns); | 438 | try self.stack.append(allocator, self.current_row.columns); |
| 429 | self.current_row.copy_on_write = true; | 439 | self.current_row.copy_on_write = true; |
| 430 | |||
| 431 | // const new_start = self.columns.items.len; | ||
| 432 | // if (self.current_row.columns.len > 0) { | ||
| 433 | // try self.columns.ensureUnusedCapacity(allocator, self.current_row.columns.len); | ||
| 434 | // self.columns.appendSliceAssumeCapacity(self.rowColumns(self.current_row)); | ||
| 435 | // self.current_row.columns.start = new_start; | ||
| 436 | // } | ||
| 437 | }, | 440 | }, |
| 438 | .restore_state => { | 441 | .restore_state => { |
| 439 | const restored_columns = self.stack.popOrNull() orelse return error.InvalidOperation; | 442 | const restored_columns = self.stack.popOrNull() orelse return error.InvalidOperation; |