| author | |
| committer | |
| log | de5c0c9f4092a9d5914013e3428af2252da0be81 |
| tree | 601e35d18140e4a83ca2550b9aeaa7e1415d48e7 |
| parent | 6bade0b825c37699346a414568e79fe4c1918409 |
| parent | 6568be575cb87c2f54aad2dfa20d1f35471d2224 |
13 files changed, 649 insertions(+), 140 deletions(-)
CMakeLists.txt+1| ... | ... | @@ -386,6 +386,7 @@ set(ZIG_STD_FILES |
| 386 | 386 | "index.zig" |
| 387 | 387 | "io.zig" |
| 388 | 388 | "linked_list.zig" |
| 389 | "macho.zig" | |
| 389 | 390 | "math/acos.zig" |
| 390 | 391 | "math/acosh.zig" |
| 391 | 392 | "math/asin.zig" |
doc/langref.html.in+84-15| ... | ... | @@ -2782,30 +2782,96 @@ test "fn reflection" { |
| 2782 | 2782 | {#header_close#} |
| 2783 | 2783 | {#header_close#} |
| 2784 | 2784 | {#header_open|Errors#} |
| 2785 | {#header_open|Error Set Type#} | |
| 2785 | 2786 | <p> |
| 2786 | One of the distinguishing features of Zig is its exception handling strategy. | |
| 2787 | An error set is like an {#link|enum#}. | |
| 2788 | However, each error name across the entire compilation gets assigned an unsigned integer | |
| 2789 | greater than 0. You are allowed to declare the same error name more than once, and if you do, it | |
| 2790 | gets assigned the same integer value. | |
| 2787 | 2791 | </p> |
| 2788 | 2792 | <p> |
| 2789 | TODO rewrite the errors section to take into account error sets | |
| 2793 | The number of unique error values across the entire compilation should determine the size of the error set type. | |
| 2794 | However right now it is hard coded to be a <code>u16</code>. See <a href="https://github.com/zig-lang/zig/issues/786">#768</a>. | |
| 2790 | 2795 | </p> |
| 2791 | 2796 | <p> |
| 2792 | These error values are assigned an unsigned integer value greater than 0 at | |
| 2793 | compile time. You are allowed to declare the same error value more than once, | |
| 2794 | and if you do, it gets assigned the same integer value. | |
| 2797 | You can implicitly cast an error from a subset to its superset: | |
| 2795 | 2798 | </p> |
| 2799 | {#code_begin|test#} | |
| 2800 | const std = @import("std"); | |
| 2801 | ||
| 2802 | const FileOpenError = error { | |
| 2803 | AccessDenied, | |
| 2804 | OutOfMemory, | |
| 2805 | FileNotFound, | |
| 2806 | }; | |
| 2807 | ||
| 2808 | const AllocationError = error { | |
| 2809 | OutOfMemory, | |
| 2810 | }; | |
| 2811 | ||
| 2812 | test "implicit cast subset to superset" { | |
| 2813 | const err = foo(AllocationError.OutOfMemory); | |
| 2814 | std.debug.assert(err == FileOpenError.OutOfMemory); | |
| 2815 | } | |
| 2816 | ||
| 2817 | fn foo(err: AllocationError) FileOpenError { | |
| 2818 | return err; | |
| 2819 | } | |
| 2820 | {#code_end#} | |
| 2796 | 2821 | <p> |
| 2797 | You can refer to these error values with the error namespace such as | |
| 2798 | <code>error.FileNotFound</code>. | |
| 2822 | But you cannot implicitly cast an error from a superset to a subset: | |
| 2823 | </p> | |
| 2824 | {#code_begin|test_err|not a member of destination error set#} | |
| 2825 | const FileOpenError = error { | |
| 2826 | AccessDenied, | |
| 2827 | OutOfMemory, | |
| 2828 | FileNotFound, | |
| 2829 | }; | |
| 2830 | ||
| 2831 | const AllocationError = error { | |
| 2832 | OutOfMemory, | |
| 2833 | }; | |
| 2834 | ||
| 2835 | test "implicit cast superset to subset" { | |
| 2836 | foo(FileOpenError.OutOfMemory) catch {}; | |
| 2837 | } | |
| 2838 | ||
| 2839 | fn foo(err: FileOpenError) AllocationError { | |
| 2840 | return err; | |
| 2841 | } | |
| 2842 | {#code_end#} | |
| 2843 | <p> | |
| 2844 | There is a shortcut for declaring an error set with only 1 value, and then getting that value: | |
| 2845 | </p> | |
| 2846 | {#code_begin|syntax#} | |
| 2847 | const err = error.FileNotFound; | |
| 2848 | {#code_end#} | |
| 2849 | <p>This is equivalent to:</p> | |
| 2850 | {#code_begin|syntax#} | |
| 2851 | const err = (error {FileNotFound}).FileNotFound; | |
| 2852 | {#code_end#} | |
| 2853 | <p> | |
| 2854 | This becomes useful when using {#link|Inferred Error Sets#}. | |
| 2855 | </p> | |
| 2856 | {#header_open|The Global Error Set#} | |
| 2857 | <p><code>error</code> refers to the global error set. | |
| 2858 | This is the error set that contains all errors in the entire compilation unit. | |
| 2859 | It is a superset of all other error sets and a subset of none of them. | |
| 2799 | 2860 | </p> |
| 2800 | 2861 | <p> |
| 2801 | Each error value across the entire compilation unit gets a unique integer, | |
| 2802 | and this determines the size of the error set type. | |
| 2862 | You can implicitly cast any error set to the global one, and you can explicitly | |
| 2863 | cast an error of global error set to a non-global one. This inserts a language-level | |
| 2864 | assert to make sure the error value is in fact in the destination error set. | |
| 2803 | 2865 | </p> |
| 2804 | 2866 | <p> |
| 2805 | The error set type is one of the error values, and in the same way that pointers | |
| 2806 | cannot be null, a error set instance is always an error. | |
| 2867 | The global error set should generally be avoided when possible, because it prevents | |
| 2868 | the compiler from knowing what errors are possible at compile-time. Knowing | |
| 2869 | the error set at compile-time is better for generated documentationt and for | |
| 2870 | helpful error messages such as forgetting a possible error value in a {#link|switch#}. | |
| 2807 | 2871 | </p> |
| 2808 | {#code_begin|syntax#}const pure_error = error.FileNotFound;{#code_end#} | |
| 2872 | {#header_close#} | |
| 2873 | {#header_close#} | |
| 2874 | {#header_open|Error Union Type#} | |
| 2809 | 2875 | <p> |
| 2810 | 2876 | Most of the time you will not find yourself using an error set type. Instead, |
| 2811 | 2877 | likely you will be using the error union type. This is when you take an error set |
| ... | ... | @@ -2918,7 +2984,6 @@ fn doAThing(str: []u8) !void { |
| 2918 | 2984 | a panic in Debug and ReleaseSafe modes and undefined behavior in ReleaseFast mode. So, while we're debugging the |
| 2919 | 2985 | application, if there <em>was</em> a surprise error here, the application would crash |
| 2920 | 2986 | appropriately. |
| 2921 | TODO: mention error return traces | |
| 2922 | 2987 | </p> |
| 2923 | 2988 | <p> |
| 2924 | 2989 | Finally, you may want to take a different action for every situation. For that, we combine |
| ... | ... | @@ -2986,7 +3051,7 @@ fn createFoo(param: i32) !Foo { |
| 2986 | 3051 | </li> |
| 2987 | 3052 | </ul> |
| 2988 | 3053 | {#see_also|defer|if|switch#} |
| 2989 | {#header_open|Error Union Type#} | |
| 3054 | ||
| 2990 | 3055 | <p>An error union is created with the <code>!</code> binary operator. |
| 2991 | 3056 | You can use compile-time reflection to access the child type of an error union:</p> |
| 2992 | 3057 | {#code_begin|test#} |
| ... | ... | @@ -3008,8 +3073,12 @@ test "error union" { |
| 3008 | 3073 | comptime assert(@typeOf(foo).ErrorSet == error); |
| 3009 | 3074 | } |
| 3010 | 3075 | {#code_end#} |
| 3076 | <p>TODO the <code>||</code> operator for error sets</p> | |
| 3077 | {#header_open|Inferred Error Sets#} | |
| 3078 | <p>TODO</p> | |
| 3011 | 3079 | {#header_close#} |
| 3012 | {#header_open|Error Set Type#} | |
| 3080 | {#header_close#} | |
| 3081 | {#header_open|Error Return Traces#} | |
| 3013 | 3082 | <p>TODO</p> |
| 3014 | 3083 | {#header_close#} |
| 3015 | 3084 | {#header_close#} |
src/analyze.cpp+3-1| ... | ... | @@ -1083,7 +1083,7 @@ TypeTableEntry *get_fn_type(CodeGen *g, FnTypeId *fn_type_id) { |
| 1083 | 1083 | gen_param_info->src_index = i; |
| 1084 | 1084 | gen_param_info->gen_index = SIZE_MAX; |
| 1085 | 1085 | |
| 1086 | ensure_complete_type(g, type_entry); | |
| 1086 | type_ensure_zero_bits_known(g, type_entry); | |
| 1087 | 1087 | if (type_has_bits(type_entry)) { |
| 1088 | 1088 | TypeTableEntry *gen_type; |
| 1089 | 1089 | if (handle_is_ptr(type_entry)) { |
| ... | ... | @@ -2240,6 +2240,7 @@ static void resolve_enum_zero_bits(CodeGen *g, TypeTableEntry *enum_type) { |
| 2240 | 2240 | |
| 2241 | 2241 | if (enum_type->data.enumeration.zero_bits_loop_flag) { |
| 2242 | 2242 | enum_type->data.enumeration.zero_bits_known = true; |
| 2243 | enum_type->data.enumeration.zero_bits_loop_flag = false; | |
| 2243 | 2244 | return; |
| 2244 | 2245 | } |
| 2245 | 2246 | |
| ... | ... | @@ -2394,6 +2395,7 @@ static void resolve_struct_zero_bits(CodeGen *g, TypeTableEntry *struct_type) { |
| 2394 | 2395 | // the alignment is pointer width, then assert that the first field is within that |
| 2395 | 2396 | // alignment |
| 2396 | 2397 | struct_type->data.structure.zero_bits_known = true; |
| 2398 | struct_type->data.structure.zero_bits_loop_flag = false; | |
| 2397 | 2399 | if (struct_type->data.structure.abi_alignment == 0) { |
| 2398 | 2400 | if (struct_type->data.structure.layout == ContainerLayoutPacked) { |
| 2399 | 2401 | struct_type->data.structure.abi_alignment = 1; |
src/ir.cpp+22-2| ... | ... | @@ -4510,7 +4510,13 @@ static IrInstruction *ir_gen_var_decl(IrBuilder *irb, Scope *scope, AstNode *nod |
| 4510 | 4510 | buf_sprintf("cannot set section of local variable '%s'", buf_ptr(variable_declaration->symbol))); |
| 4511 | 4511 | } |
| 4512 | 4512 | |
| 4513 | // Temporarily set the name of the IrExecutable to the VariableDeclaration | |
| 4514 | // so that the struct or enum from the init expression inherits the name. | |
| 4515 | Buf *old_exec_name = irb->exec->name; | |
| 4516 | irb->exec->name = variable_declaration->symbol; | |
| 4513 | 4517 | IrInstruction *init_value = ir_gen_node(irb, variable_declaration->expr, scope); |
| 4518 | irb->exec->name = old_exec_name; | |
| 4519 | ||
| 4514 | 4520 | if (init_value == irb->codegen->invalid_instruction) |
| 4515 | 4521 | return init_value; |
| 4516 | 4522 | |
| ... | ... | @@ -9504,16 +9510,30 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst |
| 9504 | 9510 | |
| 9505 | 9511 | // explicit cast from child type of maybe type to maybe type |
| 9506 | 9512 | if (wanted_type->id == TypeTableEntryIdMaybe) { |
| 9507 | if (types_match_const_cast_only(ira, wanted_type->data.maybe.child_type, actual_type, source_node).id == ConstCastResultIdOk) { | |
| 9513 | TypeTableEntry *wanted_child_type = wanted_type->data.maybe.child_type; | |
| 9514 | if (types_match_const_cast_only(ira, wanted_child_type, actual_type, source_node).id == ConstCastResultIdOk) { | |
| 9508 | 9515 | return ir_analyze_maybe_wrap(ira, source_instr, value, wanted_type); |
| 9509 | 9516 | } else if (actual_type->id == TypeTableEntryIdNumLitInt || |
| 9510 | 9517 | actual_type->id == TypeTableEntryIdNumLitFloat) |
| 9511 | 9518 | { |
| 9512 | if (ir_num_lit_fits_in_other_type(ira, value, wanted_type->data.maybe.child_type, true)) { | |
| 9519 | if (ir_num_lit_fits_in_other_type(ira, value, wanted_child_type, true)) { | |
| 9513 | 9520 | return ir_analyze_maybe_wrap(ira, source_instr, value, wanted_type); |
| 9514 | 9521 | } else { |
| 9515 | 9522 | return ira->codegen->invalid_instruction; |
| 9516 | 9523 | } |
| 9524 | } else if (wanted_child_type->id == TypeTableEntryIdPointer && | |
| 9525 | wanted_child_type->data.pointer.is_const && | |
| 9526 | (actual_type->id == TypeTableEntryIdPointer || is_container(actual_type))) | |
| 9527 | { | |
| 9528 | IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_child_type, value); | |
| 9529 | if (type_is_invalid(cast1->value.type)) | |
| 9530 | return ira->codegen->invalid_instruction; | |
| 9531 | ||
| 9532 | IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1); | |
| 9533 | if (type_is_invalid(cast2->value.type)) | |
| 9534 | return ira->codegen->invalid_instruction; | |
| 9535 | ||
| 9536 | return cast2; | |
| 9517 | 9537 | } |
| 9518 | 9538 | } |
| 9519 | 9539 |
std/debug/index.zig+92-57| ... | ... | @@ -5,6 +5,7 @@ const io = std.io; |
| 5 | 5 | const os = std.os; |
| 6 | 6 | const elf = std.elf; |
| 7 | 7 | const DW = std.dwarf; |
| 8 | const macho = std.macho; | |
| 8 | 9 | const ArrayList = std.ArrayList; |
| 9 | 10 | const builtin = @import("builtin"); |
| 10 | 11 | |
| ... | ... | @@ -178,43 +179,57 @@ pub fn writeCurrentStackTrace(out_stream: var, allocator: &mem.Allocator, |
| 178 | 179 | } |
| 179 | 180 | |
| 180 | 181 | fn printSourceAtAddress(debug_info: &ElfStackTrace, out_stream: var, address: usize) !void { |
| 181 | if (builtin.os == builtin.Os.windows) { | |
| 182 | return error.UnsupportedDebugInfo; | |
| 183 | } | |
| 184 | 182 | // TODO we really should be able to convert @sizeOf(usize) * 2 to a string literal |
| 185 | 183 | // at compile time. I'll call it issue #313 |
| 186 | 184 | const ptr_hex = if (@sizeOf(usize) == 4) "0x{x8}" else "0x{x16}"; |
| 187 | 185 | |
| 188 | const compile_unit = findCompileUnit(debug_info, address) catch { | |
| 189 | try out_stream.print("???:?:?: " ++ DIM ++ ptr_hex ++ " in ??? (???)" ++ RESET ++ "\n ???\n\n", | |
| 190 | address); | |
| 191 | return; | |
| 192 | }; | |
| 193 | const compile_unit_name = try compile_unit.die.getAttrString(debug_info, DW.AT_name); | |
| 194 | if (getLineNumberInfo(debug_info, compile_unit, address - 1)) |line_info| { | |
| 195 | defer line_info.deinit(); | |
| 196 | try out_stream.print(WHITE ++ "{}:{}:{}" ++ RESET ++ ": " ++ | |
| 197 | DIM ++ ptr_hex ++ " in ??? ({})" ++ RESET ++ "\n", | |
| 198 | line_info.file_name, line_info.line, line_info.column, | |
| 199 | address, compile_unit_name); | |
| 200 | if (printLineFromFile(debug_info.allocator(), out_stream, line_info)) { | |
| 201 | if (line_info.column == 0) { | |
| 202 | try out_stream.write("\n"); | |
| 203 | } else { | |
| 204 | {var col_i: usize = 1; while (col_i < line_info.column) : (col_i += 1) { | |
| 205 | try out_stream.writeByte(' '); | |
| 206 | }} | |
| 207 | try out_stream.write(GREEN ++ "^" ++ RESET ++ "\n"); | |
| 186 | switch (builtin.os) { | |
| 187 | builtin.Os.windows => return error.UnsupportedDebugInfo, | |
| 188 | builtin.Os.macosx => { | |
| 189 | // TODO(bnoordhuis) It's theoretically possible to obtain the | |
| 190 | // compilation unit from the symbtab but it's not that useful | |
| 191 | // in practice because the compiler dumps everything in a single | |
| 192 | // object file. Future improvement: use external dSYM data when | |
| 193 | // available. | |
| 194 | const unknown = macho.Symbol { .name = "???", .address = address }; | |
| 195 | const symbol = debug_info.symbol_table.search(address) ?? &unknown; | |
| 196 | try out_stream.print(WHITE ++ "{}" ++ RESET ++ ": " ++ | |
| 197 | DIM ++ ptr_hex ++ " in ??? (???)" ++ RESET ++ "\n", | |
| 198 | symbol.name, address); | |
| 199 | }, | |
| 200 | else => { | |
| 201 | const compile_unit = findCompileUnit(debug_info, address) catch { | |
| 202 | try out_stream.print("???:?:?: " ++ DIM ++ ptr_hex ++ " in ??? (???)" ++ RESET ++ "\n ???\n\n", | |
| 203 | address); | |
| 204 | return; | |
| 205 | }; | |
| 206 | const compile_unit_name = try compile_unit.die.getAttrString(debug_info, DW.AT_name); | |
| 207 | if (getLineNumberInfo(debug_info, compile_unit, address - 1)) |line_info| { | |
| 208 | defer line_info.deinit(); | |
| 209 | try out_stream.print(WHITE ++ "{}:{}:{}" ++ RESET ++ ": " ++ | |
| 210 | DIM ++ ptr_hex ++ " in ??? ({})" ++ RESET ++ "\n", | |
| 211 | line_info.file_name, line_info.line, line_info.column, | |
| 212 | address, compile_unit_name); | |
| 213 | if (printLineFromFile(debug_info.allocator(), out_stream, line_info)) { | |
| 214 | if (line_info.column == 0) { | |
| 215 | try out_stream.write("\n"); | |
| 216 | } else { | |
| 217 | {var col_i: usize = 1; while (col_i < line_info.column) : (col_i += 1) { | |
| 218 | try out_stream.writeByte(' '); | |
| 219 | }} | |
| 220 | try out_stream.write(GREEN ++ "^" ++ RESET ++ "\n"); | |
| 221 | } | |
| 222 | } else |err| switch (err) { | |
| 223 | error.EndOfFile => {}, | |
| 224 | else => return err, | |
| 225 | } | |
| 226 | } else |err| switch (err) { | |
| 227 | error.MissingDebugInfo, error.InvalidDebugInfo => { | |
| 228 | try out_stream.print(ptr_hex ++ " in ??? ({})\n", address, compile_unit_name); | |
| 229 | }, | |
| 230 | else => return err, | |
| 208 | 231 | } |
| 209 | } else |err| switch (err) { | |
| 210 | error.EndOfFile => {}, | |
| 211 | else => return err, | |
| 212 | } | |
| 213 | } else |err| switch (err) { | |
| 214 | error.MissingDebugInfo, error.InvalidDebugInfo => { | |
| 215 | try out_stream.print(ptr_hex ++ " in ??? ({})\n", address, compile_unit_name); | |
| 216 | 232 | }, |
| 217 | else => return err, | |
| 218 | 233 | } |
| 219 | 234 | } |
| 220 | 235 | |
| ... | ... | @@ -222,6 +237,7 @@ pub fn openSelfDebugInfo(allocator: &mem.Allocator) !&ElfStackTrace { |
| 222 | 237 | switch (builtin.object_format) { |
| 223 | 238 | builtin.ObjectFormat.elf => { |
| 224 | 239 | const st = try allocator.create(ElfStackTrace); |
| 240 | errdefer allocator.destroy(st); | |
| 225 | 241 | *st = ElfStackTrace { |
| 226 | 242 | .self_exe_file = undefined, |
| 227 | 243 | .elf = undefined, |
| ... | ... | @@ -247,12 +263,22 @@ pub fn openSelfDebugInfo(allocator: &mem.Allocator) !&ElfStackTrace { |
| 247 | 263 | try scanAllCompileUnits(st); |
| 248 | 264 | return st; |
| 249 | 265 | }, |
| 266 | builtin.ObjectFormat.macho => { | |
| 267 | var exe_file = try os.openSelfExe(); | |
| 268 | defer exe_file.close(); | |
| 269 | ||
| 270 | const st = try allocator.create(ElfStackTrace); | |
| 271 | errdefer allocator.destroy(st); | |
| 272 | ||
| 273 | *st = ElfStackTrace { | |
| 274 | .symbol_table = try macho.loadSymbols(allocator, &io.FileInStream.init(&exe_file)), | |
| 275 | }; | |
| 276 | ||
| 277 | return st; | |
| 278 | }, | |
| 250 | 279 | builtin.ObjectFormat.coff => { |
| 251 | 280 | return error.TodoSupportCoffDebugInfo; |
| 252 | 281 | }, |
| 253 | builtin.ObjectFormat.macho => { | |
| 254 | return error.TodoSupportMachoDebugInfo; | |
| 255 | }, | |
| 256 | 282 | builtin.ObjectFormat.wasm => { |
| 257 | 283 | return error.TodoSupportCOFFDebugInfo; |
| 258 | 284 | }, |
| ... | ... | @@ -295,31 +321,40 @@ fn printLineFromFile(allocator: &mem.Allocator, out_stream: var, line_info: &con |
| 295 | 321 | } |
| 296 | 322 | } |
| 297 | 323 | |
| 298 | pub const ElfStackTrace = struct { | |
| 299 | self_exe_file: os.File, | |
| 300 | elf: elf.Elf, | |
| 301 | debug_info: &elf.SectionHeader, | |
| 302 | debug_abbrev: &elf.SectionHeader, | |
| 303 | debug_str: &elf.SectionHeader, | |
| 304 | debug_line: &elf.SectionHeader, | |
| 305 | debug_ranges: ?&elf.SectionHeader, | |
| 306 | abbrev_table_list: ArrayList(AbbrevTableHeader), | |
| 307 | compile_unit_list: ArrayList(CompileUnit), | |
| 308 | ||
| 309 | pub fn allocator(self: &const ElfStackTrace) &mem.Allocator { | |
| 310 | return self.abbrev_table_list.allocator; | |
| 311 | } | |
| 324 | pub const ElfStackTrace = switch (builtin.os) { | |
| 325 | builtin.Os.macosx => struct { | |
| 326 | symbol_table: macho.SymbolTable, | |
| 312 | 327 | |
| 313 | pub fn readString(self: &ElfStackTrace) ![]u8 { | |
| 314 | var in_file_stream = io.FileInStream.init(&self.self_exe_file); | |
| 315 | const in_stream = &in_file_stream.stream; | |
| 316 | return readStringRaw(self.allocator(), in_stream); | |
| 317 | } | |
| 328 | pub fn close(self: &ElfStackTrace) void { | |
| 329 | self.symbol_table.deinit(); | |
| 330 | } | |
| 331 | }, | |
| 332 | else => struct { | |
| 333 | self_exe_file: os.File, | |
| 334 | elf: elf.Elf, | |
| 335 | debug_info: &elf.SectionHeader, | |
| 336 | debug_abbrev: &elf.SectionHeader, | |
| 337 | debug_str: &elf.SectionHeader, | |
| 338 | debug_line: &elf.SectionHeader, | |
| 339 | debug_ranges: ?&elf.SectionHeader, | |
| 340 | abbrev_table_list: ArrayList(AbbrevTableHeader), | |
| 341 | compile_unit_list: ArrayList(CompileUnit), | |
| 342 | ||
| 343 | pub fn allocator(self: &const ElfStackTrace) &mem.Allocator { | |
| 344 | return self.abbrev_table_list.allocator; | |
| 345 | } | |
| 318 | 346 | |
| 319 | pub fn close(self: &ElfStackTrace) void { | |
| 320 | self.self_exe_file.close(); | |
| 321 | self.elf.close(); | |
| 322 | } | |
| 347 | pub fn readString(self: &ElfStackTrace) ![]u8 { | |
| 348 | var in_file_stream = io.FileInStream.init(&self.self_exe_file); | |
| 349 | const in_stream = &in_file_stream.stream; | |
| 350 | return readStringRaw(self.allocator(), in_stream); | |
| 351 | } | |
| 352 | ||
| 353 | pub fn close(self: &ElfStackTrace) void { | |
| 354 | self.self_exe_file.close(); | |
| 355 | self.elf.close(); | |
| 356 | } | |
| 357 | }, | |
| 323 | 358 | }; |
| 324 | 359 | |
| 325 | 360 | const PcRange = struct { |
std/fmt/index.zig+4-6| ... | ... | @@ -550,12 +550,6 @@ test "parse unsigned comptime" { |
| 550 | 550 | } |
| 551 | 551 | } |
| 552 | 552 | |
| 553 | // Dummy field because of https://github.com/zig-lang/zig/issues/557. | |
| 554 | // At top level because of https://github.com/zig-lang/zig/issues/675. | |
| 555 | const Struct = struct { | |
| 556 | unused: u8, | |
| 557 | }; | |
| 558 | ||
| 559 | 553 | test "fmt.format" { |
| 560 | 554 | { |
| 561 | 555 | var buf1: [32]u8 = undefined; |
| ... | ... | @@ -588,6 +582,10 @@ test "fmt.format" { |
| 588 | 582 | assert(mem.eql(u8, result, "u3: 5\n")); |
| 589 | 583 | } |
| 590 | 584 | { |
| 585 | // Dummy field because of https://github.com/zig-lang/zig/issues/557. | |
| 586 | const Struct = struct { | |
| 587 | unused: u8, | |
| 588 | }; | |
| 591 | 589 | var buf1: [32]u8 = undefined; |
| 592 | 590 | const value = Struct { |
| 593 | 591 | .unused = 42, |
std/index.zig+2| ... | ... | @@ -21,6 +21,7 @@ pub const endian = @import("endian.zig"); |
| 21 | 21 | pub const fmt = @import("fmt/index.zig"); |
| 22 | 22 | pub const heap = @import("heap.zig"); |
| 23 | 23 | pub const io = @import("io.zig"); |
| 24 | pub const macho = @import("macho.zig"); | |
| 24 | 25 | pub const math = @import("math/index.zig"); |
| 25 | 26 | pub const mem = @import("mem.zig"); |
| 26 | 27 | pub const net = @import("net.zig"); |
| ... | ... | @@ -51,6 +52,7 @@ test "std" { |
| 51 | 52 | _ = @import("endian.zig"); |
| 52 | 53 | _ = @import("fmt/index.zig"); |
| 53 | 54 | _ = @import("io.zig"); |
| 55 | _ = @import("macho.zig"); | |
| 54 | 56 | _ = @import("math/index.zig"); |
| 55 | 57 | _ = @import("mem.zig"); |
| 56 | 58 | _ = @import("heap.zig"); |
std/macho.zig created+170| ... | ... | @@ -0,0 +1,170 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | const std = @import("index.zig"); | |
| 3 | const io = std.io; | |
| 4 | const mem = std.mem; | |
| 5 | ||
| 6 | const MH_MAGIC_64 = 0xFEEDFACF; | |
| 7 | const MH_PIE = 0x200000; | |
| 8 | const LC_SYMTAB = 2; | |
| 9 | ||
| 10 | const MachHeader64 = packed struct { | |
| 11 | magic: u32, | |
| 12 | cputype: u32, | |
| 13 | cpusubtype: u32, | |
| 14 | filetype: u32, | |
| 15 | ncmds: u32, | |
| 16 | sizeofcmds: u32, | |
| 17 | flags: u32, | |
| 18 | reserved: u32, | |
| 19 | }; | |
| 20 | ||
| 21 | const LoadCommand = packed struct { | |
| 22 | cmd: u32, | |
| 23 | cmdsize: u32, | |
| 24 | }; | |
| 25 | ||
| 26 | const SymtabCommand = packed struct { | |
| 27 | symoff: u32, | |
| 28 | nsyms: u32, | |
| 29 | stroff: u32, | |
| 30 | strsize: u32, | |
| 31 | }; | |
| 32 | ||
| 33 | const Nlist64 = packed struct { | |
| 34 | n_strx: u32, | |
| 35 | n_type: u8, | |
| 36 | n_sect: u8, | |
| 37 | n_desc: u16, | |
| 38 | n_value: u64, | |
| 39 | }; | |
| 40 | ||
| 41 | pub const Symbol = struct { | |
| 42 | name: []const u8, | |
| 43 | address: u64, | |
| 44 | ||
| 45 | fn addressLessThan(lhs: &const Symbol, rhs: &const Symbol) bool { | |
| 46 | return lhs.address < rhs.address; | |
| 47 | } | |
| 48 | }; | |
| 49 | ||
| 50 | pub const SymbolTable = struct { | |
| 51 | allocator: &mem.Allocator, | |
| 52 | symbols: []const Symbol, | |
| 53 | strings: []const u8, | |
| 54 | ||
| 55 | // Doubles as an eyecatcher to calculate the PIE slide, see loadSymbols(). | |
| 56 | // Ideally we'd use _mh_execute_header because it's always at 0x100000000 | |
| 57 | // in the image but as it's located in a different section than executable | |
| 58 | // code, its displacement is different. | |
| 59 | pub fn deinit(self: &SymbolTable) void { | |
| 60 | self.allocator.free(self.symbols); | |
| 61 | self.symbols = []const Symbol {}; | |
| 62 | ||
| 63 | self.allocator.free(self.strings); | |
| 64 | self.strings = []const u8 {}; | |
| 65 | } | |
| 66 | ||
| 67 | pub fn search(self: &const SymbolTable, address: usize) ?&const Symbol { | |
| 68 | var min: usize = 0; | |
| 69 | var max: usize = self.symbols.len - 1; // Exclude sentinel. | |
| 70 | while (min < max) { | |
| 71 | const mid = min + (max - min) / 2; | |
| 72 | const curr = &self.symbols[mid]; | |
| 73 | const next = &self.symbols[mid + 1]; | |
| 74 | if (address >= next.address) { | |
| 75 | min = mid + 1; | |
| 76 | } else if (address < curr.address) { | |
| 77 | max = mid; | |
| 78 | } else { | |
| 79 | return curr; | |
| 80 | } | |
| 81 | } | |
| 82 | return null; | |
| 83 | } | |
| 84 | }; | |
| 85 | ||
| 86 | pub fn loadSymbols(allocator: &mem.Allocator, in: &io.FileInStream) !SymbolTable { | |
| 87 | var file = in.file; | |
| 88 | try file.seekTo(0); | |
| 89 | ||
| 90 | var hdr: MachHeader64 = undefined; | |
| 91 | try readOneNoEof(in, MachHeader64, &hdr); | |
| 92 | if (hdr.magic != MH_MAGIC_64) return error.MissingDebugInfo; | |
| 93 | const is_pie = MH_PIE == (hdr.flags & MH_PIE); | |
| 94 | ||
| 95 | var pos: usize = @sizeOf(@typeOf(hdr)); | |
| 96 | var ncmd: u32 = hdr.ncmds; | |
| 97 | while (ncmd != 0) : (ncmd -= 1) { | |
| 98 | try file.seekTo(pos); | |
| 99 | var lc: LoadCommand = undefined; | |
| 100 | try readOneNoEof(in, LoadCommand, &lc); | |
| 101 | if (lc.cmd == LC_SYMTAB) break; | |
| 102 | pos += lc.cmdsize; | |
| 103 | } else { | |
| 104 | return error.MissingDebugInfo; | |
| 105 | } | |
| 106 | ||
| 107 | var cmd: SymtabCommand = undefined; | |
| 108 | try readOneNoEof(in, SymtabCommand, &cmd); | |
| 109 | ||
| 110 | try file.seekTo(cmd.symoff); | |
| 111 | var syms = try allocator.alloc(Nlist64, cmd.nsyms); | |
| 112 | defer allocator.free(syms); | |
| 113 | try readNoEof(in, Nlist64, syms); | |
| 114 | ||
| 115 | try file.seekTo(cmd.stroff); | |
| 116 | var strings = try allocator.alloc(u8, cmd.strsize); | |
| 117 | errdefer allocator.free(strings); | |
| 118 | try in.stream.readNoEof(strings); | |
| 119 | ||
| 120 | var nsyms: usize = 0; | |
| 121 | for (syms) |sym| if (isSymbol(sym)) nsyms += 1; | |
| 122 | if (nsyms == 0) return error.MissingDebugInfo; | |
| 123 | ||
| 124 | var symbols = try allocator.alloc(Symbol, nsyms + 1); // Room for sentinel. | |
| 125 | errdefer allocator.free(symbols); | |
| 126 | ||
| 127 | var pie_slide: usize = 0; | |
| 128 | var nsym: usize = 0; | |
| 129 | for (syms) |sym| { | |
| 130 | if (!isSymbol(sym)) continue; | |
| 131 | const start = sym.n_strx; | |
| 132 | const end = ??mem.indexOfScalarPos(u8, strings, start, 0); | |
| 133 | const name = strings[start..end]; | |
| 134 | const address = sym.n_value; | |
| 135 | symbols[nsym] = Symbol { .name = name, .address = address }; | |
| 136 | nsym += 1; | |
| 137 | if (is_pie and mem.eql(u8, name, "_SymbolTable_deinit")) { | |
| 138 | pie_slide = @ptrToInt(SymbolTable.deinit) - address; | |
| 139 | } | |
| 140 | } | |
| 141 | ||
| 142 | // Effectively a no-op, lld emits symbols in ascending order. | |
| 143 | std.sort.insertionSort(Symbol, symbols[0..nsyms], Symbol.addressLessThan); | |
| 144 | ||
| 145 | // Insert the sentinel. Since we don't know where the last function ends, | |
| 146 | // we arbitrarily limit it to the start address + 4 KB. | |
| 147 | const top = symbols[nsyms - 1].address + 4096; | |
| 148 | symbols[nsyms] = Symbol { .name = "", .address = top }; | |
| 149 | ||
| 150 | if (pie_slide != 0) { | |
| 151 | for (symbols) |*symbol| symbol.address += pie_slide; | |
| 152 | } | |
| 153 | ||
| 154 | return SymbolTable { | |
| 155 | .allocator = allocator, | |
| 156 | .symbols = symbols, | |
| 157 | .strings = strings, | |
| 158 | }; | |
| 159 | } | |
| 160 | ||
| 161 | fn readNoEof(in: &io.FileInStream, comptime T: type, result: []T) !void { | |
| 162 | return in.stream.readNoEof(([]u8)(result)); | |
| 163 | } | |
| 164 | fn readOneNoEof(in: &io.FileInStream, comptime T: type, result: &T) !void { | |
| 165 | return readNoEof(in, T, result[0..1]); | |
| 166 | } | |
| 167 | ||
| 168 | fn isSymbol(sym: &const Nlist64) bool { | |
| 169 | return sym.n_value != 0 and sym.n_desc == 0; | |
| 170 | } |
std/os/child_process.zig-50| ... | ... | @@ -32,9 +32,6 @@ pub const ChildProcess = struct { |
| 32 | 32 | |
| 33 | 33 | pub argv: []const []const u8, |
| 34 | 34 | |
| 35 | /// Possibly called from a signal handler. Must set this before calling `spawn`. | |
| 36 | pub onTerm: ?fn(&ChildProcess)void, | |
| 37 | ||
| 38 | 35 | /// Leave as null to use the current env map using the supplied allocator. |
| 39 | 36 | pub env_map: ?&const BufMap, |
| 40 | 37 | |
| ... | ... | @@ -102,7 +99,6 @@ pub const ChildProcess = struct { |
| 102 | 99 | .err_pipe = undefined, |
| 103 | 100 | .llnode = undefined, |
| 104 | 101 | .term = null, |
| 105 | .onTerm = null, | |
| 106 | 102 | .env_map = null, |
| 107 | 103 | .cwd = null, |
| 108 | 104 | .uid = if (is_windows) {} else null, |
| ... | ... | @@ -124,7 +120,6 @@ pub const ChildProcess = struct { |
| 124 | 120 | self.gid = user_info.gid; |
| 125 | 121 | } |
| 126 | 122 | |
| 127 | /// onTerm can be called before `spawn` returns. | |
| 128 | 123 | /// On success must call `kill` or `wait`. |
| 129 | 124 | pub fn spawn(self: &ChildProcess) !void { |
| 130 | 125 | if (is_windows) { |
| ... | ... | @@ -165,9 +160,6 @@ pub const ChildProcess = struct { |
| 165 | 160 | } |
| 166 | 161 | |
| 167 | 162 | pub fn killPosix(self: &ChildProcess) !Term { |
| 168 | block_SIGCHLD(); | |
| 169 | defer restore_SIGCHLD(); | |
| 170 | ||
| 171 | 163 | if (self.term) |term| { |
| 172 | 164 | self.cleanupStreams(); |
| 173 | 165 | return term; |
| ... | ... | @@ -246,9 +238,6 @@ pub const ChildProcess = struct { |
| 246 | 238 | } |
| 247 | 239 | |
| 248 | 240 | fn waitPosix(self: &ChildProcess) !Term { |
| 249 | block_SIGCHLD(); | |
| 250 | defer restore_SIGCHLD(); | |
| 251 | ||
| 252 | 241 | if (self.term) |term| { |
| 253 | 242 | self.cleanupStreams(); |
| 254 | 243 | return term; |
| ... | ... | @@ -298,10 +287,6 @@ pub const ChildProcess = struct { |
| 298 | 287 | |
| 299 | 288 | fn handleWaitResult(self: &ChildProcess, status: i32) void { |
| 300 | 289 | self.term = self.cleanupAfterWait(status); |
| 301 | ||
| 302 | if (self.onTerm) |onTerm| { | |
| 303 | onTerm(self); | |
| 304 | } | |
| 305 | 290 | } |
| 306 | 291 | |
| 307 | 292 | fn cleanupStreams(self: &ChildProcess) void { |
| ... | ... | @@ -347,9 +332,6 @@ pub const ChildProcess = struct { |
| 347 | 332 | } |
| 348 | 333 | |
| 349 | 334 | fn spawnPosix(self: &ChildProcess) !void { |
| 350 | // TODO atomically set a flag saying that we already did this | |
| 351 | install_SIGCHLD_handler(); | |
| 352 | ||
| 353 | 335 | const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try makePipe() else undefined; |
| 354 | 336 | errdefer if (self.stdin_behavior == StdIo.Pipe) { destroyPipe(stdin_pipe); }; |
| 355 | 337 | |
| ... | ... | @@ -387,11 +369,9 @@ pub const ChildProcess = struct { |
| 387 | 369 | const err_pipe = try makePipe(); |
| 388 | 370 | errdefer destroyPipe(err_pipe); |
| 389 | 371 | |
| 390 | block_SIGCHLD(); | |
| 391 | 372 | const pid_result = posix.fork(); |
| 392 | 373 | const pid_err = posix.getErrno(pid_result); |
| 393 | 374 | if (pid_err > 0) { |
| 394 | restore_SIGCHLD(); | |
| 395 | 375 | return switch (pid_err) { |
| 396 | 376 | posix.EAGAIN, posix.ENOMEM, posix.ENOSYS => error.SystemResources, |
| 397 | 377 | else => os.unexpectedErrorPosix(pid_err), |
| ... | ... | @@ -399,7 +379,6 @@ pub const ChildProcess = struct { |
| 399 | 379 | } |
| 400 | 380 | if (pid_result == 0) { |
| 401 | 381 | // we are the child |
| 402 | restore_SIGCHLD(); | |
| 403 | 382 | |
| 404 | 383 | setUpChildIo(self.stdin_behavior, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |
| 405 | 384 | |err| forkChildErrReport(err_pipe[1], err); |
| ... | ... | @@ -451,8 +430,6 @@ pub const ChildProcess = struct { |
| 451 | 430 | // TODO make this atomic so it works even with threads |
| 452 | 431 | children_nodes.prepend(&self.llnode); |
| 453 | 432 | |
| 454 | restore_SIGCHLD(); | |
| 455 | ||
| 456 | 433 | if (self.stdin_behavior == StdIo.Pipe) { os.close(stdin_pipe[0]); } |
| 457 | 434 | if (self.stdout_behavior == StdIo.Pipe) { os.close(stdout_pipe[1]); } |
| 458 | 435 | if (self.stderr_behavior == StdIo.Pipe) { os.close(stderr_pipe[1]); } |
| ... | ... | @@ -824,30 +801,3 @@ fn handleTerm(pid: i32, status: i32) void { |
| 824 | 801 | } |
| 825 | 802 | } |
| 826 | 803 | } |
| 827 | ||
| 828 | const sigchld_set = x: { | |
| 829 | var signal_set = posix.empty_sigset; | |
| 830 | posix.sigaddset(&signal_set, posix.SIGCHLD); | |
| 831 | break :x signal_set; | |
| 832 | }; | |
| 833 | ||
| 834 | fn block_SIGCHLD() void { | |
| 835 | const err = posix.getErrno(posix.sigprocmask(posix.SIG_BLOCK, &sigchld_set, null)); | |
| 836 | assert(err == 0); | |
| 837 | } | |
| 838 | ||
| 839 | fn restore_SIGCHLD() void { | |
| 840 | const err = posix.getErrno(posix.sigprocmask(posix.SIG_UNBLOCK, &sigchld_set, null)); | |
| 841 | assert(err == 0); | |
| 842 | } | |
| 843 | ||
| 844 | const sigchld_action = posix.Sigaction { | |
| 845 | .handler = sigchld_handler, | |
| 846 | .mask = posix.empty_sigset, | |
| 847 | .flags = posix.SA_RESTART | posix.SA_NOCLDSTOP, | |
| 848 | }; | |
| 849 | ||
| 850 | fn install_SIGCHLD_handler() void { | |
| 851 | const err = posix.getErrno(posix.sigaction(posix.SIGCHLD, &sigchld_action, null)); | |
| 852 | assert(err == 0); | |
| 853 | } |
std/unicode.zig+140-9| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | const std = @import("./index.zig"); |
| 2 | const debug = std.debug; | |
| 2 | 3 | |
| 3 | 4 | /// Given the first byte of a UTF-8 codepoint, |
| 4 | 5 | /// returns a number 1-4 indicating the total length of the codepoint in bytes. |
| ... | ... | @@ -25,8 +26,8 @@ pub fn utf8Decode(bytes: []const u8) !u32 { |
| 25 | 26 | }; |
| 26 | 27 | } |
| 27 | 28 | pub fn utf8Decode2(bytes: []const u8) !u32 { |
| 28 | std.debug.assert(bytes.len == 2); | |
| 29 | std.debug.assert(bytes[0] & 0b11100000 == 0b11000000); | |
| 29 | debug.assert(bytes.len == 2); | |
| 30 | debug.assert(bytes[0] & 0b11100000 == 0b11000000); | |
| 30 | 31 | var value: u32 = bytes[0] & 0b00011111; |
| 31 | 32 | |
| 32 | 33 | if (bytes[1] & 0b11000000 != 0b10000000) return error.Utf8ExpectedContinuation; |
| ... | ... | @@ -38,8 +39,8 @@ pub fn utf8Decode2(bytes: []const u8) !u32 { |
| 38 | 39 | return value; |
| 39 | 40 | } |
| 40 | 41 | pub fn utf8Decode3(bytes: []const u8) !u32 { |
| 41 | std.debug.assert(bytes.len == 3); | |
| 42 | std.debug.assert(bytes[0] & 0b11110000 == 0b11100000); | |
| 42 | debug.assert(bytes.len == 3); | |
| 43 | debug.assert(bytes[0] & 0b11110000 == 0b11100000); | |
| 43 | 44 | var value: u32 = bytes[0] & 0b00001111; |
| 44 | 45 | |
| 45 | 46 | if (bytes[1] & 0b11000000 != 0b10000000) return error.Utf8ExpectedContinuation; |
| ... | ... | @@ -56,8 +57,8 @@ pub fn utf8Decode3(bytes: []const u8) !u32 { |
| 56 | 57 | return value; |
| 57 | 58 | } |
| 58 | 59 | pub fn utf8Decode4(bytes: []const u8) !u32 { |
| 59 | std.debug.assert(bytes.len == 4); | |
| 60 | std.debug.assert(bytes[0] & 0b11111000 == 0b11110000); | |
| 60 | debug.assert(bytes.len == 4); | |
| 61 | debug.assert(bytes[0] & 0b11111000 == 0b11110000); | |
| 61 | 62 | var value: u32 = bytes[0] & 0b00000111; |
| 62 | 63 | |
| 63 | 64 | if (bytes[1] & 0b11000000 != 0b10000000) return error.Utf8ExpectedContinuation; |
| ... | ... | @@ -78,6 +79,136 @@ pub fn utf8Decode4(bytes: []const u8) !u32 { |
| 78 | 79 | return value; |
| 79 | 80 | } |
| 80 | 81 | |
| 82 | pub fn utf8ValidateSlice(s: []const u8) bool { | |
| 83 | var i: usize = 0; | |
| 84 | while (i < s.len) { | |
| 85 | if (utf8ByteSequenceLength(s[i])) |cp_len| { | |
| 86 | if (i + cp_len > s.len) { | |
| 87 | return false; | |
| 88 | } | |
| 89 | ||
| 90 | if (utf8Decode(s[i..i+cp_len])) |_| {} else |_| { return false; } | |
| 91 | i += cp_len; | |
| 92 | } else |err| { | |
| 93 | return false; | |
| 94 | } | |
| 95 | } | |
| 96 | return true; | |
| 97 | } | |
| 98 | ||
| 99 | const Utf8View = struct { | |
| 100 | bytes: []const u8, | |
| 101 | ||
| 102 | pub fn init(s: []const u8) !Utf8View { | |
| 103 | if (!utf8ValidateSlice(s)) { | |
| 104 | return error.InvalidUtf8; | |
| 105 | } | |
| 106 | ||
| 107 | return initUnchecked(s); | |
| 108 | } | |
| 109 | ||
| 110 | pub fn initUnchecked(s: []const u8) Utf8View { | |
| 111 | return Utf8View { | |
| 112 | .bytes = s, | |
| 113 | }; | |
| 114 | } | |
| 115 | ||
| 116 | pub fn initComptime(comptime s: []const u8) Utf8View { | |
| 117 | if (comptime init(s)) |r| { | |
| 118 | return r; | |
| 119 | } else |err| switch (err) { | |
| 120 | error.InvalidUtf8 => { | |
| 121 | @compileError("invalid utf8"); | |
| 122 | unreachable; | |
| 123 | } | |
| 124 | } | |
| 125 | } | |
| 126 | ||
| 127 | pub fn Iterator(s: &const Utf8View) Utf8Iterator { | |
| 128 | return Utf8Iterator { | |
| 129 | .bytes = s.bytes, | |
| 130 | .i = 0, | |
| 131 | }; | |
| 132 | } | |
| 133 | }; | |
| 134 | ||
| 135 | const Utf8Iterator = struct { | |
| 136 | bytes: []const u8, | |
| 137 | i: usize, | |
| 138 | ||
| 139 | pub fn nextCodepointSlice(it: &Utf8Iterator) ?[]const u8 { | |
| 140 | if (it.i >= it.bytes.len) { | |
| 141 | return null; | |
| 142 | } | |
| 143 | ||
| 144 | const cp_len = utf8ByteSequenceLength(it.bytes[it.i]) catch unreachable; | |
| 145 | ||
| 146 | it.i += cp_len; | |
| 147 | return it.bytes[it.i-cp_len..it.i]; | |
| 148 | } | |
| 149 | ||
| 150 | pub fn nextCodepoint(it: &Utf8Iterator) ?u32 { | |
| 151 | const slice = it.nextCodepointSlice() ?? return null; | |
| 152 | ||
| 153 | const r = switch (slice.len) { | |
| 154 | 1 => u32(slice[0]), | |
| 155 | 2 => utf8Decode2(slice), | |
| 156 | 3 => utf8Decode3(slice), | |
| 157 | 4 => utf8Decode4(slice), | |
| 158 | else => unreachable, | |
| 159 | }; | |
| 160 | ||
| 161 | return r catch unreachable; | |
| 162 | } | |
| 163 | }; | |
| 164 | ||
| 165 | test "utf8 iterator on ascii" { | |
| 166 | const s = Utf8View.initComptime("abc"); | |
| 167 | ||
| 168 | var it1 = s.Iterator(); | |
| 169 | debug.assert(std.mem.eql(u8, "a", ??it1.nextCodepointSlice())); | |
| 170 | debug.assert(std.mem.eql(u8, "b", ??it1.nextCodepointSlice())); | |
| 171 | debug.assert(std.mem.eql(u8, "c", ??it1.nextCodepointSlice())); | |
| 172 | debug.assert(it1.nextCodepointSlice() == null); | |
| 173 | ||
| 174 | var it2 = s.Iterator(); | |
| 175 | debug.assert(??it2.nextCodepoint() == 'a'); | |
| 176 | debug.assert(??it2.nextCodepoint() == 'b'); | |
| 177 | debug.assert(??it2.nextCodepoint() == 'c'); | |
| 178 | debug.assert(it2.nextCodepoint() == null); | |
| 179 | } | |
| 180 | ||
| 181 | test "utf8 view bad" { | |
| 182 | // Compile-time error. | |
| 183 | // const s3 = Utf8View.initComptime("\xfe\xf2"); | |
| 184 | ||
| 185 | const s = Utf8View.init("hel\xadlo"); | |
| 186 | if (s) |_| { unreachable; } else |err| { debug.assert(err == error.InvalidUtf8); } | |
| 187 | } | |
| 188 | ||
| 189 | test "utf8 view ok" { | |
| 190 | const s = Utf8View.initComptime("東京市"); | |
| 191 | ||
| 192 | var it1 = s.Iterator(); | |
| 193 | debug.assert(std.mem.eql(u8, "東", ??it1.nextCodepointSlice())); | |
| 194 | debug.assert(std.mem.eql(u8, "京", ??it1.nextCodepointSlice())); | |
| 195 | debug.assert(std.mem.eql(u8, "市", ??it1.nextCodepointSlice())); | |
| 196 | debug.assert(it1.nextCodepointSlice() == null); | |
| 197 | ||
| 198 | var it2 = s.Iterator(); | |
| 199 | debug.assert(??it2.nextCodepoint() == 0x6771); | |
| 200 | debug.assert(??it2.nextCodepoint() == 0x4eac); | |
| 201 | debug.assert(??it2.nextCodepoint() == 0x5e02); | |
| 202 | debug.assert(it2.nextCodepoint() == null); | |
| 203 | } | |
| 204 | ||
| 205 | test "bad utf8 slice" { | |
| 206 | debug.assert(utf8ValidateSlice("abc")); | |
| 207 | debug.assert(!utf8ValidateSlice("abc\xc0")); | |
| 208 | debug.assert(!utf8ValidateSlice("abc\xc0abc")); | |
| 209 | debug.assert(utf8ValidateSlice("abc\xdf\xbf")); | |
| 210 | } | |
| 211 | ||
| 81 | 212 | test "valid utf8" { |
| 82 | 213 | testValid("\x00", 0x0); |
| 83 | 214 | testValid("\x20", 0x20); |
| ... | ... | @@ -145,17 +276,17 @@ fn testError(bytes: []const u8, expected_err: error) void { |
| 145 | 276 | if (testDecode(bytes)) |_| { |
| 146 | 277 | unreachable; |
| 147 | 278 | } else |err| { |
| 148 | std.debug.assert(err == expected_err); | |
| 279 | debug.assert(err == expected_err); | |
| 149 | 280 | } |
| 150 | 281 | } |
| 151 | 282 | |
| 152 | 283 | fn testValid(bytes: []const u8, expected_codepoint: u32) void { |
| 153 | std.debug.assert((testDecode(bytes) catch unreachable) == expected_codepoint); | |
| 284 | debug.assert((testDecode(bytes) catch unreachable) == expected_codepoint); | |
| 154 | 285 | } |
| 155 | 286 | |
| 156 | 287 | fn testDecode(bytes: []const u8) !u32 { |
| 157 | 288 | const length = try utf8ByteSequenceLength(bytes[0]); |
| 158 | 289 | if (bytes.len < length) return error.UnexpectedEof; |
| 159 | std.debug.assert(bytes.len == length); | |
| 290 | debug.assert(bytes.len == length); | |
| 160 | 291 | return utf8Decode(bytes); |
| 161 | 292 | } |
test/cases/cast.zig+102| ... | ... | @@ -32,6 +32,108 @@ fn funcWithConstPtrPtr(x: &const &i32) void { |
| 32 | 32 | **x += 1; |
| 33 | 33 | } |
| 34 | 34 | |
| 35 | test "implicitly cast a container to a const pointer of it" { | |
| 36 | const z = Struct(void) { .x = void{} }; | |
| 37 | assert(0 == @sizeOf(@typeOf(z))); | |
| 38 | assert(void{} == Struct(void).pointer(z).x); | |
| 39 | assert(void{} == Struct(void).pointer(&z).x); | |
| 40 | assert(void{} == Struct(void).maybePointer(z).x); | |
| 41 | assert(void{} == Struct(void).maybePointer(&z).x); | |
| 42 | assert(void{} == Struct(void).maybePointer(null).x); | |
| 43 | const s = Struct(u8) { .x = 42 }; | |
| 44 | assert(0 != @sizeOf(@typeOf(s))); | |
| 45 | assert(42 == Struct(u8).pointer(s).x); | |
| 46 | assert(42 == Struct(u8).pointer(&s).x); | |
| 47 | assert(42 == Struct(u8).maybePointer(s).x); | |
| 48 | assert(42 == Struct(u8).maybePointer(&s).x); | |
| 49 | assert(0 == Struct(u8).maybePointer(null).x); | |
| 50 | const u = Union { .x = 42 }; | |
| 51 | assert(42 == Union.pointer(u).x); | |
| 52 | assert(42 == Union.pointer(&u).x); | |
| 53 | assert(42 == Union.maybePointer(u).x); | |
| 54 | assert(42 == Union.maybePointer(&u).x); | |
| 55 | assert(0 == Union.maybePointer(null).x); | |
| 56 | const e = Enum.Some; | |
| 57 | assert(Enum.Some == Enum.pointer(e)); | |
| 58 | assert(Enum.Some == Enum.pointer(&e)); | |
| 59 | assert(Enum.Some == Enum.maybePointer(e)); | |
| 60 | assert(Enum.Some == Enum.maybePointer(&e)); | |
| 61 | assert(Enum.None == Enum.maybePointer(null)); | |
| 62 | } | |
| 63 | ||
| 64 | fn Struct(comptime T: type) type { | |
| 65 | return struct { | |
| 66 | const Self = this; | |
| 67 | x: T, | |
| 68 | ||
| 69 | fn pointer(self: &const Self) Self { | |
| 70 | return *self; | |
| 71 | } | |
| 72 | ||
| 73 | fn maybePointer(self: ?&const Self) Self { | |
| 74 | const none = Self { .x = if (T == void) void{} else 0 }; | |
| 75 | return *(self ?? &none); | |
| 76 | } | |
| 77 | }; | |
| 78 | } | |
| 79 | ||
| 80 | const Union = union { | |
| 81 | x: u8, | |
| 82 | ||
| 83 | fn pointer(self: &const Union) Union { | |
| 84 | return *self; | |
| 85 | } | |
| 86 | ||
| 87 | fn maybePointer(self: ?&const Union) Union { | |
| 88 | const none = Union { .x = 0 }; | |
| 89 | return *(self ?? &none); | |
| 90 | } | |
| 91 | }; | |
| 92 | ||
| 93 | const Enum = enum { | |
| 94 | None, | |
| 95 | Some, | |
| 96 | ||
| 97 | fn pointer(self: &const Enum) Enum { | |
| 98 | return *self; | |
| 99 | } | |
| 100 | ||
| 101 | fn maybePointer(self: ?&const Enum) Enum { | |
| 102 | return *(self ?? &Enum.None); | |
| 103 | } | |
| 104 | }; | |
| 105 | ||
| 106 | test "implicitly cast indirect pointer to maybe-indirect pointer" { | |
| 107 | const S = struct { | |
| 108 | const Self = this; | |
| 109 | x: u8, | |
| 110 | fn constConst(p: &const &const Self) u8 { | |
| 111 | return (*p).x; | |
| 112 | } | |
| 113 | fn maybeConstConst(p: ?&const &const Self) u8 { | |
| 114 | return (*??p).x; | |
| 115 | } | |
| 116 | fn constConstConst(p: &const &const &const Self) u8 { | |
| 117 | return (**p).x; | |
| 118 | } | |
| 119 | fn maybeConstConstConst(p: ?&const &const &const Self) u8 { | |
| 120 | return (**??p).x; | |
| 121 | } | |
| 122 | }; | |
| 123 | const s = S { .x = 42 }; | |
| 124 | const p = &s; | |
| 125 | const q = &p; | |
| 126 | const r = &q; | |
| 127 | assert(42 == S.constConst(p)); | |
| 128 | assert(42 == S.constConst(q)); | |
| 129 | assert(42 == S.maybeConstConst(p)); | |
| 130 | assert(42 == S.maybeConstConst(q)); | |
| 131 | assert(42 == S.constConstConst(q)); | |
| 132 | assert(42 == S.constConstConst(r)); | |
| 133 | assert(42 == S.maybeConstConstConst(q)); | |
| 134 | assert(42 == S.maybeConstConstConst(r)); | |
| 135 | } | |
| 136 | ||
| 35 | 137 | test "explicit cast from integer to error type" { |
| 36 | 138 | testCastIntToErr(error.ItBroke); |
| 37 | 139 | comptime testCastIntToErr(error.ItBroke); |
test/cases/misc.zig+17| ... | ... | @@ -499,12 +499,29 @@ test "@canImplicitCast" { |
| 499 | 499 | } |
| 500 | 500 | |
| 501 | 501 | test "@typeName" { |
| 502 | const Struct = struct { | |
| 503 | }; | |
| 504 | const Union = union { | |
| 505 | unused: u8, | |
| 506 | }; | |
| 507 | const Enum = enum { | |
| 508 | Unused, | |
| 509 | }; | |
| 502 | 510 | comptime { |
| 503 | 511 | assert(mem.eql(u8, @typeName(i64), "i64")); |
| 504 | 512 | assert(mem.eql(u8, @typeName(&usize), "&usize")); |
| 513 | // https://github.com/zig-lang/zig/issues/675 | |
| 514 | assert(mem.eql(u8, @typeName(TypeFromFn(u8)), "TypeFromFn(u8)")); | |
| 515 | assert(mem.eql(u8, @typeName(Struct), "Struct")); | |
| 516 | assert(mem.eql(u8, @typeName(Union), "Union")); | |
| 517 | assert(mem.eql(u8, @typeName(Enum), "Enum")); | |
| 505 | 518 | } |
| 506 | 519 | } |
| 507 | 520 | |
| 521 | fn TypeFromFn(comptime T: type) type { | |
| 522 | return struct {}; | |
| 523 | } | |
| 524 | ||
| 508 | 525 | test "volatile load and store" { |
| 509 | 526 | var number: i32 = 1234; |
| 510 | 527 | const ptr = (&volatile i32)(&number); |
test/compile_errors.zig+12| ... | ... | @@ -3090,4 +3090,16 @@ pub fn addCases(cases: &tests.CompileErrorContext) void { |
| 3090 | 3090 | , |
| 3091 | 3091 | ".tmp_source.zig:11:20: error: runtime cast to union 'Value' which has non-void fields", |
| 3092 | 3092 | ".tmp_source.zig:3:5: note: field 'A' has type 'i32'"); |
| 3093 | ||
| 3094 | cases.add("self-referencing function pointer field", | |
| 3095 | \\const S = struct { | |
| 3096 | \\ f: fn(_: S) void, | |
| 3097 | \\}; | |
| 3098 | \\fn f(_: S) void { | |
| 3099 | \\} | |
| 3100 | \\export fn entry() void { | |
| 3101 | \\ var _ = S { .f = f }; | |
| 3102 | \\} | |
| 3103 | , | |
| 3104 | ".tmp_source.zig:4:9: error: type 'S' is not copyable; cannot pass by value"); | |
| 3093 | 3105 | } |