diff --git a/CMakeLists.txt b/CMakeLists.txt index 12b7efb06a..f47e3c0f47 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -447,13 +447,17 @@ set(ZIG_STD_FILES "c/index.zig" "c/linux.zig" "c/windows.zig" + "coff.zig" "crypto/blake2.zig" + "crypto/chacha20.zig" "crypto/hmac.zig" "crypto/index.zig" "crypto/md5.zig" "crypto/sha1.zig" "crypto/sha2.zig" "crypto/sha3.zig" + "crypto/poly1305.zig" + "crypto/x25519.zig" "cstr.zig" "debug/failing_allocator.zig" "debug/index.zig" @@ -579,12 +583,12 @@ set(ZIG_STD_FILES "os/windows/error.zig" "os/windows/index.zig" "os/windows/kernel32.zig" + "os/windows/ntdll.zig" "os/windows/ole32.zig" "os/windows/shell32.zig" - "os/windows/shlwapi.zig" - "os/windows/user32.zig" "os/windows/util.zig" "os/zen.zig" + "pdb.zig" "rand/index.zig" "rand/ziggurat.zig" "segmented_list.zig" diff --git a/doc/docgen.zig b/doc/docgen.zig index 3145c4483e..c1158dc03f 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -40,11 +40,11 @@ pub fn main() !void { var out_file = try os.File.openWrite(out_file_name); defer out_file.close(); - var file_in_stream = io.FileInStream.init(&in_file); + var file_in_stream = io.FileInStream.init(in_file); const input_file_bytes = try file_in_stream.stream.readAllAlloc(allocator, max_doc_file_size); - var file_out_stream = io.FileOutStream.init(&out_file); + var file_out_stream = io.FileOutStream.init(out_file); var buffered_out_stream = io.BufferedOutStream(io.FileOutStream.Error).init(&file_out_stream.stream); var tokenizer = Tokenizer.init(in_file_name, input_file_bytes); diff --git a/doc/langref.html.in b/doc/langref.html.in index 465f3c56a7..bebbdaaa13 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -566,7 +566,7 @@ const c_string_literal = {#header_close#} {#header_close#} {#header_open|Assignment#} -

Use const to assign a value to an identifier:

+

Use the const keyword to assign a value to an identifier:

{#code_begin|test_err|cannot assign to constant#} const x = 1234; @@ -582,7 +582,8 @@ test "assignment" { foo(); } {#code_end#} -

If you need a variable that you can modify, use var:

+

const applies to all of the bytes that the identifier immediately addresses. {#link|Pointers#} have their own const-ness.

+

If you need a variable that you can modify, use the var keyword:

{#code_begin|test#} const assert = @import("std").debug.assert; @@ -1918,6 +1919,32 @@ test "linked list" { assert(list2.first.?.data == 1234); } {#code_end#} + {#header_open|struct Naming#} +

Since all structs are anonymous, Zig infers the type name based on a few rules.

+ + {#code_begin|exe|struct_name#} +const std = @import("std"); + +pub fn main() void { + const Foo = struct {}; + std.debug.warn("variable: {}\n", @typeName(Foo)); + std.debug.warn("anonymous: {}\n", @typeName(struct {})); + std.debug.warn("function: {}\n", @typeName(List(i32))); +} + +fn List(comptime T: type) type { + return struct { + x: T, + }; +} + {#code_end#} + {#header_close#} {#see_also|comptime|@fieldParentPtr#} {#header_close#} {#header_open|enum#} @@ -2179,6 +2206,39 @@ test "@tagName" { sorts the order of the tag and union field by the largest alignment.

{#header_close#} + {#header_open|blocks#} +

+ Blocks are used to limit the scope of variable declarations: +

+ {#code_begin|test_err|undeclared identifier#} +test "access variable after block scope" { + { + var x: i32 = 1; + } + x += 1; +} + {#code_end#} +

Blocks are expressions. When labeled, break can be used + to return a value from the block: +

+ {#code_begin|test#} +const std = @import("std"); +const assert = std.debug.assert; + +test "labeled break from labeled block expression" { + var y: i32 = 123; + + const x = blk: { + y += 1; + break :blk y; + }; + assert(x == 124); + assert(y == 124); +} + {#code_end#} +

Here, blk can be any name.

+ {#see_also|Labeled while|Labeled for#} + {#header_close#} {#header_open|switch#} {#code_begin|test|switch#} const assert = @import("std").debug.assert; @@ -2374,6 +2434,28 @@ fn rangeHasNumber(begin: usize, end: usize, number: usize) bool { } else false; } {#code_end#} + {#header_open|Labeled while#} +

When a while loop is labeled, it can be referenced from a break + or continue from within a nested loop:

+ {#code_begin|test#} +test "nested break" { + outer: while (true) { + while (true) { + break :outer; + } + } +} + +test "nested continue" { + var i: usize = 0; + outer: while (i < 10) : (i += 1) { + while (true) { + continue :outer; + } + } +} + {#code_end#} + {#header_close#} {#header_open|while with Optionals#}

Just like {#link|if#} expressions, while loops can take an optional as the @@ -2560,6 +2642,37 @@ test "for else" { }; } {#code_end#} + {#header_open|Labeled for#} +

When a for loop is labeled, it can be referenced from a break + or continue from within a nested loop:

+ {#code_begin|test#} +const std = @import("std"); +const assert = std.debug.assert; + +test "nested break" { + var count: usize = 0; + outer: for ([]i32{ 1, 2, 3, 4, 5 }) |_| { + for ([]i32{ 1, 2, 3, 4, 5 }) |_| { + count += 1; + break :outer; + } + } + assert(count == 1); +} + +test "nested continue" { + var count: usize = 0; + outer: for ([]i32{ 1, 2, 3, 4, 5, 6, 7, 8 }) |_| { + for ([]i32{ 1, 2, 3, 4, 5 }) |_| { + count += 1; + continue :outer; + } + } + + assert(count == 8); +} + {#code_end#} + {#header_close#} {#header_open|inline for#}

For loops can be inlined. This causes the loop to be unrolled, which @@ -7057,6 +7170,61 @@ const c = @cImport({ }); {#code_end#} {#see_also|@cImport|@cInclude|@cDefine|@cUndef|@import#} + {#header_close#} + {#header_open|Exporting a C Library#} +

+ One of the primary use cases for Zig is exporting a library with the C ABI for other programming languages + to call into. The export keyword in front of functions, variables, and types causes them to + be part of the library API: +

+

mathtest.zig

+ {#code_begin|syntax#} +export fn add(a: i32, b: i32) i32 { + return a + b; +} + {#code_end#} +

To make a shared library:

+
$ zig build-lib mathtest.zig
+
+

To make a static library:

+
$ zig build-lib mathtest.zig --static
+
+

Here is an example with the {#link|Zig Build System#}:

+

test.c

+
// This header is generated by zig from mathtest.zig
+#include "mathtest.h"
+#include <assert.h>
+
+int main(int argc, char **argv) {
+    assert(add(42, 1337) == 1379);
+    return 0;
+}
+

build.zig

+ {#code_begin|syntax#} +const Builder = @import("std").build.Builder; + +pub fn build(b: *Builder) void { + const lib = b.addSharedLibrary("mathtest", "mathtest.zig", b.version(1, 0, 0)); + + const exe = b.addCExecutable("test"); + exe.addCompileFlags([][]const u8{"-std=c99"}); + exe.addSourceFile("test.c"); + exe.linkLibrary(lib); + + b.default_step.dependOn(&exe.step); + + const run_cmd = b.addCommand(".", b.env_map, [][]const u8{exe.getOutputPath()}); + run_cmd.step.dependOn(&exe.step); + + const test_step = b.step("test", "Test the program"); + test_step.dependOn(&run_cmd.step); +} + {#code_end#} +

terminal

+
$ zig build
+$ ./test
+$ echo $?
+0
{#header_close#} {#header_open|Mixing Object Files#}

diff --git a/example/guess_number/main.zig b/example/guess_number/main.zig index bed132b25c..062f93e7f7 100644 --- a/example/guess_number/main.zig +++ b/example/guess_number/main.zig @@ -6,7 +6,7 @@ const os = std.os; pub fn main() !void { var stdout_file = try io.getStdOut(); - var stdout_file_stream = io.FileOutStream.init(&stdout_file); + var stdout_file_stream = io.FileOutStream.init(stdout_file); const stdout = &stdout_file_stream.stream; try stdout.print("Welcome to the Guess Number Game in Zig.\n"); diff --git a/src-self-hosted/errmsg.zig b/src-self-hosted/errmsg.zig index 028c2e2174..6cf29b9441 100644 --- a/src-self-hosted/errmsg.zig +++ b/src-self-hosted/errmsg.zig @@ -272,7 +272,7 @@ pub const Msg = struct { try stream.write("\n"); } - pub fn printToFile(msg: *const Msg, file: *os.File, color: Color) !void { + pub fn printToFile(msg: *const Msg, file: os.File, color: Color) !void { const color_on = switch (color) { Color.Auto => file.isTty(), Color.On => true, diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 64c55a24e8..6a450030ca 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -55,11 +55,11 @@ pub fn main() !void { const allocator = std.heap.c_allocator; var stdout_file = try std.io.getStdOut(); - var stdout_out_stream = std.io.FileOutStream.init(&stdout_file); + var stdout_out_stream = std.io.FileOutStream.init(stdout_file); stdout = &stdout_out_stream.stream; stderr_file = try std.io.getStdErr(); - var stderr_out_stream = std.io.FileOutStream.init(&stderr_file); + var stderr_out_stream = std.io.FileOutStream.init(stderr_file); stderr = &stderr_out_stream.stream; const args = try os.argsAlloc(allocator); @@ -491,7 +491,7 @@ async fn processBuildEvents(comp: *Compilation, color: errmsg.Color) void { stderr.print("Build {} compile errors:\n", count) catch os.exit(1); for (msgs) |msg| { defer msg.destroy(); - msg.printToFile(&stderr_file, color) catch os.exit(1); + msg.printToFile(stderr_file, color) catch os.exit(1); } }, } @@ -619,7 +619,7 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { } var stdin_file = try io.getStdIn(); - var stdin = io.FileInStream.init(&stdin_file); + var stdin = io.FileInStream.init(stdin_file); const source_code = try stdin.stream.readAllAlloc(allocator, max_src_size); defer allocator.free(source_code); @@ -635,7 +635,7 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { const msg = try errmsg.Msg.createFromParseError(allocator, parse_error, &tree, ""); defer msg.destroy(); - try msg.printToFile(&stderr_file, color); + try msg.printToFile(stderr_file, color); } if (tree.errors.len != 0) { os.exit(1); @@ -772,7 +772,7 @@ async fn fmtPath(fmt: *Fmt, file_path_ref: []const u8) FmtError!void { const msg = try errmsg.Msg.createFromParseError(fmt.loop.allocator, parse_error, &tree, file_path); defer fmt.loop.allocator.destroy(msg); - try msg.printToFile(&stderr_file, fmt.color); + try msg.printToFile(stderr_file, fmt.color); } if (tree.errors.len != 0) { fmt.any_error = true; diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index d4a45e7a04..4f377d4247 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -185,7 +185,7 @@ pub const TestContext = struct { try stderr.write("build incorrectly failed:\n"); for (msgs) |msg| { defer msg.destroy(); - try msg.printToFile(&stderr, errmsg.Color.Auto); + try msg.printToFile(stderr, errmsg.Color.Auto); } }, } @@ -234,7 +234,7 @@ pub const TestContext = struct { var stderr = try std.io.getStdErr(); for (msgs) |msg| { defer msg.destroy(); - try msg.printToFile(&stderr, errmsg.Color.Auto); + try msg.printToFile(stderr, errmsg.Color.Auto); } std.debug.warn("============\n"); return error.TestFailed; diff --git a/src/all_types.hpp b/src/all_types.hpp index 496ab9b70d..e7247e8469 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -43,6 +43,7 @@ struct IrAnalyze; struct IrExecutable { ZigList basic_block_list; Buf *name; + FnTableEntry *name_fn; size_t mem_slot_count; size_t next_debug_id; size_t *backward_branch_count; @@ -1805,6 +1806,11 @@ struct VariableTableEntry { VarLinkage linkage; IrInstruction *decl_instruction; uint32_t align_bytes; + + // In an inline loop, multiple variables may be created, + // In this case, a reference to a variable should follow + // this pointer to the redefined variable. + VariableTableEntry *next_var; }; struct ErrorTableEntry { diff --git a/src/analyze.cpp b/src/analyze.cpp index a8b3ea7132..85b34cb017 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -1575,7 +1575,7 @@ static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *c switch (type_entry->id) { case TypeTableEntryIdInvalid: - return g->builtin_types.entry_invalid; + zig_unreachable(); case TypeTableEntryIdUnreachable: case TypeTableEntryIdUndefined: case TypeTableEntryIdNull: @@ -1680,14 +1680,6 @@ static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *c case TypeTableEntryIdBlock: case TypeTableEntryIdBoundFn: case TypeTableEntryIdMetaType: - if (!calling_convention_allows_zig_types(fn_type_id.cc)) { - add_node_error(g, fn_proto->return_type, - buf_sprintf("return type '%s' not allowed in function with calling convention '%s'", - buf_ptr(&fn_type_id.return_type->name), - calling_convention_name(fn_type_id.cc))); - return g->builtin_types.entry_invalid; - } - return get_generic_fn_type(g, &fn_type_id); case TypeTableEntryIdUnreachable: case TypeTableEntryIdVoid: case TypeTableEntryIdBool: @@ -1703,6 +1695,11 @@ static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *c case TypeTableEntryIdUnion: case TypeTableEntryIdFn: case TypeTableEntryIdPromise: + if ((err = type_ensure_zero_bits_known(g, fn_type_id.return_type))) + return g->builtin_types.entry_invalid; + if (type_requires_comptime(fn_type_id.return_type)) { + return get_generic_fn_type(g, &fn_type_id); + } break; } @@ -3245,6 +3242,13 @@ static void add_top_level_decl(CodeGen *g, ScopeDecls *decls_scope, Tld *tld) { } else if (tld->id == TldIdFn) { assert(tld->source_node->type == NodeTypeFnProto); is_export = tld->source_node->data.fn_proto.is_export; + + if (!is_export && !tld->source_node->data.fn_proto.is_extern && + tld->source_node->data.fn_proto.fn_def_node == nullptr) + { + add_node_error(g, tld->source_node, buf_sprintf("non-extern function has no body")); + return; + } } if (is_export) { g->resolve_queue.append(tld); @@ -5620,8 +5624,6 @@ void eval_min_max_value(CodeGen *g, TypeTableEntry *type_entry, ConstExprValue * if (type_entry->id == TypeTableEntryIdInt) { const_val->special = ConstValSpecialStatic; eval_min_max_value_int(g, type_entry, &const_val->data.x_bigint, is_max); - } else if (type_entry->id == TypeTableEntryIdFloat) { - zig_panic("TODO analyze_min_max_value float"); } else if (type_entry->id == TypeTableEntryIdBool) { const_val->special = ConstValSpecialStatic; const_val->data.x_bool = is_max; diff --git a/src/codegen.cpp b/src/codegen.cpp index 318f8e4f19..e4405867ba 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -2618,6 +2618,9 @@ static LLVMValueRef ir_render_ptr_cast(CodeGen *g, IrExecutable *executable, IrInstructionPtrCast *instruction) { TypeTableEntry *wanted_type = instruction->base.value.type; + if (!type_has_bits(wanted_type)) { + return nullptr; + } LLVMValueRef ptr = ir_llvm_value(g, instruction->ptr); return LLVMBuildBitCast(g->builder, ptr, wanted_type->type_ref, ""); } @@ -3036,6 +3039,12 @@ static void gen_set_stack_pointer(CodeGen *g, LLVMValueRef aligned_end_addr) { LLVMBuildCall(g->builder, write_register_fn_val, params, 2, ""); } +static void set_call_instr_sret(CodeGen *g, LLVMValueRef call_instr) { + unsigned attr_kind_id = LLVMGetEnumAttributeKindForName("sret", 4); + LLVMAttributeRef sret_attr = LLVMCreateEnumAttribute(LLVMGetGlobalContext(), attr_kind_id, 1); + LLVMAddCallSiteAttribute(call_instr, 1, sret_attr); +} + static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstructionCall *instruction) { LLVMValueRef fn_val; TypeTableEntry *fn_type; @@ -3131,6 +3140,7 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr } else if (!ret_has_bits) { return nullptr; } else if (first_arg_ret) { + set_call_instr_sret(g, result); return instruction->tmp_ptr; } else if (handle_is_ptr(src_return_type)) { auto store_instr = LLVMBuildStore(g->builder, result, instruction->tmp_ptr); @@ -4573,8 +4583,9 @@ static LLVMValueRef get_coro_alloc_helper_fn_val(CodeGen *g, LLVMTypeRef alloc_f args.append(allocator_val); args.append(coro_size); args.append(alignment_val); - ZigLLVMBuildCall(g->builder, alloc_fn_val, args.items, args.length, + LLVMValueRef call_instruction = ZigLLVMBuildCall(g->builder, alloc_fn_val, args.items, args.length, get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_FnInlineAuto, ""); + set_call_instr_sret(g, call_instruction); LLVMValueRef err_val_ptr = LLVMBuildStructGEP(g->builder, sret_ptr, err_union_err_index, ""); LLVMValueRef err_val = LLVMBuildLoad(g->builder, err_val_ptr, ""); LLVMBuildStore(g->builder, err_val, err_code_ptr); diff --git a/src/ir.cpp b/src/ir.cpp index 5bf39ee691..8bf0e710b3 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -17,8 +17,7 @@ #include "util.hpp" struct IrExecContext { - ConstExprValue *mem_slot_list; - size_t mem_slot_count; + ZigList mem_slot_list; }; struct IrBuilder { @@ -60,7 +59,7 @@ enum ConstCastResultId { ConstCastResultIdType, ConstCastResultIdUnresolvedInferredErrSet, ConstCastResultIdAsyncAllocatorType, - ConstCastResultIdNullWrapPtr, + ConstCastResultIdNullWrapPtr }; struct ConstCastOnly; @@ -155,18 +154,22 @@ static TypeTableEntry *adjust_slice_align(CodeGen *g, TypeTableEntry *slice_type ConstExprValue *const_ptr_pointee(CodeGen *g, ConstExprValue *const_val) { assert(get_codegen_ptr_type(const_val->type) != nullptr); assert(const_val->special == ConstValSpecialStatic); + ConstExprValue *result; switch (const_val->data.x_ptr.special) { case ConstPtrSpecialInvalid: zig_unreachable(); case ConstPtrSpecialRef: - return const_val->data.x_ptr.data.ref.pointee; + result = const_val->data.x_ptr.data.ref.pointee; + break; case ConstPtrSpecialBaseArray: expand_undef_array(g, const_val->data.x_ptr.data.base_array.array_val); - return &const_val->data.x_ptr.data.base_array.array_val->data.x_array.s_none.elements[ + result = &const_val->data.x_ptr.data.base_array.array_val->data.x_array.s_none.elements[ const_val->data.x_ptr.data.base_array.elem_index]; + break; case ConstPtrSpecialBaseStruct: - return &const_val->data.x_ptr.data.base_struct.struct_val->data.x_struct.fields[ + result = &const_val->data.x_ptr.data.base_struct.struct_val->data.x_struct.fields[ const_val->data.x_ptr.data.base_struct.field_index]; + break; case ConstPtrSpecialHardCodedAddr: zig_unreachable(); case ConstPtrSpecialDiscard: @@ -174,7 +177,8 @@ ConstExprValue *const_ptr_pointee(CodeGen *g, ConstExprValue *const_val) { case ConstPtrSpecialFunction: zig_unreachable(); } - zig_unreachable(); + assert(result != nullptr); + return result; } static bool ir_should_inline(IrExecutable *exec, Scope *scope) { @@ -3181,7 +3185,11 @@ static IrInstruction *ir_gen_return(IrBuilder *irb, Scope *scope, AstNode *node, { IrInstruction *return_value; if (expr_node) { + // Temporarily set this so that if we return a type it gets the name of the function + FnTableEntry *prev_name_fn = irb->exec->name_fn; + irb->exec->name_fn = exec_fn_entry(irb->exec); return_value = ir_gen_node(irb, expr_node, scope); + irb->exec->name_fn = prev_name_fn; if (return_value == irb->codegen->invalid_instruction) return irb->codegen->invalid_instruction; } else { @@ -3275,7 +3283,8 @@ static IrInstruction *ir_gen_return(IrBuilder *irb, Scope *scope, AstNode *node, } static VariableTableEntry *create_local_var(CodeGen *codegen, AstNode *node, Scope *parent_scope, - Buf *name, bool src_is_const, bool gen_is_const, bool is_shadowable, IrInstruction *is_comptime) + Buf *name, bool src_is_const, bool gen_is_const, bool is_shadowable, IrInstruction *is_comptime, + bool skip_name_check) { VariableTableEntry *variable_entry = allocate(1); variable_entry->parent_scope = parent_scope; @@ -3288,29 +3297,30 @@ static VariableTableEntry *create_local_var(CodeGen *codegen, AstNode *node, Sco if (name) { buf_init_from_buf(&variable_entry->name, name); - VariableTableEntry *existing_var = find_variable(codegen, parent_scope, name); - if (existing_var && !existing_var->shadowable) { - ErrorMsg *msg = add_node_error(codegen, node, - buf_sprintf("redeclaration of variable '%s'", buf_ptr(name))); - add_error_note(codegen, msg, existing_var->decl_node, buf_sprintf("previous declaration is here")); - variable_entry->value->type = codegen->builtin_types.entry_invalid; - } else { - TypeTableEntry *type = get_primitive_type(codegen, name); - if (type != nullptr) { - add_node_error(codegen, node, - buf_sprintf("variable shadows type '%s'", buf_ptr(&type->name))); + if (!skip_name_check) { + VariableTableEntry *existing_var = find_variable(codegen, parent_scope, name); + if (existing_var && !existing_var->shadowable) { + ErrorMsg *msg = add_node_error(codegen, node, + buf_sprintf("redeclaration of variable '%s'", buf_ptr(name))); + add_error_note(codegen, msg, existing_var->decl_node, buf_sprintf("previous declaration is here")); variable_entry->value->type = codegen->builtin_types.entry_invalid; } else { - Tld *tld = find_decl(codegen, parent_scope, name); - if (tld != nullptr) { - ErrorMsg *msg = add_node_error(codegen, node, - buf_sprintf("redefinition of '%s'", buf_ptr(name))); - add_error_note(codegen, msg, tld->source_node, buf_sprintf("previous definition is here")); + TypeTableEntry *type = get_primitive_type(codegen, name); + if (type != nullptr) { + add_node_error(codegen, node, + buf_sprintf("variable shadows type '%s'", buf_ptr(&type->name))); variable_entry->value->type = codegen->builtin_types.entry_invalid; + } else { + Tld *tld = find_decl(codegen, parent_scope, name); + if (tld != nullptr) { + ErrorMsg *msg = add_node_error(codegen, node, + buf_sprintf("redefinition of '%s'", buf_ptr(name))); + add_error_note(codegen, msg, tld->source_node, buf_sprintf("previous definition is here")); + variable_entry->value->type = codegen->builtin_types.entry_invalid; + } } } } - } else { assert(is_shadowable); // TODO make this name not actually be in scope. user should be able to make a variable called "_anon" @@ -3333,14 +3343,9 @@ static VariableTableEntry *ir_create_var(IrBuilder *irb, AstNode *node, Scope *s bool src_is_const, bool gen_is_const, bool is_shadowable, IrInstruction *is_comptime) { bool is_underscored = name ? buf_eql_str(name, "_") : false; - VariableTableEntry *var = create_local_var( irb->codegen - , node - , scope - , (is_underscored ? nullptr : name) - , src_is_const - , gen_is_const - , (is_underscored ? true : is_shadowable) - , is_comptime ); + VariableTableEntry *var = create_local_var(irb->codegen, node, scope, + (is_underscored ? nullptr : name), src_is_const, gen_is_const, + (is_underscored ? true : is_shadowable), is_comptime, false); if (is_comptime != nullptr || gen_is_const) { var->mem_slot_index = exec_next_mem_slot(irb->exec); var->owner_exec = irb->exec; @@ -6479,20 +6484,17 @@ static bool render_instance_name_recursive(CodeGen *codegen, Buf *name, Scope *o static Buf *get_anon_type_name(CodeGen *codegen, IrExecutable *exec, const char *kind_name, AstNode *source_node) { if (exec->name) { return exec->name; + } else if (exec->name_fn != nullptr) { + Buf *name = buf_alloc(); + buf_append_buf(name, &exec->name_fn->symbol_name); + buf_appendf(name, "("); + render_instance_name_recursive(codegen, name, &exec->name_fn->fndef_scope->base, exec->begin_scope); + buf_appendf(name, ")"); + return name; } else { - FnTableEntry *fn_entry = exec_fn_entry(exec); - if (fn_entry) { - Buf *name = buf_alloc(); - buf_append_buf(name, &fn_entry->symbol_name); - buf_appendf(name, "("); - render_instance_name_recursive(codegen, name, &fn_entry->fndef_scope->base, exec->begin_scope); - buf_appendf(name, ")"); - return name; - } else { - //Note: C-imports do not have valid location information - return buf_sprintf("(anonymous %s at %s:%" ZIG_PRI_usize ":%" ZIG_PRI_usize ")", kind_name, - (source_node->owner->path != nullptr) ? buf_ptr(source_node->owner->path) : "(null)", source_node->line + 1, source_node->column + 1); - } + //Note: C-imports do not have valid location information + return buf_sprintf("(anonymous %s at %s:%" ZIG_PRI_usize ":%" ZIG_PRI_usize ")", kind_name, + (source_node->owner->path != nullptr) ? buf_ptr(source_node->owner->path) : "(null)", source_node->line + 1, source_node->column + 1); } } @@ -6690,7 +6692,10 @@ static IrInstruction *ir_gen_fn_proto(IrBuilder *irb, Scope *parent_scope, AstNo return irb->codegen->invalid_instruction; } } else { - return_type = nullptr; + add_node_error(irb->codegen, node, + buf_sprintf("TODO implement inferred return types https://github.com/ziglang/zig/issues/447")); + return irb->codegen->invalid_instruction; + //return_type = nullptr; } IrInstruction *async_allocator_type_value = nullptr; @@ -8466,9 +8471,9 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, TypeTableEntry if (wanted_type == actual_type) return result; - // * and [*] can do a const-cast-only to ?* and ?[*], respectively - // but not if there is a mutable parent pointer - // and not if the pointer is zero bits + // *T and [*]T may const-cast-only to ?*U and ?[*]U, respectively + // but not if we want a mutable pointer + // and not if the actual pointer has zero bits if (!wanted_is_mutable && wanted_type->id == TypeTableEntryIdOptional && wanted_type->data.maybe.child_type->id == TypeTableEntryIdPointer && actual_type->id == TypeTableEntryIdPointer && type_has_bits(actual_type)) @@ -8483,6 +8488,18 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, TypeTableEntry return result; } + // *T and [*]T can always cast to *c_void + if (wanted_type->id == TypeTableEntryIdPointer && + wanted_type->data.pointer.ptr_len == PtrLenSingle && + wanted_type->data.pointer.child_type == g->builtin_types.entry_c_void && + actual_type->id == TypeTableEntryIdPointer && + (!actual_type->data.pointer.is_const || wanted_type->data.pointer.is_const) && + (!actual_type->data.pointer.is_volatile || wanted_type->data.pointer.is_volatile)) + { + assert(actual_type->data.pointer.alignment >= wanted_type->data.pointer.alignment); + return result; + } + // pointer const if (wanted_type->id == TypeTableEntryIdPointer && actual_type->id == TypeTableEntryIdPointer) { ConstCastOnly child = types_match_const_cast_only(ira, wanted_type->data.pointer.child_type, @@ -12478,6 +12495,24 @@ static TypeTableEntry *ir_analyze_instruction_decl_var(IrAnalyze *ira, IrInstruc } } + if (var->value->type != nullptr && !is_comptime_var) { + // This is at least the second time we've seen this variable declaration during analysis. + // This means that this is actually a different variable due to, e.g. an inline while loop. + // We make a new variable so that it can hold a different type, and so the debug info can + // be distinct. + VariableTableEntry *new_var = create_local_var(ira->codegen, var->decl_node, var->child_scope, + &var->name, var->src_is_const, var->gen_is_const, var->shadowable, var->is_comptime, true); + new_var->owner_exec = var->owner_exec; + if (var->mem_slot_index != SIZE_MAX) { + ConstExprValue *vals = create_const_vals(1); + new_var->mem_slot_index = ira->exec_context.mem_slot_list.length; + ira->exec_context.mem_slot_list.append(vals); + } + + var->next_var = new_var; + var = new_var; + } + var->value->type = result_type; assert(var->value->type); @@ -12496,10 +12531,9 @@ static TypeTableEntry *ir_analyze_instruction_decl_var(IrAnalyze *ira, IrInstruc if (casted_init_value->value.special != ConstValSpecialRuntime) { if (var->mem_slot_index != SIZE_MAX) { - assert(var->mem_slot_index < ira->exec_context.mem_slot_count); - ConstExprValue *mem_slot = &ira->exec_context.mem_slot_list[var->mem_slot_index]; - copy_const_val(mem_slot, &casted_init_value->value, - !is_comptime_var || var->gen_is_const); + assert(var->mem_slot_index < ira->exec_context.mem_slot_list.length); + ConstExprValue *mem_slot = ira->exec_context.mem_slot_list.at(var->mem_slot_index); + copy_const_val(mem_slot, &casted_init_value->value, !is_comptime_var || var->gen_is_const); if (is_comptime_var || (var_class_requires_const && var->gen_is_const)) { ir_build_const_from(ira, &decl_var_instruction->base); @@ -12960,6 +12994,10 @@ static IrInstruction *ir_get_var_ptr(IrAnalyze *ira, IrInstruction *instruction, VariableTableEntry *var) { Error err; + while (var->next_var != nullptr) { + var = var->next_var; + } + if (var->mem_slot_index != SIZE_MAX && var->owner_exec->analysis == nullptr) { assert(ira->codegen->errors.length != 0); return ira->codegen->invalid_instruction; @@ -12979,8 +13017,8 @@ static IrInstruction *ir_get_var_ptr(IrAnalyze *ira, IrInstruction *instruction, assert(var->owner_exec != nullptr); assert(var->owner_exec->analysis != nullptr); IrExecContext *exec_context = &var->owner_exec->analysis->exec_context; - assert(var->mem_slot_index < exec_context->mem_slot_count); - mem_slot = &exec_context->mem_slot_list[var->mem_slot_index]; + assert(var->mem_slot_index < exec_context->mem_slot_list.length); + mem_slot = exec_context->mem_slot_list.at(var->mem_slot_index); } } @@ -14439,8 +14477,7 @@ static IrInstruction *ir_analyze_container_field_ptr(IrAnalyze *ira, Buf *field_ ConstExprValue *payload_val = union_val->data.x_union.payload; TypeTableEntry *field_type = field->type_entry; - if (field_type->id == TypeTableEntryIdVoid) - { + if (field_type->id == TypeTableEntryIdVoid) { assert(payload_val == nullptr); payload_val = create_const_vals(1); payload_val->special = ConstValSpecialStatic; @@ -16445,12 +16482,6 @@ static TypeTableEntry *ir_analyze_min_max(IrAnalyze *ira, IrInstruction *source_ eval_min_max_value(ira->codegen, target_type, out_val, is_max); return ira->codegen->builtin_types.entry_num_lit_int; } - case TypeTableEntryIdFloat: - { - ConstExprValue *out_val = ir_build_const_from(ira, source_instruction); - eval_min_max_value(ira->codegen, target_type, out_val, is_max); - return ira->codegen->builtin_types.entry_num_lit_float; - } case TypeTableEntryIdBool: case TypeTableEntryIdVoid: { @@ -16459,7 +16490,7 @@ static TypeTableEntry *ir_analyze_min_max(IrAnalyze *ira, IrInstruction *source_ return target_type; } case TypeTableEntryIdEnum: - zig_panic("TODO min/max value for enum type"); + case TypeTableEntryIdFloat: case TypeTableEntryIdMetaType: case TypeTableEntryIdUnreachable: case TypeTableEntryIdPointer: @@ -16792,12 +16823,11 @@ static TypeTableEntry *ir_type_info_get_type(IrAnalyze *ira, const char *type_na return var->value->data.x_type; } -static bool ir_make_type_info_defs(IrAnalyze *ira, ConstExprValue *out_val, ScopeDecls *decls_scope) -{ +static Error ir_make_type_info_defs(IrAnalyze *ira, ConstExprValue *out_val, ScopeDecls *decls_scope) { Error err; TypeTableEntry *type_info_definition_type = ir_type_info_get_type(ira, "Definition", nullptr); if ((err = ensure_complete_type(ira->codegen, type_info_definition_type))) - return false; + return err; ensure_field_index(type_info_definition_type, "name", 0); ensure_field_index(type_info_definition_type, "is_pub", 1); @@ -16805,38 +16835,33 @@ static bool ir_make_type_info_defs(IrAnalyze *ira, ConstExprValue *out_val, Scop TypeTableEntry *type_info_definition_data_type = ir_type_info_get_type(ira, "Data", type_info_definition_type); if ((err = ensure_complete_type(ira->codegen, type_info_definition_data_type))) - return false; + return err; TypeTableEntry *type_info_fn_def_type = ir_type_info_get_type(ira, "FnDef", type_info_definition_data_type); if ((err = ensure_complete_type(ira->codegen, type_info_fn_def_type))) - return false; + return err; TypeTableEntry *type_info_fn_def_inline_type = ir_type_info_get_type(ira, "Inline", type_info_fn_def_type); if ((err = ensure_complete_type(ira->codegen, type_info_fn_def_inline_type))) - return false; + return err; // Loop through our definitions once to figure out how many definitions we will generate info for. auto decl_it = decls_scope->decl_table.entry_iterator(); decltype(decls_scope->decl_table)::Entry *curr_entry = nullptr; int definition_count = 0; - while ((curr_entry = decl_it.next()) != nullptr) - { + while ((curr_entry = decl_it.next()) != nullptr) { // If the definition is unresolved, force it to be resolved again. - if (curr_entry->value->resolution == TldResolutionUnresolved) - { + if (curr_entry->value->resolution == TldResolutionUnresolved) { resolve_top_level_decl(ira->codegen, curr_entry->value, false, curr_entry->value->source_node); - if (curr_entry->value->resolution != TldResolutionOk) - { - return false; + if (curr_entry->value->resolution != TldResolutionOk) { + return ErrorSemanticAnalyzeFail; } } // Skip comptime blocks and test functions. - if (curr_entry->value->id != TldIdCompTime) - { - if (curr_entry->value->id == TldIdFn) - { + if (curr_entry->value->id != TldIdCompTime) { + if (curr_entry->value->id == TldIdFn) { FnTableEntry *fn_entry = ((TldFn *)curr_entry->value)->fn_entry; if (fn_entry->is_test) continue; @@ -16858,13 +16883,11 @@ static bool ir_make_type_info_defs(IrAnalyze *ira, ConstExprValue *out_val, Scop decl_it = decls_scope->decl_table.entry_iterator(); curr_entry = nullptr; int definition_index = 0; - while ((curr_entry = decl_it.next()) != nullptr) - { + while ((curr_entry = decl_it.next()) != nullptr) { // Skip comptime blocks and test functions. - if (curr_entry->value->id == TldIdCompTime) + if (curr_entry->value->id == TldIdCompTime) { continue; - else if (curr_entry->value->id == TldIdFn) - { + } else if (curr_entry->value->id == TldIdFn) { FnTableEntry *fn_entry = ((TldFn *)curr_entry->value)->fn_entry; if (fn_entry->is_test) continue; @@ -16887,13 +16910,12 @@ static bool ir_make_type_info_defs(IrAnalyze *ira, ConstExprValue *out_val, Scop inner_fields[2].data.x_union.parent.data.p_struct.struct_val = definition_val; inner_fields[2].data.x_union.parent.data.p_struct.field_index = 1; - switch (curr_entry->value->id) - { + switch (curr_entry->value->id) { case TldIdVar: { VariableTableEntry *var = ((TldVar *)curr_entry->value)->var; if ((err = ensure_complete_type(ira->codegen, var->value->type))) - return false; + return ErrorSemanticAnalyzeFail; if (var->value->type->id == TypeTableEntryIdMetaType) { @@ -17024,7 +17046,7 @@ static bool ir_make_type_info_defs(IrAnalyze *ira, ConstExprValue *out_val, Scop { TypeTableEntry *type_entry = ((TldContainer *)curr_entry->value)->type_entry; if ((err = ensure_complete_type(ira->codegen, type_entry))) - return false; + return ErrorSemanticAnalyzeFail; // This is a type. bigint_init_unsigned(&inner_fields[2].data.x_union.tag, 0); @@ -17046,7 +17068,7 @@ static bool ir_make_type_info_defs(IrAnalyze *ira, ConstExprValue *out_val, Scop } assert(definition_index == definition_count); - return true; + return ErrorNone; } static ConstExprValue *create_ptr_like_type_info(IrAnalyze *ira, TypeTableEntry *ptr_type_entry) { @@ -17104,30 +17126,31 @@ static ConstExprValue *create_ptr_like_type_info(IrAnalyze *ira, TypeTableEntry return result; }; -static ConstExprValue *ir_make_type_info_value(IrAnalyze *ira, TypeTableEntry *type_entry) { +static void make_enum_field_val(IrAnalyze *ira, ConstExprValue *enum_field_val, TypeEnumField *enum_field, + TypeTableEntry *type_info_enum_field_type) +{ + enum_field_val->special = ConstValSpecialStatic; + enum_field_val->type = type_info_enum_field_type; + + ConstExprValue *inner_fields = create_const_vals(2); + inner_fields[1].special = ConstValSpecialStatic; + inner_fields[1].type = ira->codegen->builtin_types.entry_usize; + + ConstExprValue *name = create_const_str_lit(ira->codegen, enum_field->name); + init_const_slice(ira->codegen, &inner_fields[0], name, 0, buf_len(enum_field->name), true); + + bigint_init_bigint(&inner_fields[1].data.x_bigint, &enum_field->value); + + enum_field_val->data.x_struct.fields = inner_fields; +} + +static Error ir_make_type_info_value(IrAnalyze *ira, TypeTableEntry *type_entry, ConstExprValue **out) { Error err; assert(type_entry != nullptr); assert(!type_is_invalid(type_entry)); if ((err = ensure_complete_type(ira->codegen, type_entry))) - return nullptr; - - const auto make_enum_field_val = [ira](ConstExprValue *enum_field_val, TypeEnumField *enum_field, - TypeTableEntry *type_info_enum_field_type) { - enum_field_val->special = ConstValSpecialStatic; - enum_field_val->type = type_info_enum_field_type; - - ConstExprValue *inner_fields = create_const_vals(2); - inner_fields[1].special = ConstValSpecialStatic; - inner_fields[1].type = ira->codegen->builtin_types.entry_usize; - - ConstExprValue *name = create_const_str_lit(ira->codegen, enum_field->name); - init_const_slice(ira->codegen, &inner_fields[0], name, 0, buf_len(enum_field->name), true); - - bigint_init_bigint(&inner_fields[1].data.x_bigint, &enum_field->value); - - enum_field_val->data.x_struct.fields = inner_fields; - }; + return err; if (type_entry == ira->codegen->builtin_types.entry_global_error_set) { zig_panic("TODO implement @typeInfo for global error set"); @@ -17150,13 +17173,16 @@ static ConstExprValue *ir_make_type_info_value(IrAnalyze *ira, TypeTableEntry *t case TypeTableEntryIdBlock: case TypeTableEntryIdArgTuple: case TypeTableEntryIdOpaque: - return nullptr; + *out = nullptr; + return ErrorNone; default: { // Lookup an available value in our cache. auto entry = ira->codegen->type_info_cache.maybe_get(type_entry); - if (entry != nullptr) - return entry->value; + if (entry != nullptr) { + *out = entry->value; + return ErrorNone; + } // Fallthrough if we don't find one. } @@ -17307,15 +17333,15 @@ static ConstExprValue *ir_make_type_info_value(IrAnalyze *ira, TypeTableEntry *t { TypeEnumField *enum_field = &type_entry->data.enumeration.fields[enum_field_index]; ConstExprValue *enum_field_val = &enum_field_array->data.x_array.s_none.elements[enum_field_index]; - make_enum_field_val(enum_field_val, enum_field, type_info_enum_field_type); + make_enum_field_val(ira, enum_field_val, enum_field, type_info_enum_field_type); enum_field_val->data.x_struct.parent.id = ConstParentIdArray; enum_field_val->data.x_struct.parent.data.p_array.array_val = enum_field_array; enum_field_val->data.x_struct.parent.data.p_array.elem_index = enum_field_index; } // defs: []TypeInfo.Definition ensure_field_index(result->type, "defs", 3); - if (!ir_make_type_info_defs(ira, &fields[3], type_entry->data.enumeration.decls_scope)) - return nullptr; + if ((err = ir_make_type_info_defs(ira, &fields[3], type_entry->data.enumeration.decls_scope))) + return err; break; } @@ -17341,8 +17367,7 @@ static ConstExprValue *ir_make_type_info_value(IrAnalyze *ira, TypeTableEntry *t error_array->data.x_array.s_none.elements = create_const_vals(error_count); init_const_slice(ira->codegen, &fields[0], error_array, 0, error_count, false); - for (uint32_t error_index = 0; error_index < error_count; error_index++) - { + for (uint32_t error_index = 0; error_index < error_count; error_index++) { ErrorTableEntry *error = type_entry->data.error_set.errors[error_index]; ConstExprValue *error_val = &error_array->data.x_array.s_none.elements[error_index]; @@ -17420,9 +17445,9 @@ static ConstExprValue *ir_make_type_info_value(IrAnalyze *ira, TypeTableEntry *t tag_type->type = ira->codegen->builtin_types.entry_type; tag_type->data.x_type = type_entry->data.unionation.tag_type; fields[1].data.x_optional = tag_type; - } - else + } else { fields[1].data.x_optional = nullptr; + } // fields: []TypeInfo.UnionField ensure_field_index(result->type, "fields", 2); @@ -17455,7 +17480,7 @@ static ConstExprValue *ir_make_type_info_value(IrAnalyze *ira, TypeTableEntry *t inner_fields[1].data.x_optional = nullptr; } else { inner_fields[1].data.x_optional = create_const_vals(1); - make_enum_field_val(inner_fields[1].data.x_optional, union_field->enum_field, type_info_enum_field_type); + make_enum_field_val(ira, inner_fields[1].data.x_optional, union_field->enum_field, type_info_enum_field_type); } inner_fields[2].special = ConstValSpecialStatic; @@ -17472,8 +17497,8 @@ static ConstExprValue *ir_make_type_info_value(IrAnalyze *ira, TypeTableEntry *t } // defs: []TypeInfo.Definition ensure_field_index(result->type, "defs", 3); - if (!ir_make_type_info_defs(ira, &fields[3], type_entry->data.unionation.decls_scope)) - return nullptr; + if ((err = ir_make_type_info_defs(ira, &fields[3], type_entry->data.unionation.decls_scope))) + return err; break; } @@ -17546,8 +17571,8 @@ static ConstExprValue *ir_make_type_info_value(IrAnalyze *ira, TypeTableEntry *t } // defs: []TypeInfo.Definition ensure_field_index(result->type, "defs", 2); - if (!ir_make_type_info_defs(ira, &fields[2], type_entry->data.structure.decls_scope)) - return nullptr; + if ((err = ir_make_type_info_defs(ira, &fields[2], type_entry->data.structure.decls_scope))) + return err; break; } @@ -17660,7 +17685,8 @@ static ConstExprValue *ir_make_type_info_value(IrAnalyze *ira, TypeTableEntry *t { TypeTableEntry *fn_type = type_entry->data.bound_fn.fn_type; assert(fn_type->id == TypeTableEntryIdFn); - result = ir_make_type_info_value(ira, fn_type); + if ((err = ir_make_type_info_value(ira, fn_type, &result))) + return err; break; } @@ -17668,12 +17694,14 @@ static ConstExprValue *ir_make_type_info_value(IrAnalyze *ira, TypeTableEntry *t assert(result != nullptr); ira->codegen->type_info_cache.put(type_entry, result); - return result; + *out = result; + return ErrorNone; } static TypeTableEntry *ir_analyze_instruction_type_info(IrAnalyze *ira, IrInstructionTypeInfo *instruction) { + Error err; IrInstruction *type_value = instruction->type_value->other; TypeTableEntry *type_entry = ir_resolve_type(ira, type_value); if (type_is_invalid(type_entry)) @@ -17681,15 +17709,16 @@ static TypeTableEntry *ir_analyze_instruction_type_info(IrAnalyze *ira, TypeTableEntry *result_type = ir_type_info_get_type(ira, nullptr, nullptr); + ConstExprValue *payload; + if ((err = ir_make_type_info_value(ira, type_entry, &payload))) + return ira->codegen->builtin_types.entry_invalid; + ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base); out_val->type = result_type; bigint_init_unsigned(&out_val->data.x_union.tag, type_id_index(type_entry)); - - ConstExprValue *payload = ir_make_type_info_value(ira, type_entry); out_val->data.x_union.payload = payload; - if (payload != nullptr) - { + if (payload != nullptr) { assert(payload->type->id == TypeTableEntryIdStruct); payload->data.x_struct.parent.id = ConstParentIdUnion; payload->data.x_struct.parent.data.p_union.union_val = out_val; @@ -19731,6 +19760,8 @@ static IrInstruction *ir_align_cast(IrAnalyze *ira, IrInstruction *target, uint3 } static TypeTableEntry *ir_analyze_instruction_ptr_cast(IrAnalyze *ira, IrInstructionPtrCast *instruction) { + Error err; + IrInstruction *dest_type_value = instruction->dest_type->other; TypeTableEntry *dest_type = ir_resolve_type(ira, dest_type_value); if (type_is_invalid(dest_type)) @@ -19784,9 +19815,13 @@ static TypeTableEntry *ir_analyze_instruction_ptr_cast(IrAnalyze *ira, IrInstruc instruction->base.source_node, nullptr, ptr); casted_ptr->value.type = dest_type; - // keep the bigger alignment, it can only help + // Keep the bigger alignment, it can only help- + // unless the target is zero bits. + if ((err = type_ensure_zero_bits_known(ira->codegen, dest_type))) + return ira->codegen->builtin_types.entry_invalid; + IrInstruction *result; - if (src_align_bytes > dest_align_bytes) { + if (src_align_bytes > dest_align_bytes && type_has_bits(dest_type)) { result = ir_align_cast(ira, casted_ptr, src_align_bytes, false); if (type_is_invalid(result->value.type)) return ira->codegen->builtin_types.entry_invalid; @@ -21192,8 +21227,11 @@ TypeTableEntry *ir_analyze(CodeGen *codegen, IrExecutable *old_exec, IrExecutabl ira->new_irb.codegen = codegen; ira->new_irb.exec = new_exec; - ira->exec_context.mem_slot_count = ira->old_irb.exec->mem_slot_count; - ira->exec_context.mem_slot_list = create_const_vals(ira->exec_context.mem_slot_count); + ConstExprValue *vals = create_const_vals(ira->old_irb.exec->mem_slot_count); + ira->exec_context.mem_slot_list.resize(ira->old_irb.exec->mem_slot_count); + for (size_t i = 0; i < ira->exec_context.mem_slot_list.length; i += 1) { + ira->exec_context.mem_slot_list.items[i] = &vals[i]; + } IrBasicBlock *old_entry_bb = ira->old_irb.exec->basic_block_list.at(0); IrBasicBlock *new_entry_bb = ir_get_new_bb(ira, old_entry_bb, nullptr); diff --git a/src/translate_c.cpp b/src/translate_c.cpp index f521077513..df48c9e4a2 100644 --- a/src/translate_c.cpp +++ b/src/translate_c.cpp @@ -3075,12 +3075,19 @@ static int trans_stmt_extra(Context *c, TransScope *scope, const Stmt *stmt, trans_unary_operator(c, result_used, scope, (const UnaryOperator *)stmt)); case Stmt::DeclStmtClass: return trans_local_declaration(c, scope, (const DeclStmt *)stmt, out_node, out_child_scope); + case Stmt::DoStmtClass: case Stmt::WhileStmtClass: { - AstNode *while_node = trans_while_loop(c, scope, (const WhileStmt *)stmt); + AstNode *while_node = sc == Stmt::DoStmtClass + ? trans_do_loop(c, scope, (const DoStmt *)stmt) + : trans_while_loop(c, scope, (const WhileStmt *)stmt); + + if (while_node == nullptr) + return ErrorUnexpected; + assert(while_node->type == NodeTypeWhileExpr); - if (while_node->data.while_expr.body == nullptr) { + if (while_node->data.while_expr.body == nullptr) while_node->data.while_expr.body = trans_create_node(c, NodeTypeBlock); - } + return wrap_stmt(out_node, out_child_scope, scope, while_node); } case Stmt::IfStmtClass: @@ -3105,14 +3112,6 @@ static int trans_stmt_extra(Context *c, TransScope *scope, const Stmt *stmt, case Stmt::UnaryExprOrTypeTraitExprClass: return wrap_stmt(out_node, out_child_scope, scope, trans_unary_expr_or_type_trait_expr(c, scope, (const UnaryExprOrTypeTraitExpr *)stmt)); - case Stmt::DoStmtClass: { - AstNode *while_node = trans_do_loop(c, scope, (const DoStmt *)stmt); - assert(while_node->type == NodeTypeWhileExpr); - if (while_node->data.while_expr.body == nullptr) { - while_node->data.while_expr.body = trans_create_node(c, NodeTypeBlock); - } - return wrap_stmt(out_node, out_child_scope, scope, while_node); - } case Stmt::ForStmtClass: { AstNode *node = trans_for_loop(c, scope, (const ForStmt *)stmt); return wrap_stmt(out_node, out_child_scope, scope, node); diff --git a/src/windows_sdk.cpp b/src/windows_sdk.cpp index 0f9d0fc301..c6e79f85cd 100644 --- a/src/windows_sdk.cpp +++ b/src/windows_sdk.cpp @@ -204,7 +204,11 @@ static ZigFindWindowsSdkError find_10_version(ZigWindowsSDKPrivate *priv) { // https://developer.microsoft.com/en-us/windows/downloads/sdk-archive c2 = 26624; } - if ((c0 > v0) || (c1 > v1) || (c2 > v2) || (c3 > v3)) { + + if ( (c0 > v0) + || (c0 == v0 && c1 > v1) + || (c0 == v0 && c1 == v1 && c2 > v2) + || (c0 == v0 && c1 == v1 && c2 == v2 && c3 > v3) ) { v0 = c0, v1 = c1, v2 = c2, v3 = c3; free((void*)priv->base.version10_ptr); priv->base.version10_ptr = strdup(ffd.cFileName); @@ -244,7 +248,8 @@ static ZigFindWindowsSdkError find_81_version(ZigWindowsSDKPrivate *priv) { if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { int c0 = 0, c1 = 0; sscanf(ffd.cFileName, "winv%d.%d", &c0, &c1); - if ((c0 > v0) || (c1 > v1)) { + + if ( (c0 > v0) || (c0 == v0 && c1 > v1) ) { v0 = c0, v1 = c1; free((void*)priv->base.version81_ptr); priv->base.version81_ptr = strdup(ffd.cFileName); diff --git a/std/coff.zig b/std/coff.zig new file mode 100644 index 0000000000..379fd1af42 --- /dev/null +++ b/std/coff.zig @@ -0,0 +1,230 @@ +const builtin = @import("builtin"); +const std = @import("index.zig"); +const io = std.io; +const mem = std.mem; +const os = std.os; + +const ArrayList = std.ArrayList; + +// CoffHeader.machine values +// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms680313(v=vs.85).aspx +const IMAGE_FILE_MACHINE_I386 = 0x014c; +const IMAGE_FILE_MACHINE_IA64 = 0x0200; +const IMAGE_FILE_MACHINE_AMD64 = 0x8664; + +// OptionalHeader.magic values +// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=vs.85).aspx +const IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b; +const IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b; + +const IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16; +const DEBUG_DIRECTORY = 6; + +pub const CoffError = error { + InvalidPEMagic, + InvalidPEHeader, + InvalidMachine, + MissingCoffSection, +}; + +pub const Coff = struct { + in_file: os.File, + allocator: *mem.Allocator, + + coff_header: CoffHeader, + pe_header: OptionalHeader, + sections: ArrayList(Section), + + guid: [16]u8, + age: u32, + + pub fn loadHeader(self: *Coff) !void { + const pe_pointer_offset = 0x3C; + + var file_stream = io.FileInStream.init(self.in_file); + const in = &file_stream.stream; + + var magic: [2]u8 = undefined; + try in.readNoEof(magic[0..]); + if (!mem.eql(u8, magic, "MZ")) + return error.InvalidPEMagic; + + // Seek to PE File Header (coff header) + try self.in_file.seekTo(pe_pointer_offset); + const pe_magic_offset = try in.readIntLe(u32); + try self.in_file.seekTo(pe_magic_offset); + + var pe_header_magic: [4]u8 = undefined; + try in.readNoEof(pe_header_magic[0..]); + if (!mem.eql(u8, pe_header_magic, []u8{'P', 'E', 0, 0})) + return error.InvalidPEHeader; + + self.coff_header = CoffHeader { + .machine = try in.readIntLe(u16), + .number_of_sections = try in.readIntLe(u16), + .timedate_stamp = try in.readIntLe(u32), + .pointer_to_symbol_table = try in.readIntLe(u32), + .number_of_symbols = try in.readIntLe(u32), + .size_of_optional_header = try in.readIntLe(u16), + .characteristics = try in.readIntLe(u16), + }; + + switch (self.coff_header.machine) { + IMAGE_FILE_MACHINE_I386, + IMAGE_FILE_MACHINE_AMD64, + IMAGE_FILE_MACHINE_IA64 + => {}, + else => return error.InvalidMachine, + } + + try self.loadOptionalHeader(&file_stream); + } + + fn loadOptionalHeader(self: *Coff, file_stream: *io.FileInStream) !void { + const in = &file_stream.stream; + self.pe_header.magic = try in.readIntLe(u16); + // For now we're only interested in finding the reference to the .pdb, + // so we'll skip most of this header, which size is different in 32 + // 64 bits by the way. + var skip_size: u16 = undefined; + if (self.pe_header.magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) { + skip_size = 2 * @sizeOf(u8) + 8 * @sizeOf(u16) + 18 * @sizeOf(u32); + } + else if (self.pe_header.magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) { + skip_size = 2 * @sizeOf(u8) + 8 * @sizeOf(u16) + 12 * @sizeOf(u32) + 5 * @sizeOf(u64); + } + else + return error.InvalidPEMagic; + + try self.in_file.seekForward(skip_size); + + const number_of_rva_and_sizes = try in.readIntLe(u32); + if (number_of_rva_and_sizes != IMAGE_NUMBEROF_DIRECTORY_ENTRIES) + return error.InvalidPEHeader; + + for (self.pe_header.data_directory) |*data_dir| { + data_dir.* = OptionalHeader.DataDirectory { + .virtual_address = try in.readIntLe(u32), + .size = try in.readIntLe(u32), + }; + } + } + + pub fn getPdbPath(self: *Coff, buffer: []u8) !usize { + try self.loadSections(); + const header = (self.getSection(".rdata") orelse return error.MissingCoffSection).header; + + // The linker puts a chunk that contains the .pdb path right after the + // debug_directory. + const debug_dir = &self.pe_header.data_directory[DEBUG_DIRECTORY]; + const file_offset = debug_dir.virtual_address - header.virtual_address + header.pointer_to_raw_data; + try self.in_file.seekTo(file_offset + debug_dir.size); + + var file_stream = io.FileInStream.init(self.in_file); + const in = &file_stream.stream; + + var cv_signature: [4]u8 = undefined; // CodeView signature + try in.readNoEof(cv_signature[0..]); + // 'RSDS' indicates PDB70 format, used by lld. + if (!mem.eql(u8, cv_signature, "RSDS")) + return error.InvalidPEMagic; + try in.readNoEof(self.guid[0..]); + self.age = try in.readIntLe(u32); + + // Finally read the null-terminated string. + var byte = try in.readByte(); + var i: usize = 0; + while (byte != 0 and i < buffer.len) : (i += 1) { + buffer[i] = byte; + byte = try in.readByte(); + } + + if (byte != 0 and i == buffer.len) + return error.NameTooLong; + + return i; + } + + pub fn loadSections(self: *Coff) !void { + if (self.sections.len != 0) + return; + + self.sections = ArrayList(Section).init(self.allocator); + + var file_stream = io.FileInStream.init(self.in_file); + const in = &file_stream.stream; + + var name: [8]u8 = undefined; + + var i: u16 = 0; + while (i < self.coff_header.number_of_sections) : (i += 1) { + try in.readNoEof(name[0..]); + try self.sections.append(Section { + .header = SectionHeader { + .name = name, + .misc = SectionHeader.Misc { .physical_address = try in.readIntLe(u32) }, + .virtual_address = try in.readIntLe(u32), + .size_of_raw_data = try in.readIntLe(u32), + .pointer_to_raw_data = try in.readIntLe(u32), + .pointer_to_relocations = try in.readIntLe(u32), + .pointer_to_line_numbers = try in.readIntLe(u32), + .number_of_relocations = try in.readIntLe(u16), + .number_of_line_numbers = try in.readIntLe(u16), + .characteristics = try in.readIntLe(u32), + }, + }); + } + } + + pub fn getSection(self: *Coff, comptime name: []const u8) ?*Section { + for (self.sections.toSlice()) |*sec| { + if (mem.eql(u8, sec.header.name[0..name.len], name)) { + return sec; + } + } + return null; + } + +}; + +const CoffHeader = struct { + machine: u16, + number_of_sections: u16, + timedate_stamp: u32, + pointer_to_symbol_table: u32, + number_of_symbols: u32, + size_of_optional_header: u16, + characteristics: u16 +}; + +const OptionalHeader = struct { + const DataDirectory = struct { + virtual_address: u32, + size: u32 + }; + + magic: u16, + data_directory: [IMAGE_NUMBEROF_DIRECTORY_ENTRIES]DataDirectory, +}; + +pub const Section = struct { + header: SectionHeader, +}; + +const SectionHeader = struct { + const Misc = union { + physical_address: u32, + virtual_size: u32 + }; + + name: [8]u8, + misc: Misc, + virtual_address: u32, + size_of_raw_data: u32, + pointer_to_raw_data: u32, + pointer_to_relocations: u32, + pointer_to_line_numbers: u32, + number_of_relocations: u16, + number_of_line_numbers: u16, + characteristics: u32, +}; diff --git a/std/crypto/blake2.zig b/std/crypto/blake2.zig index 947133e4cf..467ddde5db 100644 --- a/std/crypto/blake2.zig +++ b/std/crypto/blake2.zig @@ -34,8 +34,8 @@ pub const Blake2s256 = Blake2s(256); fn Blake2s(comptime out_len: usize) type { return struct { const Self = this; - const block_size = 64; - const digest_size = out_len / 8; + const block_length = 64; + const digest_length = out_len / 8; const iv = [8]u32{ 0x6A09E667, @@ -250,8 +250,8 @@ test "blake2s256 streaming" { } test "blake2s256 aligned final" { - var block = []u8{0} ** Blake2s256.block_size; - var out: [Blake2s256.digest_size]u8 = undefined; + var block = []u8{0} ** Blake2s256.block_length; + var out: [Blake2s256.digest_length]u8 = undefined; var h = Blake2s256.init(); h.update(block); @@ -267,8 +267,8 @@ pub const Blake2b512 = Blake2b(512); fn Blake2b(comptime out_len: usize) type { return struct { const Self = this; - const block_size = 128; - const digest_size = out_len / 8; + const block_length = 128; + const digest_length = out_len / 8; const iv = [8]u64{ 0x6a09e667f3bcc908, @@ -483,8 +483,8 @@ test "blake2b512 streaming" { } test "blake2b512 aligned final" { - var block = []u8{0} ** Blake2b512.block_size; - var out: [Blake2b512.digest_size]u8 = undefined; + var block = []u8{0} ** Blake2b512.block_length; + var out: [Blake2b512.digest_length]u8 = undefined; var h = Blake2b512.init(); h.update(block); diff --git a/std/crypto/chacha20.zig b/std/crypto/chacha20.zig new file mode 100644 index 0000000000..743d1033e3 --- /dev/null +++ b/std/crypto/chacha20.zig @@ -0,0 +1,432 @@ +// Based on public domain Supercop by Daniel J. Bernstein + +const std = @import("../index.zig"); +const mem = std.mem; +const endian = std.endian; +const assert = std.debug.assert; +const builtin = @import("builtin"); + +const QuarterRound = struct { + a: usize, + b: usize, + c: usize, + d: usize, +}; + +fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound { + return QuarterRound{ + .a = a, + .b = b, + .c = c, + .d = d, + }; +} + +// The chacha family of ciphers are based on the salsa family. +fn salsa20_wordtobyte(out: []u8, input: [16]u32) void { + assert(out.len >= 64); + + var x: [16]u32 = undefined; + + for (x) |_, i| + x[i] = input[i]; + + const rounds = comptime []QuarterRound{ + Rp(0, 4, 8, 12), + Rp(1, 5, 9, 13), + Rp(2, 6, 10, 14), + Rp(3, 7, 11, 15), + Rp(0, 5, 10, 15), + Rp(1, 6, 11, 12), + Rp(2, 7, 8, 13), + Rp(3, 4, 9, 14), + }; + + comptime var j: usize = 0; + inline while (j < 20) : (j += 2) { + // two-round cycles + inline for (rounds) |r| { + x[r.a] +%= x[r.b]; + x[r.d] = std.math.rotl(u32, x[r.d] ^ x[r.a], u32(16)); + x[r.c] +%= x[r.d]; + x[r.b] = std.math.rotl(u32, x[r.b] ^ x[r.c], u32(12)); + x[r.a] +%= x[r.b]; + x[r.d] = std.math.rotl(u32, x[r.d] ^ x[r.a], u32(8)); + x[r.c] +%= x[r.d]; + x[r.b] = std.math.rotl(u32, x[r.b] ^ x[r.c], u32(7)); + } + } + + for (x) |_, i| { + mem.writeInt(out[4 * i .. 4 * i + 4], x[i] +% input[i], builtin.Endian.Little); + } +} + +fn chaCha20_internal(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void { + var ctx: [16]u32 = undefined; + var remaining: usize = if (in.len > out.len) in.len else out.len; + var cursor: usize = 0; + + const c = "expand 32-byte k"; + const constant_le = []u32{ + mem.readIntLE(u32, c[0..4]), + mem.readIntLE(u32, c[4..8]), + mem.readIntLE(u32, c[8..12]), + mem.readIntLE(u32, c[12..16]), + }; + + mem.copy(u32, ctx[0..], constant_le[0..4]); + mem.copy(u32, ctx[4..12], key[0..8]); + mem.copy(u32, ctx[12..16], counter[0..4]); + + while (true) { + var buf: [64]u8 = undefined; + salsa20_wordtobyte(buf[0..], ctx); + + if (remaining < 64) { + var i: usize = 0; + while (i < remaining) : (i += 1) + out[cursor + i] = in[cursor + i] ^ buf[i]; + return; + } + + var i: usize = 0; + while (i < 64) : (i += 1) + out[cursor + i] = in[cursor + i] ^ buf[i]; + + cursor += 64; + remaining -= 64; + + ctx[12] += 1; + } +} + +/// ChaCha20 avoids the possibility of timing attacks, as there are no branches +/// on secret key data. +/// +/// in and out should be the same length. +/// counter should generally be 0 or 1 +/// +/// ChaCha20 is self-reversing. To decrypt just run the cipher with the same +/// counter, nonce, and key. +pub fn chaCha20IETF(out: []u8, in: []const u8, counter: u32, key: [32]u8, nonce: [12]u8) void { + assert(in.len >= out.len); + assert((in.len >> 6) + counter <= @maxValue(u32)); + + var k: [8]u32 = undefined; + var c: [4]u32 = undefined; + + k[0] = mem.readIntLE(u32, key[0..4]); + k[1] = mem.readIntLE(u32, key[4..8]); + k[2] = mem.readIntLE(u32, key[8..12]); + k[3] = mem.readIntLE(u32, key[12..16]); + k[4] = mem.readIntLE(u32, key[16..20]); + k[5] = mem.readIntLE(u32, key[20..24]); + k[6] = mem.readIntLE(u32, key[24..28]); + k[7] = mem.readIntLE(u32, key[28..32]); + + c[0] = counter; + c[1] = mem.readIntLE(u32, nonce[0..4]); + c[2] = mem.readIntLE(u32, nonce[4..8]); + c[3] = mem.readIntLE(u32, nonce[8..12]); + chaCha20_internal(out, in, k, c); +} + +/// This is the original ChaCha20 before RFC 7539, which recommends using the +/// orgininal version on applications such as disk or file encryption that might +/// exceed the 256 GiB limit of the 96-bit nonce version. +pub fn chaCha20With64BitNonce(out: []u8, in: []const u8, counter: u64, key: [32]u8, nonce: [8]u8) void { + assert(in.len >= out.len); + assert(counter +% (in.len >> 6) >= counter); + + var cursor: u64 = 0; + var k: [8]u32 = undefined; + var c: [4]u32 = undefined; + + k[0] = mem.readIntLE(u32, key[0..4]); + k[1] = mem.readIntLE(u32, key[4..8]); + k[2] = mem.readIntLE(u32, key[8..12]); + k[3] = mem.readIntLE(u32, key[12..16]); + k[4] = mem.readIntLE(u32, key[16..20]); + k[5] = mem.readIntLE(u32, key[20..24]); + k[6] = mem.readIntLE(u32, key[24..28]); + k[7] = mem.readIntLE(u32, key[28..32]); + + c[0] = @truncate(u32, counter); + c[1] = @truncate(u32, counter >> 32); + c[2] = mem.readIntLE(u32, nonce[0..4]); + c[3] = mem.readIntLE(u32, nonce[4..8]); + + const block_size = (1 << 6); + const big_block = (block_size << 32); + + // first partial big block + if (((@intCast(u64, @maxValue(u32) - @truncate(u32, counter)) + 1) << 6) < in.len) { + chaCha20_internal(out[cursor..big_block], in[cursor..big_block], k, c); + cursor = big_block - cursor; + c[1] += 1; + if (comptime @sizeOf(usize) > 4) { + // A big block is giant: 256 GiB, but we can avoid this limitation + var remaining_blocks: u32 = @intCast(u32, (in.len / big_block)); + var i: u32 = 0; + while (remaining_blocks > 0) : (remaining_blocks -= 1) { + chaCha20_internal(out[cursor .. cursor + big_block], in[cursor .. cursor + big_block], k, c); + c[1] += 1; // upper 32-bit of counter, generic chaCha20_internal() doesn't know about this. + cursor += big_block; + } + } + } + + chaCha20_internal(out[cursor..], in[cursor..], k, c); +} + +// https://tools.ietf.org/html/rfc7539#section-2.4.2 +test "crypto.chacha20 test vector sunscreen" { + const expected_result = []u8{ + 0x6e, 0x2e, 0x35, 0x9a, 0x25, 0x68, 0xf9, 0x80, + 0x41, 0xba, 0x07, 0x28, 0xdd, 0x0d, 0x69, 0x81, + 0xe9, 0x7e, 0x7a, 0xec, 0x1d, 0x43, 0x60, 0xc2, + 0x0a, 0x27, 0xaf, 0xcc, 0xfd, 0x9f, 0xae, 0x0b, + 0xf9, 0x1b, 0x65, 0xc5, 0x52, 0x47, 0x33, 0xab, + 0x8f, 0x59, 0x3d, 0xab, 0xcd, 0x62, 0xb3, 0x57, + 0x16, 0x39, 0xd6, 0x24, 0xe6, 0x51, 0x52, 0xab, + 0x8f, 0x53, 0x0c, 0x35, 0x9f, 0x08, 0x61, 0xd8, + 0x07, 0xca, 0x0d, 0xbf, 0x50, 0x0d, 0x6a, 0x61, + 0x56, 0xa3, 0x8e, 0x08, 0x8a, 0x22, 0xb6, 0x5e, + 0x52, 0xbc, 0x51, 0x4d, 0x16, 0xcc, 0xf8, 0x06, + 0x81, 0x8c, 0xe9, 0x1a, 0xb7, 0x79, 0x37, 0x36, + 0x5a, 0xf9, 0x0b, 0xbf, 0x74, 0xa3, 0x5b, 0xe6, + 0xb4, 0x0b, 0x8e, 0xed, 0xf2, 0x78, 0x5e, 0x42, + 0x87, 0x4d, + }; + const input = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."; + var result: [114]u8 = undefined; + const key = []u8{ + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + }; + const nonce = []u8{ + 0, 0, 0, 0, + 0, 0, 0, 0x4a, + 0, 0, 0, 0, + }; + + chaCha20IETF(result[0..], input[0..], 1, key, nonce); + assert(mem.eql(u8, expected_result, result)); + + // Chacha20 is self-reversing. + var plaintext: [114]u8 = undefined; + chaCha20IETF(plaintext[0..], result[0..], 1, key, nonce); + assert(mem.compare(u8, input, plaintext) == mem.Compare.Equal); +} + +// https://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04#section-7 +test "crypto.chacha20 test vector 1" { + const expected_result = []u8{ + 0x76, 0xb8, 0xe0, 0xad, 0xa0, 0xf1, 0x3d, 0x90, + 0x40, 0x5d, 0x6a, 0xe5, 0x53, 0x86, 0xbd, 0x28, + 0xbd, 0xd2, 0x19, 0xb8, 0xa0, 0x8d, 0xed, 0x1a, + 0xa8, 0x36, 0xef, 0xcc, 0x8b, 0x77, 0x0d, 0xc7, + 0xda, 0x41, 0x59, 0x7c, 0x51, 0x57, 0x48, 0x8d, + 0x77, 0x24, 0xe0, 0x3f, 0xb8, 0xd8, 0x4a, 0x37, + 0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c, + 0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86, + }; + const input = []u8{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + var result: [64]u8 = undefined; + const key = []u8{ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + const nonce = []u8{ 0, 0, 0, 0, 0, 0, 0, 0 }; + + chaCha20With64BitNonce(result[0..], input[0..], 0, key, nonce); + assert(mem.eql(u8, expected_result, result)); +} + +test "crypto.chacha20 test vector 2" { + const expected_result = []u8{ + 0x45, 0x40, 0xf0, 0x5a, 0x9f, 0x1f, 0xb2, 0x96, + 0xd7, 0x73, 0x6e, 0x7b, 0x20, 0x8e, 0x3c, 0x96, + 0xeb, 0x4f, 0xe1, 0x83, 0x46, 0x88, 0xd2, 0x60, + 0x4f, 0x45, 0x09, 0x52, 0xed, 0x43, 0x2d, 0x41, + 0xbb, 0xe2, 0xa0, 0xb6, 0xea, 0x75, 0x66, 0xd2, + 0xa5, 0xd1, 0xe7, 0xe2, 0x0d, 0x42, 0xaf, 0x2c, + 0x53, 0xd7, 0x92, 0xb1, 0xc4, 0x3f, 0xea, 0x81, + 0x7e, 0x9a, 0xd2, 0x75, 0xae, 0x54, 0x69, 0x63, + }; + const input = []u8{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + var result: [64]u8 = undefined; + const key = []u8{ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, + }; + const nonce = []u8{ 0, 0, 0, 0, 0, 0, 0, 0 }; + + chaCha20With64BitNonce(result[0..], input[0..], 0, key, nonce); + assert(mem.eql(u8, expected_result, result)); +} + +test "crypto.chacha20 test vector 3" { + const expected_result = []u8{ + 0xde, 0x9c, 0xba, 0x7b, 0xf3, 0xd6, 0x9e, 0xf5, + 0xe7, 0x86, 0xdc, 0x63, 0x97, 0x3f, 0x65, 0x3a, + 0x0b, 0x49, 0xe0, 0x15, 0xad, 0xbf, 0xf7, 0x13, + 0x4f, 0xcb, 0x7d, 0xf1, 0x37, 0x82, 0x10, 0x31, + 0xe8, 0x5a, 0x05, 0x02, 0x78, 0xa7, 0x08, 0x45, + 0x27, 0x21, 0x4f, 0x73, 0xef, 0xc7, 0xfa, 0x5b, + 0x52, 0x77, 0x06, 0x2e, 0xb7, 0xa0, 0x43, 0x3e, + 0x44, 0x5f, 0x41, 0xe3, + }; + const input = []u8{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + var result: [60]u8 = undefined; + const key = []u8{ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + const nonce = []u8{ 0, 0, 0, 0, 0, 0, 0, 1 }; + + chaCha20With64BitNonce(result[0..], input[0..], 0, key, nonce); + assert(mem.eql(u8, expected_result, result)); +} + +test "crypto.chacha20 test vector 4" { + const expected_result = []u8{ + 0xef, 0x3f, 0xdf, 0xd6, 0xc6, 0x15, 0x78, 0xfb, + 0xf5, 0xcf, 0x35, 0xbd, 0x3d, 0xd3, 0x3b, 0x80, + 0x09, 0x63, 0x16, 0x34, 0xd2, 0x1e, 0x42, 0xac, + 0x33, 0x96, 0x0b, 0xd1, 0x38, 0xe5, 0x0d, 0x32, + 0x11, 0x1e, 0x4c, 0xaf, 0x23, 0x7e, 0xe5, 0x3c, + 0xa8, 0xad, 0x64, 0x26, 0x19, 0x4a, 0x88, 0x54, + 0x5d, 0xdc, 0x49, 0x7a, 0x0b, 0x46, 0x6e, 0x7d, + 0x6b, 0xbd, 0xb0, 0x04, 0x1b, 0x2f, 0x58, 0x6b, + }; + const input = []u8{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + var result: [64]u8 = undefined; + const key = []u8{ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + const nonce = []u8{ 1, 0, 0, 0, 0, 0, 0, 0 }; + + chaCha20With64BitNonce(result[0..], input[0..], 0, key, nonce); + assert(mem.eql(u8, expected_result, result)); +} + +test "crypto.chacha20 test vector 5" { + const expected_result = []u8{ + 0xf7, 0x98, 0xa1, 0x89, 0xf1, 0x95, 0xe6, 0x69, + 0x82, 0x10, 0x5f, 0xfb, 0x64, 0x0b, 0xb7, 0x75, + 0x7f, 0x57, 0x9d, 0xa3, 0x16, 0x02, 0xfc, 0x93, + 0xec, 0x01, 0xac, 0x56, 0xf8, 0x5a, 0xc3, 0xc1, + 0x34, 0xa4, 0x54, 0x7b, 0x73, 0x3b, 0x46, 0x41, + 0x30, 0x42, 0xc9, 0x44, 0x00, 0x49, 0x17, 0x69, + 0x05, 0xd3, 0xbe, 0x59, 0xea, 0x1c, 0x53, 0xf1, + 0x59, 0x16, 0x15, 0x5c, 0x2b, 0xe8, 0x24, 0x1a, + + 0x38, 0x00, 0x8b, 0x9a, 0x26, 0xbc, 0x35, 0x94, + 0x1e, 0x24, 0x44, 0x17, 0x7c, 0x8a, 0xde, 0x66, + 0x89, 0xde, 0x95, 0x26, 0x49, 0x86, 0xd9, 0x58, + 0x89, 0xfb, 0x60, 0xe8, 0x46, 0x29, 0xc9, 0xbd, + 0x9a, 0x5a, 0xcb, 0x1c, 0xc1, 0x18, 0xbe, 0x56, + 0x3e, 0xb9, 0xb3, 0xa4, 0xa4, 0x72, 0xf8, 0x2e, + 0x09, 0xa7, 0xe7, 0x78, 0x49, 0x2b, 0x56, 0x2e, + 0xf7, 0x13, 0x0e, 0x88, 0xdf, 0xe0, 0x31, 0xc7, + + 0x9d, 0xb9, 0xd4, 0xf7, 0xc7, 0xa8, 0x99, 0x15, + 0x1b, 0x9a, 0x47, 0x50, 0x32, 0xb6, 0x3f, 0xc3, + 0x85, 0x24, 0x5f, 0xe0, 0x54, 0xe3, 0xdd, 0x5a, + 0x97, 0xa5, 0xf5, 0x76, 0xfe, 0x06, 0x40, 0x25, + 0xd3, 0xce, 0x04, 0x2c, 0x56, 0x6a, 0xb2, 0xc5, + 0x07, 0xb1, 0x38, 0xdb, 0x85, 0x3e, 0x3d, 0x69, + 0x59, 0x66, 0x09, 0x96, 0x54, 0x6c, 0xc9, 0xc4, + 0xa6, 0xea, 0xfd, 0xc7, 0x77, 0xc0, 0x40, 0xd7, + + 0x0e, 0xaf, 0x46, 0xf7, 0x6d, 0xad, 0x39, 0x79, + 0xe5, 0xc5, 0x36, 0x0c, 0x33, 0x17, 0x16, 0x6a, + 0x1c, 0x89, 0x4c, 0x94, 0xa3, 0x71, 0x87, 0x6a, + 0x94, 0xdf, 0x76, 0x28, 0xfe, 0x4e, 0xaa, 0xf2, + 0xcc, 0xb2, 0x7d, 0x5a, 0xaa, 0xe0, 0xad, 0x7a, + 0xd0, 0xf9, 0xd4, 0xb6, 0xad, 0x3b, 0x54, 0x09, + 0x87, 0x46, 0xd4, 0x52, 0x4d, 0x38, 0x40, 0x7a, + 0x6d, 0xeb, 0x3a, 0xb7, 0x8f, 0xab, 0x78, 0xc9, + }; + const input = []u8{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + var result: [256]u8 = undefined; + const key = []u8{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + }; + const nonce = []u8{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + }; + + chaCha20With64BitNonce(result[0..], input[0..], 0, key, nonce); + assert(mem.eql(u8, expected_result, result)); +} diff --git a/std/crypto/hmac.zig b/std/crypto/hmac.zig index 1415e88cf4..23eeff2a00 100644 --- a/std/crypto/hmac.zig +++ b/std/crypto/hmac.zig @@ -7,46 +7,63 @@ pub const HmacMd5 = Hmac(crypto.Md5); pub const HmacSha1 = Hmac(crypto.Sha1); pub const HmacSha256 = Hmac(crypto.Sha256); -pub fn Hmac(comptime H: type) type { +pub fn Hmac(comptime Hash: type) type { return struct { - const digest_size = H.digest_size; + const Self = this; + pub const mac_length = Hash.digest_length; + pub const minimum_key_length = 0; - pub fn hash(output: []u8, key: []const u8, message: []const u8) void { - debug.assert(output.len >= H.digest_size); - debug.assert(H.digest_size <= H.block_size); // HMAC makes this assumption - var scratch: [H.block_size]u8 = undefined; + o_key_pad: [Hash.block_length]u8, + i_key_pad: [Hash.block_length]u8, + scratch: [Hash.block_length]u8, + hash: Hash, + + // HMAC(k, m) = H(o_key_pad | H(i_key_pad | msg)) where | is concatenation + pub fn create(out: []u8, msg: []const u8, key: []const u8) void { + var ctx = Self.init(key); + ctx.update(msg); + ctx.final(out[0..]); + } + + pub fn init(key: []const u8) Self { + var ctx: Self = undefined; // Normalize key length to block size of hash - if (key.len > H.block_size) { - H.hash(key, scratch[0..H.digest_size]); - mem.set(u8, scratch[H.digest_size..H.block_size], 0); - } else if (key.len < H.block_size) { - mem.copy(u8, scratch[0..key.len], key); - mem.set(u8, scratch[key.len..H.block_size], 0); + if (key.len > Hash.block_length) { + Hash.hash(key, ctx.scratch[0..mac_length]); + mem.set(u8, ctx.scratch[mac_length..Hash.block_length], 0); + } else if (key.len < Hash.block_length) { + mem.copy(u8, ctx.scratch[0..key.len], key); + mem.set(u8, ctx.scratch[key.len..Hash.block_length], 0); } else { - mem.copy(u8, scratch[0..], key); + mem.copy(u8, ctx.scratch[0..], key); } - var o_key_pad: [H.block_size]u8 = undefined; - for (o_key_pad) |*b, i| { - b.* = scratch[i] ^ 0x5c; + for (ctx.o_key_pad) |*b, i| { + b.* = ctx.scratch[i] ^ 0x5c; } - var i_key_pad: [H.block_size]u8 = undefined; - for (i_key_pad) |*b, i| { - b.* = scratch[i] ^ 0x36; + for (ctx.i_key_pad) |*b, i| { + b.* = ctx.scratch[i] ^ 0x36; } - // HMAC(k, m) = H(o_key_pad | H(i_key_pad | message)) where | is concatenation - var hmac = H.init(); - hmac.update(i_key_pad[0..]); - hmac.update(message); - hmac.final(scratch[0..H.digest_size]); + ctx.hash = Hash.init(); + ctx.hash.update(ctx.i_key_pad[0..]); + return ctx; + } - hmac.reset(); - hmac.update(o_key_pad[0..]); - hmac.update(scratch[0..H.digest_size]); - hmac.final(output[0..H.digest_size]); + pub fn update(ctx: *Self, msg: []const u8) void { + ctx.hash.update(msg); + } + + pub fn final(ctx: *Self, out: []u8) void { + debug.assert(Hash.block_length >= out.len and out.len >= mac_length); + + ctx.hash.final(ctx.scratch[0..mac_length]); + ctx.hash.reset(); + ctx.hash.update(ctx.o_key_pad[0..]); + ctx.hash.update(ctx.scratch[0..mac_length]); + ctx.hash.final(out[0..mac_length]); } }; } @@ -54,28 +71,28 @@ pub fn Hmac(comptime H: type) type { const htest = @import("test.zig"); test "hmac md5" { - var out: [crypto.Md5.digest_size]u8 = undefined; - HmacMd5.hash(out[0..], "", ""); + var out: [HmacMd5.mac_length]u8 = undefined; + HmacMd5.create(out[0..], "", ""); htest.assertEqual("74e6f7298a9c2d168935f58c001bad88", out[0..]); - HmacMd5.hash(out[0..], "key", "The quick brown fox jumps over the lazy dog"); + HmacMd5.create(out[0..], "The quick brown fox jumps over the lazy dog", "key"); htest.assertEqual("80070713463e7749b90c2dc24911e275", out[0..]); } test "hmac sha1" { - var out: [crypto.Sha1.digest_size]u8 = undefined; - HmacSha1.hash(out[0..], "", ""); + var out: [HmacSha1.mac_length]u8 = undefined; + HmacSha1.create(out[0..], "", ""); htest.assertEqual("fbdb1d1b18aa6c08324b7d64b71fb76370690e1d", out[0..]); - HmacSha1.hash(out[0..], "key", "The quick brown fox jumps over the lazy dog"); + HmacSha1.create(out[0..], "The quick brown fox jumps over the lazy dog", "key"); htest.assertEqual("de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9", out[0..]); } test "hmac sha256" { - var out: [crypto.Sha256.digest_size]u8 = undefined; - HmacSha256.hash(out[0..], "", ""); + var out: [HmacSha256.mac_length]u8 = undefined; + HmacSha256.create(out[0..], "", ""); htest.assertEqual("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad", out[0..]); - HmacSha256.hash(out[0..], "key", "The quick brown fox jumps over the lazy dog"); + HmacSha256.create(out[0..], "The quick brown fox jumps over the lazy dog", "key"); htest.assertEqual("f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8", out[0..]); } diff --git a/std/crypto/index.zig b/std/crypto/index.zig index 2f39020228..3a88fe2b2c 100644 --- a/std/crypto/index.zig +++ b/std/crypto/index.zig @@ -21,14 +21,24 @@ pub const Blake2b512 = blake2.Blake2b512; const hmac = @import("hmac.zig"); pub const HmacMd5 = hmac.HmacMd5; -pub const HmacSha1 = hmac.Sha1; -pub const HmacSha256 = hmac.Sha256; +pub const HmacSha1 = hmac.HmacSha1; +pub const HmacSha256 = hmac.HmacSha256; + +const import_chaCha20 = @import("chacha20.zig"); +pub const chaCha20IETF = import_chaCha20.chaCha20IETF; +pub const chaCha20With64BitNonce = import_chaCha20.chaCha20With64BitNonce; + +pub const Poly1305 = @import("poly1305.zig").Poly1305; +pub const X25519 = @import("x25519.zig").X25519; test "crypto" { + _ = @import("blake2.zig"); + _ = @import("chacha20.zig"); + _ = @import("hmac.zig"); _ = @import("md5.zig"); + _ = @import("poly1305.zig"); _ = @import("sha1.zig"); _ = @import("sha2.zig"); _ = @import("sha3.zig"); - _ = @import("blake2.zig"); - _ = @import("hmac.zig"); + _ = @import("x25519.zig"); } diff --git a/std/crypto/md5.zig b/std/crypto/md5.zig index 23fe2313a0..20334ec7d8 100644 --- a/std/crypto/md5.zig +++ b/std/crypto/md5.zig @@ -29,8 +29,8 @@ fn Rp(a: usize, b: usize, c: usize, d: usize, k: usize, s: u32, t: u32) RoundPar pub const Md5 = struct { const Self = this; - const block_size = 64; - const digest_size = 16; + const block_length = 64; + const digest_length = 16; s: [4]u32, // Streaming Cache @@ -271,8 +271,8 @@ test "md5 streaming" { } test "md5 aligned final" { - var block = []u8{0} ** Md5.block_size; - var out: [Md5.digest_size]u8 = undefined; + var block = []u8{0} ** Md5.block_length; + var out: [Md5.digest_length]u8 = undefined; var h = Md5.init(); h.update(block); diff --git a/std/crypto/poly1305.zig b/std/crypto/poly1305.zig new file mode 100644 index 0000000000..f5e11fc0a1 --- /dev/null +++ b/std/crypto/poly1305.zig @@ -0,0 +1,233 @@ +// Translated from monocypher which is licensed under CC-0/BSD-3. +// +// https://monocypher.org/ + +const std = @import("../index.zig"); +const builtin = @import("builtin"); + +const Endian = builtin.Endian; +const readInt = std.mem.readInt; +const writeInt = std.mem.writeInt; + +pub const Poly1305 = struct { + const Self = this; + + pub const mac_length = 16; + pub const minimum_key_length = 32; + + // constant multiplier (from the secret key) + r: [4]u32, + // accumulated hash + h: [5]u32, + // chunk of the message + c: [5]u32, + // random number added at the end (from the secret key) + pad: [4]u32, + // How many bytes are there in the chunk. + c_idx: usize, + + fn secureZero(self: *Self) void { + std.mem.secureZero(u8, @ptrCast([*]u8, self)[0..@sizeOf(Poly1305)]); + } + + pub fn create(out: []u8, msg: []const u8, key: []const u8) void { + std.debug.assert(out.len >= mac_length); + std.debug.assert(key.len >= minimum_key_length); + + var ctx = Poly1305.init(key); + ctx.update(msg); + ctx.final(out); + } + + // Initialize the MAC context. + // - key.len is sufficient size. + pub fn init(key: []const u8) Self { + var ctx: Poly1305 = undefined; + + // Initial hash is zero + { + var i: usize = 0; + while (i < 5) : (i += 1) { + ctx.h[i] = 0; + } + } + // add 2^130 to every input block + ctx.c[4] = 1; + polyClearC(&ctx); + + // load r and pad (r has some of its bits cleared) + { + var i: usize = 0; + while (i < 1) : (i += 1) { + ctx.r[0] = readInt(key[0..4], u32, Endian.Little) & 0x0fffffff; + } + } + { + var i: usize = 1; + while (i < 4) : (i += 1) { + ctx.r[i] = readInt(key[i * 4 .. i * 4 + 4], u32, Endian.Little) & 0x0ffffffc; + } + } + { + var i: usize = 0; + while (i < 4) : (i += 1) { + ctx.pad[i] = readInt(key[i * 4 + 16 .. i * 4 + 16 + 4], u32, Endian.Little); + } + } + + return ctx; + } + + // h = (h + c) * r + // preconditions: + // ctx->h <= 4_ffffffff_ffffffff_ffffffff_ffffffff + // ctx->c <= 1_ffffffff_ffffffff_ffffffff_ffffffff + // ctx->r <= 0ffffffc_0ffffffc_0ffffffc_0fffffff + // Postcondition: + // ctx->h <= 4_ffffffff_ffffffff_ffffffff_ffffffff + fn polyBlock(ctx: *Self) void { + // s = h + c, without carry propagation + const s0 = u64(ctx.h[0]) + ctx.c[0]; // s0 <= 1_fffffffe + const s1 = u64(ctx.h[1]) + ctx.c[1]; // s1 <= 1_fffffffe + const s2 = u64(ctx.h[2]) + ctx.c[2]; // s2 <= 1_fffffffe + const s3 = u64(ctx.h[3]) + ctx.c[3]; // s3 <= 1_fffffffe + const s4 = u64(ctx.h[4]) + ctx.c[4]; // s4 <= 5 + + // Local all the things! + const r0 = ctx.r[0]; // r0 <= 0fffffff + const r1 = ctx.r[1]; // r1 <= 0ffffffc + const r2 = ctx.r[2]; // r2 <= 0ffffffc + const r3 = ctx.r[3]; // r3 <= 0ffffffc + const rr0 = (r0 >> 2) * 5; // rr0 <= 13fffffb // lose 2 bits... + const rr1 = (r1 >> 2) + r1; // rr1 <= 13fffffb // rr1 == (r1 >> 2) * 5 + const rr2 = (r2 >> 2) + r2; // rr2 <= 13fffffb // rr1 == (r2 >> 2) * 5 + const rr3 = (r3 >> 2) + r3; // rr3 <= 13fffffb // rr1 == (r3 >> 2) * 5 + + // (h + c) * r, without carry propagation + const x0 = s0 * r0 + s1 * rr3 + s2 * rr2 + s3 * rr1 + s4 * rr0; //<=97ffffe007fffff8 + const x1 = s0 * r1 + s1 * r0 + s2 * rr3 + s3 * rr2 + s4 * rr1; //<=8fffffe20ffffff6 + const x2 = s0 * r2 + s1 * r1 + s2 * r0 + s3 * rr3 + s4 * rr2; //<=87ffffe417fffff4 + const x3 = s0 * r3 + s1 * r2 + s2 * r1 + s3 * r0 + s4 * rr3; //<=7fffffe61ffffff2 + const x4 = s4 * (r0 & 3); // ...recover 2 bits //<= f + + // partial reduction modulo 2^130 - 5 + const _u5 = @truncate(u32, x4 + (x3 >> 32)); // u5 <= 7ffffff5 + const _u0 = (_u5 >> 2) * 5 + (x0 & 0xffffffff); + const _u1 = (_u0 >> 32) + (x1 & 0xffffffff) + (x0 >> 32); + const _u2 = (_u1 >> 32) + (x2 & 0xffffffff) + (x1 >> 32); + const _u3 = (_u2 >> 32) + (x3 & 0xffffffff) + (x2 >> 32); + const _u4 = (_u3 >> 32) + (_u5 & 3); + + // Update the hash + ctx.h[0] = @truncate(u32, _u0); // u0 <= 1_9ffffff0 + ctx.h[1] = @truncate(u32, _u1); // u1 <= 1_97ffffe0 + ctx.h[2] = @truncate(u32, _u2); // u2 <= 1_8fffffe2 + ctx.h[3] = @truncate(u32, _u3); // u3 <= 1_87ffffe4 + ctx.h[4] = @truncate(u32, _u4); // u4 <= 4 + } + + // (re-)initializes the input counter and input buffer + fn polyClearC(ctx: *Self) void { + ctx.c[0] = 0; + ctx.c[1] = 0; + ctx.c[2] = 0; + ctx.c[3] = 0; + ctx.c_idx = 0; + } + + fn polyTakeInput(ctx: *Self, input: u8) void { + const word = ctx.c_idx >> 2; + const byte = ctx.c_idx & 3; + ctx.c[word] |= std.math.shl(u32, input, byte * 8); + ctx.c_idx += 1; + } + + fn polyUpdate(ctx: *Self, msg: []const u8) void { + for (msg) |b| { + polyTakeInput(ctx, b); + if (ctx.c_idx == 16) { + polyBlock(ctx); + polyClearC(ctx); + } + } + } + + fn alignTo(x: usize, block_size: usize) usize { + return ((~x) +% 1) & (block_size - 1); + } + + // Feed data into the MAC context. + pub fn update(ctx: *Self, msg: []const u8) void { + // Align ourselves with block boundaries + const alignm = std.math.min(alignTo(ctx.c_idx, 16), msg.len); + polyUpdate(ctx, msg[0..alignm]); + + var nmsg = msg[alignm..]; + + // Process the msg block by block + const nb_blocks = nmsg.len >> 4; + var i: usize = 0; + while (i < nb_blocks) : (i += 1) { + ctx.c[0] = readInt(nmsg[0..4], u32, Endian.Little); + ctx.c[1] = readInt(nmsg[4..8], u32, Endian.Little); + ctx.c[2] = readInt(nmsg[8..12], u32, Endian.Little); + ctx.c[3] = readInt(nmsg[12..16], u32, Endian.Little); + polyBlock(ctx); + nmsg = nmsg[16..]; + } + if (nb_blocks > 0) { + polyClearC(ctx); + } + + // remaining bytes + polyUpdate(ctx, nmsg[0..]); + } + + // Finalize the MAC and output into buffer provided by caller. + pub fn final(ctx: *Self, out: []u8) void { + // Process the last block (if any) + if (ctx.c_idx != 0) { + // move the final 1 according to remaining input length + // (We may add less than 2^130 to the last input block) + ctx.c[4] = 0; + polyTakeInput(ctx, 1); + // one last hash update + polyBlock(ctx); + } + + // check if we should subtract 2^130-5 by performing the + // corresponding carry propagation. + const _u0 = u64(5) + ctx.h[0]; // <= 1_00000004 + const _u1 = (_u0 >> 32) + ctx.h[1]; // <= 1_00000000 + const _u2 = (_u1 >> 32) + ctx.h[2]; // <= 1_00000000 + const _u3 = (_u2 >> 32) + ctx.h[3]; // <= 1_00000000 + const _u4 = (_u3 >> 32) + ctx.h[4]; // <= 5 + // u4 indicates how many times we should subtract 2^130-5 (0 or 1) + + // h + pad, minus 2^130-5 if u4 exceeds 3 + const uu0 = (_u4 >> 2) * 5 + ctx.h[0] + ctx.pad[0]; // <= 2_00000003 + const uu1 = (uu0 >> 32) + ctx.h[1] + ctx.pad[1]; // <= 2_00000000 + const uu2 = (uu1 >> 32) + ctx.h[2] + ctx.pad[2]; // <= 2_00000000 + const uu3 = (uu2 >> 32) + ctx.h[3] + ctx.pad[3]; // <= 2_00000000 + + writeInt(out[0..], @truncate(u32, uu0), Endian.Little); + writeInt(out[4..], @truncate(u32, uu1), Endian.Little); + writeInt(out[8..], @truncate(u32, uu2), Endian.Little); + writeInt(out[12..], @truncate(u32, uu3), Endian.Little); + + ctx.secureZero(); + } +}; + +test "poly1305 rfc7439 vector1" { + const expected_mac = "\xa8\x06\x1d\xc1\x30\x51\x36\xc6\xc2\x2b\x8b\xaf\x0c\x01\x27\xa9"; + + const msg = "Cryptographic Forum Research Group"; + const key = "\x85\xd6\xbe\x78\x57\x55\x6d\x33\x7f\x44\x52\xfe\x42\xd5\x06\xa8" ++ + "\x01\x03\x80\x8a\xfb\x0d\xb2\xfd\x4a\xbf\xf6\xaf\x41\x49\xf5\x1b"; + + var mac: [16]u8 = undefined; + Poly1305.create(mac[0..], msg, key); + + std.debug.assert(std.mem.eql(u8, mac, expected_mac)); +} diff --git a/std/crypto/sha1.zig b/std/crypto/sha1.zig index 451cfb3122..6d6b4dbd3f 100644 --- a/std/crypto/sha1.zig +++ b/std/crypto/sha1.zig @@ -26,8 +26,8 @@ fn Rp(a: usize, b: usize, c: usize, d: usize, e: usize, i: u32) RoundParam { pub const Sha1 = struct { const Self = this; - const block_size = 64; - const digest_size = 20; + const block_length = 64; + const digest_length = 20; s: [5]u32, // Streaming Cache @@ -292,8 +292,8 @@ test "sha1 streaming" { } test "sha1 aligned final" { - var block = []u8{0} ** Sha1.block_size; - var out: [Sha1.digest_size]u8 = undefined; + var block = []u8{0} ** Sha1.block_length; + var out: [Sha1.digest_length]u8 = undefined; var h = Sha1.init(); h.update(block); diff --git a/std/crypto/sha2.zig b/std/crypto/sha2.zig index d1b915835c..8a25fecc43 100644 --- a/std/crypto/sha2.zig +++ b/std/crypto/sha2.zig @@ -78,8 +78,8 @@ pub const Sha256 = Sha2_32(Sha256Params); fn Sha2_32(comptime params: Sha2Params32) type { return struct { const Self = this; - const block_size = 64; - const digest_size = params.out_len / 8; + const block_length = 64; + const digest_length = params.out_len / 8; s: [8]u32, // Streaming Cache @@ -338,8 +338,8 @@ test "sha256 streaming" { } test "sha256 aligned final" { - var block = []u8{0} ** Sha256.block_size; - var out: [Sha256.digest_size]u8 = undefined; + var block = []u8{0} ** Sha256.block_length; + var out: [Sha256.digest_length]u8 = undefined; var h = Sha256.init(); h.update(block); @@ -419,8 +419,8 @@ pub const Sha512 = Sha2_64(Sha512Params); fn Sha2_64(comptime params: Sha2Params64) type { return struct { const Self = this; - const block_size = 128; - const digest_size = params.out_len / 8; + const block_length = 128; + const digest_length = params.out_len / 8; s: [8]u64, // Streaming Cache @@ -715,8 +715,8 @@ test "sha512 streaming" { } test "sha512 aligned final" { - var block = []u8{0} ** Sha512.block_size; - var out: [Sha512.digest_size]u8 = undefined; + var block = []u8{0} ** Sha512.block_length; + var out: [Sha512.digest_length]u8 = undefined; var h = Sha512.init(); h.update(block); diff --git a/std/crypto/sha3.zig b/std/crypto/sha3.zig index ae02d7a482..827bbd0680 100644 --- a/std/crypto/sha3.zig +++ b/std/crypto/sha3.zig @@ -13,8 +13,8 @@ pub const Sha3_512 = Keccak(512, 0x06); fn Keccak(comptime bits: usize, comptime delim: u8) type { return struct { const Self = this; - const block_size = 200; - const digest_size = bits / 8; + const block_length = 200; + const digest_length = bits / 8; s: [200]u8, offset: usize, @@ -87,97 +87,24 @@ fn Keccak(comptime bits: usize, comptime delim: u8) type { } const RC = []const u64{ - 0x0000000000000001, - 0x0000000000008082, - 0x800000000000808a, - 0x8000000080008000, - 0x000000000000808b, - 0x0000000080000001, - 0x8000000080008081, - 0x8000000000008009, - 0x000000000000008a, - 0x0000000000000088, - 0x0000000080008009, - 0x000000008000000a, - 0x000000008000808b, - 0x800000000000008b, - 0x8000000000008089, - 0x8000000000008003, - 0x8000000000008002, - 0x8000000000000080, - 0x000000000000800a, - 0x800000008000000a, - 0x8000000080008081, - 0x8000000000008080, - 0x0000000080000001, - 0x8000000080008008, + 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, 0x8000000080008000, + 0x000000000000808b, 0x0000000080000001, 0x8000000080008081, 0x8000000000008009, + 0x000000000000008a, 0x0000000000000088, 0x0000000080008009, 0x000000008000000a, + 0x000000008000808b, 0x800000000000008b, 0x8000000000008089, 0x8000000000008003, + 0x8000000000008002, 0x8000000000000080, 0x000000000000800a, 0x800000008000000a, + 0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008, }; const ROTC = []const usize{ - 1, - 3, - 6, - 10, - 15, - 21, - 28, - 36, - 45, - 55, - 2, - 14, - 27, - 41, - 56, - 8, - 25, - 43, - 62, - 18, - 39, - 61, - 20, - 44, + 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44, }; const PIL = []const usize{ - 10, - 7, - 11, - 17, - 18, - 3, - 5, - 16, - 8, - 21, - 24, - 4, - 15, - 23, - 19, - 13, - 12, - 2, - 20, - 14, - 22, - 9, - 6, - 1, + 10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1, }; const M5 = []const usize{ - 0, - 1, - 2, - 3, - 4, - 0, - 1, - 2, - 3, - 4, + 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, }; fn keccak_f(comptime F: usize, d: []u8) void { @@ -297,8 +224,8 @@ test "sha3-256 streaming" { } test "sha3-256 aligned final" { - var block = []u8{0} ** Sha3_256.block_size; - var out: [Sha3_256.digest_size]u8 = undefined; + var block = []u8{0} ** Sha3_256.block_length; + var out: [Sha3_256.digest_length]u8 = undefined; var h = Sha3_256.init(); h.update(block); @@ -368,8 +295,8 @@ test "sha3-512 streaming" { } test "sha3-512 aligned final" { - var block = []u8{0} ** Sha3_512.block_size; - var out: [Sha3_512.digest_size]u8 = undefined; + var block = []u8{0} ** Sha3_512.block_length; + var out: [Sha3_512.digest_length]u8 = undefined; var h = Sha3_512.init(); h.update(block); diff --git a/std/crypto/throughput_test.zig b/std/crypto/throughput_test.zig index c21838e607..294ef5df51 100644 --- a/std/crypto/throughput_test.zig +++ b/std/crypto/throughput_test.zig @@ -1,38 +1,193 @@ -// Modify the HashFunction variable to the one wanted to test. -// -// ``` -// zig build-exe --release-fast throughput_test.zig -// ./throughput_test -// ``` - +const builtin = @import("builtin"); const std = @import("std"); const time = std.os.time; const Timer = time.Timer; -const HashFunction = @import("md5.zig").Md5; +const crypto = @import("index.zig"); -const MiB = 1024 * 1024; -const BytesToHash = 1024 * MiB; +const KiB = 1024; +const MiB = 1024 * KiB; + +var prng = std.rand.DefaultPrng.init(0); + +const Crypto = struct { + ty: type, + name: []const u8, +}; + +const hashes = []Crypto{ + Crypto{ .ty = crypto.Md5, .name = "md5" }, + Crypto{ .ty = crypto.Sha1, .name = "sha1" }, + Crypto{ .ty = crypto.Sha256, .name = "sha256" }, + Crypto{ .ty = crypto.Sha512, .name = "sha512" }, + Crypto{ .ty = crypto.Sha3_256, .name = "sha3-256" }, + Crypto{ .ty = crypto.Sha3_512, .name = "sha3-512" }, + Crypto{ .ty = crypto.Blake2s256, .name = "blake2s" }, + Crypto{ .ty = crypto.Blake2b512, .name = "blake2b" }, +}; + +pub fn benchmarkHash(comptime Hash: var, comptime bytes: comptime_int) !u64 { + var h = Hash.init(); + + var block: [Hash.digest_length]u8 = undefined; + prng.random.bytes(block[0..]); + + var offset: usize = 0; + var timer = try Timer.start(); + const start = timer.lap(); + while (offset < bytes) : (offset += block.len) { + h.update(block[0..]); + } + const end = timer.read(); + + const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s; + const throughput = @floatToInt(u64, bytes / elapsed_s); + + return throughput; +} + +const macs = []Crypto{ + Crypto{ .ty = crypto.Poly1305, .name = "poly1305" }, + Crypto{ .ty = crypto.HmacMd5, .name = "hmac-md5" }, + Crypto{ .ty = crypto.HmacSha1, .name = "hmac-sha1" }, + Crypto{ .ty = crypto.HmacSha256, .name = "hmac-sha256" }, +}; + +pub fn benchmarkMac(comptime Mac: var, comptime bytes: comptime_int) !u64 { + std.debug.assert(32 >= Mac.mac_length and 32 >= Mac.minimum_key_length); + + var in: [1 * MiB]u8 = undefined; + prng.random.bytes(in[0..]); + + var key: [32]u8 = undefined; + prng.random.bytes(key[0..]); + + var offset: usize = 0; + var timer = try Timer.start(); + const start = timer.lap(); + while (offset < bytes) : (offset += in.len) { + Mac.create(key[0..], in[0..], key); + } + const end = timer.read(); + + const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s; + const throughput = @floatToInt(u64, bytes / elapsed_s); + + return throughput; +} + +const exchanges = []Crypto{Crypto{ .ty = crypto.X25519, .name = "x25519" }}; + +pub fn benchmarkKeyExchange(comptime DhKeyExchange: var, comptime exchange_count: comptime_int) !u64 { + std.debug.assert(DhKeyExchange.minimum_key_length >= DhKeyExchange.secret_length); + + var in: [DhKeyExchange.minimum_key_length]u8 = undefined; + prng.random.bytes(in[0..]); + + var out: [DhKeyExchange.minimum_key_length]u8 = undefined; + prng.random.bytes(out[0..]); + + var offset: usize = 0; + var timer = try Timer.start(); + const start = timer.lap(); + { + var i: usize = 0; + while (i < exchange_count) : (i += 1) { + _ = DhKeyExchange.create(out[0..], out, in); + } + } + const end = timer.read(); + + const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s; + const throughput = @floatToInt(u64, exchange_count / elapsed_s); + + return throughput; +} + +fn usage() void { + std.debug.warn( + \\throughput_test [options] + \\ + \\Options: + \\ --filter [test-name] + \\ --seed [int] + \\ --help + \\ + ); +} + +fn mode(comptime x: comptime_int) comptime_int { + return if (builtin.mode == builtin.Mode.Debug) x / 64 else x; +} + +// TODO(#1358): Replace with builtin formatted padding when available. +fn printPad(stdout: var, s: []const u8) !void { + var i: usize = 0; + while (i < 12 - s.len) : (i += 1) { + try stdout.print(" "); + } + try stdout.print("{}", s); +} pub fn main() !void { var stdout_file = try std.io.getStdOut(); var stdout_out_stream = std.io.FileOutStream.init(&stdout_file); const stdout = &stdout_out_stream.stream; - var block: [HashFunction.block_size]u8 = undefined; - std.mem.set(u8, block[0..], 0); + var buffer: [1024]u8 = undefined; + var fixed = std.heap.FixedBufferAllocator.init(buffer[0..]); + const args = try std.os.argsAlloc(&fixed.allocator); - var h = HashFunction.init(); - var offset: usize = 0; + var filter: ?[]u8 = ""; - var timer = try Timer.start(); - const start = timer.lap(); - while (offset < BytesToHash) : (offset += block.len) { - h.update(block[0..]); + var i: usize = 1; + while (i < args.len) : (i += 1) { + if (std.mem.eql(u8, args[i], "--seed")) { + i += 1; + if (i == args.len) { + usage(); + std.os.exit(1); + } + + const seed = try std.fmt.parseUnsigned(u32, args[i], 10); + prng.seed(seed); + } else if (std.mem.eql(u8, args[i], "--filter")) { + i += 1; + if (i == args.len) { + usage(); + std.os.exit(1); + } + + filter = args[i]; + } else if (std.mem.eql(u8, args[i], "--help")) { + usage(); + return; + } else { + usage(); + std.os.exit(1); + } } - const end = timer.read(); - const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s; - const throughput = @floatToInt(u64, BytesToHash / elapsed_s); + inline for (hashes) |H| { + if (filter == null or std.mem.indexOf(u8, H.name, filter.?) != null) { + const throughput = try benchmarkHash(H.ty, mode(32 * MiB)); + try printPad(stdout, H.name); + try stdout.print(": {} MiB/s\n", throughput / (1 * MiB)); + } + } - try stdout.print("{}: {} MiB/s\n", @typeName(HashFunction), throughput / (1 * MiB)); + inline for (macs) |M| { + if (filter == null or std.mem.indexOf(u8, M.name, filter.?) != null) { + const throughput = try benchmarkMac(M.ty, mode(128 * MiB)); + try printPad(stdout, M.name); + try stdout.print(": {} MiB/s\n", throughput / (1 * MiB)); + } + } + + inline for (exchanges) |E| { + if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) { + const throughput = try benchmarkKeyExchange(E.ty, mode(1000)); + try printPad(stdout, E.name); + try stdout.print(": {} exchanges/s\n", throughput); + } + } } diff --git a/std/crypto/x25519.zig b/std/crypto/x25519.zig new file mode 100644 index 0000000000..16ec09f66e --- /dev/null +++ b/std/crypto/x25519.zig @@ -0,0 +1,664 @@ +// Translated from monocypher which is licensed under CC-0/BSD-3. +// +// https://monocypher.org/ + +const std = @import("../index.zig"); +const builtin = @import("builtin"); + +const Endian = builtin.Endian; +const readInt = std.mem.readInt; +const writeInt = std.mem.writeInt; + +// Based on Supercop's ref10 implementation. +pub const X25519 = struct { + pub const secret_length = 32; + pub const minimum_key_length = 32; + + fn trimScalar(s: []u8) void { + s[0] &= 248; + s[31] &= 127; + s[31] |= 64; + } + + fn scalarBit(s: []const u8, i: usize) i32 { + return (s[i >> 3] >> @intCast(u3, i & 7)) & 1; + } + + pub fn create(out: []u8, private_key: []const u8, public_key: []const u8) bool { + std.debug.assert(out.len >= secret_length); + std.debug.assert(private_key.len >= minimum_key_length); + std.debug.assert(public_key.len >= minimum_key_length); + + var storage: [7]Fe = undefined; + var x1 = &storage[0]; + var x2 = &storage[1]; + var z2 = &storage[2]; + var x3 = &storage[3]; + var z3 = &storage[4]; + var t0 = &storage[5]; + var t1 = &storage[6]; + + // computes the scalar product + Fe.fromBytes(x1, public_key); + + // restrict the possible scalar values + var e: [32]u8 = undefined; + for (e[0..]) |_, i| { + e[i] = private_key[i]; + } + trimScalar(e[0..]); + + // computes the actual scalar product (the result is in x2 and z2) + + // Montgomery ladder + // In projective coordinates, to avoid divisons: x = X / Z + // We don't care about the y coordinate, it's only 1 bit of information + Fe.init1(x2); + Fe.init0(z2); // "zero" point + Fe.copy(x3, x1); + Fe.init1(z3); + + var swap: i32 = 0; + var pos: isize = 254; + while (pos >= 0) : (pos -= 1) { + // constant time conditional swap before ladder step + const b = scalarBit(e, @intCast(usize, pos)); + swap ^= b; // xor trick avoids swapping at the end of the loop + Fe.cswap(x2, x3, swap); + Fe.cswap(z2, z3, swap); + swap = b; // anticipates one last swap after the loop + + // Montgomery ladder step: replaces (P2, P3) by (P2*2, P2+P3) + // with differential addition + Fe.sub(t0, x3, z3); + Fe.sub(t1, x2, z2); + Fe.add(x2, x2, z2); + Fe.add(z2, x3, z3); + Fe.mul(z3, t0, x2); + Fe.mul(z2, z2, t1); + Fe.sq(t0, t1); + Fe.sq(t1, x2); + Fe.add(x3, z3, z2); + Fe.sub(z2, z3, z2); + Fe.mul(x2, t1, t0); + Fe.sub(t1, t1, t0); + Fe.sq(z2, z2); + Fe.mulSmall(z3, t1, 121666); + Fe.sq(x3, x3); + Fe.add(t0, t0, z3); + Fe.mul(z3, x1, z2); + Fe.mul(z2, t1, t0); + } + + // last swap is necessary to compensate for the xor trick + // Note: after this swap, P3 == P2 + P1. + Fe.cswap(x2, x3, swap); + Fe.cswap(z2, z3, swap); + + // normalises the coordinates: x == X / Z + Fe.invert(z2, z2); + Fe.mul(x2, x2, z2); + Fe.toBytes(out, x2); + + x1.secureZero(); + x2.secureZero(); + x3.secureZero(); + t0.secureZero(); + t1.secureZero(); + z2.secureZero(); + z3.secureZero(); + std.mem.secureZero(u8, e[0..]); + + // Returns false if the output is all zero + // (happens with some malicious public keys) + return !zerocmp(u8, out); + } + + pub fn createPublicKey(public_key: []const u8, private_key: []const u8) bool { + var base_point = []u8{9} ++ []u8{0} ** 31; + return create(public_key, private_key, base_point); + } +}; + +// Constant time compare to zero. +fn zerocmp(comptime T: type, a: []const T) bool { + var s: T = 0; + for (a) |b| { + s |= b; + } + return s == 0; +} + +//////////////////////////////////// +/// Arithmetic modulo 2^255 - 19 /// +//////////////////////////////////// +// Taken from Supercop's ref10 implementation. +// A bit bigger than TweetNaCl, over 4 times faster. + +// field element +const Fe = struct { + b: [10]i32, + + fn secureZero(self: *Fe) void { + std.mem.secureZero(u8, @ptrCast([*]u8, self)[0..@sizeOf(Fe)]); + } + + fn init0(h: *Fe) void { + for (h.b) |*e| { + e.* = 0; + } + } + + fn init1(h: *Fe) void { + for (h.b[1..]) |*e| { + e.* = 0; + } + h.b[0] = 1; + } + + fn copy(h: *Fe, f: *const Fe) void { + for (h.b) |_, i| { + h.b[i] = f.b[i]; + } + } + + fn neg(h: *Fe, f: *const Fe) void { + for (h.b) |_, i| { + h.b[i] = -f.b[i]; + } + } + + fn add(h: *Fe, f: *const Fe, g: *const Fe) void { + for (h.b) |_, i| { + h.b[i] = f.b[i] + g.b[i]; + } + } + + fn sub(h: *Fe, f: *const Fe, g: *const Fe) void { + for (h.b) |_, i| { + h.b[i] = f.b[i] - g.b[i]; + } + } + + fn cswap(f: *Fe, g: *Fe, b: i32) void { + for (f.b) |_, i| { + const x = (f.b[i] ^ g.b[i]) & -b; + f.b[i] ^= x; + g.b[i] ^= x; + } + } + + fn ccopy(f: *Fe, g: *const Fe, b: i32) void { + for (f.b) |_, i| { + const x = (f.b[i] ^ g.b[i]) & -b; + f.b[i] ^= x; + } + } + + inline fn carryRound(c: []i64, t: []i64, comptime i: comptime_int, comptime shift: comptime_int, comptime mult: comptime_int) void { + const j = (i + 1) % 10; + + c[i] = (t[i] + (i64(1) << shift)) >> (shift + 1); + t[j] += c[i] * mult; + t[i] -= c[i] * (i64(1) << (shift + 1)); + } + + fn carry1(h: *Fe, t: []i64) void { + var c: [10]i64 = undefined; + + var sc = c[0..]; + var st = t[0..]; + + carryRound(sc, st, 9, 24, 19); + carryRound(sc, st, 1, 24, 1); + carryRound(sc, st, 3, 24, 1); + carryRound(sc, st, 5, 24, 1); + carryRound(sc, st, 7, 24, 1); + carryRound(sc, st, 0, 25, 1); + carryRound(sc, st, 2, 25, 1); + carryRound(sc, st, 4, 25, 1); + carryRound(sc, st, 6, 25, 1); + carryRound(sc, st, 8, 25, 1); + + for (h.b) |_, i| { + h.b[i] = @intCast(i32, t[i]); + } + } + + fn carry2(h: *Fe, t: []i64) void { + var c: [10]i64 = undefined; + + var sc = c[0..]; + var st = t[0..]; + + carryRound(sc, st, 0, 25, 1); + carryRound(sc, st, 4, 25, 1); + carryRound(sc, st, 1, 24, 1); + carryRound(sc, st, 5, 24, 1); + carryRound(sc, st, 2, 25, 1); + carryRound(sc, st, 6, 25, 1); + carryRound(sc, st, 3, 24, 1); + carryRound(sc, st, 7, 24, 1); + carryRound(sc, st, 4, 25, 1); + carryRound(sc, st, 8, 25, 1); + carryRound(sc, st, 9, 24, 19); + carryRound(sc, st, 0, 25, 1); + + for (h.b) |_, i| { + h.b[i] = @intCast(i32, t[i]); + } + } + + fn fromBytes(h: *Fe, s: []const u8) void { + std.debug.assert(s.len >= 32); + + var t: [10]i64 = undefined; + + t[0] = readInt(s[0..4], u32, Endian.Little); + t[1] = readInt(s[4..7], u32, Endian.Little) << 6; + t[2] = readInt(s[7..10], u32, Endian.Little) << 5; + t[3] = readInt(s[10..13], u32, Endian.Little) << 3; + t[4] = readInt(s[13..16], u32, Endian.Little) << 2; + t[5] = readInt(s[16..20], u32, Endian.Little); + t[6] = readInt(s[20..23], u32, Endian.Little) << 7; + t[7] = readInt(s[23..26], u32, Endian.Little) << 5; + t[8] = readInt(s[26..29], u32, Endian.Little) << 4; + t[9] = (readInt(s[29..32], u32, Endian.Little) & 0x7fffff) << 2; + + carry1(h, t[0..]); + } + + fn mulSmall(h: *Fe, f: *const Fe, comptime g: comptime_int) void { + var t: [10]i64 = undefined; + + for (t[0..]) |_, i| { + t[i] = i64(f.b[i]) * g; + } + + carry1(h, t[0..]); + } + + fn mul(h: *Fe, f1: *const Fe, g1: *const Fe) void { + const f = f1.b; + const g = g1.b; + + var F: [10]i32 = undefined; + var G: [10]i32 = undefined; + + F[1] = f[1] * 2; + F[3] = f[3] * 2; + F[5] = f[5] * 2; + F[7] = f[7] * 2; + F[9] = f[9] * 2; + + G[1] = g[1] * 19; + G[2] = g[2] * 19; + G[3] = g[3] * 19; + G[4] = g[4] * 19; + G[5] = g[5] * 19; + G[6] = g[6] * 19; + G[7] = g[7] * 19; + G[8] = g[8] * 19; + G[9] = g[9] * 19; + + // t's become h + var t: [10]i64 = undefined; + + t[0] = f[0] * i64(g[0]) + F[1] * i64(G[9]) + f[2] * i64(G[8]) + F[3] * i64(G[7]) + f[4] * i64(G[6]) + F[5] * i64(G[5]) + f[6] * i64(G[4]) + F[7] * i64(G[3]) + f[8] * i64(G[2]) + F[9] * i64(G[1]); + t[1] = f[0] * i64(g[1]) + f[1] * i64(g[0]) + f[2] * i64(G[9]) + f[3] * i64(G[8]) + f[4] * i64(G[7]) + f[5] * i64(G[6]) + f[6] * i64(G[5]) + f[7] * i64(G[4]) + f[8] * i64(G[3]) + f[9] * i64(G[2]); + t[2] = f[0] * i64(g[2]) + F[1] * i64(g[1]) + f[2] * i64(g[0]) + F[3] * i64(G[9]) + f[4] * i64(G[8]) + F[5] * i64(G[7]) + f[6] * i64(G[6]) + F[7] * i64(G[5]) + f[8] * i64(G[4]) + F[9] * i64(G[3]); + t[3] = f[0] * i64(g[3]) + f[1] * i64(g[2]) + f[2] * i64(g[1]) + f[3] * i64(g[0]) + f[4] * i64(G[9]) + f[5] * i64(G[8]) + f[6] * i64(G[7]) + f[7] * i64(G[6]) + f[8] * i64(G[5]) + f[9] * i64(G[4]); + t[4] = f[0] * i64(g[4]) + F[1] * i64(g[3]) + f[2] * i64(g[2]) + F[3] * i64(g[1]) + f[4] * i64(g[0]) + F[5] * i64(G[9]) + f[6] * i64(G[8]) + F[7] * i64(G[7]) + f[8] * i64(G[6]) + F[9] * i64(G[5]); + t[5] = f[0] * i64(g[5]) + f[1] * i64(g[4]) + f[2] * i64(g[3]) + f[3] * i64(g[2]) + f[4] * i64(g[1]) + f[5] * i64(g[0]) + f[6] * i64(G[9]) + f[7] * i64(G[8]) + f[8] * i64(G[7]) + f[9] * i64(G[6]); + t[6] = f[0] * i64(g[6]) + F[1] * i64(g[5]) + f[2] * i64(g[4]) + F[3] * i64(g[3]) + f[4] * i64(g[2]) + F[5] * i64(g[1]) + f[6] * i64(g[0]) + F[7] * i64(G[9]) + f[8] * i64(G[8]) + F[9] * i64(G[7]); + t[7] = f[0] * i64(g[7]) + f[1] * i64(g[6]) + f[2] * i64(g[5]) + f[3] * i64(g[4]) + f[4] * i64(g[3]) + f[5] * i64(g[2]) + f[6] * i64(g[1]) + f[7] * i64(g[0]) + f[8] * i64(G[9]) + f[9] * i64(G[8]); + t[8] = f[0] * i64(g[8]) + F[1] * i64(g[7]) + f[2] * i64(g[6]) + F[3] * i64(g[5]) + f[4] * i64(g[4]) + F[5] * i64(g[3]) + f[6] * i64(g[2]) + F[7] * i64(g[1]) + f[8] * i64(g[0]) + F[9] * i64(G[9]); + t[9] = f[0] * i64(g[9]) + f[1] * i64(g[8]) + f[2] * i64(g[7]) + f[3] * i64(g[6]) + f[4] * i64(g[5]) + f[5] * i64(g[4]) + f[6] * i64(g[3]) + f[7] * i64(g[2]) + f[8] * i64(g[1]) + f[9] * i64(g[0]); + + carry2(h, t[0..]); + } + + // we could use Fe.mul() for this, but this is significantly faster + fn sq(h: *Fe, fz: *const Fe) void { + const f0 = fz.b[0]; + const f1 = fz.b[1]; + const f2 = fz.b[2]; + const f3 = fz.b[3]; + const f4 = fz.b[4]; + const f5 = fz.b[5]; + const f6 = fz.b[6]; + const f7 = fz.b[7]; + const f8 = fz.b[8]; + const f9 = fz.b[9]; + + const f0_2 = f0 * 2; + const f1_2 = f1 * 2; + const f2_2 = f2 * 2; + const f3_2 = f3 * 2; + const f4_2 = f4 * 2; + const f5_2 = f5 * 2; + const f6_2 = f6 * 2; + const f7_2 = f7 * 2; + const f5_38 = f5 * 38; + const f6_19 = f6 * 19; + const f7_38 = f7 * 38; + const f8_19 = f8 * 19; + const f9_38 = f9 * 38; + + var t: [10]i64 = undefined; + + t[0] = f0 * i64(f0) + f1_2 * i64(f9_38) + f2_2 * i64(f8_19) + f3_2 * i64(f7_38) + f4_2 * i64(f6_19) + f5 * i64(f5_38); + t[1] = f0_2 * i64(f1) + f2 * i64(f9_38) + f3_2 * i64(f8_19) + f4 * i64(f7_38) + f5_2 * i64(f6_19); + t[2] = f0_2 * i64(f2) + f1_2 * i64(f1) + f3_2 * i64(f9_38) + f4_2 * i64(f8_19) + f5_2 * i64(f7_38) + f6 * i64(f6_19); + t[3] = f0_2 * i64(f3) + f1_2 * i64(f2) + f4 * i64(f9_38) + f5_2 * i64(f8_19) + f6 * i64(f7_38); + t[4] = f0_2 * i64(f4) + f1_2 * i64(f3_2) + f2 * i64(f2) + f5_2 * i64(f9_38) + f6_2 * i64(f8_19) + f7 * i64(f7_38); + t[5] = f0_2 * i64(f5) + f1_2 * i64(f4) + f2_2 * i64(f3) + f6 * i64(f9_38) + f7_2 * i64(f8_19); + t[6] = f0_2 * i64(f6) + f1_2 * i64(f5_2) + f2_2 * i64(f4) + f3_2 * i64(f3) + f7_2 * i64(f9_38) + f8 * i64(f8_19); + t[7] = f0_2 * i64(f7) + f1_2 * i64(f6) + f2_2 * i64(f5) + f3_2 * i64(f4) + f8 * i64(f9_38); + t[8] = f0_2 * i64(f8) + f1_2 * i64(f7_2) + f2_2 * i64(f6) + f3_2 * i64(f5_2) + f4 * i64(f4) + f9 * i64(f9_38); + t[9] = f0_2 * i64(f9) + f1_2 * i64(f8) + f2_2 * i64(f7) + f3_2 * i64(f6) + f4 * i64(f5_2); + + carry2(h, t[0..]); + } + + fn sq2(h: *Fe, f: *const Fe) void { + Fe.sq(h, f); + Fe.mul_small(h, h, 2); + } + + // This could be simplified, but it would be slower + fn invert(out: *Fe, z: *const Fe) void { + var i: usize = undefined; + + var t: [4]Fe = undefined; + var t0 = &t[0]; + var t1 = &t[1]; + var t2 = &t[2]; + var t3 = &t[3]; + + Fe.sq(t0, z); + Fe.sq(t1, t0); + Fe.sq(t1, t1); + Fe.mul(t1, z, t1); + Fe.mul(t0, t0, t1); + + Fe.sq(t2, t0); + Fe.mul(t1, t1, t2); + + Fe.sq(t2, t1); + i = 1; + while (i < 5) : (i += 1) Fe.sq(t2, t2); + Fe.mul(t1, t2, t1); + + Fe.sq(t2, t1); + i = 1; + while (i < 10) : (i += 1) Fe.sq(t2, t2); + Fe.mul(t2, t2, t1); + + Fe.sq(t3, t2); + i = 1; + while (i < 20) : (i += 1) Fe.sq(t3, t3); + Fe.mul(t2, t3, t2); + + Fe.sq(t2, t2); + i = 1; + while (i < 10) : (i += 1) Fe.sq(t2, t2); + Fe.mul(t1, t2, t1); + + Fe.sq(t2, t1); + i = 1; + while (i < 50) : (i += 1) Fe.sq(t2, t2); + Fe.mul(t2, t2, t1); + + Fe.sq(t3, t2); + i = 1; + while (i < 100) : (i += 1) Fe.sq(t3, t3); + Fe.mul(t2, t3, t2); + + Fe.sq(t2, t2); + i = 1; + while (i < 50) : (i += 1) Fe.sq(t2, t2); + Fe.mul(t1, t2, t1); + + Fe.sq(t1, t1); + i = 1; + while (i < 5) : (i += 1) Fe.sq(t1, t1); + Fe.mul(out, t1, t0); + + t0.secureZero(); + t1.secureZero(); + t2.secureZero(); + t3.secureZero(); + } + + // This could be simplified, but it would be slower + fn pow22523(out: *Fe, z: *const Fe) void { + var i: usize = undefined; + + var t: [3]Fe = undefined; + var t0 = &t[0]; + var t1 = &t[1]; + var t2 = &t[2]; + + Fe.sq(t0, z); + Fe.sq(t1, t0); + Fe.sq(t1, t1); + Fe.mul(t1, z, t1); + Fe.mul(t0, t0, t1); + + Fe.sq(t0, t0); + Fe.mul(t0, t1, t0); + + Fe.sq(t1, t0); + i = 1; + while (i < 5) : (i += 1) Fe.sq(t1, t1); + Fe.mul(t0, t1, t0); + + Fe.sq(t1, t0); + i = 1; + while (i < 10) : (i += 1) Fe.sq(t1, t1); + Fe.mul(t1, t1, t0); + + Fe.sq(t2, t1); + i = 1; + while (i < 20) : (i += 1) Fe.sq(t2, t2); + Fe.mul(t1, t2, t1); + + Fe.sq(t1, t1); + i = 1; + while (i < 10) : (i += 1) Fe.sq(t1, t1); + Fe.mul(t0, t1, t0); + + Fe.sq(t1, t0); + i = 1; + while (i < 50) : (i += 1) Fe.sq(t1, t1); + Fe.mul(t1, t1, t0); + + Fe.sq(t2, t1); + i = 1; + while (i < 100) : (i += 1) Fe.sq(t2, t2); + Fe.mul(t1, t2, t1); + + Fe.sq(t1, t1); + i = 1; + while (i < 50) : (i += 1) Fe.sq(t1, t1); + Fe.mul(t0, t1, t0); + + Fe.sq(t0, t0); + i = 1; + while (i < 2) : (i += 1) Fe.sq(t0, t0); + Fe.mul(out, t0, z); + + t0.secureZero(); + t1.secureZero(); + t2.secureZero(); + } + + inline fn toBytesRound(c: []i64, t: []i64, comptime i: comptime_int, comptime shift: comptime_int) void { + c[i] = t[i] >> shift; + if (i + 1 < 10) { + t[i + 1] += c[i]; + } + t[i] -= c[i] * (i32(1) << shift); + } + + fn toBytes(s: []u8, h: *const Fe) void { + std.debug.assert(s.len >= 32); + + var t: [10]i64 = undefined; + for (h.b[0..]) |_, i| { + t[i] = h.b[i]; + } + + var q = (19 * t[9] + ((i32(1) << 24))) >> 25; + { + var i: usize = 0; + while (i < 5) : (i += 1) { + q += t[2 * i]; + q >>= 26; + q += t[2 * i + 1]; + q >>= 25; + } + } + t[0] += 19 * q; + + var c: [10]i64 = undefined; + + var st = t[0..]; + var sc = c[0..]; + + toBytesRound(sc, st, 0, 26); + toBytesRound(sc, st, 1, 25); + toBytesRound(sc, st, 2, 26); + toBytesRound(sc, st, 3, 25); + toBytesRound(sc, st, 4, 26); + toBytesRound(sc, st, 5, 25); + toBytesRound(sc, st, 6, 26); + toBytesRound(sc, st, 7, 25); + toBytesRound(sc, st, 8, 26); + toBytesRound(sc, st, 9, 25); + + var ut: [10]u32 = undefined; + for (ut[0..]) |_, i| { + ut[i] = @bitCast(u32, @intCast(i32, t[i])); + } + + writeInt(s[0..], (ut[0] >> 0) | (ut[1] << 26), Endian.Little); + writeInt(s[4..], (ut[1] >> 6) | (ut[2] << 19), Endian.Little); + writeInt(s[8..], (ut[2] >> 13) | (ut[3] << 13), Endian.Little); + writeInt(s[12..], (ut[3] >> 19) | (ut[4] << 6), Endian.Little); + writeInt(s[16..], (ut[5] >> 0) | (ut[6] << 25), Endian.Little); + writeInt(s[20..], (ut[6] >> 7) | (ut[7] << 19), Endian.Little); + writeInt(s[24..], (ut[7] >> 13) | (ut[8] << 12), Endian.Little); + writeInt(s[28..], (ut[8] >> 20) | (ut[9] << 6), Endian.Little); + + std.mem.secureZero(i64, t[0..]); + } + + // Parity check. Returns 0 if even, 1 if odd + fn isNegative(f: *const Fe) bool { + var s: [32]u8 = undefined; + Fe.toBytes(s[0..], f); + const isneg = s[0] & 1; + s.secureZero(); + return isneg; + } + + fn isNonZero(f: *const Fe) bool { + var s: [32]u8 = undefined; + Fe.toBytes(s[0..], f); + const isnonzero = zerocmp(u8, s[0..]); + s.secureZero(); + return isneg; + } +}; + +test "x25519 rfc7748 vector1" { + const secret_key = "\xa5\x46\xe3\x6b\xf0\x52\x7c\x9d\x3b\x16\x15\x4b\x82\x46\x5e\xdd\x62\x14\x4c\x0a\xc1\xfc\x5a\x18\x50\x6a\x22\x44\xba\x44\x9a\xc4"; + const public_key = "\xe6\xdb\x68\x67\x58\x30\x30\xdb\x35\x94\xc1\xa4\x24\xb1\x5f\x7c\x72\x66\x24\xec\x26\xb3\x35\x3b\x10\xa9\x03\xa6\xd0\xab\x1c\x4c"; + + const expected_output = "\xc3\xda\x55\x37\x9d\xe9\xc6\x90\x8e\x94\xea\x4d\xf2\x8d\x08\x4f\x32\xec\xcf\x03\x49\x1c\x71\xf7\x54\xb4\x07\x55\x77\xa2\x85\x52"; + + var output: [32]u8 = undefined; + + std.debug.assert(X25519.create(output[0..], secret_key, public_key)); + std.debug.assert(std.mem.eql(u8, output, expected_output)); +} + +test "x25519 rfc7748 vector2" { + const secret_key = "\x4b\x66\xe9\xd4\xd1\xb4\x67\x3c\x5a\xd2\x26\x91\x95\x7d\x6a\xf5\xc1\x1b\x64\x21\xe0\xea\x01\xd4\x2c\xa4\x16\x9e\x79\x18\xba\x0d"; + const public_key = "\xe5\x21\x0f\x12\x78\x68\x11\xd3\xf4\xb7\x95\x9d\x05\x38\xae\x2c\x31\xdb\xe7\x10\x6f\xc0\x3c\x3e\xfc\x4c\xd5\x49\xc7\x15\xa4\x93"; + + const expected_output = "\x95\xcb\xde\x94\x76\xe8\x90\x7d\x7a\xad\xe4\x5c\xb4\xb8\x73\xf8\x8b\x59\x5a\x68\x79\x9f\xa1\x52\xe6\xf8\xf7\x64\x7a\xac\x79\x57"; + + var output: [32]u8 = undefined; + + std.debug.assert(X25519.create(output[0..], secret_key, public_key)); + std.debug.assert(std.mem.eql(u8, output, expected_output)); +} + +test "x25519 rfc7748 one iteration" { + const initial_value = "\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; + const expected_output = "\x42\x2c\x8e\x7a\x62\x27\xd7\xbc\xa1\x35\x0b\x3e\x2b\xb7\x27\x9f\x78\x97\xb8\x7b\xb6\x85\x4b\x78\x3c\x60\xe8\x03\x11\xae\x30\x79"; + + var k: [32]u8 = initial_value; + var u: [32]u8 = initial_value; + + var i: usize = 0; + while (i < 1) : (i += 1) { + var output: [32]u8 = undefined; + std.debug.assert(X25519.create(output[0..], k, u)); + + std.mem.copy(u8, u[0..], k[0..]); + std.mem.copy(u8, k[0..], output[0..]); + } + + std.debug.assert(std.mem.eql(u8, k[0..], expected_output)); +} + +test "x25519 rfc7748 1,000 iterations" { + // These iteration tests are slow so we always skip them. Results have been verified. + if (true) { + return error.SkipZigTest; + } + + const initial_value = "\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; + const expected_output = "\x68\x4c\xf5\x9b\xa8\x33\x09\x55\x28\x00\xef\x56\x6f\x2f\x4d\x3c\x1c\x38\x87\xc4\x93\x60\xe3\x87\x5f\x2e\xb9\x4d\x99\x53\x2c\x51"; + + var k: [32]u8 = initial_value; + var u: [32]u8 = initial_value; + + var i: usize = 0; + while (i < 1000) : (i += 1) { + var output: [32]u8 = undefined; + std.debug.assert(X25519.create(output[0..], k, u)); + + std.mem.copy(u8, u[0..], k[0..]); + std.mem.copy(u8, k[0..], output[0..]); + } + + std.debug.assert(std.mem.eql(u8, k[0..], expected_output)); +} + +test "x25519 rfc7748 1,000,000 iterations" { + if (true) { + return error.SkipZigTest; + } + + const initial_value = "\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; + const expected_output = "\x7c\x39\x11\xe0\xab\x25\x86\xfd\x86\x44\x97\x29\x7e\x57\x5e\x6f\x3b\xc6\x01\xc0\x88\x3c\x30\xdf\x5f\x4d\xd2\xd2\x4f\x66\x54\x24"; + + var k: [32]u8 = initial_value; + var u: [32]u8 = initial_value; + + var i: usize = 0; + while (i < 1000000) : (i += 1) { + var output: [32]u8 = undefined; + std.debug.assert(X25519.create(output[0..], k, u)); + + std.mem.copy(u8, u[0..], k[0..]); + std.mem.copy(u8, k[0..], output[0..]); + } + + std.debug.assert(std.mem.eql(u8, k[0..], expected_output)); +} diff --git a/std/debug/index.zig b/std/debug/index.zig index 39c41d4bc1..8db7c75d2c 100644 --- a/std/debug/index.zig +++ b/std/debug/index.zig @@ -4,8 +4,11 @@ const mem = std.mem; const io = std.io; const os = std.os; const elf = std.elf; -const macho = std.macho; const DW = std.dwarf; +const macho = std.macho; +const coff = std.coff; +const pdb = std.pdb; +const windows = os.windows; const ArrayList = std.ArrayList; const builtin = @import("builtin"); @@ -17,6 +20,17 @@ pub const runtime_safety = switch (builtin.mode) { builtin.Mode.ReleaseFast, builtin.Mode.ReleaseSmall => false, }; +const Module = struct { + mod_info: pdb.ModInfo, + module_name: []u8, + obj_file_name: []u8, + + populated: bool, + symbols: []u8, + subsect_info: []u8, + checksum_offset: ?usize, +}; + /// Tries to write to stderr, unbuffered, and ignores any error returned. /// Does not append a newline. var stderr_file: os.File = undefined; @@ -37,7 +51,7 @@ pub fn getStderrStream() !*io.OutStream(io.FileOutStream.Error) { return st; } else { stderr_file = try io.getStdErr(); - stderr_file_out_stream = io.FileOutStream.init(&stderr_file); + stderr_file_out_stream = io.FileOutStream.init(stderr_file); const st = &stderr_file_out_stream.stream; stderr_stream = st; return st; @@ -70,7 +84,7 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void { stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", @errorName(err)) catch return; return; }; - writeCurrentStackTrace(stderr, getDebugInfoAllocator(), debug_info, wantTtyColor(), start_addr) catch |err| { + writeCurrentStackTrace(stderr, debug_info, wantTtyColor(), start_addr) catch |err| { stderr.print("Unable to dump stack trace: {}\n", @errorName(err)) catch return; return; }; @@ -191,7 +205,11 @@ pub inline fn getReturnAddress(frame_count: usize) usize { return @intToPtr(*const usize, fp + @sizeOf(usize)).*; } -pub fn writeCurrentStackTrace(out_stream: var, allocator: *mem.Allocator, debug_info: *DebugInfo, tty_color: bool, start_addr: ?usize) !void { +pub fn writeCurrentStackTrace(out_stream: var, debug_info: *DebugInfo, tty_color: bool, start_addr: ?usize) !void { + switch (builtin.os) { + builtin.Os.windows => return writeCurrentStackTraceWindows(out_stream, debug_info, tty_color, start_addr), + else => {}, + } const AddressState = union(enum) { NotLookingForStartAddress, LookingForStartAddress: usize, @@ -224,18 +242,296 @@ pub fn writeCurrentStackTrace(out_stream: var, allocator: *mem.Allocator, debug_ } } +pub fn writeCurrentStackTraceWindows(out_stream: var, debug_info: *DebugInfo, + tty_color: bool, start_addr: ?usize) !void +{ + var addr_buf: [1024]usize = undefined; + const casted_len = @intCast(u32, addr_buf.len); // TODO shouldn't need this cast + const n = windows.RtlCaptureStackBackTrace(0, casted_len, @ptrCast(**c_void, &addr_buf), null); + const addrs = addr_buf[0..n]; + var start_i: usize = if (start_addr) |saddr| blk: { + for (addrs) |addr, i| { + if (addr == saddr) break :blk i; + } + return; + } else 0; + for (addrs[start_i..]) |addr| { + try printSourceAtAddress(debug_info, out_stream, addr, tty_color); + } +} + pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: var, address: usize, tty_color: bool) !void { switch (builtin.os) { builtin.Os.macosx => return printSourceAtAddressMacOs(debug_info, out_stream, address, tty_color), builtin.Os.linux => return printSourceAtAddressLinux(debug_info, out_stream, address, tty_color), - builtin.Os.windows => { - // TODO https://github.com/ziglang/zig/issues/721 - return error.UnsupportedOperatingSystem; - }, + builtin.Os.windows => return printSourceAtAddressWindows(debug_info, out_stream, address, tty_color), else => return error.UnsupportedOperatingSystem, } } +fn printSourceAtAddressWindows(di: *DebugInfo, out_stream: var, relocated_address: usize, tty_color: bool) !void { + const allocator = getDebugInfoAllocator(); + const base_address = os.getBaseAddress(); + const relative_address = relocated_address - base_address; + + var coff_section: *coff.Section = undefined; + const mod_index = for (di.sect_contribs) |sect_contrib| { + if (sect_contrib.Section >= di.coff.sections.len) continue; + coff_section = &di.coff.sections.toSlice()[sect_contrib.Section]; + + const vaddr_start = coff_section.header.virtual_address + sect_contrib.Offset; + const vaddr_end = vaddr_start + sect_contrib.Size; + if (relative_address >= vaddr_start and relative_address < vaddr_end) { + break sect_contrib.ModuleIndex; + } + } else { + // we have no information to add to the address + if (tty_color) { + try out_stream.print("???:?:?: "); + setTtyColor(TtyColor.Dim); + try out_stream.print("0x{x} in ??? (???)", relocated_address); + setTtyColor(TtyColor.Reset); + try out_stream.print("\n\n\n"); + } else { + try out_stream.print("???:?:?: 0x{x} in ??? (???)\n\n\n", relocated_address); + } + return; + }; + + const mod = &di.modules[mod_index]; + try populateModule(di, mod); + const obj_basename = os.path.basename(mod.obj_file_name); + + var symbol_i: usize = 0; + const symbol_name = while (symbol_i != mod.symbols.len) { + const prefix = @ptrCast(*pdb.RecordPrefix, &mod.symbols[symbol_i]); + if (prefix.RecordLen < 2) + return error.InvalidDebugInfo; + switch (prefix.RecordKind) { + pdb.SymbolKind.S_LPROC32 => { + const proc_sym = @ptrCast(*pdb.ProcSym, &mod.symbols[symbol_i + @sizeOf(pdb.RecordPrefix)]); + const vaddr_start = coff_section.header.virtual_address + proc_sym.CodeOffset; + const vaddr_end = vaddr_start + proc_sym.CodeSize; + if (relative_address >= vaddr_start and relative_address < vaddr_end) { + break mem.toSliceConst(u8, @ptrCast([*]u8, proc_sym) + @sizeOf(pdb.ProcSym)); + } + }, + else => {}, + } + symbol_i += prefix.RecordLen + @sizeOf(u16); + if (symbol_i > mod.symbols.len) + return error.InvalidDebugInfo; + } else "???"; + + const subsect_info = mod.subsect_info; + + var sect_offset: usize = 0; + var skip_len: usize = undefined; + const opt_line_info = subsections: { + const checksum_offset = mod.checksum_offset orelse break :subsections null; + while (sect_offset != subsect_info.len) : (sect_offset += skip_len) { + const subsect_hdr = @ptrCast(*pdb.DebugSubsectionHeader, &subsect_info[sect_offset]); + skip_len = subsect_hdr.Length; + sect_offset += @sizeOf(pdb.DebugSubsectionHeader); + + switch (subsect_hdr.Kind) { + pdb.DebugSubsectionKind.Lines => { + var line_index: usize = sect_offset; + + const line_hdr = @ptrCast(*pdb.LineFragmentHeader, &subsect_info[line_index]); + if (line_hdr.RelocSegment == 0) return error.MissingDebugInfo; + line_index += @sizeOf(pdb.LineFragmentHeader); + + const block_hdr = @ptrCast(*pdb.LineBlockFragmentHeader, &subsect_info[line_index]); + line_index += @sizeOf(pdb.LineBlockFragmentHeader); + + const has_column = line_hdr.Flags.LF_HaveColumns; + + const frag_vaddr_start = coff_section.header.virtual_address + line_hdr.RelocOffset; + const frag_vaddr_end = frag_vaddr_start + line_hdr.CodeSize; + if (relative_address >= frag_vaddr_start and relative_address < frag_vaddr_end) { + var line_i: usize = 0; + const start_line_index = line_index; + while (line_i < block_hdr.NumLines) : (line_i += 1) { + const line_num_entry = @ptrCast(*pdb.LineNumberEntry, &subsect_info[line_index]); + line_index += @sizeOf(pdb.LineNumberEntry); + const flags = @ptrCast(*pdb.LineNumberEntry.Flags, &line_num_entry.Flags); + const vaddr_start = frag_vaddr_start + line_num_entry.Offset; + const vaddr_end = if (flags.End == 0) frag_vaddr_end else vaddr_start + flags.End; + if (relative_address >= vaddr_start and relative_address < vaddr_end) { + const subsect_index = checksum_offset + block_hdr.NameIndex; + const chksum_hdr = @ptrCast(*pdb.FileChecksumEntryHeader, &mod.subsect_info[subsect_index]); + const strtab_offset = @sizeOf(pdb.PDBStringTableHeader) + chksum_hdr.FileNameOffset; + try di.pdb.string_table.seekTo(strtab_offset); + const source_file_name = try di.pdb.string_table.readNullTermString(allocator); + const line = flags.Start; + const column = if (has_column) blk: { + line_index = start_line_index + @sizeOf(pdb.LineNumberEntry) * block_hdr.NumLines; + line_index += @sizeOf(pdb.ColumnNumberEntry) * line_i; + const col_num_entry = @ptrCast(*pdb.ColumnNumberEntry, &subsect_info[line_index]); + break :blk col_num_entry.StartColumn; + } else 0; + break :subsections LineInfo{ + .allocator = allocator, + .file_name = source_file_name, + .line = line, + .column = column, + }; + } + } + break :subsections null; + } + }, + else => {}, + } + + if (sect_offset > subsect_info.len) + return error.InvalidDebugInfo; + } else { + break :subsections null; + } + }; + + if (tty_color) { + setTtyColor(TtyColor.White); + if (opt_line_info) |li| { + try out_stream.print("{}:{}:{}", li.file_name, li.line, li.column); + } else { + try out_stream.print("???:?:?"); + } + setTtyColor(TtyColor.Reset); + try out_stream.print(": "); + setTtyColor(TtyColor.Dim); + try out_stream.print("0x{x} in {} ({})", relocated_address, symbol_name, obj_basename); + setTtyColor(TtyColor.Reset); + + if (opt_line_info) |line_info| { + try out_stream.print("\n"); + if (printLineFromFile(out_stream, line_info)) { + if (line_info.column == 0) { + try out_stream.write("\n"); + } else { + { + var col_i: usize = 1; + while (col_i < line_info.column) : (col_i += 1) { + try out_stream.writeByte(' '); + } + } + setTtyColor(TtyColor.Green); + try out_stream.write("^"); + setTtyColor(TtyColor.Reset); + try out_stream.write("\n"); + } + } else |err| switch (err) { + error.EndOfFile => {}, + else => return err, + } + } else { + try out_stream.print("\n\n\n"); + } + } else { + if (opt_line_info) |li| { + try out_stream.print("{}:{}:{}: 0x{x} in {} ({})\n\n\n", li.file_name, li.line, li.column, relocated_address, symbol_name, obj_basename); + } else { + try out_stream.print("???:?:?: 0x{x} in {} ({})\n\n\n", relocated_address, symbol_name, obj_basename); + } + } +} + +const TtyColor = enum{ + Red, + Green, + Cyan, + White, + Dim, + Bold, + Reset, +}; + +/// TODO this is a special case hack right now. clean it up and maybe make it part of std.fmt +fn setTtyColor(tty_color: TtyColor) void { + const S = struct { + var attrs: windows.WORD = undefined; + var init_attrs = false; + }; + if (!S.init_attrs) { + S.init_attrs = true; + var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; + // TODO handle error + _ = windows.GetConsoleScreenBufferInfo(stderr_file.handle, &info); + S.attrs = info.wAttributes; + } + + // TODO handle errors + switch (tty_color) { + TtyColor.Red => { + _ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_RED|windows.FOREGROUND_INTENSITY); + }, + TtyColor.Green => { + _ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_GREEN|windows.FOREGROUND_INTENSITY); + }, + TtyColor.Cyan => { + _ = windows.SetConsoleTextAttribute(stderr_file.handle, + windows.FOREGROUND_GREEN|windows.FOREGROUND_BLUE|windows.FOREGROUND_INTENSITY); + }, + TtyColor.White, TtyColor.Bold => { + _ = windows.SetConsoleTextAttribute(stderr_file.handle, + windows.FOREGROUND_RED|windows.FOREGROUND_GREEN|windows.FOREGROUND_BLUE|windows.FOREGROUND_INTENSITY); + }, + TtyColor.Dim => { + _ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_INTENSITY); + }, + TtyColor.Reset => { + _ = windows.SetConsoleTextAttribute(stderr_file.handle, S.attrs); + }, + } +} + +fn populateModule(di: *DebugInfo, mod: *Module) !void { + if (mod.populated) + return; + const allocator = getDebugInfoAllocator(); + + if (mod.mod_info.C11ByteSize != 0) + return error.InvalidDebugInfo; + + if (mod.mod_info.C13ByteSize == 0) + return error.MissingDebugInfo; + + const modi = di.pdb.getStreamById(mod.mod_info.ModuleSymStream) orelse return error.MissingDebugInfo; + + const signature = try modi.stream.readIntLe(u32); + if (signature != 4) + return error.InvalidDebugInfo; + + mod.symbols = try allocator.alloc(u8, mod.mod_info.SymByteSize - 4); + try modi.stream.readNoEof(mod.symbols); + + mod.subsect_info = try allocator.alloc(u8, mod.mod_info.C13ByteSize); + try modi.stream.readNoEof(mod.subsect_info); + + var sect_offset: usize = 0; + var skip_len: usize = undefined; + while (sect_offset != mod.subsect_info.len) : (sect_offset += skip_len) { + const subsect_hdr = @ptrCast(*pdb.DebugSubsectionHeader, &mod.subsect_info[sect_offset]); + skip_len = subsect_hdr.Length; + sect_offset += @sizeOf(pdb.DebugSubsectionHeader); + + switch (subsect_hdr.Kind) { + pdb.DebugSubsectionKind.FileChecksums => { + mod.checksum_offset = sect_offset; + break; + }, + else => {}, + } + + if (sect_offset > mod.subsect_info.len) + return error.InvalidDebugInfo; + } + + mod.populated = true; +} + fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol { var min: usize = 0; var max: usize = symbols.len - 1; // Exclude sentinel. @@ -372,14 +668,185 @@ pub fn openSelfDebugInfo(allocator: *mem.Allocator) !DebugInfo { switch (builtin.os) { builtin.Os.linux => return openSelfDebugInfoLinux(allocator), builtin.Os.macosx, builtin.Os.ios => return openSelfDebugInfoMacOs(allocator), - builtin.Os.windows => { - // TODO: https://github.com/ziglang/zig/issues/721 - return error.UnsupportedOperatingSystem; - }, + builtin.Os.windows => return openSelfDebugInfoWindows(allocator), else => return error.UnsupportedOperatingSystem, } } +fn openSelfDebugInfoWindows(allocator: *mem.Allocator) !DebugInfo { + const self_file = try os.openSelfExe(); + defer self_file.close(); + + const coff_obj = try allocator.createOne(coff.Coff); + coff_obj.* = coff.Coff{ + .in_file = self_file, + .allocator = allocator, + .coff_header = undefined, + .pe_header = undefined, + .sections = undefined, + .guid = undefined, + .age = undefined, + }; + + var di = DebugInfo{ + .coff = coff_obj, + .pdb = undefined, + .sect_contribs = undefined, + .modules = undefined, + }; + + try di.coff.loadHeader(); + + var path_buf: [windows.MAX_PATH]u8 = undefined; + const len = try di.coff.getPdbPath(path_buf[0..]); + const raw_path = path_buf[0..len]; + + const path = try os.path.resolve(allocator, raw_path); + + try di.pdb.openFile(di.coff, path); + + var pdb_stream = di.pdb.getStream(pdb.StreamType.Pdb) orelse return error.InvalidDebugInfo; + const version = try pdb_stream.stream.readIntLe(u32); + const signature = try pdb_stream.stream.readIntLe(u32); + const age = try pdb_stream.stream.readIntLe(u32); + var guid: [16]u8 = undefined; + try pdb_stream.stream.readNoEof(guid[0..]); + if (!mem.eql(u8, di.coff.guid, guid) or di.coff.age != age) + return error.InvalidDebugInfo; + // We validated the executable and pdb match. + + const string_table_index = str_tab_index: { + const name_bytes_len = try pdb_stream.stream.readIntLe(u32); + const name_bytes = try allocator.alloc(u8, name_bytes_len); + try pdb_stream.stream.readNoEof(name_bytes); + + const HashTableHeader = packed struct { + Size: u32, + Capacity: u32, + + fn maxLoad(cap: u32) u32 { + return cap * 2 / 3 + 1; + } + }; + var hash_tbl_hdr: HashTableHeader = undefined; + try pdb_stream.stream.readStruct(HashTableHeader, &hash_tbl_hdr); + if (hash_tbl_hdr.Capacity == 0) + return error.InvalidDebugInfo; + + if (hash_tbl_hdr.Size > HashTableHeader.maxLoad(hash_tbl_hdr.Capacity)) + return error.InvalidDebugInfo; + + const present = try readSparseBitVector(&pdb_stream.stream, allocator); + if (present.len != hash_tbl_hdr.Size) + return error.InvalidDebugInfo; + const deleted = try readSparseBitVector(&pdb_stream.stream, allocator); + + const Bucket = struct { + first: u32, + second: u32, + }; + const bucket_list = try allocator.alloc(Bucket, present.len); + for (present) |_| { + const name_offset = try pdb_stream.stream.readIntLe(u32); + const name_index = try pdb_stream.stream.readIntLe(u32); + const name = mem.toSlice(u8, name_bytes.ptr + name_offset); + if (mem.eql(u8, name, "/names")) { + break :str_tab_index name_index; + } + } + return error.MissingDebugInfo; + }; + + di.pdb.string_table = di.pdb.getStreamById(string_table_index) orelse return error.InvalidDebugInfo; + di.pdb.dbi = di.pdb.getStream(pdb.StreamType.Dbi) orelse return error.MissingDebugInfo; + + const dbi = di.pdb.dbi; + + // Dbi Header + var dbi_stream_header: pdb.DbiStreamHeader = undefined; + try dbi.stream.readStruct(pdb.DbiStreamHeader, &dbi_stream_header); + const mod_info_size = dbi_stream_header.ModInfoSize; + const section_contrib_size = dbi_stream_header.SectionContributionSize; + + var modules = ArrayList(Module).init(allocator); + + // Module Info Substream + var mod_info_offset: usize = 0; + while (mod_info_offset != mod_info_size) { + var mod_info: pdb.ModInfo = undefined; + try dbi.stream.readStruct(pdb.ModInfo, &mod_info); + var this_record_len: usize = @sizeOf(pdb.ModInfo); + + const module_name = try dbi.readNullTermString(allocator); + this_record_len += module_name.len + 1; + + const obj_file_name = try dbi.readNullTermString(allocator); + this_record_len += obj_file_name.len + 1; + + const march_forward_bytes = this_record_len % 4; + if (march_forward_bytes != 0) { + try dbi.seekForward(march_forward_bytes); + this_record_len += march_forward_bytes; + } + + try modules.append(Module{ + .mod_info = mod_info, + .module_name = module_name, + .obj_file_name = obj_file_name, + + .populated = false, + .symbols = undefined, + .subsect_info = undefined, + .checksum_offset = null, + }); + + mod_info_offset += this_record_len; + if (mod_info_offset > mod_info_size) + return error.InvalidDebugInfo; + } + + di.modules = modules.toOwnedSlice(); + + // Section Contribution Substream + var sect_contribs = ArrayList(pdb.SectionContribEntry).init(allocator); + var sect_cont_offset: usize = 0; + if (section_contrib_size != 0) { + const ver = @intToEnum(pdb.SectionContrSubstreamVersion, try dbi.stream.readIntLe(u32)); + if (ver != pdb.SectionContrSubstreamVersion.Ver60) + return error.InvalidDebugInfo; + sect_cont_offset += @sizeOf(u32); + } + while (sect_cont_offset != section_contrib_size) { + const entry = try sect_contribs.addOne(); + try dbi.stream.readStruct(pdb.SectionContribEntry, entry); + sect_cont_offset += @sizeOf(pdb.SectionContribEntry); + + if (sect_cont_offset > section_contrib_size) + return error.InvalidDebugInfo; + } + + di.sect_contribs = sect_contribs.toOwnedSlice(); + + return di; +} + +fn readSparseBitVector(stream: var, allocator: *mem.Allocator) ![]usize { + const num_words = try stream.readIntLe(u32); + var word_i: usize = 0; + var list = ArrayList(usize).init(allocator); + while (word_i != num_words) : (word_i += 1) { + const word = try stream.readIntLe(u32); + var bit_i: u5 = 0; + while (true) : (bit_i += 1) { + if (word & (u32(1) << bit_i) != 0) { + try list.append(word_i * 32 + bit_i); + } + if (bit_i == @maxValue(u5)) break; + } + } + return list.toOwnedSlice(); +} + fn openSelfDebugInfoLinux(allocator: *mem.Allocator) !DebugInfo { var di = DebugInfo{ .self_exe_file = undefined, @@ -395,7 +862,7 @@ fn openSelfDebugInfoLinux(allocator: *mem.Allocator) !DebugInfo { di.self_exe_file = try os.openSelfExe(); errdefer di.self_exe_file.close(); - try di.elf.openFile(allocator, &di.self_exe_file); + try di.elf.openFile(allocator, di.self_exe_file); errdefer di.elf.close(); di.debug_info = (try di.elf.findSection(".debug_info")) orelse return error.MissingDebugInfo; @@ -578,7 +1045,13 @@ pub const DebugInfo = switch (builtin.os) { return self.ofiles.allocator; } }, - else => struct { + builtin.Os.windows => struct { + pdb: pdb.Pdb, + coff: *coff.Coff, + sect_contribs: []pdb.SectionContribEntry, + modules: []Module, + }, + builtin.Os.linux => struct { self_exe_file: os.File, elf: elf.Elf, debug_info: *elf.SectionHeader, @@ -594,7 +1067,7 @@ pub const DebugInfo = switch (builtin.os) { } pub fn readString(self: *DebugInfo) ![]u8 { - var in_file_stream = io.FileInStream.init(&self.self_exe_file); + var in_file_stream = io.FileInStream.init(self.self_exe_file); const in_stream = &in_file_stream.stream; return readStringRaw(self.allocator(), in_stream); } @@ -604,6 +1077,7 @@ pub const DebugInfo = switch (builtin.os) { self.elf.close(); } }, + else => @compileError("Unsupported OS"), }; const PcRange = struct { @@ -929,7 +1403,7 @@ fn parseFormValue(allocator: *mem.Allocator, in_stream: var, form_id: u64, is_64 } fn parseAbbrevTable(st: *DebugInfo) !AbbrevTable { - const in_file = &st.self_exe_file; + const in_file = st.self_exe_file; var in_file_stream = io.FileInStream.init(in_file); const in_stream = &in_file_stream.stream; var result = AbbrevTable.init(st.allocator()); @@ -980,7 +1454,7 @@ fn getAbbrevTableEntry(abbrev_table: *const AbbrevTable, abbrev_code: u64) ?*con } fn parseDie(st: *DebugInfo, abbrev_table: *const AbbrevTable, is_64: bool) !Die { - const in_file = &st.self_exe_file; + const in_file = st.self_exe_file; var in_file_stream = io.FileInStream.init(in_file); const in_stream = &in_file_stream.stream; const abbrev_code = try readULeb128(in_stream); @@ -1202,7 +1676,7 @@ fn getLineNumberInfoMacOs(di: *DebugInfo, symbol: MachoSymbol, target_address: u fn getLineNumberInfoLinux(di: *DebugInfo, compile_unit: *const CompileUnit, target_address: usize) !LineInfo { const compile_unit_cwd = try compile_unit.die.getAttrString(di, DW.AT_comp_dir); - const in_file = &di.self_exe_file; + const in_file = di.self_exe_file; const debug_line_end = di.debug_line.offset + di.debug_line.size; var this_offset = di.debug_line.offset; var this_index: usize = 0; @@ -1382,7 +1856,7 @@ fn scanAllCompileUnits(st: *DebugInfo) !void { var this_unit_offset = st.debug_info.offset; var cu_index: usize = 0; - var in_file_stream = io.FileInStream.init(&st.self_exe_file); + var in_file_stream = io.FileInStream.init(st.self_exe_file); const in_stream = &in_file_stream.stream; while (this_unit_offset < debug_info_end) { @@ -1448,7 +1922,7 @@ fn scanAllCompileUnits(st: *DebugInfo) !void { } fn findCompileUnit(st: *DebugInfo, target_address: u64) !*const CompileUnit { - var in_file_stream = io.FileInStream.init(&st.self_exe_file); + var in_file_stream = io.FileInStream.init(st.self_exe_file); const in_stream = &in_file_stream.stream; for (st.compile_unit_list.toSlice()) |*compile_unit| { if (compile_unit.pc_range) |range| { diff --git a/std/elf.zig b/std/elf.zig index 3d81555319..a3a72dc728 100644 --- a/std/elf.zig +++ b/std/elf.zig @@ -353,7 +353,7 @@ pub const SectionHeader = struct { }; pub const Elf = struct { - in_file: *os.File, + in_file: os.File, auto_close_stream: bool, is_64: bool, endian: builtin.Endian, @@ -376,7 +376,7 @@ pub const Elf = struct { } /// Call close when done. - pub fn openFile(elf: *Elf, allocator: *mem.Allocator, file: *os.File) !void { + pub fn openFile(elf: *Elf, allocator: *mem.Allocator, file: os.File) !void { elf.allocator = allocator; elf.in_file = file; elf.auto_close_stream = false; diff --git a/std/event/lock.zig b/std/event/lock.zig index 2ee9dc981f..46e0d13468 100644 --- a/std/event/lock.zig +++ b/std/event/lock.zig @@ -7,7 +7,7 @@ const AtomicOrder = builtin.AtomicOrder; const Loop = std.event.Loop; /// Thread-safe async/await lock. -/// Does not make any syscalls - coroutines which are waiting for the lock are suspended, and +/// coroutines which are waiting for the lock are suspended, and /// are resumed when the lock is released, in order. /// Allows only one actor to hold the lock. pub const Lock = struct { diff --git a/std/event/locked.zig b/std/event/locked.zig index e7ad544d78..7df56c4571 100644 --- a/std/event/locked.zig +++ b/std/event/locked.zig @@ -3,7 +3,7 @@ const Lock = std.event.Lock; const Loop = std.event.Loop; /// Thread-safe async/await lock that protects one piece of data. -/// Does not make any syscalls - coroutines which are waiting for the lock are suspended, and +/// coroutines which are waiting for the lock are suspended, and /// are resumed when the lock is released, in order. pub fn Locked(comptime T: type) type { return struct { diff --git a/std/event/rwlock.zig b/std/event/rwlock.zig index 186c81eb76..03f2ac702b 100644 --- a/std/event/rwlock.zig +++ b/std/event/rwlock.zig @@ -7,7 +7,7 @@ const AtomicOrder = builtin.AtomicOrder; const Loop = std.event.Loop; /// Thread-safe async/await lock. -/// Does not make any syscalls - coroutines which are waiting for the lock are suspended, and +/// coroutines which are waiting for the lock are suspended, and /// are resumed when the lock is released, in order. /// Many readers can hold the lock at the same time; however locking for writing is exclusive. /// When a read lock is held, it will not be released until the reader queue is empty. diff --git a/std/event/rwlocked.zig b/std/event/rwlocked.zig index ef7e83d20c..1a6e77c27a 100644 --- a/std/event/rwlocked.zig +++ b/std/event/rwlocked.zig @@ -3,7 +3,7 @@ const RwLock = std.event.RwLock; const Loop = std.event.Loop; /// Thread-safe async/await RW lock that protects one piece of data. -/// Does not make any syscalls - coroutines which are waiting for the lock are suspended, and +/// coroutines which are waiting for the lock are suspended, and /// are resumed when the lock is released, in order. pub fn RwLocked(comptime T: type) type { return struct { diff --git a/std/event/tcp.zig b/std/event/tcp.zig index 19cce4a5e5..491acab39d 100644 --- a/std/event/tcp.zig +++ b/std/event/tcp.zig @@ -89,7 +89,7 @@ pub const Server = struct { error.ProcessFdQuotaExceeded => { errdefer std.os.emfile_promise_queue.remove(&self.waiting_for_emfile_node); suspend { - self.waiting_for_emfile_node = PromiseNode.init( @handle() ); + self.waiting_for_emfile_node = PromiseNode.init(@handle()); std.os.emfile_promise_queue.append(&self.waiting_for_emfile_node); } continue; @@ -145,11 +145,11 @@ test "listen on a port, send bytes, receive bytes" { cancel @handle(); } } - async fn errorableHandler(self: *Self, _addr: *const std.net.Address, _socket: *const std.os.File) !void { + async fn errorableHandler(self: *Self, _addr: *const std.net.Address, _socket: std.os.File) !void { const addr = _addr.*; // TODO https://github.com/ziglang/zig/issues/733 - var socket = _socket.*; // TODO https://github.com/ziglang/zig/issues/733 + var socket = _socket; // TODO https://github.com/ziglang/zig/issues/733 - var adapter = std.io.FileOutStream.init(&socket); + var adapter = std.io.FileOutStream.init(socket); var stream = &adapter.stream; try stream.print("hello from server\n"); } diff --git a/std/fmt/index.zig b/std/fmt/index.zig index 82e9a5ba39..80af750f3d 100644 --- a/std/fmt/index.zig +++ b/std/fmt/index.zig @@ -163,26 +163,47 @@ pub fn formatType( } break :cf false; }; - if (has_cust_fmt) return value.format(fmt, context, Errors, output); + try output(context, @typeName(T)); - if (comptime @typeId(T) == builtin.TypeId.Enum) { - try output(context, "."); - try formatType(@tagName(value), "", context, Errors, output); - return; + switch (comptime @typeId(T)) { + builtin.TypeId.Enum => { + try output(context, "."); + try formatType(@tagName(value), "", context, Errors, output); + return; + }, + builtin.TypeId.Struct => { + comptime var field_i = 0; + inline while (field_i < @memberCount(T)) : (field_i += 1) { + if (field_i == 0) { + try output(context, "{ ."); + } else { + try output(context, ", ."); + } + try output(context, @memberName(T, field_i)); + try output(context, " = "); + try formatType(@field(value, @memberName(T, field_i)), "", context, Errors, output); + } + try output(context, " }"); + }, + builtin.TypeId.Union => { + const info = @typeInfo(T).Union; + if (info.tag_type) |UnionTagType| { + try output(context, "{ ."); + try output(context, @tagName(UnionTagType(value))); + try output(context, " = "); + inline for (info.fields) |u_field| { + if (@enumToInt(UnionTagType(value)) == u_field.enum_field.?.value) { + try formatType(@field(value, u_field.name), "", context, Errors, output); + } + } + try output(context, " }"); + } else { + try format(context, Errors, output, "@{x}", @ptrToInt(&value)); + } + }, + else => unreachable, } - comptime var field_i = 0; - inline while (field_i < @memberCount(T)) : (field_i += 1) { - if (field_i == 0) { - try output(context, "{ ."); - } else { - try output(context, ", ."); - } - try output(context, @memberName(T, field_i)); - try output(context, " = "); - try formatType(@field(value, @memberName(T, field_i)), "", context, Errors, output); - } - try output(context, " }"); return; }, builtin.TypeId.Pointer => |ptr_info| switch (ptr_info.size) { @@ -329,6 +350,11 @@ pub fn formatText( comptime var width = 0; if (fmt.len > 1) width = comptime (parseUnsigned(usize, fmt[1..], 10) catch unreachable); return formatBuf(bytes, width, context, Errors, output); + } else if ((fmt[0] == 'x') or (fmt[0] == 'X')) { + for (bytes) |c| { + try formatInt(c, 16, fmt[0] == 'X', 2, context, Errors, output); + } + return; } else @compileError("Unknown format character: " ++ []u8{fmt[0]}); } return output(context, bytes); @@ -1194,6 +1220,70 @@ test "fmt.format" { try testFmt("point: (10.200,2.220)\n", "point: {}\n", value); try testFmt("dim: 10.200x2.220\n", "dim: {d}\n", value); } + //struct format + { + const S = struct { + a: u32, + b: error, + }; + + const inst = S{ + .a = 456, + .b = error.Unused, + }; + + try testFmt("S{ .a = 456, .b = error.Unused }", "{}", inst); + } + //union format + { + const TU = union(enum) { + float: f32, + int: u32, + }; + + const UU = union { + float: f32, + int: u32, + }; + + const EU = extern union { + float: f32, + int: u32, + }; + + const tu_inst = TU{ .int = 123 }; + const uu_inst = UU{ .int = 456 }; + const eu_inst = EU{ .float = 321.123 }; + + try testFmt("TU{ .int = 123 }", "{}", tu_inst); + + var buf: [100]u8 = undefined; + const uu_result = try bufPrint(buf[0..], "{}", uu_inst); + debug.assert(mem.eql(u8, uu_result[0..3], "UU@")); + + const eu_result = try bufPrint(buf[0..], "{}", eu_inst); + debug.assert(mem.eql(u8, uu_result[0..3], "EU@")); + } + //enum format + { + const E = enum { + One, + Two, + Three, + }; + + const inst = E.Two; + + try testFmt("E.Two", "{}", inst); + } + //print bytes as hex + { + const some_bytes = "\xCA\xFE\xBA\xBE"; + try testFmt("lowercase: cafebabe\n", "lowercase: {x}\n", some_bytes); + try testFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", some_bytes); + const bytes_with_zeros = "\x00\x0E\xBA\xBE"; + try testFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", bytes_with_zeros); + } } fn testFmt(expected: []const u8, comptime template: []const u8, args: ...) !void { @@ -1241,3 +1331,22 @@ pub fn isWhiteSpace(byte: u8) bool { else => false, }; } + +pub fn hexToBytes(out: []u8, input: []const u8) !void { + if (out.len * 2 < input.len) + return error.InvalidLength; + + var in_i: usize = 0; + while (in_i != input.len) : (in_i += 2) { + const hi = try charToDigit(input[in_i], 16); + const lo = try charToDigit(input[in_i + 1], 16); + out[in_i / 2] = (hi << 4) | lo; + } +} + +test "fmt.hexToBytes" { + const test_hex_str = "909A312BB12ED1F819B3521AC4C1E896F2160507FFC1C8381E3B07BB16BD1706"; + var pb: [32]u8 = undefined; + try hexToBytes(pb[0..], test_hex_str); + try testFmt(test_hex_str, "{X}", pb); +} diff --git a/std/index.zig b/std/index.zig index 8dfc59b1d2..cf61ff53b5 100644 --- a/std/index.zig +++ b/std/index.zig @@ -15,6 +15,7 @@ pub const atomic = @import("atomic/index.zig"); pub const base64 = @import("base64.zig"); pub const build = @import("build.zig"); pub const c = @import("c/index.zig"); +pub const coff = @import("coff.zig"); pub const crypto = @import("crypto/index.zig"); pub const cstr = @import("cstr.zig"); pub const debug = @import("debug/index.zig"); @@ -33,6 +34,7 @@ pub const math = @import("math/index.zig"); pub const mem = @import("mem.zig"); pub const net = @import("net.zig"); pub const os = @import("os/index.zig"); +pub const pdb = @import("pdb.zig"); pub const rand = @import("rand/index.zig"); pub const rb = @import("rb.zig"); pub const sort = @import("sort.zig"); @@ -56,6 +58,7 @@ test "std" { _ = @import("base64.zig"); _ = @import("build.zig"); _ = @import("c/index.zig"); + _ = @import("coff.zig"); _ = @import("crypto/index.zig"); _ = @import("cstr.zig"); _ = @import("debug/index.zig"); @@ -74,6 +77,7 @@ test "std" { _ = @import("heap.zig"); _ = @import("os/index.zig"); _ = @import("rand/index.zig"); + _ = @import("pdb.zig"); _ = @import("sort.zig"); _ = @import("unicode.zig"); _ = @import("zig/index.zig"); diff --git a/std/io.zig b/std/io.zig index 369f6eede3..2b31bc0548 100644 --- a/std/io.zig +++ b/std/io.zig @@ -34,13 +34,13 @@ pub fn getStdIn() GetStdIoErrs!File { /// Implementation of InStream trait for File pub const FileInStream = struct { - file: *File, + file: File, stream: Stream, pub const Error = @typeOf(File.read).ReturnType.ErrorSet; pub const Stream = InStream(Error); - pub fn init(file: *File) FileInStream { + pub fn init(file: File) FileInStream { return FileInStream{ .file = file, .stream = Stream{ .readFn = readFn }, @@ -55,13 +55,13 @@ pub const FileInStream = struct { /// Implementation of OutStream trait for File pub const FileOutStream = struct { - file: *File, + file: File, stream: Stream, pub const Error = File.WriteError; pub const Stream = OutStream(Error); - pub fn init(file: *File) FileOutStream { + pub fn init(file: File) FileOutStream { return FileOutStream{ .file = file, .stream = Stream{ .writeFn = writeFn }, @@ -210,7 +210,7 @@ pub fn InStream(comptime ReadError: type) type { pub fn readStruct(self: *Self, comptime T: type, ptr: *T) !void { // Only extern and packed structs have defined in-memory layout. - assert(@typeInfo(T).Struct.layout != builtin.TypeInfo.ContainerLayout.Auto); + comptime assert(@typeInfo(T).Struct.layout != builtin.TypeInfo.ContainerLayout.Auto); return self.readNoEof(@sliceToBytes((*[1]T)(ptr)[0..])); } }; @@ -280,7 +280,7 @@ pub fn readFileAllocAligned(allocator: *mem.Allocator, path: []const u8, comptim const buf = try allocator.alignedAlloc(u8, A, size); errdefer allocator.free(buf); - var adapter = FileInStream.init(&file); + var adapter = FileInStream.init(file); try adapter.stream.readNoEof(buf[0..size]); return buf; } @@ -592,7 +592,7 @@ pub const BufferedAtomicFile = struct { self.atomic_file = try os.AtomicFile.init(allocator, dest_path, os.File.default_mode); errdefer self.atomic_file.deinit(); - self.file_stream = FileOutStream.init(&self.atomic_file.file); + self.file_stream = FileOutStream.init(self.atomic_file.file); self.buffered_stream = BufferedOutStream(FileOutStream.Error).init(&self.file_stream.stream); return self; } @@ -622,7 +622,7 @@ test "import io tests" { pub fn readLine(buf: []u8) !usize { var stdin = getStdIn() catch return error.StdInUnavailable; - var adapter = FileInStream.init(&stdin); + var adapter = FileInStream.init(stdin); var stream = &adapter.stream; var index: usize = 0; while (true) { diff --git a/std/io_test.zig b/std/io_test.zig index 7a44032673..7403c96994 100644 --- a/std/io_test.zig +++ b/std/io_test.zig @@ -19,7 +19,7 @@ test "write a file, read it, then delete it" { var file = try os.File.openWrite(tmp_file_name); defer file.close(); - var file_out_stream = io.FileOutStream.init(&file); + var file_out_stream = io.FileOutStream.init(file); var buf_stream = io.BufferedOutStream(io.FileOutStream.Error).init(&file_out_stream.stream); const st = &buf_stream.stream; try st.print("begin"); @@ -35,7 +35,7 @@ test "write a file, read it, then delete it" { const expected_file_size = "begin".len + data.len + "end".len; assert(file_size == expected_file_size); - var file_in_stream = io.FileInStream.init(&file); + var file_in_stream = io.FileInStream.init(file); var buf_stream = io.BufferedInStream(io.FileInStream.Error).init(&file_in_stream.stream); const st = &buf_stream.stream; const contents = try st.readAllAlloc(allocator, 2 * 1024); diff --git a/std/macho.zig b/std/macho.zig index 4325810b03..e1bbd755c6 100644 --- a/std/macho.zig +++ b/std/macho.zig @@ -1,4 +1,3 @@ - pub const mach_header = extern struct { magic: u32, cputype: cpu_type_t, @@ -25,26 +24,43 @@ pub const load_command = extern struct { cmdsize: u32, }; - /// The symtab_command contains the offsets and sizes of the link-edit 4.3BSD /// "stab" style symbol table information as described in the header files /// and . pub const symtab_command = extern struct { - cmd: u32, /// LC_SYMTAB - cmdsize: u32, /// sizeof(struct symtab_command) - symoff: u32, /// symbol table offset - nsyms: u32, /// number of symbol table entries - stroff: u32, /// string table offset - strsize: u32, /// string table size in bytes + /// LC_SYMTAB + cmd: u32, + + /// sizeof(struct symtab_command) + cmdsize: u32, + + /// symbol table offset + symoff: u32, + + /// number of symbol table entries + nsyms: u32, + + /// string table offset + stroff: u32, + + /// string table size in bytes + strsize: u32, }; /// The linkedit_data_command contains the offsets and sizes of a blob -/// of data in the __LINKEDIT segment. +/// of data in the __LINKEDIT segment. const linkedit_data_command = extern struct { - cmd: u32,/// LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE, LC_DYLIB_CODE_SIGN_DRS or LC_LINKER_OPTIMIZATION_HINT. - cmdsize: u32, /// sizeof(struct linkedit_data_command) - dataoff: u32 , /// file offset of data in __LINKEDIT segment - datasize: u32 , /// file size of data in __LINKEDIT segment + /// LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE, LC_DYLIB_CODE_SIGN_DRS or LC_LINKER_OPTIMIZATION_HINT. + cmd: u32, + + /// sizeof(struct linkedit_data_command) + cmdsize: u32, + + /// file offset of data in __LINKEDIT segment + dataoff: u32, + + /// file size of data in __LINKEDIT segment + datasize: u32, }; /// The segment load command indicates that a part of this file is to be @@ -58,16 +74,35 @@ const linkedit_data_command = extern struct { /// section structures directly follow the segment command and their size is /// reflected in cmdsize. pub const segment_command = extern struct { - cmd: u32,/// LC_SEGMENT - cmdsize: u32,/// includes sizeof section structs - segname: [16]u8,/// segment name - vmaddr: u32,/// memory address of this segment - vmsize: u32,/// memory size of this segment - fileoff: u32,/// file offset of this segment - filesize: u32,/// amount to map from the file - maxprot: vm_prot_t,/// maximum VM protection - initprot: vm_prot_t,/// initial VM protection - nsects: u32,/// number of sections in segment + /// LC_SEGMENT + cmd: u32, + + /// includes sizeof section structs + cmdsize: u32, + + /// segment name + segname: [16]u8, + + /// memory address of this segment + vmaddr: u32, + + /// memory size of this segment + vmsize: u32, + + /// file offset of this segment + fileoff: u32, + + /// amount to map from the file + filesize: u32, + + /// maximum VM protection + maxprot: vm_prot_t, + + /// initial VM protection + initprot: vm_prot_t, + + /// number of sections in segment + nsects: u32, flags: u32, }; @@ -76,17 +111,36 @@ pub const segment_command = extern struct { /// sections then section_64 structures directly follow the 64-bit segment /// command and their size is reflected in cmdsize. pub const segment_command_64 = extern struct { - cmd: u32, /// LC_SEGMENT_64 - cmdsize: u32, /// includes sizeof section_64 structs - segname: [16]u8, /// segment name - vmaddr: u64, /// memory address of this segment - vmsize: u64, /// memory size of this segment - fileoff: u64, /// file offset of this segment - filesize: u64, /// amount to map from the file - maxprot: vm_prot_t, /// maximum VM protection - initprot: vm_prot_t, /// initial VM protection - nsects: u32, /// number of sections in segment - flags: u32, + /// LC_SEGMENT_64 + cmd: u32, + + /// includes sizeof section_64 structs + cmdsize: u32, + + /// segment name + segname: [16]u8, + + /// memory address of this segment + vmaddr: u64, + + /// memory size of this segment + vmsize: u64, + + /// file offset of this segment + fileoff: u64, + + /// amount to map from the file + filesize: u64, + + /// maximum VM protection + maxprot: vm_prot_t, + + /// initial VM protection + initprot: vm_prot_t, + + /// number of sections in segment + nsects: u32, + flags: u32, }; /// A segment is made up of zero or more sections. Non-MH_OBJECT files have @@ -115,32 +169,76 @@ pub const segment_command_64 = extern struct { /// fields of the section structure for mach object files is described in the /// header file . pub const @"section" = extern struct { - sectname: [16]u8, /// name of this section - segname: [16]u8, /// segment this section goes in - addr: u32, /// memory address of this section - size: u32, /// size in bytes of this section - offset: u32, /// file offset of this section - @"align": u32, /// section alignment (power of 2) - reloff: u32, /// file offset of relocation entries - nreloc: u32, /// number of relocation entries - flags: u32, /// flags (section type and attributes - reserved1: u32, /// reserved (for offset or index) - reserved2: u32, /// reserved (for count or sizeof) + /// name of this section + sectname: [16]u8, + + /// segment this section goes in + segname: [16]u8, + + /// memory address of this section + addr: u32, + + /// size in bytes of this section + size: u32, + + /// file offset of this section + offset: u32, + + /// section alignment (power of 2) + @"align": u32, + + /// file offset of relocation entries + reloff: u32, + + /// number of relocation entries + nreloc: u32, + + /// flags (section type and attributes + flags: u32, + + /// reserved (for offset or index) + reserved1: u32, + + /// reserved (for count or sizeof) + reserved2: u32, }; pub const section_64 = extern struct { - sectname: [16]u8, /// name of this section - segname: [16]u8, /// segment this section goes in - addr: u64, /// memory address of this section - size: u64, /// size in bytes of this section - offset: u32, /// file offset of this section - @"align": u32, /// section alignment (power of 2) - reloff: u32, /// file offset of relocation entries - nreloc: u32, /// number of relocation entries - flags: u32, /// flags (section type and attributes - reserved1: u32, /// reserved (for offset or index) - reserved2: u32, /// reserved (for count or sizeof) - reserved3: u32, /// reserved + /// name of this section + sectname: [16]u8, + + /// segment this section goes in + segname: [16]u8, + + /// memory address of this section + addr: u64, + + /// size in bytes of this section + size: u64, + + /// file offset of this section + offset: u32, + + /// section alignment (power of 2) + @"align": u32, + + /// file offset of relocation entries + reloff: u32, + + /// number of relocation entries + nreloc: u32, + + /// flags (section type and attributes + flags: u32, + + /// reserved (for offset or index) + reserved1: u32, + + /// reserved (for count or sizeof) + reserved2: u32, + + /// reserved + reserved3: u32, }; pub const nlist = extern struct { @@ -168,116 +266,287 @@ pub const nlist_64 = extern struct { /// simply be ignored. pub const LC_REQ_DYLD = 0x80000000; -pub const LC_SEGMENT = 0x1; /// segment of this file to be mapped -pub const LC_SYMTAB = 0x2; /// link-edit stab symbol table info -pub const LC_SYMSEG = 0x3; /// link-edit gdb symbol table info (obsolete) -pub const LC_THREAD = 0x4; /// thread -pub const LC_UNIXTHREAD = 0x5; /// unix thread (includes a stack) -pub const LC_LOADFVMLIB = 0x6; /// load a specified fixed VM shared library -pub const LC_IDFVMLIB = 0x7; /// fixed VM shared library identification -pub const LC_IDENT = 0x8; /// object identification info (obsolete) -pub const LC_FVMFILE = 0x9; /// fixed VM file inclusion (internal use) -pub const LC_PREPAGE = 0xa; /// prepage command (internal use) -pub const LC_DYSYMTAB = 0xb; /// dynamic link-edit symbol table info -pub const LC_LOAD_DYLIB = 0xc; /// load a dynamically linked shared library -pub const LC_ID_DYLIB = 0xd; /// dynamically linked shared lib ident -pub const LC_LOAD_DYLINKER = 0xe; /// load a dynamic linker -pub const LC_ID_DYLINKER = 0xf; /// dynamic linker identification -pub const LC_PREBOUND_DYLIB = 0x10; /// modules prebound for a dynamically -pub const LC_ROUTINES = 0x11; /// image routines -pub const LC_SUB_FRAMEWORK = 0x12; /// sub framework -pub const LC_SUB_UMBRELLA = 0x13; /// sub umbrella -pub const LC_SUB_CLIENT = 0x14; /// sub client -pub const LC_SUB_LIBRARY = 0x15; /// sub library -pub const LC_TWOLEVEL_HINTS = 0x16; /// two-level namespace lookup hints -pub const LC_PREBIND_CKSUM = 0x17; /// prebind checksum +/// segment of this file to be mapped +pub const LC_SEGMENT = 0x1; + +/// link-edit stab symbol table info +pub const LC_SYMTAB = 0x2; + +/// link-edit gdb symbol table info (obsolete) +pub const LC_SYMSEG = 0x3; + +/// thread +pub const LC_THREAD = 0x4; + +/// unix thread (includes a stack) +pub const LC_UNIXTHREAD = 0x5; + +/// load a specified fixed VM shared library +pub const LC_LOADFVMLIB = 0x6; + +/// fixed VM shared library identification +pub const LC_IDFVMLIB = 0x7; + +/// object identification info (obsolete) +pub const LC_IDENT = 0x8; + +/// fixed VM file inclusion (internal use) +pub const LC_FVMFILE = 0x9; + +/// prepage command (internal use) +pub const LC_PREPAGE = 0xa; + +/// dynamic link-edit symbol table info +pub const LC_DYSYMTAB = 0xb; + +/// load a dynamically linked shared library +pub const LC_LOAD_DYLIB = 0xc; + +/// dynamically linked shared lib ident +pub const LC_ID_DYLIB = 0xd; + +/// load a dynamic linker +pub const LC_LOAD_DYLINKER = 0xe; + +/// dynamic linker identification +pub const LC_ID_DYLINKER = 0xf; + +/// modules prebound for a dynamically +pub const LC_PREBOUND_DYLIB = 0x10; + +/// image routines +pub const LC_ROUTINES = 0x11; + +/// sub framework +pub const LC_SUB_FRAMEWORK = 0x12; + +/// sub umbrella +pub const LC_SUB_UMBRELLA = 0x13; + +/// sub client +pub const LC_SUB_CLIENT = 0x14; + +/// sub library +pub const LC_SUB_LIBRARY = 0x15; + +/// two-level namespace lookup hints +pub const LC_TWOLEVEL_HINTS = 0x16; + +/// prebind checksum +pub const LC_PREBIND_CKSUM = 0x17; /// load a dynamically linked shared library that is allowed to be missing /// (all symbols are weak imported). pub const LC_LOAD_WEAK_DYLIB = (0x18 | LC_REQ_DYLD); -pub const LC_SEGMENT_64 = 0x19; /// 64-bit segment of this file to be mapped -pub const LC_ROUTINES_64 = 0x1a; /// 64-bit image routines -pub const LC_UUID = 0x1b; /// the uuid -pub const LC_RPATH = (0x1c | LC_REQ_DYLD); /// runpath additions -pub const LC_CODE_SIGNATURE = 0x1d; /// local of code signature -pub const LC_SEGMENT_SPLIT_INFO = 0x1e; /// local of info to split segments -pub const LC_REEXPORT_DYLIB = (0x1f | LC_REQ_DYLD); /// load and re-export dylib -pub const LC_LAZY_LOAD_DYLIB = 0x20; /// delay load of dylib until first use -pub const LC_ENCRYPTION_INFO = 0x21; /// encrypted segment information -pub const LC_DYLD_INFO = 0x22; /// compressed dyld information -pub const LC_DYLD_INFO_ONLY = (0x22|LC_REQ_DYLD); /// compressed dyld information only -pub const LC_LOAD_UPWARD_DYLIB = (0x23 | LC_REQ_DYLD); /// load upward dylib -pub const LC_VERSION_MIN_MACOSX = 0x24; /// build for MacOSX min OS version -pub const LC_VERSION_MIN_IPHONEOS = 0x25; /// build for iPhoneOS min OS version -pub const LC_FUNCTION_STARTS = 0x26; /// compressed table of function start addresses -pub const LC_DYLD_ENVIRONMENT = 0x27; /// string for dyld to treat like environment variable -pub const LC_MAIN = (0x28|LC_REQ_DYLD); /// replacement for LC_UNIXTHREAD -pub const LC_DATA_IN_CODE = 0x29; /// table of non-instructions in __text -pub const LC_SOURCE_VERSION = 0x2A; /// source version used to build binary -pub const LC_DYLIB_CODE_SIGN_DRS = 0x2B; /// Code signing DRs copied from linked dylibs -pub const LC_ENCRYPTION_INFO_64 = 0x2C; /// 64-bit encrypted segment information -pub const LC_LINKER_OPTION = 0x2D; /// linker options in MH_OBJECT files -pub const LC_LINKER_OPTIMIZATION_HINT = 0x2E; /// optimization hints in MH_OBJECT files -pub const LC_VERSION_MIN_TVOS = 0x2F; /// build for AppleTV min OS version -pub const LC_VERSION_MIN_WATCHOS = 0x30; /// build for Watch min OS version -pub const LC_NOTE = 0x31; /// arbitrary data included within a Mach-O file -pub const LC_BUILD_VERSION = 0x32; /// build for platform min OS version +/// 64-bit segment of this file to be mapped +pub const LC_SEGMENT_64 = 0x19; -pub const MH_MAGIC = 0xfeedface; /// the mach magic number -pub const MH_CIGAM = 0xcefaedfe; /// NXSwapInt(MH_MAGIC) +/// 64-bit image routines +pub const LC_ROUTINES_64 = 0x1a; -pub const MH_MAGIC_64 = 0xfeedfacf; /// the 64-bit mach magic number -pub const MH_CIGAM_64 = 0xcffaedfe; /// NXSwapInt(MH_MAGIC_64) +/// the uuid +pub const LC_UUID = 0x1b; -pub const MH_OBJECT = 0x1; /// relocatable object file -pub const MH_EXECUTE = 0x2; /// demand paged executable file -pub const MH_FVMLIB = 0x3; /// fixed VM shared library file -pub const MH_CORE = 0x4; /// core file -pub const MH_PRELOAD = 0x5; /// preloaded executable file -pub const MH_DYLIB = 0x6; /// dynamically bound shared library -pub const MH_DYLINKER = 0x7; /// dynamic link editor -pub const MH_BUNDLE = 0x8; /// dynamically bound bundle file -pub const MH_DYLIB_STUB = 0x9; /// shared library stub for static linking only, no section contents -pub const MH_DSYM = 0xa; /// companion file with only debug sections -pub const MH_KEXT_BUNDLE = 0xb; /// x86_64 kexts +/// runpath additions +pub const LC_RPATH = (0x1c | LC_REQ_DYLD); + +/// local of code signature +pub const LC_CODE_SIGNATURE = 0x1d; + +/// local of info to split segments +pub const LC_SEGMENT_SPLIT_INFO = 0x1e; + +/// load and re-export dylib +pub const LC_REEXPORT_DYLIB = (0x1f | LC_REQ_DYLD); + +/// delay load of dylib until first use +pub const LC_LAZY_LOAD_DYLIB = 0x20; + +/// encrypted segment information +pub const LC_ENCRYPTION_INFO = 0x21; + +/// compressed dyld information +pub const LC_DYLD_INFO = 0x22; + +/// compressed dyld information only +pub const LC_DYLD_INFO_ONLY = (0x22 | LC_REQ_DYLD); + +/// load upward dylib +pub const LC_LOAD_UPWARD_DYLIB = (0x23 | LC_REQ_DYLD); + +/// build for MacOSX min OS version +pub const LC_VERSION_MIN_MACOSX = 0x24; + +/// build for iPhoneOS min OS version +pub const LC_VERSION_MIN_IPHONEOS = 0x25; + +/// compressed table of function start addresses +pub const LC_FUNCTION_STARTS = 0x26; + +/// string for dyld to treat like environment variable +pub const LC_DYLD_ENVIRONMENT = 0x27; + +/// replacement for LC_UNIXTHREAD +pub const LC_MAIN = (0x28 | LC_REQ_DYLD); + +/// table of non-instructions in __text +pub const LC_DATA_IN_CODE = 0x29; + +/// source version used to build binary +pub const LC_SOURCE_VERSION = 0x2A; + +/// Code signing DRs copied from linked dylibs +pub const LC_DYLIB_CODE_SIGN_DRS = 0x2B; + +/// 64-bit encrypted segment information +pub const LC_ENCRYPTION_INFO_64 = 0x2C; + +/// linker options in MH_OBJECT files +pub const LC_LINKER_OPTION = 0x2D; + +/// optimization hints in MH_OBJECT files +pub const LC_LINKER_OPTIMIZATION_HINT = 0x2E; + +/// build for AppleTV min OS version +pub const LC_VERSION_MIN_TVOS = 0x2F; + +/// build for Watch min OS version +pub const LC_VERSION_MIN_WATCHOS = 0x30; + +/// arbitrary data included within a Mach-O file +pub const LC_NOTE = 0x31; + +/// build for platform min OS version +pub const LC_BUILD_VERSION = 0x32; + +/// the mach magic number +pub const MH_MAGIC = 0xfeedface; + +/// NXSwapInt(MH_MAGIC) +pub const MH_CIGAM = 0xcefaedfe; + +/// the 64-bit mach magic number +pub const MH_MAGIC_64 = 0xfeedfacf; + +/// NXSwapInt(MH_MAGIC_64) +pub const MH_CIGAM_64 = 0xcffaedfe; + +/// relocatable object file +pub const MH_OBJECT = 0x1; + +/// demand paged executable file +pub const MH_EXECUTE = 0x2; + +/// fixed VM shared library file +pub const MH_FVMLIB = 0x3; + +/// core file +pub const MH_CORE = 0x4; + +/// preloaded executable file +pub const MH_PRELOAD = 0x5; + +/// dynamically bound shared library +pub const MH_DYLIB = 0x6; + +/// dynamic link editor +pub const MH_DYLINKER = 0x7; + +/// dynamically bound bundle file +pub const MH_BUNDLE = 0x8; + +/// shared library stub for static linking only, no section contents +pub const MH_DYLIB_STUB = 0x9; + +/// companion file with only debug sections +pub const MH_DSYM = 0xa; + +/// x86_64 kexts +pub const MH_KEXT_BUNDLE = 0xb; // Constants for the flags field of the mach_header -pub const MH_NOUNDEFS = 0x1; /// the object file has no undefined references -pub const MH_INCRLINK = 0x2; /// the object file is the output of an incremental link against a base file and can't be link edited again -pub const MH_DYLDLINK = 0x4; /// the object file is input for the dynamic linker and can't be staticly link edited again -pub const MH_BINDATLOAD = 0x8; /// the object file's undefined references are bound by the dynamic linker when loaded. -pub const MH_PREBOUND = 0x10; /// the file has its dynamic undefined references prebound. -pub const MH_SPLIT_SEGS = 0x20; /// the file has its read-only and read-write segments split -pub const MH_LAZY_INIT = 0x40; /// the shared library init routine is to be run lazily via catching memory faults to its writeable segments (obsolete) -pub const MH_TWOLEVEL = 0x80; /// the image is using two-level name space bindings -pub const MH_FORCE_FLAT = 0x100; /// the executable is forcing all images to use flat name space bindings -pub const MH_NOMULTIDEFS = 0x200; /// this umbrella guarantees no multiple defintions of symbols in its sub-images so the two-level namespace hints can always be used. -pub const MH_NOFIXPREBINDING = 0x400; /// do not have dyld notify the prebinding agent about this executable -pub const MH_PREBINDABLE = 0x800; /// the binary is not prebound but can have its prebinding redone. only used when MH_PREBOUND is not set. -pub const MH_ALLMODSBOUND = 0x1000; /// indicates that this binary binds to all two-level namespace modules of its dependent libraries. only used when MH_PREBINDABLE and MH_TWOLEVEL are both set. -pub const MH_SUBSECTIONS_VIA_SYMBOLS = 0x2000;/// safe to divide up the sections into sub-sections via symbols for dead code stripping -pub const MH_CANONICAL = 0x4000; /// the binary has been canonicalized via the unprebind operation -pub const MH_WEAK_DEFINES = 0x8000; /// the final linked image contains external weak symbols -pub const MH_BINDS_TO_WEAK = 0x10000; /// the final linked image uses weak symbols +/// the object file has no undefined references +pub const MH_NOUNDEFS = 0x1; -pub const MH_ALLOW_STACK_EXECUTION = 0x20000;/// When this bit is set, all stacks in the task will be given stack execution privilege. Only used in MH_EXECUTE filetypes. -pub const MH_ROOT_SAFE = 0x40000; /// When this bit is set, the binary declares it is safe for use in processes with uid zero - -pub const MH_SETUID_SAFE = 0x80000; /// When this bit is set, the binary declares it is safe for use in processes when issetugid() is true +/// the object file is the output of an incremental link against a base file and can't be link edited again +pub const MH_INCRLINK = 0x2; -pub const MH_NO_REEXPORTED_DYLIBS = 0x100000; /// When this bit is set on a dylib, the static linker does not need to examine dependent dylibs to see if any are re-exported -pub const MH_PIE = 0x200000; /// When this bit is set, the OS will load the main executable at a random address. Only used in MH_EXECUTE filetypes. -pub const MH_DEAD_STRIPPABLE_DYLIB = 0x400000; /// Only for use on dylibs. When linking against a dylib that has this bit set, the static linker will automatically not create a LC_LOAD_DYLIB load command to the dylib if no symbols are being referenced from the dylib. -pub const MH_HAS_TLV_DESCRIPTORS = 0x800000; /// Contains a section of type S_THREAD_LOCAL_VARIABLES +/// the object file is input for the dynamic linker and can't be staticly link edited again +pub const MH_DYLDLINK = 0x4; -pub const MH_NO_HEAP_EXECUTION = 0x1000000; /// When this bit is set, the OS will run the main executable with a non-executable heap even on platforms (e.g. i386) that don't require it. Only used in MH_EXECUTE filetypes. +/// the object file's undefined references are bound by the dynamic linker when loaded. +pub const MH_BINDATLOAD = 0x8; -pub const MH_APP_EXTENSION_SAFE = 0x02000000; /// The code was linked for use in an application extension. +/// the file has its dynamic undefined references prebound. +pub const MH_PREBOUND = 0x10; -pub const MH_NLIST_OUTOFSYNC_WITH_DYLDINFO = 0x04000000; /// The external symbols listed in the nlist symbol table do not include all the symbols listed in the dyld info. +/// the file has its read-only and read-write segments split +pub const MH_SPLIT_SEGS = 0x20; +/// the shared library init routine is to be run lazily via catching memory faults to its writeable segments (obsolete) +pub const MH_LAZY_INIT = 0x40; + +/// the image is using two-level name space bindings +pub const MH_TWOLEVEL = 0x80; + +/// the executable is forcing all images to use flat name space bindings +pub const MH_FORCE_FLAT = 0x100; + +/// this umbrella guarantees no multiple defintions of symbols in its sub-images so the two-level namespace hints can always be used. +pub const MH_NOMULTIDEFS = 0x200; + +/// do not have dyld notify the prebinding agent about this executable +pub const MH_NOFIXPREBINDING = 0x400; + +/// the binary is not prebound but can have its prebinding redone. only used when MH_PREBOUND is not set. +pub const MH_PREBINDABLE = 0x800; + +/// indicates that this binary binds to all two-level namespace modules of its dependent libraries. only used when MH_PREBINDABLE and MH_TWOLEVEL are both set. +pub const MH_ALLMODSBOUND = 0x1000; + +/// safe to divide up the sections into sub-sections via symbols for dead code stripping +pub const MH_SUBSECTIONS_VIA_SYMBOLS = 0x2000; + +/// the binary has been canonicalized via the unprebind operation +pub const MH_CANONICAL = 0x4000; + +/// the final linked image contains external weak symbols +pub const MH_WEAK_DEFINES = 0x8000; + +/// the final linked image uses weak symbols +pub const MH_BINDS_TO_WEAK = 0x10000; + +/// When this bit is set, all stacks in the task will be given stack execution privilege. Only used in MH_EXECUTE filetypes. +pub const MH_ALLOW_STACK_EXECUTION = 0x20000; + +/// When this bit is set, the binary declares it is safe for use in processes with uid zero +pub const MH_ROOT_SAFE = 0x40000; + +/// When this bit is set, the binary declares it is safe for use in processes when issetugid() is true +pub const MH_SETUID_SAFE = 0x80000; + +/// When this bit is set on a dylib, the static linker does not need to examine dependent dylibs to see if any are re-exported +pub const MH_NO_REEXPORTED_DYLIBS = 0x100000; + +/// When this bit is set, the OS will load the main executable at a random address. Only used in MH_EXECUTE filetypes. +pub const MH_PIE = 0x200000; + +/// Only for use on dylibs. When linking against a dylib that has this bit set, the static linker will automatically not create a LC_LOAD_DYLIB load command to the dylib if no symbols are being referenced from the dylib. +pub const MH_DEAD_STRIPPABLE_DYLIB = 0x400000; + +/// Contains a section of type S_THREAD_LOCAL_VARIABLES +pub const MH_HAS_TLV_DESCRIPTORS = 0x800000; + +/// When this bit is set, the OS will run the main executable with a non-executable heap even on platforms (e.g. i386) that don't require it. Only used in MH_EXECUTE filetypes. +pub const MH_NO_HEAP_EXECUTION = 0x1000000; + +/// The code was linked for use in an application extension. +pub const MH_APP_EXTENSION_SAFE = 0x02000000; + +/// The external symbols listed in the nlist symbol table do not include all the symbols listed in the dyld info. +pub const MH_NLIST_OUTOFSYNC_WITH_DYLDINFO = 0x04000000; /// The flags field of a section structure is separated into two parts a section /// type and section attributes. The section types are mutually exclusive (it @@ -285,52 +554,129 @@ pub const MH_NLIST_OUTOFSYNC_WITH_DYLDINFO = 0x04000000; /// The external symbol /// than one attribute). /// 256 section types pub const SECTION_TYPE = 0x000000ff; -pub const SECTION_ATTRIBUTES = 0xffffff00; /// 24 section attributes -pub const S_REGULAR = 0x0; /// regular section -pub const S_ZEROFILL = 0x1; /// zero fill on demand section -pub const S_CSTRING_LITERALS = 0x2; /// section with only literal C string -pub const S_4BYTE_LITERALS = 0x3; /// section with only 4 byte literals -pub const S_8BYTE_LITERALS = 0x4; /// section with only 8 byte literals -pub const S_LITERAL_POINTERS = 0x5; /// section with only pointers to +/// 24 section attributes +pub const SECTION_ATTRIBUTES = 0xffffff00; +/// regular section +pub const S_REGULAR = 0x0; -pub const N_STAB = 0xe0; /// if any of these bits set, a symbolic debugging entry -pub const N_PEXT = 0x10; /// private external symbol bit -pub const N_TYPE = 0x0e; /// mask for the type bits -pub const N_EXT = 0x01; /// external symbol bit, set for external symbols +/// zero fill on demand section +pub const S_ZEROFILL = 0x1; +/// section with only literal C string +pub const S_CSTRING_LITERALS = 0x2; -pub const N_GSYM = 0x20; /// global symbol: name,,NO_SECT,type,0 -pub const N_FNAME = 0x22; /// procedure name (f77 kludge): name,,NO_SECT,0,0 -pub const N_FUN = 0x24; /// procedure: name,,n_sect,linenumber,address -pub const N_STSYM = 0x26; /// static symbol: name,,n_sect,type,address -pub const N_LCSYM = 0x28; /// .lcomm symbol: name,,n_sect,type,address -pub const N_BNSYM = 0x2e; /// begin nsect sym: 0,,n_sect,0,address -pub const N_AST = 0x32; /// AST file path: name,,NO_SECT,0,0 -pub const N_OPT = 0x3c; /// emitted with gcc2_compiled and in gcc source -pub const N_RSYM = 0x40; /// register sym: name,,NO_SECT,type,register -pub const N_SLINE = 0x44; /// src line: 0,,n_sect,linenumber,address -pub const N_ENSYM = 0x4e; /// end nsect sym: 0,,n_sect,0,address -pub const N_SSYM = 0x60; /// structure elt: name,,NO_SECT,type,struct_offset -pub const N_SO = 0x64; /// source file name: name,,n_sect,0,address -pub const N_OSO = 0x66; /// object file name: name,,0,0,st_mtime -pub const N_LSYM = 0x80; /// local sym: name,,NO_SECT,type,offset -pub const N_BINCL = 0x82; /// include file beginning: name,,NO_SECT,0,sum -pub const N_SOL = 0x84; /// #included file name: name,,n_sect,0,address -pub const N_PARAMS = 0x86; /// compiler parameters: name,,NO_SECT,0,0 -pub const N_VERSION = 0x88; /// compiler version: name,,NO_SECT,0,0 -pub const N_OLEVEL = 0x8A; /// compiler -O level: name,,NO_SECT,0,0 -pub const N_PSYM = 0xa0; /// parameter: name,,NO_SECT,type,offset -pub const N_EINCL = 0xa2; /// include file end: name,,NO_SECT,0,0 -pub const N_ENTRY = 0xa4; /// alternate entry: name,,n_sect,linenumber,address -pub const N_LBRAC = 0xc0; /// left bracket: 0,,NO_SECT,nesting level,address -pub const N_EXCL = 0xc2; /// deleted include file: name,,NO_SECT,0,sum -pub const N_RBRAC = 0xe0; /// right bracket: 0,,NO_SECT,nesting level,address -pub const N_BCOMM = 0xe2; /// begin common: name,,NO_SECT,0,0 -pub const N_ECOMM = 0xe4; /// end common: name,,n_sect,0,0 -pub const N_ECOML = 0xe8; /// end common (local name): 0,,n_sect,0,address -pub const N_LENG = 0xfe; /// second stab entry with length information +/// section with only 4 byte literals +pub const S_4BYTE_LITERALS = 0x3; + +/// section with only 8 byte literals +pub const S_8BYTE_LITERALS = 0x4; + +/// section with only pointers to +pub const S_LITERAL_POINTERS = 0x5; + +/// if any of these bits set, a symbolic debugging entry +pub const N_STAB = 0xe0; + +/// private external symbol bit +pub const N_PEXT = 0x10; + +/// mask for the type bits +pub const N_TYPE = 0x0e; + +/// external symbol bit, set for external symbols +pub const N_EXT = 0x01; + +/// global symbol: name,,NO_SECT,type,0 +pub const N_GSYM = 0x20; + +/// procedure name (f77 kludge): name,,NO_SECT,0,0 +pub const N_FNAME = 0x22; + +/// procedure: name,,n_sect,linenumber,address +pub const N_FUN = 0x24; + +/// static symbol: name,,n_sect,type,address +pub const N_STSYM = 0x26; + +/// .lcomm symbol: name,,n_sect,type,address +pub const N_LCSYM = 0x28; + +/// begin nsect sym: 0,,n_sect,0,address +pub const N_BNSYM = 0x2e; + +/// AST file path: name,,NO_SECT,0,0 +pub const N_AST = 0x32; + +/// emitted with gcc2_compiled and in gcc source +pub const N_OPT = 0x3c; + +/// register sym: name,,NO_SECT,type,register +pub const N_RSYM = 0x40; + +/// src line: 0,,n_sect,linenumber,address +pub const N_SLINE = 0x44; + +/// end nsect sym: 0,,n_sect,0,address +pub const N_ENSYM = 0x4e; + +/// structure elt: name,,NO_SECT,type,struct_offset +pub const N_SSYM = 0x60; + +/// source file name: name,,n_sect,0,address +pub const N_SO = 0x64; + +/// object file name: name,,0,0,st_mtime +pub const N_OSO = 0x66; + +/// local sym: name,,NO_SECT,type,offset +pub const N_LSYM = 0x80; + +/// include file beginning: name,,NO_SECT,0,sum +pub const N_BINCL = 0x82; + +/// #included file name: name,,n_sect,0,address +pub const N_SOL = 0x84; + +/// compiler parameters: name,,NO_SECT,0,0 +pub const N_PARAMS = 0x86; + +/// compiler version: name,,NO_SECT,0,0 +pub const N_VERSION = 0x88; + +/// compiler -O level: name,,NO_SECT,0,0 +pub const N_OLEVEL = 0x8A; + +/// parameter: name,,NO_SECT,type,offset +pub const N_PSYM = 0xa0; + +/// include file end: name,,NO_SECT,0,0 +pub const N_EINCL = 0xa2; + +/// alternate entry: name,,n_sect,linenumber,address +pub const N_ENTRY = 0xa4; + +/// left bracket: 0,,NO_SECT,nesting level,address +pub const N_LBRAC = 0xc0; + +/// deleted include file: name,,NO_SECT,0,sum +pub const N_EXCL = 0xc2; + +/// right bracket: 0,,NO_SECT,nesting level,address +pub const N_RBRAC = 0xe0; + +/// begin common: name,,NO_SECT,0,0 +pub const N_BCOMM = 0xe2; + +/// end common: name,,n_sect,0,0 +pub const N_ECOMM = 0xe4; + +/// end common (local name): 0,,n_sect,0,address +pub const N_ECOML = 0xe8; + +/// second stab entry with length information +pub const N_LENG = 0xfe; /// If a segment contains any sections marked with S_ATTR_DEBUG then all /// sections in that segment must have this attribute. No section other than @@ -339,10 +685,10 @@ pub const N_LENG = 0xfe; /// second stab entry with length information /// a section type S_REGULAR. The static linker will not copy section contents /// from sections with this attribute into its output file. These sections /// generally contain DWARF debugging info. -pub const S_ATTR_DEBUG = 0x02000000; /// a debug section +/// a debug section +pub const S_ATTR_DEBUG = 0x02000000; pub const cpu_type_t = integer_t; pub const cpu_subtype_t = integer_t; pub const integer_t = c_int; pub const vm_prot_t = c_int; - diff --git a/std/os/child_process.zig b/std/os/child_process.zig index b79a8de16f..a8f307f3b6 100644 --- a/std/os/child_process.zig +++ b/std/os/child_process.zig @@ -1,5 +1,6 @@ const std = @import("../index.zig"); const cstr = std.cstr; +const unicode = std.unicode; const io = std.io; const os = std.os; const posix = os.posix; @@ -12,6 +13,7 @@ const Buffer = std.Buffer; const builtin = @import("builtin"); const Os = builtin.Os; const LinkedList = std.LinkedList; +const windows_util = @import("windows/util.zig"); const is_windows = builtin.os == Os.windows; @@ -209,8 +211,8 @@ pub const ChildProcess = struct { defer Buffer.deinit(&stdout); defer Buffer.deinit(&stderr); - var stdout_file_in_stream = io.FileInStream.init(&child.stdout.?); - var stderr_file_in_stream = io.FileInStream.init(&child.stderr.?); + var stdout_file_in_stream = io.FileInStream.init(child.stdout.?); + var stderr_file_in_stream = io.FileInStream.init(child.stderr.?); try stdout_file_in_stream.stream.readAllBuffer(&stdout, max_output_size); try stderr_file_in_stream.stream.readAllBuffer(&stderr, max_output_size); @@ -520,8 +522,8 @@ pub const ChildProcess = struct { const cmd_line = try windowsCreateCommandLine(self.allocator, self.argv); defer self.allocator.free(cmd_line); - var siStartInfo = windows.STARTUPINFOA{ - .cb = @sizeOf(windows.STARTUPINFOA), + var siStartInfo = windows.STARTUPINFOW{ + .cb = @sizeOf(windows.STARTUPINFOW), .hStdError = g_hChildStd_ERR_Wr, .hStdOutput = g_hChildStd_OUT_Wr, .hStdInput = g_hChildStd_IN_Rd, @@ -545,7 +547,9 @@ pub const ChildProcess = struct { const cwd_slice = if (self.cwd) |cwd| try cstr.addNullByte(self.allocator, cwd) else null; defer if (cwd_slice) |cwd| self.allocator.free(cwd); - const cwd_ptr = if (cwd_slice) |cwd| cwd.ptr else null; + const cwd_w = if (cwd_slice) |cwd| try unicode.utf8ToUtf16LeWithNull(self.allocator, cwd) else null; + defer if (cwd_w) |cwd| self.allocator.free(cwd); + const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null; const maybe_envp_buf = if (self.env_map) |env_map| try os.createWindowsEnvBlock(self.allocator, env_map) else null; defer if (maybe_envp_buf) |envp_buf| self.allocator.free(envp_buf); @@ -564,7 +568,13 @@ pub const ChildProcess = struct { }; defer self.allocator.free(app_name); - windowsCreateProcess(app_name.ptr, cmd_line.ptr, envp_ptr, cwd_ptr, &siStartInfo, &piProcInfo) catch |no_path_err| { + const app_name_w = try unicode.utf8ToUtf16LeWithNull(self.allocator, app_name); + defer self.allocator.free(app_name_w); + + const cmd_line_w = try unicode.utf8ToUtf16LeWithNull(self.allocator, cmd_line); + defer self.allocator.free(cmd_line_w); + + windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo) catch |no_path_err| { if (no_path_err != error.FileNotFound) return no_path_err; const PATH = try os.getEnvVarOwned(self.allocator, "PATH"); @@ -575,7 +585,10 @@ pub const ChildProcess = struct { const joined_path = try os.path.join(self.allocator, search_path, app_name); defer self.allocator.free(joined_path); - if (windowsCreateProcess(joined_path.ptr, cmd_line.ptr, envp_ptr, cwd_ptr, &siStartInfo, &piProcInfo)) |_| { + const joined_path_w = try unicode.utf8ToUtf16LeWithNull(self.allocator, app_name); + defer self.allocator.free(joined_path_w); + + if (windowsCreateProcess(joined_path_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo)) |_| { break; } else |err| if (err == error.FileNotFound) { continue; @@ -626,15 +639,36 @@ pub const ChildProcess = struct { } }; -fn windowsCreateProcess(app_name: [*]u8, cmd_line: [*]u8, envp_ptr: ?[*]u8, cwd_ptr: ?[*]u8, lpStartupInfo: *windows.STARTUPINFOA, lpProcessInformation: *windows.PROCESS_INFORMATION) !void { - if (windows.CreateProcessA(app_name, cmd_line, null, null, windows.TRUE, 0, @ptrCast(?*c_void, envp_ptr), cwd_ptr, lpStartupInfo, lpProcessInformation) == 0) { +fn windowsCreateProcess(app_name: [*]u16, cmd_line: [*]u16, envp_ptr: ?[*]u16, cwd_ptr: ?[*]u16, lpStartupInfo: *windows.STARTUPINFOW, lpProcessInformation: *windows.PROCESS_INFORMATION) !void { + // TODO the docs for environment pointer say: + // > A pointer to the environment block for the new process. If this parameter + // > is NULL, the new process uses the environment of the calling process. + // > ... + // > An environment block can contain either Unicode or ANSI characters. If + // > the environment block pointed to by lpEnvironment contains Unicode + // > characters, be sure that dwCreationFlags includes CREATE_UNICODE_ENVIRONMENT. + // > If this parameter is NULL and the environment block of the parent process + // > contains Unicode characters, you must also ensure that dwCreationFlags + // > includes CREATE_UNICODE_ENVIRONMENT. + // This seems to imply that we have to somehow know whether our process parent passed + // CREATE_UNICODE_ENVIRONMENT if we want to pass NULL for the environment parameter. + // Since we do not know this information that would imply that we must not pass NULL + // for the parameter. + // However this would imply that programs compiled with -DUNICODE could not pass + // environment variables to programs that were not, which seems unlikely. + // More investigation is needed. + if (windows.CreateProcessW( + app_name, cmd_line, null, null, windows.TRUE, windows.CREATE_UNICODE_ENVIRONMENT, + @ptrCast(?*c_void, envp_ptr), cwd_ptr, lpStartupInfo, lpProcessInformation, + ) == 0) { const err = windows.GetLastError(); - return switch (err) { - windows.ERROR.FILE_NOT_FOUND, windows.ERROR.PATH_NOT_FOUND => error.FileNotFound, + switch (err) { + windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound, + windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound, windows.ERROR.INVALID_PARAMETER => unreachable, - windows.ERROR.INVALID_NAME => error.InvalidName, - else => os.unexpectedErrorWindows(err), - }; + windows.ERROR.INVALID_NAME => return error.InvalidName, + else => return os.unexpectedErrorWindows(err), + } } } diff --git a/std/os/file.zig b/std/os/file.zig index 1f5ce7cf9d..020a5dca54 100644 --- a/std/os/file.zig +++ b/std/os/file.zig @@ -48,18 +48,23 @@ pub const File = struct { return openReadC(&path_c); } if (is_windows) { - const handle = try os.windowsOpen( - path, - windows.GENERIC_READ, - windows.FILE_SHARE_READ, - windows.OPEN_EXISTING, - windows.FILE_ATTRIBUTE_NORMAL, - ); - return openHandle(handle); + const path_w = try windows_util.sliceToPrefixedFileW(path); + return openReadW(&path_w); } @compileError("Unsupported OS"); } + pub fn openReadW(path_w: [*]const u16) OpenError!File { + const handle = try os.windowsOpenW( + path_w, + windows.GENERIC_READ, + windows.FILE_SHARE_READ, + windows.OPEN_EXISTING, + windows.FILE_ATTRIBUTE_NORMAL, + ); + return openHandle(handle); + } + /// Calls `openWriteMode` with os.File.default_mode for the mode. pub fn openWrite(path: []const u8) OpenError!File { return openWriteMode(path, os.File.default_mode); @@ -74,19 +79,24 @@ pub const File = struct { const fd = try os.posixOpen(path, flags, file_mode); return openHandle(fd); } else if (is_windows) { - const handle = try os.windowsOpen( - path, - windows.GENERIC_WRITE, - windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, - windows.CREATE_ALWAYS, - windows.FILE_ATTRIBUTE_NORMAL, - ); - return openHandle(handle); + const path_w = try windows_util.sliceToPrefixedFileW(path); + return openWriteModeW(&path_w, file_mode); } else { @compileError("TODO implement openWriteMode for this OS"); } } + pub fn openWriteModeW(path_w: [*]const u16, file_mode: Mode) OpenError!File { + const handle = try os.windowsOpenW( + path_w, + windows.GENERIC_WRITE, + windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, + windows.CREATE_ALWAYS, + windows.FILE_ATTRIBUTE_NORMAL, + ); + return openHandle(handle); + } + /// If the path does not exist it will be created. /// If a file already exists in the destination this returns OpenError.PathAlreadyExists /// Call close to clean up. @@ -96,19 +106,24 @@ pub const File = struct { const fd = try os.posixOpen(path, flags, file_mode); return openHandle(fd); } else if (is_windows) { - const handle = try os.windowsOpen( - path, - windows.GENERIC_WRITE, - windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, - windows.CREATE_NEW, - windows.FILE_ATTRIBUTE_NORMAL, - ); - return openHandle(handle); + const path_w = try windows_util.sliceToPrefixedFileW(path); + return openWriteNoClobberW(&path_w, file_mode); } else { @compileError("TODO implement openWriteMode for this OS"); } } + pub fn openWriteNoClobberW(path_w: [*]const u16, file_mode: Mode) OpenError!File { + const handle = try os.windowsOpenW( + path_w, + windows.GENERIC_WRITE, + windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, + windows.CREATE_NEW, + windows.FILE_ATTRIBUTE_NORMAL, + ); + return openHandle(handle); + } + pub fn openHandle(handle: os.FileHandle) File { return File{ .handle = handle }; } @@ -190,17 +205,16 @@ pub const File = struct { /// Upon success, the stream is in an uninitialized state. To continue using it, /// you must use the open() function. - pub fn close(self: *File) void { + pub fn close(self: File) void { os.close(self.handle); - self.handle = undefined; } /// Calls `os.isTty` on `self.handle`. - pub fn isTty(self: *File) bool { + pub fn isTty(self: File) bool { return os.isTty(self.handle); } - pub fn seekForward(self: *File, amount: isize) !void { + pub fn seekForward(self: File, amount: isize) !void { switch (builtin.os) { Os.linux, Os.macosx, Os.ios => { const result = posix.lseek(self.handle, amount, posix.SEEK_CUR); @@ -231,7 +245,7 @@ pub const File = struct { } } - pub fn seekTo(self: *File, pos: usize) !void { + pub fn seekTo(self: File, pos: usize) !void { switch (builtin.os) { Os.linux, Os.macosx, Os.ios => { const ipos = try math.cast(isize, pos); @@ -256,6 +270,7 @@ pub const File = struct { const err = windows.GetLastError(); return switch (err) { windows.ERROR.INVALID_PARAMETER => unreachable, + windows.ERROR.INVALID_HANDLE => unreachable, else => os.unexpectedErrorWindows(err), }; } @@ -264,7 +279,7 @@ pub const File = struct { } } - pub fn getPos(self: *File) !usize { + pub fn getPos(self: File) !usize { switch (builtin.os) { Os.linux, Os.macosx, Os.ios => { const result = posix.lseek(self.handle, 0, posix.SEEK_CUR); @@ -300,7 +315,7 @@ pub const File = struct { } } - pub fn getEndPos(self: *File) !usize { + pub fn getEndPos(self: File) !usize { if (is_posix) { const stat = try os.posixFStat(self.handle); return @intCast(usize, stat.size); @@ -325,7 +340,7 @@ pub const File = struct { Unexpected, }; - pub fn mode(self: *File) ModeError!Mode { + pub fn mode(self: File) ModeError!Mode { if (is_posix) { var stat: posix.Stat = undefined; const err = posix.getErrno(posix.fstat(self.handle, &stat)); @@ -359,7 +374,7 @@ pub const File = struct { Unexpected, }; - pub fn read(self: *File, buffer: []u8) ReadError!usize { + pub fn read(self: File, buffer: []u8) ReadError!usize { if (is_posix) { var index: usize = 0; while (index < buffer.len) { @@ -407,7 +422,7 @@ pub const File = struct { pub const WriteError = os.WindowsWriteError || os.PosixWriteError; - pub fn write(self: *File, bytes: []const u8) WriteError!void { + pub fn write(self: File, bytes: []const u8) WriteError!void { if (is_posix) { try os.posixWrite(self.handle, bytes); } else if (is_windows) { diff --git a/std/os/index.zig b/std/os/index.zig index 29d887e214..3ab73a79ac 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -57,6 +57,7 @@ pub const windowsWaitSingle = windows_util.windowsWaitSingle; pub const windowsWrite = windows_util.windowsWrite; pub const windowsIsCygwinPty = windows_util.windowsIsCygwinPty; pub const windowsOpen = windows_util.windowsOpen; +pub const windowsOpenW = windows_util.windowsOpenW; pub const windowsLoadDll = windows_util.windowsLoadDll; pub const windowsUnloadDll = windows_util.windowsUnloadDll; pub const createWindowsEnvBlock = windows_util.createWindowsEnvBlock; @@ -660,6 +661,7 @@ pub fn getBaseAddress() usize { return phdr - @sizeOf(ElfHeader); }, builtin.Os.macosx => return @ptrToInt(&std.c._mh_execute_header), + builtin.Os.windows => return @ptrToInt(windows.GetModuleHandleW(null)), else => @compileError("Unsupported OS"), } } @@ -817,37 +819,40 @@ test "os.getCwd" { pub const SymLinkError = PosixSymLinkError || WindowsSymLinkError; -pub fn symLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) SymLinkError!void { +/// TODO add a symLinkC variant +pub fn symLink(existing_path: []const u8, new_path: []const u8) SymLinkError!void { if (is_windows) { - return symLinkWindows(allocator, existing_path, new_path); + return symLinkWindows(existing_path, new_path); } else { - return symLinkPosix(allocator, existing_path, new_path); + return symLinkPosix(existing_path, new_path); } } pub const WindowsSymLinkError = error{ - OutOfMemory, + NameTooLong, + InvalidUtf8, + BadPathName, /// See https://github.com/ziglang/zig/issues/1396 Unexpected, }; -pub fn symLinkWindows(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) WindowsSymLinkError!void { - const existing_with_null = try cstr.addNullByte(allocator, existing_path); - defer allocator.free(existing_with_null); - const new_with_null = try cstr.addNullByte(allocator, new_path); - defer allocator.free(new_with_null); - - if (windows.CreateSymbolicLinkA(existing_with_null.ptr, new_with_null.ptr, 0) == 0) { +pub fn symLinkW(existing_path_w: [*]const u16, new_path_w: [*]const u16) WindowsSymLinkError!void { + if (windows.CreateSymbolicLinkW(existing_path_w, new_path_w, 0) == 0) { const err = windows.GetLastError(); - return switch (err) { - else => unexpectedErrorWindows(err), - }; + switch (err) { + else => return unexpectedErrorWindows(err), + } } } +pub fn symLinkWindows(existing_path: []const u8, new_path: []const u8) WindowsSymLinkError!void { + const existing_path_w = try windows_util.sliceToPrefixedFileW(existing_path); + const new_path_w = try windows_util.sliceToPrefixedFileW(new_path); + return symLinkW(&existing_path_w, &new_path_w); +} + pub const PosixSymLinkError = error{ - OutOfMemory, AccessDenied, DiskQuota, PathAlreadyExists, @@ -864,43 +869,40 @@ pub const PosixSymLinkError = error{ Unexpected, }; -pub fn symLinkPosix(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) PosixSymLinkError!void { - const full_buf = try allocator.alloc(u8, existing_path.len + new_path.len + 2); - defer allocator.free(full_buf); - - const existing_buf = full_buf; - mem.copy(u8, existing_buf, existing_path); - existing_buf[existing_path.len] = 0; - - const new_buf = full_buf[existing_path.len + 1 ..]; - mem.copy(u8, new_buf, new_path); - new_buf[new_path.len] = 0; - - const err = posix.getErrno(posix.symlink(existing_buf.ptr, new_buf.ptr)); - if (err > 0) { - return switch (err) { - posix.EFAULT, posix.EINVAL => unreachable, - posix.EACCES, posix.EPERM => error.AccessDenied, - posix.EDQUOT => error.DiskQuota, - posix.EEXIST => error.PathAlreadyExists, - posix.EIO => error.FileSystem, - posix.ELOOP => error.SymLinkLoop, - posix.ENAMETOOLONG => error.NameTooLong, - posix.ENOENT => error.FileNotFound, - posix.ENOTDIR => error.NotDir, - posix.ENOMEM => error.SystemResources, - posix.ENOSPC => error.NoSpaceLeft, - posix.EROFS => error.ReadOnlyFileSystem, - else => unexpectedErrorPosix(err), - }; +pub fn symLinkPosixC(existing_path: [*]const u8, new_path: [*]const u8) PosixSymLinkError!void { + const err = posix.getErrno(posix.symlink(existing_path, new_path)); + switch (err) { + 0 => return, + posix.EFAULT => unreachable, + posix.EINVAL => unreachable, + posix.EACCES => return error.AccessDenied, + posix.EPERM => return error.AccessDenied, + posix.EDQUOT => return error.DiskQuota, + posix.EEXIST => return error.PathAlreadyExists, + posix.EIO => return error.FileSystem, + posix.ELOOP => return error.SymLinkLoop, + posix.ENAMETOOLONG => return error.NameTooLong, + posix.ENOENT => return error.FileNotFound, + posix.ENOTDIR => return error.NotDir, + posix.ENOMEM => return error.SystemResources, + posix.ENOSPC => return error.NoSpaceLeft, + posix.EROFS => return error.ReadOnlyFileSystem, + else => return unexpectedErrorPosix(err), } } +pub fn symLinkPosix(existing_path: []const u8, new_path: []const u8) PosixSymLinkError!void { + const existing_path_c = try toPosixPath(existing_path); + const new_path_c = try toPosixPath(new_path); + return symLinkPosixC(&existing_path_c, &new_path_c); +} + // here we replace the standard +/ with -_ so that it can be used in a file name const b64_fs_encoder = base64.Base64Encoder.init("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", base64.standard_pad_char); +/// TODO remove the allocator requirement from this API pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void { - if (symLink(allocator, existing_path, new_path)) { + if (symLink(existing_path, new_path)) { return; } else |err| switch (err) { error.PathAlreadyExists => {}, @@ -918,7 +920,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: try getRandomBytes(rand_buf[0..]); b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf); - if (symLink(allocator, existing_path, tmp_path)) { + if (symLink(existing_path, tmp_path)) { return rename(tmp_path, new_path); } else |err| switch (err) { error.PathAlreadyExists => continue, @@ -1250,54 +1252,70 @@ pub const DeleteDirError = error{ NotDir, DirNotEmpty, ReadOnlyFileSystem, - OutOfMemory, + InvalidUtf8, + BadPathName, /// See https://github.com/ziglang/zig/issues/1396 Unexpected, }; -/// Returns ::error.DirNotEmpty if the directory is not empty. -/// To delete a directory recursively, see ::deleteTree -pub fn deleteDir(allocator: *Allocator, dir_path: []const u8) DeleteDirError!void { - const path_buf = try allocator.alloc(u8, dir_path.len + 1); - defer allocator.free(path_buf); - - mem.copy(u8, path_buf, dir_path); - path_buf[dir_path.len] = 0; - +pub fn deleteDirC(dir_path: [*]const u8) DeleteDirError!void { switch (builtin.os) { Os.windows => { - if (windows.RemoveDirectoryA(path_buf.ptr) == 0) { - const err = windows.GetLastError(); - return switch (err) { - windows.ERROR.PATH_NOT_FOUND => error.FileNotFound, - windows.ERROR.DIR_NOT_EMPTY => error.DirNotEmpty, - else => unexpectedErrorWindows(err), - }; - } + const dir_path_w = try windows_util.cStrToPrefixedFileW(dir_path); + return deleteDirW(&dir_path_w); }, Os.linux, Os.macosx, Os.ios => { - const err = posix.getErrno(posix.rmdir(path_buf.ptr)); - if (err > 0) { - return switch (err) { - posix.EACCES, posix.EPERM => error.AccessDenied, - posix.EBUSY => error.FileBusy, - posix.EFAULT, posix.EINVAL => unreachable, - posix.ELOOP => error.SymLinkLoop, - posix.ENAMETOOLONG => error.NameTooLong, - posix.ENOENT => error.FileNotFound, - posix.ENOMEM => error.SystemResources, - posix.ENOTDIR => error.NotDir, - posix.EEXIST, posix.ENOTEMPTY => error.DirNotEmpty, - posix.EROFS => error.ReadOnlyFileSystem, - else => unexpectedErrorPosix(err), - }; + const err = posix.getErrno(posix.rmdir(dir_path)); + switch (err) { + 0 => return, + posix.EACCES => return error.AccessDenied, + posix.EPERM => return error.AccessDenied, + posix.EBUSY => return error.FileBusy, + posix.EFAULT => unreachable, + posix.EINVAL => unreachable, + posix.ELOOP => return error.SymLinkLoop, + posix.ENAMETOOLONG => return error.NameTooLong, + posix.ENOENT => return error.FileNotFound, + posix.ENOMEM => return error.SystemResources, + posix.ENOTDIR => return error.NotDir, + posix.EEXIST => return error.DirNotEmpty, + posix.ENOTEMPTY => return error.DirNotEmpty, + posix.EROFS => return error.ReadOnlyFileSystem, + else => return unexpectedErrorPosix(err), } }, else => @compileError("unimplemented"), } } +pub fn deleteDirW(dir_path_w: [*]const u16) DeleteDirError!void { + if (windows.RemoveDirectoryW(dir_path_w) == 0) { + const err = windows.GetLastError(); + switch (err) { + windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound, + windows.ERROR.DIR_NOT_EMPTY => return error.DirNotEmpty, + else => return unexpectedErrorWindows(err), + } + } +} + +/// Returns ::error.DirNotEmpty if the directory is not empty. +/// To delete a directory recursively, see ::deleteTree +pub fn deleteDir(dir_path: []const u8) DeleteDirError!void { + switch (builtin.os) { + Os.windows => { + const dir_path_w = try windows_util.sliceToPrefixedFileW(dir_path); + return deleteDirW(&dir_path_w); + }, + Os.linux, Os.macosx, Os.ios => { + const dir_path_c = try toPosixPath(dir_path); + return deleteDirC(&dir_path_c); + }, + else => @compileError("unimplemented"), + } +} + /// Whether ::full_path describes a symlink, file, or directory, this function /// removes it. If it cannot be removed because it is a non-empty directory, /// this function recursively removes its entries and then tries again. @@ -1344,6 +1362,7 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError! error.IsDir => {}, error.AccessDenied => got_access_denied = true, + error.InvalidUtf8, error.SymLinkLoop, error.NameTooLong, error.SystemResources, @@ -1351,7 +1370,6 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError! error.NotDir, error.FileSystem, error.FileBusy, - error.InvalidUtf8, error.BadPathName, error.Unexpected, => return err, @@ -1379,6 +1397,8 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError! error.NoSpaceLeft, error.PathAlreadyExists, error.Unexpected, + error.InvalidUtf8, + error.BadPathName, => return err, }; defer dir.close(); @@ -1396,7 +1416,7 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError! try deleteTree(allocator, full_entry_path); } } - return deleteDir(allocator, full_path); + return deleteDir(full_path); } } @@ -1420,8 +1440,9 @@ pub const Dir = struct { }, Os.windows => struct { handle: windows.HANDLE, - find_file_data: windows.WIN32_FIND_DATAA, + find_file_data: windows.WIN32_FIND_DATAW, first: bool, + name_data: [256]u8, }, else => @compileError("unimplemented"), }; @@ -1458,6 +1479,8 @@ pub const Dir = struct { NoSpaceLeft, PathAlreadyExists, OutOfMemory, + InvalidUtf8, + BadPathName, /// See https://github.com/ziglang/zig/issues/1396 Unexpected, @@ -1469,12 +1492,13 @@ pub const Dir = struct { .allocator = allocator, .handle = switch (builtin.os) { Os.windows => blk: { - var find_file_data: windows.WIN32_FIND_DATAA = undefined; - const handle = try windows_util.windowsFindFirstFile(allocator, dir_path, &find_file_data); + var find_file_data: windows.WIN32_FIND_DATAW = undefined; + const handle = try windows_util.windowsFindFirstFile(dir_path, &find_file_data); break :blk Handle{ .handle = handle, .find_file_data = find_file_data, // TODO guaranteed copy elision .first = true, + .name_data = undefined, }; }, Os.macosx, Os.ios => Handle{ @@ -1589,9 +1613,12 @@ pub const Dir = struct { if (!try windows_util.windowsFindNextFile(self.handle.handle, &self.handle.find_file_data)) return null; } - const name = std.cstr.toSlice(self.handle.find_file_data.cFileName[0..].ptr); - if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) + const name_utf16le = mem.toSlice(u16, self.handle.find_file_data.cFileName[0..].ptr); + if (mem.eql(u16, name_utf16le, []u16{'.'}) or mem.eql(u16, name_utf16le, []u16{'.', '.'})) continue; + // Trust that Windows gives us valid UTF-16LE + const name_utf8_len = std.unicode.utf16leToUtf8(self.handle.name_data[0..], name_utf16le) catch unreachable; + const name_utf8 = self.handle.name_data[0..name_utf8_len]; const kind = blk: { const attrs = self.handle.find_file_data.dwFileAttributes; if (attrs & windows.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk Entry.Kind.Directory; @@ -1600,7 +1627,7 @@ pub const Dir = struct { break :blk Entry.Kind.Unknown; }; return Entry{ - .name = name, + .name = name_utf8, .kind = kind, }; } @@ -2087,8 +2114,9 @@ pub fn unexpectedErrorPosix(errno: usize) UnexpectedError { /// Call this when you made a windows DLL call or something that does SetLastError /// and you get an unexpected error. pub fn unexpectedErrorWindows(err: windows.DWORD) UnexpectedError { - if (true) { + if (unexpected_error_tracing) { debug.warn("unexpected GetLastError(): {}\n", err); + @breakpoint(); debug.dumpCurrentStackTrace(null); } return error.Unexpected; @@ -2103,17 +2131,35 @@ pub fn openSelfExe() !os.File { buf[self_exe_path.len] = 0; return os.File.openReadC(self_exe_path.ptr); }, + Os.windows => { + var buf: [windows_util.PATH_MAX_WIDE]u16 = undefined; + const wide_slice = try selfExePathW(&buf); + return os.File.openReadW(wide_slice.ptr); + }, else => @compileError("Unsupported OS"), } } test "openSelfExe" { switch (builtin.os) { - Os.linux, Os.macosx, Os.ios => (try openSelfExe()).close(), - else => return error.SkipZigTest, // Unsupported OS + Os.linux, Os.macosx, Os.ios, Os.windows => (try openSelfExe()).close(), + else => return error.SkipZigTest, // Unsupported OS. } } +pub fn selfExePathW(out_buffer: *[windows_util.PATH_MAX_WIDE]u16) ![]u16 { + const casted_len = @intCast(windows.DWORD, out_buffer.len); // TODO shouldn't need this cast + const rc = windows.GetModuleFileNameW(null, out_buffer, casted_len); + assert(rc <= out_buffer.len); + if (rc == 0) { + const err = windows.GetLastError(); + switch (err) { + else => return unexpectedErrorWindows(err), + } + } + return out_buffer[0..rc]; +} + /// Get the path to the current executable. /// If you only need the directory, use selfExeDirPath. /// If you only want an open file handle, use openSelfExe. @@ -2129,16 +2175,7 @@ pub fn selfExePath(out_buffer: *[MAX_PATH_BYTES]u8) ![]u8 { Os.linux => return readLink(out_buffer, "/proc/self/exe"), Os.windows => { var utf16le_buf: [windows_util.PATH_MAX_WIDE]u16 = undefined; - const casted_len = @intCast(windows.DWORD, utf16le_buf.len); // TODO shouldn't need this cast - const rc = windows.GetModuleFileNameW(null, &utf16le_buf, casted_len); - assert(rc <= utf16le_buf.len); - if (rc == 0) { - const err = windows.GetLastError(); - switch (err) { - else => return unexpectedErrorWindows(err), - } - } - const utf16le_slice = utf16le_buf[0..rc]; + const utf16le_slice = try selfExePathW(&utf16le_buf); // Trust that Windows gives us valid UTF-16LE. const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice) catch unreachable; return out_buffer[0..end_index]; diff --git a/std/os/windows/advapi32.zig b/std/os/windows/advapi32.zig index 2f3195475c..d0a3ec41e9 100644 --- a/std/os/windows/advapi32.zig +++ b/std/os/windows/advapi32.zig @@ -23,11 +23,22 @@ pub extern "advapi32" stdcallcc fn CryptReleaseContext(hProv: HCRYPTPROV, dwFlag pub extern "advapi32" stdcallcc fn CryptGenRandom(hProv: HCRYPTPROV, dwLen: DWORD, pbBuffer: [*]BYTE) BOOL; -pub extern "advapi32" stdcallcc fn RegOpenKeyExW(hKey: HKEY, lpSubKey: LPCWSTR, ulOptions: DWORD, samDesired: REGSAM, - phkResult: &HKEY,) LSTATUS; +pub extern "advapi32" stdcallcc fn RegOpenKeyExW( + hKey: HKEY, + lpSubKey: LPCWSTR, + ulOptions: DWORD, + samDesired: REGSAM, + phkResult: &HKEY, +) LSTATUS; -pub extern "advapi32" stdcallcc fn RegQueryValueExW(hKey: HKEY, lpValueName: LPCWSTR, lpReserved: LPDWORD, - lpType: LPDWORD, lpData: LPBYTE, lpcbData: LPDWORD,) LSTATUS; +pub extern "advapi32" stdcallcc fn RegQueryValueExW( + hKey: HKEY, + lpValueName: LPCWSTR, + lpReserved: LPDWORD, + lpType: LPDWORD, + lpData: LPBYTE, + lpcbData: LPDWORD, +) LSTATUS; // RtlGenRandom is known as SystemFunction036 under advapi32 // http://msdn.microsoft.com/en-us/library/windows/desktop/aa387694.aspx */ diff --git a/std/os/windows/index.zig b/std/os/windows/index.zig index bb055468a5..fc64db7c37 100644 --- a/std/os/windows/index.zig +++ b/std/os/windows/index.zig @@ -3,10 +3,9 @@ const assert = std.debug.assert; pub use @import("advapi32.zig"); pub use @import("kernel32.zig"); +pub use @import("ntdll.zig"); pub use @import("ole32.zig"); pub use @import("shell32.zig"); -pub use @import("shlwapi.zig"); -pub use @import("user32.zig"); test "import" { _ = @import("util.zig"); @@ -14,6 +13,7 @@ test "import" { pub const ERROR = @import("error.zig"); +pub const SHORT = c_short; pub const BOOL = c_int; pub const BOOLEAN = BYTE; pub const BYTE = u8; @@ -172,11 +172,11 @@ pub const PROCESS_INFORMATION = extern struct { dwThreadId: DWORD, }; -pub const STARTUPINFOA = extern struct { +pub const STARTUPINFOW = extern struct { cb: DWORD, - lpReserved: ?LPSTR, - lpDesktop: ?LPSTR, - lpTitle: ?LPSTR, + lpReserved: ?LPWSTR, + lpDesktop: ?LPWSTR, + lpTitle: ?LPWSTR, dwX: DWORD, dwY: DWORD, dwXSize: DWORD, @@ -236,7 +236,7 @@ pub const HEAP_NO_SERIALIZE = 0x00000001; pub const PTHREAD_START_ROUTINE = extern fn (LPVOID) DWORD; pub const LPTHREAD_START_ROUTINE = PTHREAD_START_ROUTINE; -pub const WIN32_FIND_DATAA = extern struct { +pub const WIN32_FIND_DATAW = extern struct { dwFileAttributes: DWORD, ftCreationTime: FILETIME, ftLastAccessTime: FILETIME, @@ -245,8 +245,8 @@ pub const WIN32_FIND_DATAA = extern struct { nFileSizeLow: DWORD, dwReserved0: DWORD, dwReserved1: DWORD, - cFileName: [260]CHAR, - cAlternateFileName: [14]CHAR, + cFileName: [260]u16, + cAlternateFileName: [14]u16, }; pub const FILETIME = extern struct { @@ -288,27 +288,27 @@ pub const GUID = extern struct { assert(str[index] == '{'); index += 1; - guid.Data1 = std.fmt.parseUnsigned(c_ulong, str[index..index + 8], 16) catch unreachable; + guid.Data1 = std.fmt.parseUnsigned(c_ulong, str[index .. index + 8], 16) catch unreachable; index += 8; assert(str[index] == '-'); index += 1; - guid.Data2 = std.fmt.parseUnsigned(c_ushort, str[index..index + 4], 16) catch unreachable; + guid.Data2 = std.fmt.parseUnsigned(c_ushort, str[index .. index + 4], 16) catch unreachable; index += 4; assert(str[index] == '-'); index += 1; - guid.Data3 = std.fmt.parseUnsigned(c_ushort, str[index..index + 4], 16) catch unreachable; + guid.Data3 = std.fmt.parseUnsigned(c_ushort, str[index .. index + 4], 16) catch unreachable; index += 4; assert(str[index] == '-'); index += 1; - guid.Data4[0] = std.fmt.parseUnsigned(u8, str[index..index + 2], 16) catch unreachable; + guid.Data4[0] = std.fmt.parseUnsigned(u8, str[index .. index + 2], 16) catch unreachable; index += 2; - guid.Data4[1] = std.fmt.parseUnsigned(u8, str[index..index + 2], 16) catch unreachable; + guid.Data4[1] = std.fmt.parseUnsigned(u8, str[index .. index + 2], 16) catch unreachable; index += 2; assert(str[index] == '-'); @@ -316,7 +316,7 @@ pub const GUID = extern struct { var i: usize = 2; while (i < guid.Data4.len) : (i += 1) { - guid.Data4[i] = std.fmt.parseUnsigned(u8, str[index..index + 2], 16) catch unreachable; + guid.Data4[i] = std.fmt.parseUnsigned(u8, str[index .. index + 2], 16) catch unreachable; index += 2; } @@ -363,3 +363,17 @@ pub const FILE_FLAG_RANDOM_ACCESS = 0x10000000; pub const FILE_FLAG_SESSION_AWARE = 0x00800000; pub const FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000; pub const FILE_FLAG_WRITE_THROUGH = 0x80000000; + +pub const SMALL_RECT = extern struct { + Left: SHORT, + Top: SHORT, + Right: SHORT, + Bottom: SHORT, +}; + +pub const COORD = extern struct { + X: SHORT, + Y: SHORT, +}; + +pub const CREATE_UNICODE_ENVIRONMENT = 1024; diff --git a/std/os/windows/kernel32.zig b/std/os/windows/kernel32.zig index 66b5291189..1a7b28eac2 100644 --- a/std/os/windows/kernel32.zig +++ b/std/os/windows/kernel32.zig @@ -4,19 +4,8 @@ pub extern "kernel32" stdcallcc fn CancelIoEx(hFile: HANDLE, lpOverlapped: LPOVE pub extern "kernel32" stdcallcc fn CloseHandle(hObject: HANDLE) BOOL; -pub extern "kernel32" stdcallcc fn CreateDirectoryA(lpPathName: [*]const u8, lpSecurityAttributes: ?*SECURITY_ATTRIBUTES) BOOL; pub extern "kernel32" stdcallcc fn CreateDirectoryW(lpPathName: [*]const u16, lpSecurityAttributes: ?*SECURITY_ATTRIBUTES) BOOL; -pub extern "kernel32" stdcallcc fn CreateFileA( - lpFileName: [*]const u8, // TODO null terminated pointer type - dwDesiredAccess: DWORD, - dwShareMode: DWORD, - lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES, - dwCreationDisposition: DWORD, - dwFlagsAndAttributes: DWORD, - hTemplateFile: ?HANDLE, -) HANDLE; - pub extern "kernel32" stdcallcc fn CreateFileW( lpFileName: [*]const u16, // TODO null terminated pointer type dwDesiredAccess: DWORD, @@ -34,37 +23,32 @@ pub extern "kernel32" stdcallcc fn CreatePipe( nSize: DWORD, ) BOOL; -pub extern "kernel32" stdcallcc fn CreateProcessA( - lpApplicationName: ?LPCSTR, - lpCommandLine: LPSTR, +pub extern "kernel32" stdcallcc fn CreateProcessW( + lpApplicationName: ?LPWSTR, + lpCommandLine: LPWSTR, lpProcessAttributes: ?*SECURITY_ATTRIBUTES, lpThreadAttributes: ?*SECURITY_ATTRIBUTES, bInheritHandles: BOOL, dwCreationFlags: DWORD, lpEnvironment: ?*c_void, - lpCurrentDirectory: ?LPCSTR, - lpStartupInfo: *STARTUPINFOA, + lpCurrentDirectory: ?LPWSTR, + lpStartupInfo: *STARTUPINFOW, lpProcessInformation: *PROCESS_INFORMATION, ) BOOL; -pub extern "kernel32" stdcallcc fn CreateSymbolicLinkA( - lpSymlinkFileName: LPCSTR, - lpTargetFileName: LPCSTR, - dwFlags: DWORD, -) BOOLEAN; +pub extern "kernel32" stdcallcc fn CreateSymbolicLinkW(lpSymlinkFileName: [*]const u16, lpTargetFileName: [*]const u16, dwFlags: DWORD) BOOLEAN; pub extern "kernel32" stdcallcc fn CreateIoCompletionPort(FileHandle: HANDLE, ExistingCompletionPort: ?HANDLE, CompletionKey: ULONG_PTR, NumberOfConcurrentThreads: DWORD) ?HANDLE; pub extern "kernel32" stdcallcc fn CreateThread(lpThreadAttributes: ?LPSECURITY_ATTRIBUTES, dwStackSize: SIZE_T, lpStartAddress: LPTHREAD_START_ROUTINE, lpParameter: ?LPVOID, dwCreationFlags: DWORD, lpThreadId: ?LPDWORD) ?HANDLE; -pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: [*]const u8) BOOL; pub extern "kernel32" stdcallcc fn DeleteFileW(lpFileName: [*]const u16) BOOL; pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn; -pub extern "kernel32" stdcallcc fn FindFirstFileA(lpFileName: LPCSTR, lpFindFileData: *WIN32_FIND_DATAA) HANDLE; +pub extern "kernel32" stdcallcc fn FindFirstFileW(lpFileName: [*]const u16, lpFindFileData: *WIN32_FIND_DATAW) HANDLE; pub extern "kernel32" stdcallcc fn FindClose(hFindFile: HANDLE) BOOL; -pub extern "kernel32" stdcallcc fn FindNextFileA(hFindFile: HANDLE, lpFindFileData: *WIN32_FIND_DATAA) BOOL; +pub extern "kernel32" stdcallcc fn FindNextFileW(hFindFile: HANDLE, lpFindFileData: *WIN32_FIND_DATAW) BOOL; pub extern "kernel32" stdcallcc fn FreeEnvironmentStringsA(penv: [*]u8) BOOL; @@ -72,7 +56,8 @@ pub extern "kernel32" stdcallcc fn GetCommandLineA() LPSTR; pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out_lpMode: *DWORD) BOOL; -pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: DWORD, lpBuffer: ?[*]CHAR) DWORD; +pub extern "kernel32" stdcallcc fn GetConsoleScreenBufferInfo(hConsoleOutput: HANDLE, lpConsoleScreenBufferInfo: *CONSOLE_SCREEN_BUFFER_INFO) BOOL; + pub extern "kernel32" stdcallcc fn GetCurrentDirectoryW(nBufferLength: DWORD, lpBuffer: ?[*]WCHAR) DWORD; pub extern "kernel32" stdcallcc fn GetCurrentThread() HANDLE; @@ -86,12 +71,12 @@ pub extern "kernel32" stdcallcc fn GetExitCodeProcess(hProcess: HANDLE, lpExitCo pub extern "kernel32" stdcallcc fn GetFileSizeEx(hFile: HANDLE, lpFileSize: *LARGE_INTEGER) BOOL; -pub extern "kernel32" stdcallcc fn GetFileAttributesA(lpFileName: [*]const CHAR) DWORD; pub extern "kernel32" stdcallcc fn GetFileAttributesW(lpFileName: [*]const WCHAR) DWORD; -pub extern "kernel32" stdcallcc fn GetModuleFileNameA(hModule: ?HMODULE, lpFilename: [*]u8, nSize: DWORD) DWORD; pub extern "kernel32" stdcallcc fn GetModuleFileNameW(hModule: ?HMODULE, lpFilename: [*]u16, nSize: DWORD) DWORD; +pub extern "kernel32" stdcallcc fn GetModuleHandleW(lpModuleName: ?[*]const WCHAR) HMODULE; + pub extern "kernel32" stdcallcc fn GetLastError() DWORD; pub extern "kernel32" stdcallcc fn GetFileInformationByHandleEx( @@ -101,13 +86,6 @@ pub extern "kernel32" stdcallcc fn GetFileInformationByHandleEx( in_dwBufferSize: DWORD, ) BOOL; -pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA( - hFile: HANDLE, - lpszFilePath: LPSTR, - cchFilePath: DWORD, - dwFlags: DWORD, -) DWORD; - pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleW( hFile: HANDLE, lpszFilePath: [*]u16, @@ -138,12 +116,6 @@ pub extern "kernel32" stdcallcc fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem pub extern "kernel32" stdcallcc fn HeapValidate(hHeap: HANDLE, dwFlags: DWORD, lpMem: ?*const c_void) BOOL; -pub extern "kernel32" stdcallcc fn MoveFileExA( - lpExistingFileName: [*]const u8, - lpNewFileName: [*]const u8, - dwFlags: DWORD, -) BOOL; - pub extern "kernel32" stdcallcc fn MoveFileExW( lpExistingFileName: [*]const u16, lpNewFileName: [*]const u16, @@ -175,7 +147,9 @@ pub extern "kernel32" stdcallcc fn ReadFile( in_out_lpOverlapped: ?*OVERLAPPED, ) BOOL; -pub extern "kernel32" stdcallcc fn RemoveDirectoryA(lpPathName: LPCSTR) BOOL; +pub extern "kernel32" stdcallcc fn RemoveDirectoryW(lpPathName: [*]const u16) BOOL; + +pub extern "kernel32" stdcallcc fn SetConsoleTextAttribute(hConsoleOutput: HANDLE, wAttributes: WORD) BOOL; pub extern "kernel32" stdcallcc fn SetFilePointerEx( in_fFile: HANDLE, @@ -202,8 +176,7 @@ pub extern "kernel32" stdcallcc fn WriteFile( pub extern "kernel32" stdcallcc fn WriteFileEx(hFile: HANDLE, lpBuffer: [*]const u8, nNumberOfBytesToWrite: DWORD, lpOverlapped: LPOVERLAPPED, lpCompletionRoutine: LPOVERLAPPED_COMPLETION_ROUTINE) BOOL; -//TODO: call unicode versions instead of relying on ANSI code page -pub extern "kernel32" stdcallcc fn LoadLibraryA(lpLibFileName: LPCSTR) ?HMODULE; +pub extern "kernel32" stdcallcc fn LoadLibraryW(lpLibFileName: [*]const u16) ?HMODULE; pub extern "kernel32" stdcallcc fn FreeLibrary(hModule: HMODULE) BOOL; @@ -232,3 +205,17 @@ pub const FILE_NOTIFY_CHANGE_LAST_WRITE = 16; pub const FILE_NOTIFY_CHANGE_DIR_NAME = 2; pub const FILE_NOTIFY_CHANGE_FILE_NAME = 1; pub const FILE_NOTIFY_CHANGE_ATTRIBUTES = 4; + + +pub const CONSOLE_SCREEN_BUFFER_INFO = extern struct { + dwSize: COORD, + dwCursorPosition: COORD, + wAttributes: WORD, + srWindow: SMALL_RECT, + dwMaximumWindowSize: COORD, +}; + +pub const FOREGROUND_BLUE = 1; +pub const FOREGROUND_GREEN = 2; +pub const FOREGROUND_RED = 4; +pub const FOREGROUND_INTENSITY = 8; diff --git a/std/os/windows/ntdll.zig b/std/os/windows/ntdll.zig new file mode 100644 index 0000000000..acb78a59f4 --- /dev/null +++ b/std/os/windows/ntdll.zig @@ -0,0 +1,3 @@ +use @import("index.zig"); + +pub extern "NtDll" stdcallcc fn RtlCaptureStackBackTrace(FramesToSkip: DWORD, FramesToCapture: DWORD, BackTrace: **c_void, BackTraceHash: ?*DWORD) WORD; diff --git a/std/os/windows/ole32.zig b/std/os/windows/ole32.zig index 84d8089d07..cc737e344c 100644 --- a/std/os/windows/ole32.zig +++ b/std/os/windows/ole32.zig @@ -5,7 +5,6 @@ pub extern "ole32.dll" stdcallcc fn CoUninitialize() void; pub extern "ole32.dll" stdcallcc fn CoGetCurrentProcess() DWORD; pub extern "ole32.dll" stdcallcc fn CoInitializeEx(pvReserved: LPVOID, dwCoInit: DWORD) HRESULT; - pub const COINIT_APARTMENTTHREADED = COINIT.COINIT_APARTMENTTHREADED; pub const COINIT_MULTITHREADED = COINIT.COINIT_MULTITHREADED; pub const COINIT_DISABLE_OLE1DDE = COINIT.COINIT_DISABLE_OLE1DDE; diff --git a/std/os/windows/shlwapi.zig b/std/os/windows/shlwapi.zig deleted file mode 100644 index 6bccefaf98..0000000000 --- a/std/os/windows/shlwapi.zig +++ /dev/null @@ -1,4 +0,0 @@ -use @import("index.zig"); - -pub extern "shlwapi" stdcallcc fn PathFileExistsA(pszPath: ?LPCTSTR) BOOL; - diff --git a/std/os/windows/user32.zig b/std/os/windows/user32.zig deleted file mode 100644 index 37f9f6f3b8..0000000000 --- a/std/os/windows/user32.zig +++ /dev/null @@ -1,4 +0,0 @@ -use @import("index.zig"); - -pub extern "user32" stdcallcc fn MessageBoxA(hWnd: ?HANDLE, lpText: ?LPCTSTR, lpCaption: ?LPCTSTR, uType: UINT) c_int; - diff --git a/std/os/windows/util.zig b/std/os/windows/util.zig index 72de896996..a489cca163 100644 --- a/std/os/windows/util.zig +++ b/std/os/windows/util.zig @@ -1,6 +1,7 @@ const std = @import("../../index.zig"); const builtin = @import("builtin"); const os = std.os; +const unicode = std.unicode; const windows = std.os.windows; const assert = std.debug.assert; const mem = std.mem; @@ -118,16 +119,14 @@ pub const OpenError = error{ Unexpected, }; -pub fn windowsOpen( - file_path: []const u8, +pub fn windowsOpenW( + file_path_w: [*]const u16, desired_access: windows.DWORD, share_mode: windows.DWORD, creation_disposition: windows.DWORD, flags_and_attrs: windows.DWORD, ) OpenError!windows.HANDLE { - const file_path_w = try sliceToPrefixedFileW(file_path); - - const result = windows.CreateFileW(&file_path_w, desired_access, share_mode, null, creation_disposition, flags_and_attrs, null); + const result = windows.CreateFileW(file_path_w, desired_access, share_mode, null, creation_disposition, flags_and_attrs, null); if (result == windows.INVALID_HANDLE_VALUE) { const err = windows.GetLastError(); @@ -146,42 +145,63 @@ pub fn windowsOpen( return result; } +pub fn windowsOpen( + file_path: []const u8, + desired_access: windows.DWORD, + share_mode: windows.DWORD, + creation_disposition: windows.DWORD, + flags_and_attrs: windows.DWORD, +) OpenError!windows.HANDLE { + const file_path_w = try sliceToPrefixedFileW(file_path); + return windowsOpenW(&file_path_w, desired_access, share_mode, creation_disposition, flags_and_attrs); +} + /// Caller must free result. -pub fn createWindowsEnvBlock(allocator: *mem.Allocator, env_map: *const BufMap) ![]u8 { +pub fn createWindowsEnvBlock(allocator: *mem.Allocator, env_map: *const BufMap) ![]u16 { // count bytes needed - const bytes_needed = x: { - var bytes_needed: usize = 1; // 1 for the final null byte + const max_chars_needed = x: { + var max_chars_needed: usize = 1; // 1 for the final null byte var it = env_map.iterator(); while (it.next()) |pair| { // +1 for '=' // +1 for null byte - bytes_needed += pair.key.len + pair.value.len + 2; + max_chars_needed += pair.key.len + pair.value.len + 2; } - break :x bytes_needed; + break :x max_chars_needed; }; - const result = try allocator.alloc(u8, bytes_needed); + const result = try allocator.alloc(u16, max_chars_needed); errdefer allocator.free(result); var it = env_map.iterator(); var i: usize = 0; while (it.next()) |pair| { - mem.copy(u8, result[i..], pair.key); - i += pair.key.len; + i += try unicode.utf8ToUtf16Le(result[i..], pair.key); result[i] = '='; i += 1; - mem.copy(u8, result[i..], pair.value); - i += pair.value.len; + i += try unicode.utf8ToUtf16Le(result[i..], pair.value); result[i] = 0; i += 1; } result[i] = 0; - return result; + i += 1; + return allocator.shrink(u16, result, i); } -pub fn windowsLoadDll(allocator: *mem.Allocator, dll_path: []const u8) !windows.HMODULE { - const padded_buff = try cstr.addNullByte(allocator, dll_path); - defer allocator.free(padded_buff); - return windows.LoadLibraryA(padded_buff.ptr) orelse error.DllNotFound; +pub fn windowsLoadDllW(dll_path_w: [*]const u16) !windows.HMODULE { + return windows.LoadLibraryW(dll_path_w) orelse { + const err = windows.GetLastError(); + switch (err) { + windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound, + windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound, + windows.ERROR.MOD_NOT_FOUND => return error.FileNotFound, + else => return os.unexpectedErrorWindows(err), + } + }; +} + +pub fn windowsLoadDll(dll_path: []const u8) !windows.HMODULE { + const dll_path_w = try sliceToPrefixedFileW(dll_path); + return windowsLoadDllW(&dll_path_w); } pub fn windowsUnloadDll(hModule: windows.HMODULE) void { @@ -191,27 +211,19 @@ pub fn windowsUnloadDll(hModule: windows.HMODULE) void { test "InvalidDll" { if (builtin.os != builtin.Os.windows) return error.SkipZigTest; - const DllName = "asdf.dll"; - const allocator = std.debug.global_allocator; - const handle = os.windowsLoadDll(allocator, DllName) catch |err| { - assert(err == error.DllNotFound); + const handle = os.windowsLoadDll("asdf.dll") catch |err| { + assert(err == error.FileNotFound); return; }; + @panic("Expected error from function"); } pub fn windowsFindFirstFile( - allocator: *mem.Allocator, dir_path: []const u8, - find_file_data: *windows.WIN32_FIND_DATAA, + find_file_data: *windows.WIN32_FIND_DATAW, ) !windows.HANDLE { - const wild_and_null = []u8{ '\\', '*', 0 }; - const path_with_wild_and_null = try allocator.alloc(u8, dir_path.len + wild_and_null.len); - defer allocator.free(path_with_wild_and_null); - - mem.copy(u8, path_with_wild_and_null, dir_path); - mem.copy(u8, path_with_wild_and_null[dir_path.len..], wild_and_null); - - const handle = windows.FindFirstFileA(path_with_wild_and_null.ptr, find_file_data); + const dir_path_w = try sliceToPrefixedSuffixedFileW(dir_path, []u16{'\\', '*', 0}); + const handle = windows.FindFirstFileW(&dir_path_w, find_file_data); if (handle == windows.INVALID_HANDLE_VALUE) { const err = windows.GetLastError(); @@ -226,8 +238,8 @@ pub fn windowsFindFirstFile( } /// Returns `true` if there was another file, `false` otherwise. -pub fn windowsFindNextFile(handle: windows.HANDLE, find_file_data: *windows.WIN32_FIND_DATAA) !bool { - if (windows.FindNextFileA(handle, find_file_data) == 0) { +pub fn windowsFindNextFile(handle: windows.HANDLE, find_file_data: *windows.WIN32_FIND_DATAW) !bool { + if (windows.FindNextFileW(handle, find_file_data) == 0) { const err = windows.GetLastError(); return switch (err) { windows.ERROR.NO_MORE_FILES => false, @@ -288,8 +300,12 @@ pub fn cStrToPrefixedFileW(s: [*]const u8) ![PATH_MAX_WIDE + 1]u16 { } pub fn sliceToPrefixedFileW(s: []const u8) ![PATH_MAX_WIDE + 1]u16 { + return sliceToPrefixedSuffixedFileW(s, []u16{0}); +} + +pub fn sliceToPrefixedSuffixedFileW(s: []const u8, comptime suffix: []const u16) ![PATH_MAX_WIDE + suffix.len]u16 { // TODO well defined copy elision - var result: [PATH_MAX_WIDE + 1]u16 = undefined; + var result: [PATH_MAX_WIDE + suffix.len]u16 = undefined; // > File I/O functions in the Windows API convert "/" to "\" as part of // > converting the name to an NT-style name, except when using the "\\?\" @@ -297,11 +313,12 @@ pub fn sliceToPrefixedFileW(s: []const u8) ![PATH_MAX_WIDE + 1]u16 { // from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation // Because we want the larger maximum path length for absolute paths, we // disallow forward slashes in zig std lib file functions on Windows. - for (s) |byte| + for (s) |byte| { switch (byte) { - '/', '*', '?', '"', '<', '>', '|' => return error.BadPathName, - else => {}, - }; + '/', '*', '?', '"', '<', '>', '|' => return error.BadPathName, + else => {}, + } + } const start_index = if (mem.startsWith(u8, s, "\\\\") or !os.path.isAbsolute(s)) 0 else blk: { const prefix = []u16{ '\\', '\\', '?', '\\' }; mem.copy(u16, result[0..], prefix); @@ -309,7 +326,7 @@ pub fn sliceToPrefixedFileW(s: []const u8) ![PATH_MAX_WIDE + 1]u16 { }; const end_index = start_index + try std.unicode.utf8ToUtf16Le(result[start_index..], s); assert(end_index <= result.len); - if (end_index == result.len) return error.NameTooLong; - result[end_index] = 0; + if (end_index + suffix.len > result.len) return error.NameTooLong; + mem.copy(u16, result[end_index..], suffix); return result; } diff --git a/std/os/zen.zig b/std/os/zen.zig index 55b6d91128..6ac480b890 100644 --- a/std/os/zen.zig +++ b/std/os/zen.zig @@ -6,32 +6,32 @@ const assert = std.debug.assert; ////////////////////////// pub const Message = struct { -sender: MailboxId, + sender: MailboxId, receiver: MailboxId, - code: usize, - args: [5]usize, - payload: ?[]const u8, + code: usize, + args: [5]usize, + payload: ?[]const u8, pub fn from(mailbox_id: *const MailboxId) Message { - return Message { - .sender = MailboxId.Undefined, + return Message{ + .sender = MailboxId.Undefined, .receiver = mailbox_id.*, - .code = undefined, - .args = undefined, - .payload = null, + .code = undefined, + .args = undefined, + .payload = null, }; } pub fn to(mailbox_id: *const MailboxId, msg_code: usize, args: ...) Message { - var message = Message { - .sender = MailboxId.This, + var message = Message{ + .sender = MailboxId.This, .receiver = mailbox_id.*, - .code = msg_code, - .args = undefined, - .payload = null, + .code = msg_code, + .args = undefined, + .payload = null, }; - assert (args.len <= message.args.len); + assert(args.len <= message.args.len); comptime var i = 0; inline while (i < args.len) : (i += 1) { message.args[i] = args[i]; @@ -111,8 +111,7 @@ pub fn read(fd: i32, buf: [*]u8, count: usize) usize { pub fn write(fd: i32, buf: [*]const u8, count: usize) usize { switch (fd) { STDOUT_FILENO, STDERR_FILENO => { - send(Message.to(Server.Terminal, 1) - .withPayload(buf[0..count])); + send(Message.to(Server.Terminal, 1).withPayload(buf[0..count])); }, else => unreachable, } @@ -124,14 +123,14 @@ pub fn write(fd: i32, buf: [*]const u8, count: usize) usize { /////////////////////////// pub const Syscall = enum(usize) { - exit = 0, - send = 1, - receive = 2, - subscribeIRQ = 3, - inb = 4, - outb = 5, - map = 6, - createThread = 7, + exit = 0, + send = 1, + receive = 2, + subscribeIRQ = 3, + inb = 4, + outb = 5, + map = 6, + createThread = 7, }; //////////////////// diff --git a/std/pdb.zig b/std/pdb.zig new file mode 100644 index 0000000000..907eddff04 --- /dev/null +++ b/std/pdb.zig @@ -0,0 +1,646 @@ +const builtin = @import("builtin"); +const std = @import("index.zig"); +const io = std.io; +const math = std.math; +const mem = std.mem; +const os = std.os; +const warn = std.debug.warn; +const coff = std.coff; + +const ArrayList = std.ArrayList; + +// https://llvm.org/docs/PDB/DbiStream.html#stream-header +pub const DbiStreamHeader = packed struct { + VersionSignature: i32, + VersionHeader: u32, + Age: u32, + GlobalStreamIndex: u16, + BuildNumber: u16, + PublicStreamIndex: u16, + PdbDllVersion: u16, + SymRecordStream: u16, + PdbDllRbld: u16, + ModInfoSize: u32, + SectionContributionSize: u32, + SectionMapSize: u32, + SourceInfoSize: i32, + TypeServerSize: i32, + MFCTypeServerIndex: u32, + OptionalDbgHeaderSize: i32, + ECSubstreamSize: i32, + Flags: u16, + Machine: u16, + Padding: u32, +}; + +pub const SectionContribEntry = packed struct { + Section: u16, + Padding1: [2]u8, + Offset: u32, + Size: u32, + Characteristics: u32, + ModuleIndex: u16, + Padding2: [2]u8, + DataCrc: u32, + RelocCrc: u32, +}; + +pub const ModInfo = packed struct { + Unused1: u32, + SectionContr: SectionContribEntry, + Flags: u16, + ModuleSymStream: u16, + SymByteSize: u32, + C11ByteSize: u32, + C13ByteSize: u32, + SourceFileCount: u16, + Padding: [2]u8, + Unused2: u32, + SourceFileNameIndex: u32, + PdbFilePathNameIndex: u32, + // These fields are variable length + //ModuleName: char[], + //ObjFileName: char[], +}; + +pub const SectionMapHeader = packed struct { + Count: u16, /// Number of segment descriptors + LogCount: u16, /// Number of logical segment descriptors +}; + +pub const SectionMapEntry = packed struct { + Flags: u16 , /// See the SectionMapEntryFlags enum below. + Ovl: u16 , /// Logical overlay number + Group: u16 , /// Group index into descriptor array. + Frame: u16 , + SectionName: u16 , /// Byte index of segment / group name in string table, or 0xFFFF. + ClassName: u16 , /// Byte index of class in string table, or 0xFFFF. + Offset: u32 , /// Byte offset of the logical segment within physical segment. If group is set in flags, this is the offset of the group. + SectionLength: u32 , /// Byte count of the segment or group. +}; + +pub const StreamType = enum(u16) { + Pdb = 1, + Tpi = 2, + Dbi = 3, + Ipi = 4, +}; + +/// Duplicate copy of SymbolRecordKind, but using the official CV names. Useful +/// for reference purposes and when dealing with unknown record types. +pub const SymbolKind = packed enum(u16) { + S_COMPILE = 1, + S_REGISTER_16t = 2, + S_CONSTANT_16t = 3, + S_UDT_16t = 4, + S_SSEARCH = 5, + S_SKIP = 7, + S_CVRESERVE = 8, + S_OBJNAME_ST = 9, + S_ENDARG = 10, + S_COBOLUDT_16t = 11, + S_MANYREG_16t = 12, + S_RETURN = 13, + S_ENTRYTHIS = 14, + S_BPREL16 = 256, + S_LDATA16 = 257, + S_GDATA16 = 258, + S_PUB16 = 259, + S_LPROC16 = 260, + S_GPROC16 = 261, + S_THUNK16 = 262, + S_BLOCK16 = 263, + S_WITH16 = 264, + S_LABEL16 = 265, + S_CEXMODEL16 = 266, + S_VFTABLE16 = 267, + S_REGREL16 = 268, + S_BPREL32_16t = 512, + S_LDATA32_16t = 513, + S_GDATA32_16t = 514, + S_PUB32_16t = 515, + S_LPROC32_16t = 516, + S_GPROC32_16t = 517, + S_THUNK32_ST = 518, + S_BLOCK32_ST = 519, + S_WITH32_ST = 520, + S_LABEL32_ST = 521, + S_CEXMODEL32 = 522, + S_VFTABLE32_16t = 523, + S_REGREL32_16t = 524, + S_LTHREAD32_16t = 525, + S_GTHREAD32_16t = 526, + S_SLINK32 = 527, + S_LPROCMIPS_16t = 768, + S_GPROCMIPS_16t = 769, + S_PROCREF_ST = 1024, + S_DATAREF_ST = 1025, + S_ALIGN = 1026, + S_LPROCREF_ST = 1027, + S_OEM = 1028, + S_TI16_MAX = 4096, + S_REGISTER_ST = 4097, + S_CONSTANT_ST = 4098, + S_UDT_ST = 4099, + S_COBOLUDT_ST = 4100, + S_MANYREG_ST = 4101, + S_BPREL32_ST = 4102, + S_LDATA32_ST = 4103, + S_GDATA32_ST = 4104, + S_PUB32_ST = 4105, + S_LPROC32_ST = 4106, + S_GPROC32_ST = 4107, + S_VFTABLE32 = 4108, + S_REGREL32_ST = 4109, + S_LTHREAD32_ST = 4110, + S_GTHREAD32_ST = 4111, + S_LPROCMIPS_ST = 4112, + S_GPROCMIPS_ST = 4113, + S_COMPILE2_ST = 4115, + S_MANYREG2_ST = 4116, + S_LPROCIA64_ST = 4117, + S_GPROCIA64_ST = 4118, + S_LOCALSLOT_ST = 4119, + S_PARAMSLOT_ST = 4120, + S_ANNOTATION = 4121, + S_GMANPROC_ST = 4122, + S_LMANPROC_ST = 4123, + S_RESERVED1 = 4124, + S_RESERVED2 = 4125, + S_RESERVED3 = 4126, + S_RESERVED4 = 4127, + S_LMANDATA_ST = 4128, + S_GMANDATA_ST = 4129, + S_MANFRAMEREL_ST = 4130, + S_MANREGISTER_ST = 4131, + S_MANSLOT_ST = 4132, + S_MANMANYREG_ST = 4133, + S_MANREGREL_ST = 4134, + S_MANMANYREG2_ST = 4135, + S_MANTYPREF = 4136, + S_UNAMESPACE_ST = 4137, + S_ST_MAX = 4352, + S_WITH32 = 4356, + S_MANYREG = 4362, + S_LPROCMIPS = 4372, + S_GPROCMIPS = 4373, + S_MANYREG2 = 4375, + S_LPROCIA64 = 4376, + S_GPROCIA64 = 4377, + S_LOCALSLOT = 4378, + S_PARAMSLOT = 4379, + S_MANFRAMEREL = 4382, + S_MANREGISTER = 4383, + S_MANSLOT = 4384, + S_MANMANYREG = 4385, + S_MANREGREL = 4386, + S_MANMANYREG2 = 4387, + S_UNAMESPACE = 4388, + S_DATAREF = 4390, + S_ANNOTATIONREF = 4392, + S_TOKENREF = 4393, + S_GMANPROC = 4394, + S_LMANPROC = 4395, + S_ATTR_FRAMEREL = 4398, + S_ATTR_REGISTER = 4399, + S_ATTR_REGREL = 4400, + S_ATTR_MANYREG = 4401, + S_SEPCODE = 4402, + S_LOCAL_2005 = 4403, + S_DEFRANGE_2005 = 4404, + S_DEFRANGE2_2005 = 4405, + S_DISCARDED = 4411, + S_LPROCMIPS_ID = 4424, + S_GPROCMIPS_ID = 4425, + S_LPROCIA64_ID = 4426, + S_GPROCIA64_ID = 4427, + S_DEFRANGE_HLSL = 4432, + S_GDATA_HLSL = 4433, + S_LDATA_HLSL = 4434, + S_LOCAL_DPC_GROUPSHARED = 4436, + S_DEFRANGE_DPC_PTR_TAG = 4439, + S_DPC_SYM_TAG_MAP = 4440, + S_ARMSWITCHTABLE = 4441, + S_POGODATA = 4444, + S_INLINESITE2 = 4445, + S_MOD_TYPEREF = 4447, + S_REF_MINIPDB = 4448, + S_PDBMAP = 4449, + S_GDATA_HLSL32 = 4450, + S_LDATA_HLSL32 = 4451, + S_GDATA_HLSL32_EX = 4452, + S_LDATA_HLSL32_EX = 4453, + S_FASTLINK = 4455, + S_INLINEES = 4456, + S_END = 6, + S_INLINESITE_END = 4430, + S_PROC_ID_END = 4431, + S_THUNK32 = 4354, + S_TRAMPOLINE = 4396, + S_SECTION = 4406, + S_COFFGROUP = 4407, + S_EXPORT = 4408, + S_LPROC32 = 4367, + S_GPROC32 = 4368, + S_LPROC32_ID = 4422, + S_GPROC32_ID = 4423, + S_LPROC32_DPC = 4437, + S_LPROC32_DPC_ID = 4438, + S_REGISTER = 4358, + S_PUB32 = 4366, + S_PROCREF = 4389, + S_LPROCREF = 4391, + S_ENVBLOCK = 4413, + S_INLINESITE = 4429, + S_LOCAL = 4414, + S_DEFRANGE = 4415, + S_DEFRANGE_SUBFIELD = 4416, + S_DEFRANGE_REGISTER = 4417, + S_DEFRANGE_FRAMEPOINTER_REL = 4418, + S_DEFRANGE_SUBFIELD_REGISTER = 4419, + S_DEFRANGE_FRAMEPOINTER_REL_FULL_SCOPE = 4420, + S_DEFRANGE_REGISTER_REL = 4421, + S_BLOCK32 = 4355, + S_LABEL32 = 4357, + S_OBJNAME = 4353, + S_COMPILE2 = 4374, + S_COMPILE3 = 4412, + S_FRAMEPROC = 4114, + S_CALLSITEINFO = 4409, + S_FILESTATIC = 4435, + S_HEAPALLOCSITE = 4446, + S_FRAMECOOKIE = 4410, + S_CALLEES = 4442, + S_CALLERS = 4443, + S_UDT = 4360, + S_COBOLUDT = 4361, + S_BUILDINFO = 4428, + S_BPREL32 = 4363, + S_REGREL32 = 4369, + S_CONSTANT = 4359, + S_MANCONSTANT = 4397, + S_LDATA32 = 4364, + S_GDATA32 = 4365, + S_LMANDATA = 4380, + S_GMANDATA = 4381, + S_LTHREAD32 = 4370, + S_GTHREAD32 = 4371, +}; + +pub const TypeIndex = u32; + +pub const ProcSym = packed struct { + Parent: u32 , + End: u32 , + Next: u32 , + CodeSize: u32 , + DbgStart: u32 , + DbgEnd: u32 , + FunctionType: TypeIndex , + CodeOffset: u32, + Segment: u16, + Flags: ProcSymFlags, + // following is a null terminated string + // Name: [*]u8, +}; + +pub const ProcSymFlags = packed struct { + HasFP: bool, + HasIRET: bool, + HasFRET: bool, + IsNoReturn: bool, + IsUnreachable: bool, + HasCustomCallingConv: bool, + IsNoInline: bool, + HasOptimizedDebugInfo: bool, +}; + +pub const SectionContrSubstreamVersion = enum(u32) { + Ver60 = 0xeffe0000 + 19970605, + V2 = 0xeffe0000 + 20140516 +}; + +pub const RecordPrefix = packed struct { + RecordLen: u16, /// Record length, starting from &RecordKind. + RecordKind: SymbolKind, /// Record kind enum (SymRecordKind or TypeRecordKind) +}; + +pub const LineFragmentHeader = packed struct { + RelocOffset: u32, /// Code offset of line contribution. + RelocSegment: u16, /// Code segment of line contribution. + Flags: LineFlags, + CodeSize: u32, /// Code size of this line contribution. +}; + +pub const LineFlags = packed struct { + LF_HaveColumns: bool, /// CV_LINES_HAVE_COLUMNS + unused: u15, +}; + +/// The following two variable length arrays appear immediately after the +/// header. The structure definitions follow. +/// LineNumberEntry Lines[NumLines]; +/// ColumnNumberEntry Columns[NumLines]; +pub const LineBlockFragmentHeader = packed struct { + /// Offset of FileChecksum entry in File + /// checksums buffer. The checksum entry then + /// contains another offset into the string + /// table of the actual name. + NameIndex: u32, + NumLines: u32, + BlockSize: u32, /// code size of block, in bytes +}; + + +pub const LineNumberEntry = packed struct { + Offset: u32, /// Offset to start of code bytes for line number + Flags: u32, + + /// TODO runtime crash when I make the actual type of Flags this + const Flags = packed struct { + Start: u24, + End: u7, + IsStatement: bool, + }; +}; + +pub const ColumnNumberEntry = packed struct { + StartColumn: u16, + EndColumn: u16, +}; + +/// Checksum bytes follow. +pub const FileChecksumEntryHeader = packed struct { + FileNameOffset: u32, /// Byte offset of filename in global string table. + ChecksumSize: u8, /// Number of bytes of checksum. + ChecksumKind: u8, /// FileChecksumKind +}; + +pub const DebugSubsectionKind = packed enum(u32) { + None = 0, + Symbols = 0xf1, + Lines = 0xf2, + StringTable = 0xf3, + FileChecksums = 0xf4, + FrameData = 0xf5, + InlineeLines = 0xf6, + CrossScopeImports = 0xf7, + CrossScopeExports = 0xf8, + + // These appear to relate to .Net assembly info. + ILLines = 0xf9, + FuncMDTokenMap = 0xfa, + TypeMDTokenMap = 0xfb, + MergedAssemblyInput = 0xfc, + + CoffSymbolRVA = 0xfd, +}; + + +pub const DebugSubsectionHeader = packed struct { + Kind: DebugSubsectionKind, /// codeview::DebugSubsectionKind enum + Length: u32, /// number of bytes occupied by this record. +}; + + +pub const PDBStringTableHeader = packed struct { + Signature: u32, /// PDBStringTableSignature + HashVersion: u32, /// 1 or 2 + ByteSize: u32, /// Number of bytes of names buffer. +}; + +pub const Pdb = struct { + in_file: os.File, + allocator: *mem.Allocator, + coff: *coff.Coff, + string_table: *MsfStream, + dbi: *MsfStream, + + msf: Msf, + + pub fn openFile(self: *Pdb, coff_ptr: *coff.Coff, file_name: []u8) !void { + self.in_file = try os.File.openRead(file_name); + self.allocator = coff_ptr.allocator; + self.coff = coff_ptr; + + try self.msf.openFile(self.allocator, self.in_file); + } + + pub fn getStreamById(self: *Pdb, id: u32) ?*MsfStream { + if (id >= self.msf.streams.len) + return null; + return &self.msf.streams[id]; + } + + pub fn getStream(self: *Pdb, stream: StreamType) ?*MsfStream { + const id = @enumToInt(stream); + return self.getStreamById(id); + } +}; + +// see https://llvm.org/docs/PDB/MsfFile.html +const Msf = struct { + directory: MsfStream, + streams: []MsfStream, + + fn openFile(self: *Msf, allocator: *mem.Allocator, file: os.File) !void { + var file_stream = io.FileInStream.init(file); + const in = &file_stream.stream; + + var superblock: SuperBlock = undefined; + try in.readStruct(SuperBlock, &superblock); + + if (!mem.eql(u8, superblock.FileMagic, SuperBlock.file_magic)) + return error.InvalidDebugInfo; + + switch (superblock.BlockSize) { + // llvm only supports 4096 but we can handle any of these values + 512, 1024, 2048, 4096 => {}, + else => return error.InvalidDebugInfo + } + + if (superblock.NumBlocks * superblock.BlockSize != try file.getEndPos()) + return error.InvalidDebugInfo; + + self.directory = try MsfStream.init( + superblock.BlockSize, + blockCountFromSize(superblock.NumDirectoryBytes, superblock.BlockSize), + superblock.BlockSize * superblock.BlockMapAddr, + file, + allocator, + ); + + const stream_count = try self.directory.stream.readIntLe(u32); + + const stream_sizes = try allocator.alloc(u32, stream_count); + for (stream_sizes) |*s| { + const size = try self.directory.stream.readIntLe(u32); + s.* = blockCountFromSize(size, superblock.BlockSize); + } + + self.streams = try allocator.alloc(MsfStream, stream_count); + for (self.streams) |*stream, i| { + stream.* = try MsfStream.init( + superblock.BlockSize, + stream_sizes[i], + // MsfStream.init expects the file to be at the part where it reads [N]u32 + try file.getPos(), + file, + allocator, + ); + } + } +}; + +fn blockCountFromSize(size: u32, block_size: u32) u32 { + return (size + block_size - 1) / block_size; +} + +// https://llvm.org/docs/PDB/MsfFile.html#the-superblock +const SuperBlock = packed struct { + /// The LLVM docs list a space between C / C++ but empirically this is not the case. + const file_magic = "Microsoft C/C++ MSF 7.00\r\n\x1a\x44\x53\x00\x00\x00"; + + FileMagic: [file_magic.len]u8, + + /// The block size of the internal file system. Valid values are 512, 1024, + /// 2048, and 4096 bytes. Certain aspects of the MSF file layout vary depending + /// on the block sizes. For the purposes of LLVM, we handle only block sizes of + /// 4KiB, and all further discussion assumes a block size of 4KiB. + BlockSize: u32, + + /// The index of a block within the file, at which begins a bitfield representing + /// the set of all blocks within the file which are “free” (i.e. the data within + /// that block is not used). See The Free Block Map for more information. Important: + /// FreeBlockMapBlock can only be 1 or 2! + FreeBlockMapBlock: u32, + + /// The total number of blocks in the file. NumBlocks * BlockSize should equal the + /// size of the file on disk. + NumBlocks: u32, + + /// The size of the stream directory, in bytes. The stream directory contains + /// information about each stream’s size and the set of blocks that it occupies. + /// It will be described in more detail later. + NumDirectoryBytes: u32, + + Unknown: u32, + + /// The index of a block within the MSF file. At this block is an array of + /// ulittle32_t’s listing the blocks that the stream directory resides on. + /// For large MSF files, the stream directory (which describes the block + /// layout of each stream) may not fit entirely on a single block. As a + /// result, this extra layer of indirection is introduced, whereby this + /// block contains the list of blocks that the stream directory occupies, + /// and the stream directory itself can be stitched together accordingly. + /// The number of ulittle32_t’s in this array is given by + /// ceil(NumDirectoryBytes / BlockSize). + BlockMapAddr: u32, + +}; + +const MsfStream = struct { + in_file: os.File, + pos: usize, + blocks: []u32, + block_size: u32, + + /// Implementation of InStream trait for Pdb.MsfStream + stream: Stream, + + pub const Error = @typeOf(read).ReturnType.ErrorSet; + pub const Stream = io.InStream(Error); + + fn init(block_size: u32, block_count: u32, pos: usize, file: os.File, allocator: *mem.Allocator) !MsfStream { + var stream = MsfStream { + .in_file = file, + .pos = 0, + .blocks = try allocator.alloc(u32, block_count), + .block_size = block_size, + .stream = Stream { + .readFn = readFn, + }, + }; + + var file_stream = io.FileInStream.init(file); + const in = &file_stream.stream; + try file.seekTo(pos); + + var i: u32 = 0; + while (i < block_count) : (i += 1) { + stream.blocks[i] = try in.readIntLe(u32); + } + + return stream; + } + + fn readNullTermString(self: *MsfStream, allocator: *mem.Allocator) ![]u8 { + var list = ArrayList(u8).init(allocator); + defer list.deinit(); + while (true) { + const byte = try self.stream.readByte(); + if (byte == 0) { + return list.toSlice(); + } + try list.append(byte); + } + } + + fn read(self: *MsfStream, buffer: []u8) !usize { + var block_id = self.pos / self.block_size; + var block = self.blocks[block_id]; + var offset = self.pos % self.block_size; + + try self.in_file.seekTo(block * self.block_size + offset); + var file_stream = io.FileInStream.init(self.in_file); + const in = &file_stream.stream; + + var size: usize = 0; + for (buffer) |*byte| { + byte.* = try in.readByte(); + + offset += 1; + size += 1; + + // If we're at the end of a block, go to the next one. + if (offset == self.block_size) { + offset = 0; + block_id += 1; + block = self.blocks[block_id]; + try self.in_file.seekTo(block * self.block_size); + } + } + + self.pos += size; + return size; + } + + fn seekForward(self: *MsfStream, len: usize) !void { + self.pos += len; + if (self.pos >= self.blocks.len * self.block_size) + return error.EOF; + } + + fn seekTo(self: *MsfStream, len: usize) !void { + self.pos = len; + if (self.pos >= self.blocks.len * self.block_size) + return error.EOF; + } + + fn getSize(self: *const MsfStream) usize { + return self.blocks.len * self.block_size; + } + + fn getFilePos(self: MsfStream) usize { + const block_id = self.pos / self.block_size; + const block = self.blocks[block_id]; + const offset = self.pos % self.block_size; + + return block * self.block_size + offset; + } + + fn readFn(in_stream: *Stream, buffer: []u8) Error!usize { + const self = @fieldParentPtr(MsfStream, "stream", in_stream); + return self.read(buffer); + } +}; diff --git a/std/rb.zig b/std/rb.zig index d523069846..e42efc32af 100644 --- a/std/rb.zig +++ b/std/rb.zig @@ -9,9 +9,7 @@ const Color = enum(u1) { const Red = Color.Red; const Black = Color.Black; -const ReplaceError = error { - NotEqual, -}; +const ReplaceError = error{NotEqual}; /// Insert this into your struct that you want to add to a red-black tree. /// Do not use a pointer. Turn the *rb.Node results of the functions in rb @@ -21,13 +19,15 @@ const ReplaceError = error { /// node: rb.Node, /// value: i32, /// }; -/// fn number(node: *Node) Number { +/// fn number(node: *rb.Node) Number { /// return @fieldParentPtr(Number, "node", node); /// } pub const Node = struct { left: ?*Node, right: ?*Node, - parent_and_color: usize, /// parent | color + + /// parent | color + parent_and_color: usize, pub fn next(constnode: *Node) ?*Node { var node = constnode; @@ -130,7 +130,7 @@ pub const Node = struct { pub const Tree = struct { root: ?*Node, - compareFn: fn(*Node, *Node) mem.Compare, + compareFn: fn (*Node, *Node) mem.Compare, /// If you have a need for a version that caches this, please file a bug. pub fn first(tree: *Tree) ?*Node { @@ -180,7 +180,7 @@ pub const Tree = struct { while (node.get_parent()) |*parent| { if (parent.*.is_black()) break; - // the root is always black + // the root is always black var grandpa = parent.*.get_parent() orelse unreachable; if (parent.* == grandpa.left) { @@ -206,7 +206,7 @@ pub const Tree = struct { } } else { var maybe_uncle = grandpa.left; - + if (maybe_uncle) |uncle| { if (uncle.is_black()) break; @@ -259,7 +259,7 @@ pub const Tree = struct { if (node.left == null) { next = node.right.?; // Not both null as per above } else if (node.right == null) { - next = node.left.?; // Not both null as per above + next = node.left.?; // Not both null as per above } else next = node.right.?.get_first(); // Just checked for null above @@ -313,7 +313,7 @@ pub const Tree = struct { var parent = maybe_parent.?; if (node == parent.left) { var sibling = parent.right.?; // Same number of black nodes. - + if (sibling.is_red()) { sibling.set_color(Black); parent.set_color(Red); @@ -321,7 +321,8 @@ pub const Tree = struct { sibling = parent.right.?; // Just rotated } if ((if (sibling.left) |n| n.is_black() else true) and - (if (sibling.right) |n| n.is_black() else true)) { + (if (sibling.right) |n| n.is_black() else true)) + { sibling.set_color(Red); node = parent; maybe_parent = parent.get_parent(); @@ -341,7 +342,7 @@ pub const Tree = struct { break; } else { var sibling = parent.left.?; // Same number of black nodes. - + if (sibling.is_red()) { sibling.set_color(Black); parent.set_color(Red); @@ -349,7 +350,8 @@ pub const Tree = struct { sibling = parent.left.?; // Just rotated } if ((if (sibling.left) |n| n.is_black() else true) and - (if (sibling.right) |n| n.is_black() else true)) { + (if (sibling.right) |n| n.is_black() else true)) + { sibling.set_color(Red); node = parent; maybe_parent = parent.get_parent(); @@ -397,7 +399,7 @@ pub const Tree = struct { new.* = old.*; } - pub fn init(tree: *Tree, f: fn(*Node, *Node) mem.Compare) void { + pub fn init(tree: *Tree, f: fn (*Node, *Node) mem.Compare) void { tree.root = null; tree.compareFn = f; } diff --git a/std/special/build_runner.zig b/std/special/build_runner.zig index 982c60aed8..8cf237f634 100644 --- a/std/special/build_runner.zig +++ b/std/special/build_runner.zig @@ -49,14 +49,14 @@ pub fn main() !void { var stderr_file = io.getStdErr(); var stderr_file_stream: io.FileOutStream = undefined; - var stderr_stream = if (stderr_file) |*f| x: { + var stderr_stream = if (stderr_file) |f| x: { stderr_file_stream = io.FileOutStream.init(f); break :x &stderr_file_stream.stream; } else |err| err; var stdout_file = io.getStdOut(); var stdout_file_stream: io.FileOutStream = undefined; - var stdout_stream = if (stdout_file) |*f| x: { + var stdout_stream = if (stdout_file) |f| x: { stdout_file_stream = io.FileOutStream.init(f); break :x &stdout_file_stream.stream; } else |err| err; diff --git a/std/zig/parse.zig b/std/zig/parse.zig index fb49d2a2ba..b9c05f8da0 100644 --- a/std/zig/parse.zig +++ b/std/zig/parse.zig @@ -340,7 +340,12 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { const node_ptr = try ctx.container_decl.fields_and_decls.addOne(); node_ptr.* = &node.base; - stack.append(State{ .FieldListCommaOrEnd = ctx.container_decl }) catch unreachable; + try stack.append(State{ + .FieldListCommaOrEnd = FieldCtx{ + .doc_comments = &node.doc_comments, + .container_decl = ctx.container_decl, + }, + }); try stack.append(State{ .Expression = OptionalCtx{ .Required = &node.type_expr } }); try stack.append(State{ .ExpectToken = Token.Id.Colon }); continue; @@ -458,7 +463,12 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { const node_ptr = try container_decl.fields_and_decls.addOne(); node_ptr.* = &node.base; - try stack.append(State{ .FieldListCommaOrEnd = container_decl }); + try stack.append(State{ + .FieldListCommaOrEnd = FieldCtx{ + .doc_comments = &node.doc_comments, + .container_decl = container_decl, + }, + }); try stack.append(State{ .TypeExprBegin = OptionalCtx{ .Required = &node.type_expr } }); try stack.append(State{ .ExpectToken = Token.Id.Colon }); continue; @@ -473,7 +483,12 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }); try container_decl.fields_and_decls.push(&node.base); - stack.append(State{ .FieldListCommaOrEnd = container_decl }) catch unreachable; + try stack.append(State{ + .FieldListCommaOrEnd = FieldCtx{ + .doc_comments = &node.doc_comments, + .container_decl = container_decl, + }, + }); try stack.append(State{ .FieldInitValue = OptionalCtx{ .RequiredNull = &node.value_expr } }); try stack.append(State{ .TypeExprBegin = OptionalCtx{ .RequiredNull = &node.type_expr } }); try stack.append(State{ .IfToken = Token.Id.Colon }); @@ -488,7 +503,12 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }); try container_decl.fields_and_decls.push(&node.base); - stack.append(State{ .FieldListCommaOrEnd = container_decl }) catch unreachable; + try stack.append(State{ + .FieldListCommaOrEnd = FieldCtx{ + .doc_comments = &node.doc_comments, + .container_decl = container_decl, + }, + }); try stack.append(State{ .Expression = OptionalCtx{ .RequiredNull = &node.value } }); try stack.append(State{ .IfToken = Token.Id.Equal }); continue; @@ -1265,17 +1285,35 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, } }, - State.FieldListCommaOrEnd => |container_decl| { - switch (expectCommaOrEnd(&tok_it, &tree, Token.Id.RBrace)) { - ExpectCommaOrEndResult.end_token => |maybe_end| if (maybe_end) |end| { - container_decl.rbrace_token = end; - continue; - } else { - try stack.append(State{ .ContainerDecl = container_decl }); + State.FieldListCommaOrEnd => |field_ctx| { + const end_token = nextToken(&tok_it, &tree); + const end_token_index = end_token.index; + const end_token_ptr = end_token.ptr; + switch (end_token_ptr.id) { + Token.Id.Comma => { + if (eatToken(&tok_it, &tree, Token.Id.DocComment)) |doc_comment_token| { + const loc = tree.tokenLocation(end_token_ptr.end, doc_comment_token); + if (loc.line == 0) { + try pushDocComment(arena, doc_comment_token, field_ctx.doc_comments); + } else { + prevToken(&tok_it, &tree); + } + } + + try stack.append(State{ .ContainerDecl = field_ctx.container_decl }); continue; }, - ExpectCommaOrEndResult.parse_error => |e| { - try tree.errors.push(e); + Token.Id.RBrace => { + field_ctx.container_decl.rbrace_token = end_token_index; + continue; + }, + else => { + try tree.errors.push(Error{ + .ExpectedCommaOrEnd = Error.ExpectedCommaOrEnd{ + .token = end_token_index, + .end_id = end_token_ptr.id, + }, + }); return tree; }, } @@ -2813,6 +2851,11 @@ const ExprListCtx = struct { ptr: *TokenIndex, }; +const FieldCtx = struct { + container_decl: *ast.Node.ContainerDecl, + doc_comments: *?*ast.Node.DocComment, +}; + fn ListSave(comptime List: type) type { return struct { list: *List, @@ -2950,7 +2993,7 @@ const State = union(enum) { ExprListCommaOrEnd: ExprListCtx, FieldInitListItemOrEnd: ListSave(ast.Node.SuffixOp.Op.InitList), FieldInitListCommaOrEnd: ListSave(ast.Node.SuffixOp.Op.InitList), - FieldListCommaOrEnd: *ast.Node.ContainerDecl, + FieldListCommaOrEnd: FieldCtx, FieldInitValue: OptionalCtx, ErrorTagListItemOrEnd: ListSave(ast.Node.ErrorSetDecl.DeclList), ErrorTagListCommaOrEnd: ListSave(ast.Node.ErrorSetDecl.DeclList), diff --git a/std/zig/parser_test.zig b/std/zig/parser_test.zig index 582bffdf3d..7f3ce7bd8a 100644 --- a/std/zig/parser_test.zig +++ b/std/zig/parser_test.zig @@ -1,3 +1,20 @@ +test "zig fmt: correctly move doc comments on struct fields" { + try testTransform( + \\pub const section_64 = extern struct { + \\ sectname: [16]u8, /// name of this section + \\ segname: [16]u8, /// segment this section goes in + \\}; + , + \\pub const section_64 = extern struct { + \\ /// name of this section + \\ sectname: [16]u8, + \\ /// segment this section goes in + \\ segname: [16]u8, + \\}; + \\ + ); +} + test "zig fmt: preserve space between async fn definitions" { try testCanonical( \\async fn a() void {} @@ -1848,7 +1865,7 @@ var fixed_buffer_mem: [100 * 1024]u8 = undefined; fn testParse(source: []const u8, allocator: *mem.Allocator, anything_changed: *bool) ![]u8 { var stderr_file = try io.getStdErr(); - var stderr = &io.FileOutStream.init(&stderr_file).stream; + var stderr = &io.FileOutStream.init(stderr_file).stream; var tree = try std.zig.parse(allocator, source); defer tree.deinit(); diff --git a/test/behavior.zig b/test/behavior.zig index 5a26e206bf..a092889099 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -11,6 +11,7 @@ comptime { _ = @import("cases/bugs/1111.zig"); _ = @import("cases/bugs/1230.zig"); _ = @import("cases/bugs/1277.zig"); + _ = @import("cases/bugs/1421.zig"); _ = @import("cases/bugs/394.zig"); _ = @import("cases/bugs/655.zig"); _ = @import("cases/bugs/656.zig"); diff --git a/test/cases/bugs/1421.zig b/test/cases/bugs/1421.zig new file mode 100644 index 0000000000..fcbb8b70e4 --- /dev/null +++ b/test/cases/bugs/1421.zig @@ -0,0 +1,14 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; + +const S = struct { + fn method() builtin.TypeInfo { + return @typeInfo(S); + } +}; + +test "functions with return type required to be comptime are generic" { + const ti = S.method(); + assert(builtin.TypeId(ti) == builtin.TypeId.Struct); +} diff --git a/test/cases/cast.zig b/test/cases/cast.zig index df37bd1dd9..2b455ccc43 100644 --- a/test/cases/cast.zig +++ b/test/cases/cast.zig @@ -487,12 +487,41 @@ fn MakeType(comptime T: type) type { } test "implicit cast from *[N]T to ?[*]T" { - var x: ?[*]u16 = null; - var y: [4]u16 = [4]u16 {0, 1, 2, 3}; + var x: ?[*]u16 = null; + var y: [4]u16 = [4]u16{ 0, 1, 2, 3 }; - x = &y; - assert(std.mem.eql(u16, x.?[0..4], y[0..4])); - x.?[0] = 8; - y[3] = 6; - assert(std.mem.eql(u16, x.?[0..4], y[0..4])); -} \ No newline at end of file + x = &y; + assert(std.mem.eql(u16, x.?[0..4], y[0..4])); + x.?[0] = 8; + y[3] = 6; + assert(std.mem.eql(u16, x.?[0..4], y[0..4])); +} + +test "implicit cast from *T to ?*c_void" { + var a: u8 = 1; + incrementVoidPtrValue(&a); + std.debug.assert(a == 2); +} + +fn incrementVoidPtrValue(value: ?*c_void) void { + @ptrCast(*u8, value.?).* += 1; +} + +test "implicit cast from [*]T to ?*c_void" { + var a = []u8{ 3, 2, 1 }; + incrementVoidPtrArray(a[0..].ptr, 3); + assert(std.mem.eql(u8, a, []u8{ 4, 3, 2 })); +} + +fn incrementVoidPtrArray(array: ?*c_void, len: usize) void { + var n: usize = 0; + while (n < len) : (n += 1) { + @ptrCast([*]u8, array.?)[n] += 1; + } +} + +test "*usize to *void" { + var i = usize(0); + var v = @ptrCast(*void, &i); + v.* = {}; +} diff --git a/test/cases/eval.zig b/test/cases/eval.zig index 9da475994d..695cd4df77 100644 --- a/test/cases/eval.zig +++ b/test/cases/eval.zig @@ -652,3 +652,25 @@ fn loopNTimes(comptime n: usize) void { comptime var i = 0; inline while (i < n) : (i += 1) {} } + +test "variable inside inline loop that has different types on different iterations" { + testVarInsideInlineLoop(true, u32(42)); +} + +fn testVarInsideInlineLoop(args: ...) void { + comptime var i = 0; + inline while (i < args.len) : (i += 1) { + const x = args[i]; + if (i == 0) assert(x); + if (i == 1) assert(x == 42); + } +} + +test "inline for with same type but different values" { + var res: usize = 0; + inline for ([]type{ [2]u8, [1]u8, [2]u8 }) |T| { + var a: T = undefined; + res += a.len; + } + assert(res == 5); +} diff --git a/test/cases/for.zig b/test/cases/for.zig index 59d90c1b85..f943515cc1 100644 --- a/test/cases/for.zig +++ b/test/cases/for.zig @@ -71,8 +71,7 @@ fn testBreakOuter() void { var array = "aoeu"; var count: usize = 0; outer: for (array) |_| { - // TODO shouldn't get error for redeclaring "_" - for (array) |_2| { + for (array) |_| { count += 1; break :outer; } @@ -89,8 +88,7 @@ fn testContinueOuter() void { var array = "aoeu"; var counter: usize = 0; outer: for (array) |_| { - // TODO shouldn't get error for redeclaring "_" - for (array) |_2| { + for (array) |_| { counter += 1; continue :outer; } diff --git a/test/compare_output.zig b/test/compare_output.zig index a18a78b419..bcd9d15b9c 100644 --- a/test/compare_output.zig +++ b/test/compare_output.zig @@ -19,7 +19,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ \\pub fn main() void { \\ privateFunction(); - \\ const stdout = &(FileOutStream.init(&(getStdOut() catch unreachable)).stream); + \\ const stdout = &FileOutStream.init(getStdOut() catch unreachable).stream; \\ stdout.print("OK 2\n") catch unreachable; \\} \\ @@ -34,7 +34,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\// purposefully conflicting function with main.zig \\// but it's private so it should be OK \\fn privateFunction() void { - \\ const stdout = &(FileOutStream.init(&(getStdOut() catch unreachable)).stream); + \\ const stdout = &FileOutStream.init(getStdOut() catch unreachable).stream; \\ stdout.print("OK 1\n") catch unreachable; \\} \\ @@ -60,7 +60,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { tc.addSourceFile("foo.zig", \\use @import("std").io; \\pub fn foo_function() void { - \\ const stdout = &(FileOutStream.init(&(getStdOut() catch unreachable)).stream); + \\ const stdout = &FileOutStream.init(getStdOut() catch unreachable).stream; \\ stdout.print("OK\n") catch unreachable; \\} ); @@ -71,7 +71,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ \\pub fn bar_function() void { \\ if (foo_function()) { - \\ const stdout = &(FileOutStream.init(&(getStdOut() catch unreachable)).stream); + \\ const stdout = &FileOutStream.init(getStdOut() catch unreachable).stream; \\ stdout.print("OK\n") catch unreachable; \\ } \\} @@ -103,7 +103,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\pub const a_text = "OK\n"; \\ \\pub fn ok() void { - \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); + \\ const stdout = &io.FileOutStream.init(io.getStdOut() catch unreachable).stream; \\ stdout.print(b_text) catch unreachable; \\} ); @@ -121,7 +121,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\const io = @import("std").io; \\ \\pub fn main() void { - \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); + \\ const stdout = &io.FileOutStream.init(io.getStdOut() catch unreachable).stream; \\ stdout.print("Hello, world!\n{d4} {x3} {c}\n", u32(12), u16(0x12), u8('a')) catch unreachable; \\} , "Hello, world!\n0012 012 a\n"); @@ -274,7 +274,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ var x_local : i32 = print_ok(x); \\} \\fn print_ok(val: @typeOf(x)) @typeOf(foo) { - \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); + \\ const stdout = &io.FileOutStream.init(io.getStdOut() catch unreachable).stream; \\ stdout.print("OK\n") catch unreachable; \\ return 0; \\} @@ -356,7 +356,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\pub fn main() void { \\ const bar = Bar {.field2 = 13,}; \\ const foo = Foo {.field1 = bar,}; - \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); + \\ const stdout = &io.FileOutStream.init(io.getStdOut() catch unreachable).stream; \\ if (!foo.method()) { \\ stdout.print("BAD\n") catch unreachable; \\ } @@ -370,7 +370,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { cases.add("defer with only fallthrough", \\const io = @import("std").io; \\pub fn main() void { - \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); + \\ const stdout = &io.FileOutStream.init(io.getStdOut() catch unreachable).stream; \\ stdout.print("before\n") catch unreachable; \\ defer stdout.print("defer1\n") catch unreachable; \\ defer stdout.print("defer2\n") catch unreachable; @@ -383,7 +383,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\const io = @import("std").io; \\const os = @import("std").os; \\pub fn main() void { - \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); + \\ const stdout = &io.FileOutStream.init(io.getStdOut() catch unreachable).stream; \\ stdout.print("before\n") catch unreachable; \\ defer stdout.print("defer1\n") catch unreachable; \\ defer stdout.print("defer2\n") catch unreachable; @@ -400,7 +400,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ do_test() catch return; \\} \\fn do_test() !void { - \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); + \\ const stdout = &io.FileOutStream.init(io.getStdOut() catch unreachable).stream; \\ stdout.print("before\n") catch unreachable; \\ defer stdout.print("defer1\n") catch unreachable; \\ errdefer stdout.print("deferErr\n") catch unreachable; @@ -419,7 +419,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ do_test() catch return; \\} \\fn do_test() !void { - \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); + \\ const stdout = &io.FileOutStream.init(io.getStdOut() catch unreachable).stream; \\ stdout.print("before\n") catch unreachable; \\ defer stdout.print("defer1\n") catch unreachable; \\ errdefer stdout.print("deferErr\n") catch unreachable; @@ -436,7 +436,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\const io = @import("std").io; \\ \\pub fn main() void { - \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); + \\ const stdout = &io.FileOutStream.init(io.getStdOut() catch unreachable).stream; \\ stdout.print(foo_txt) catch unreachable; \\} , "1234\nabcd\n"); @@ -456,7 +456,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\pub fn main() !void { \\ var args_it = os.args(); \\ var stdout_file = try io.getStdOut(); - \\ var stdout_adapter = io.FileOutStream.init(&stdout_file); + \\ var stdout_adapter = io.FileOutStream.init(stdout_file); \\ const stdout = &stdout_adapter.stream; \\ var index: usize = 0; \\ _ = args_it.skip(); @@ -497,7 +497,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\pub fn main() !void { \\ var args_it = os.args(); \\ var stdout_file = try io.getStdOut(); - \\ var stdout_adapter = io.FileOutStream.init(&stdout_file); + \\ var stdout_adapter = io.FileOutStream.init(stdout_file); \\ const stdout = &stdout_adapter.stream; \\ var index: usize = 0; \\ _ = args_it.skip(); diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 56b2c51d74..9979ed666c 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1,6 +1,49 @@ const tests = @import("tests.zig"); pub fn addCases(cases: *tests.CompileErrorContext) void { + cases.add( + "switch with invalid expression parameter", + \\export fn entry() void { + \\ Test(i32); + \\} + \\fn Test(comptime T: type) void { + \\ const x = switch (T) { + \\ []u8 => |x| 123, + \\ i32 => |x| 456, + \\ else => unreachable, + \\ }; + \\} + , + ".tmp_source.zig:7:17: error: switch on type 'type' provides no expression parameter", + ); + + cases.add( + "function protoype with no body", + \\fn foo() void; + \\export fn entry() void { + \\ foo(); + \\} + , + ".tmp_source.zig:1:1: error: non-extern function has no body", + ); + + cases.add( + "@typeInfo causing depend on itself compile error", + \\const start = struct { + \\ fn crash() bug() { + \\ return bug; + \\ } + \\}; + \\fn bug() void { + \\ _ = @typeInfo(start).Struct; + \\} + \\export fn entry() void { + \\ var boom = start.crash(); + \\} + , + ".tmp_source.zig:2:5: error: 'crash' depends on itself", + ); + cases.add( "@handle() called outside of function definition", \\var handle_undef: promise = undefined; diff --git a/test/tests.zig b/test/tests.zig index aa5eed17ee..a0e1792079 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -263,8 +263,8 @@ pub const CompareOutputContext = struct { var stdout = Buffer.initNull(b.allocator); var stderr = Buffer.initNull(b.allocator); - var stdout_file_in_stream = io.FileInStream.init(&child.stdout.?); - var stderr_file_in_stream = io.FileInStream.init(&child.stderr.?); + var stdout_file_in_stream = io.FileInStream.init(child.stdout.?); + var stderr_file_in_stream = io.FileInStream.init(child.stderr.?); stdout_file_in_stream.stream.readAllBuffer(&stdout, max_stdout_size) catch unreachable; stderr_file_in_stream.stream.readAllBuffer(&stderr, max_stdout_size) catch unreachable; @@ -578,8 +578,8 @@ pub const CompileErrorContext = struct { var stdout_buf = Buffer.initNull(b.allocator); var stderr_buf = Buffer.initNull(b.allocator); - var stdout_file_in_stream = io.FileInStream.init(&child.stdout.?); - var stderr_file_in_stream = io.FileInStream.init(&child.stderr.?); + var stdout_file_in_stream = io.FileInStream.init(child.stdout.?); + var stderr_file_in_stream = io.FileInStream.init(child.stderr.?); stdout_file_in_stream.stream.readAllBuffer(&stdout_buf, max_stdout_size) catch unreachable; stderr_file_in_stream.stream.readAllBuffer(&stderr_buf, max_stdout_size) catch unreachable; @@ -842,8 +842,8 @@ pub const TranslateCContext = struct { var stdout_buf = Buffer.initNull(b.allocator); var stderr_buf = Buffer.initNull(b.allocator); - var stdout_file_in_stream = io.FileInStream.init(&child.stdout.?); - var stderr_file_in_stream = io.FileInStream.init(&child.stderr.?); + var stdout_file_in_stream = io.FileInStream.init(child.stdout.?); + var stderr_file_in_stream = io.FileInStream.init(child.stderr.?); stdout_file_in_stream.stream.readAllBuffer(&stdout_buf, max_stdout_size) catch unreachable; stderr_file_in_stream.stream.readAllBuffer(&stderr_buf, max_stdout_size) catch unreachable;