Merge remote-tracking branch 'origin/master' into async

This commit is contained in:
Andrew Kelley 2018-03-01 20:47:35 -05:00
commit de5c0c9f40
13 changed files with 651 additions and 142 deletions

View File

@ -386,6 +386,7 @@ set(ZIG_STD_FILES
"index.zig"
"io.zig"
"linked_list.zig"
"macho.zig"
"math/acos.zig"
"math/acosh.zig"
"math/asin.zig"

View File

@ -2782,30 +2782,96 @@ test "fn reflection" {
{#header_close#}
{#header_close#}
{#header_open|Errors#}
{#header_open|Error Set Type#}
<p>
One of the distinguishing features of Zig is its exception handling strategy.
An error set is like an {#link|enum#}.
However, each error name across the entire compilation gets assigned an unsigned integer
greater than 0. You are allowed to declare the same error name more than once, and if you do, it
gets assigned the same integer value.
</p>
<p>
TODO rewrite the errors section to take into account error sets
The number of unique error values across the entire compilation should determine the size of the error set type.
However right now it is hard coded to be a <code>u16</code>. See <a href="https://github.com/zig-lang/zig/issues/786">#768</a>.
</p>
<p>
These error values are assigned an unsigned integer value greater than 0 at
compile time. You are allowed to declare the same error value more than once,
and if you do, it gets assigned the same integer value.
You can implicitly cast an error from a subset to its superset:
</p>
{#code_begin|test#}
const std = @import("std");
const FileOpenError = error {
AccessDenied,
OutOfMemory,
FileNotFound,
};
const AllocationError = error {
OutOfMemory,
};
test "implicit cast subset to superset" {
const err = foo(AllocationError.OutOfMemory);
std.debug.assert(err == FileOpenError.OutOfMemory);
}
fn foo(err: AllocationError) FileOpenError {
return err;
}
{#code_end#}
<p>
But you cannot implicitly cast an error from a superset to a subset:
</p>
{#code_begin|test_err|not a member of destination error set#}
const FileOpenError = error {
AccessDenied,
OutOfMemory,
FileNotFound,
};
const AllocationError = error {
OutOfMemory,
};
test "implicit cast superset to subset" {
foo(FileOpenError.OutOfMemory) catch {};
}
fn foo(err: FileOpenError) AllocationError {
return err;
}
{#code_end#}
<p>
There is a shortcut for declaring an error set with only 1 value, and then getting that value:
</p>
{#code_begin|syntax#}
const err = error.FileNotFound;
{#code_end#}
<p>This is equivalent to:</p>
{#code_begin|syntax#}
const err = (error {FileNotFound}).FileNotFound;
{#code_end#}
<p>
This becomes useful when using {#link|Inferred Error Sets#}.
</p>
{#header_open|The Global Error Set#}
<p><code>error</code> refers to the global error set.
This is the error set that contains all errors in the entire compilation unit.
It is a superset of all other error sets and a subset of none of them.
</p>
<p>
You can refer to these error values with the error namespace such as
<code>error.FileNotFound</code>.
You can implicitly cast any error set to the global one, and you can explicitly
cast an error of global error set to a non-global one. This inserts a language-level
assert to make sure the error value is in fact in the destination error set.
</p>
<p>
Each error value across the entire compilation unit gets a unique integer,
and this determines the size of the error set type.
The global error set should generally be avoided when possible, because it prevents
the compiler from knowing what errors are possible at compile-time. Knowing
the error set at compile-time is better for generated documentationt and for
helpful error messages such as forgetting a possible error value in a {#link|switch#}.
</p>
<p>
The error set type is one of the error values, and in the same way that pointers
cannot be null, a error set instance is always an error.
</p>
{#code_begin|syntax#}const pure_error = error.FileNotFound;{#code_end#}
{#header_close#}
{#header_close#}
{#header_open|Error Union Type#}
<p>
Most of the time you will not find yourself using an error set type. Instead,
likely you will be using the error union type. This is when you take an error set
@ -2918,7 +2984,6 @@ fn doAThing(str: []u8) !void {
a panic in Debug and ReleaseSafe modes and undefined behavior in ReleaseFast mode. So, while we're debugging the
application, if there <em>was</em> a surprise error here, the application would crash
appropriately.
TODO: mention error return traces
</p>
<p>
Finally, you may want to take a different action for every situation. For that, we combine
@ -2986,7 +3051,7 @@ fn createFoo(param: i32) !Foo {
</li>
</ul>
{#see_also|defer|if|switch#}
{#header_open|Error Union Type#}
<p>An error union is created with the <code>!</code> binary operator.
You can use compile-time reflection to access the child type of an error union:</p>
{#code_begin|test#}
@ -3008,8 +3073,12 @@ test "error union" {
comptime assert(@typeOf(foo).ErrorSet == error);
}
{#code_end#}
<p>TODO the <code>||</code> operator for error sets</p>
{#header_open|Inferred Error Sets#}
<p>TODO</p>
{#header_close#}
{#header_open|Error Set Type#}
{#header_close#}
{#header_open|Error Return Traces#}
<p>TODO</p>
{#header_close#}
{#header_close#}

View File

@ -1083,7 +1083,7 @@ TypeTableEntry *get_fn_type(CodeGen *g, FnTypeId *fn_type_id) {
gen_param_info->src_index = i;
gen_param_info->gen_index = SIZE_MAX;
ensure_complete_type(g, type_entry);
type_ensure_zero_bits_known(g, type_entry);
if (type_has_bits(type_entry)) {
TypeTableEntry *gen_type;
if (handle_is_ptr(type_entry)) {
@ -2240,6 +2240,7 @@ static void resolve_enum_zero_bits(CodeGen *g, TypeTableEntry *enum_type) {
if (enum_type->data.enumeration.zero_bits_loop_flag) {
enum_type->data.enumeration.zero_bits_known = true;
enum_type->data.enumeration.zero_bits_loop_flag = false;
return;
}
@ -2394,6 +2395,7 @@ static void resolve_struct_zero_bits(CodeGen *g, TypeTableEntry *struct_type) {
// the alignment is pointer width, then assert that the first field is within that
// alignment
struct_type->data.structure.zero_bits_known = true;
struct_type->data.structure.zero_bits_loop_flag = false;
if (struct_type->data.structure.abi_alignment == 0) {
if (struct_type->data.structure.layout == ContainerLayoutPacked) {
struct_type->data.structure.abi_alignment = 1;

View File

@ -4510,7 +4510,13 @@ static IrInstruction *ir_gen_var_decl(IrBuilder *irb, Scope *scope, AstNode *nod
buf_sprintf("cannot set section of local variable '%s'", buf_ptr(variable_declaration->symbol)));
}
// Temporarily set the name of the IrExecutable to the VariableDeclaration
// so that the struct or enum from the init expression inherits the name.
Buf *old_exec_name = irb->exec->name;
irb->exec->name = variable_declaration->symbol;
IrInstruction *init_value = ir_gen_node(irb, variable_declaration->expr, scope);
irb->exec->name = old_exec_name;
if (init_value == irb->codegen->invalid_instruction)
return init_value;
@ -9504,16 +9510,30 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst
// explicit cast from child type of maybe type to maybe type
if (wanted_type->id == TypeTableEntryIdMaybe) {
if (types_match_const_cast_only(ira, wanted_type->data.maybe.child_type, actual_type, source_node).id == ConstCastResultIdOk) {
TypeTableEntry *wanted_child_type = wanted_type->data.maybe.child_type;
if (types_match_const_cast_only(ira, wanted_child_type, actual_type, source_node).id == ConstCastResultIdOk) {
return ir_analyze_maybe_wrap(ira, source_instr, value, wanted_type);
} else if (actual_type->id == TypeTableEntryIdNumLitInt ||
actual_type->id == TypeTableEntryIdNumLitFloat)
{
if (ir_num_lit_fits_in_other_type(ira, value, wanted_type->data.maybe.child_type, true)) {
if (ir_num_lit_fits_in_other_type(ira, value, wanted_child_type, true)) {
return ir_analyze_maybe_wrap(ira, source_instr, value, wanted_type);
} else {
return ira->codegen->invalid_instruction;
}
} else if (wanted_child_type->id == TypeTableEntryIdPointer &&
wanted_child_type->data.pointer.is_const &&
(actual_type->id == TypeTableEntryIdPointer || is_container(actual_type)))
{
IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_child_type, value);
if (type_is_invalid(cast1->value.type))
return ira->codegen->invalid_instruction;
IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1);
if (type_is_invalid(cast2->value.type))
return ira->codegen->invalid_instruction;
return cast2;
}
}

View File

@ -5,6 +5,7 @@ const io = std.io;
const os = std.os;
const elf = std.elf;
const DW = std.dwarf;
const macho = std.macho;
const ArrayList = std.ArrayList;
const builtin = @import("builtin");
@ -178,43 +179,57 @@ pub fn writeCurrentStackTrace(out_stream: var, allocator: &mem.Allocator,
}
fn printSourceAtAddress(debug_info: &ElfStackTrace, out_stream: var, address: usize) !void {
if (builtin.os == builtin.Os.windows) {
return error.UnsupportedDebugInfo;
}
// TODO we really should be able to convert @sizeOf(usize) * 2 to a string literal
// at compile time. I'll call it issue #313
const ptr_hex = if (@sizeOf(usize) == 4) "0x{x8}" else "0x{x16}";
const compile_unit = findCompileUnit(debug_info, address) catch {
try out_stream.print("???:?:?: " ++ DIM ++ ptr_hex ++ " in ??? (???)" ++ RESET ++ "\n ???\n\n",
address);
return;
};
const compile_unit_name = try compile_unit.die.getAttrString(debug_info, DW.AT_name);
if (getLineNumberInfo(debug_info, compile_unit, address - 1)) |line_info| {
defer line_info.deinit();
try out_stream.print(WHITE ++ "{}:{}:{}" ++ RESET ++ ": " ++
DIM ++ ptr_hex ++ " in ??? ({})" ++ RESET ++ "\n",
line_info.file_name, line_info.line, line_info.column,
address, compile_unit_name);
if (printLineFromFile(debug_info.allocator(), 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(' ');
}}
try out_stream.write(GREEN ++ "^" ++ RESET ++ "\n");
}
} else |err| switch (err) {
error.EndOfFile => {},
else => return err,
}
} else |err| switch (err) {
error.MissingDebugInfo, error.InvalidDebugInfo => {
try out_stream.print(ptr_hex ++ " in ??? ({})\n", address, compile_unit_name);
switch (builtin.os) {
builtin.Os.windows => return error.UnsupportedDebugInfo,
builtin.Os.macosx => {
// TODO(bnoordhuis) It's theoretically possible to obtain the
// compilation unit from the symbtab but it's not that useful
// in practice because the compiler dumps everything in a single
// object file. Future improvement: use external dSYM data when
// available.
const unknown = macho.Symbol { .name = "???", .address = address };
const symbol = debug_info.symbol_table.search(address) ?? &unknown;
try out_stream.print(WHITE ++ "{}" ++ RESET ++ ": " ++
DIM ++ ptr_hex ++ " in ??? (???)" ++ RESET ++ "\n",
symbol.name, address);
},
else => {
const compile_unit = findCompileUnit(debug_info, address) catch {
try out_stream.print("???:?:?: " ++ DIM ++ ptr_hex ++ " in ??? (???)" ++ RESET ++ "\n ???\n\n",
address);
return;
};
const compile_unit_name = try compile_unit.die.getAttrString(debug_info, DW.AT_name);
if (getLineNumberInfo(debug_info, compile_unit, address - 1)) |line_info| {
defer line_info.deinit();
try out_stream.print(WHITE ++ "{}:{}:{}" ++ RESET ++ ": " ++
DIM ++ ptr_hex ++ " in ??? ({})" ++ RESET ++ "\n",
line_info.file_name, line_info.line, line_info.column,
address, compile_unit_name);
if (printLineFromFile(debug_info.allocator(), 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(' ');
}}
try out_stream.write(GREEN ++ "^" ++ RESET ++ "\n");
}
} else |err| switch (err) {
error.EndOfFile => {},
else => return err,
}
} else |err| switch (err) {
error.MissingDebugInfo, error.InvalidDebugInfo => {
try out_stream.print(ptr_hex ++ " in ??? ({})\n", address, compile_unit_name);
},
else => return err,
}
},
else => return err,
}
}
@ -222,6 +237,7 @@ pub fn openSelfDebugInfo(allocator: &mem.Allocator) !&ElfStackTrace {
switch (builtin.object_format) {
builtin.ObjectFormat.elf => {
const st = try allocator.create(ElfStackTrace);
errdefer allocator.destroy(st);
*st = ElfStackTrace {
.self_exe_file = undefined,
.elf = undefined,
@ -247,12 +263,22 @@ pub fn openSelfDebugInfo(allocator: &mem.Allocator) !&ElfStackTrace {
try scanAllCompileUnits(st);
return st;
},
builtin.ObjectFormat.macho => {
var exe_file = try os.openSelfExe();
defer exe_file.close();
const st = try allocator.create(ElfStackTrace);
errdefer allocator.destroy(st);
*st = ElfStackTrace {
.symbol_table = try macho.loadSymbols(allocator, &io.FileInStream.init(&exe_file)),
};
return st;
},
builtin.ObjectFormat.coff => {
return error.TodoSupportCoffDebugInfo;
},
builtin.ObjectFormat.macho => {
return error.TodoSupportMachoDebugInfo;
},
builtin.ObjectFormat.wasm => {
return error.TodoSupportCOFFDebugInfo;
},
@ -295,31 +321,40 @@ fn printLineFromFile(allocator: &mem.Allocator, out_stream: var, line_info: &con
}
}
pub const ElfStackTrace = struct {
self_exe_file: os.File,
elf: elf.Elf,
debug_info: &elf.SectionHeader,
debug_abbrev: &elf.SectionHeader,
debug_str: &elf.SectionHeader,
debug_line: &elf.SectionHeader,
debug_ranges: ?&elf.SectionHeader,
abbrev_table_list: ArrayList(AbbrevTableHeader),
compile_unit_list: ArrayList(CompileUnit),
pub const ElfStackTrace = switch (builtin.os) {
builtin.Os.macosx => struct {
symbol_table: macho.SymbolTable,
pub fn allocator(self: &const ElfStackTrace) &mem.Allocator {
return self.abbrev_table_list.allocator;
}
pub fn close(self: &ElfStackTrace) void {
self.symbol_table.deinit();
}
},
else => struct {
self_exe_file: os.File,
elf: elf.Elf,
debug_info: &elf.SectionHeader,
debug_abbrev: &elf.SectionHeader,
debug_str: &elf.SectionHeader,
debug_line: &elf.SectionHeader,
debug_ranges: ?&elf.SectionHeader,
abbrev_table_list: ArrayList(AbbrevTableHeader),
compile_unit_list: ArrayList(CompileUnit),
pub fn readString(self: &ElfStackTrace) ![]u8 {
var in_file_stream = io.FileInStream.init(&self.self_exe_file);
const in_stream = &in_file_stream.stream;
return readStringRaw(self.allocator(), in_stream);
}
pub fn allocator(self: &const ElfStackTrace) &mem.Allocator {
return self.abbrev_table_list.allocator;
}
pub fn close(self: &ElfStackTrace) void {
self.self_exe_file.close();
self.elf.close();
}
pub fn readString(self: &ElfStackTrace) ![]u8 {
var in_file_stream = io.FileInStream.init(&self.self_exe_file);
const in_stream = &in_file_stream.stream;
return readStringRaw(self.allocator(), in_stream);
}
pub fn close(self: &ElfStackTrace) void {
self.self_exe_file.close();
self.elf.close();
}
},
};
const PcRange = struct {

View File

@ -550,12 +550,6 @@ test "parse unsigned comptime" {
}
}
// Dummy field because of https://github.com/zig-lang/zig/issues/557.
// At top level because of https://github.com/zig-lang/zig/issues/675.
const Struct = struct {
unused: u8,
};
test "fmt.format" {
{
var buf1: [32]u8 = undefined;
@ -588,6 +582,10 @@ test "fmt.format" {
assert(mem.eql(u8, result, "u3: 5\n"));
}
{
// Dummy field because of https://github.com/zig-lang/zig/issues/557.
const Struct = struct {
unused: u8,
};
var buf1: [32]u8 = undefined;
const value = Struct {
.unused = 42,

View File

@ -21,6 +21,7 @@ pub const endian = @import("endian.zig");
pub const fmt = @import("fmt/index.zig");
pub const heap = @import("heap.zig");
pub const io = @import("io.zig");
pub const macho = @import("macho.zig");
pub const math = @import("math/index.zig");
pub const mem = @import("mem.zig");
pub const net = @import("net.zig");
@ -51,6 +52,7 @@ test "std" {
_ = @import("endian.zig");
_ = @import("fmt/index.zig");
_ = @import("io.zig");
_ = @import("macho.zig");
_ = @import("math/index.zig");
_ = @import("mem.zig");
_ = @import("heap.zig");

170
std/macho.zig Normal file
View File

@ -0,0 +1,170 @@
const builtin = @import("builtin");
const std = @import("index.zig");
const io = std.io;
const mem = std.mem;
const MH_MAGIC_64 = 0xFEEDFACF;
const MH_PIE = 0x200000;
const LC_SYMTAB = 2;
const MachHeader64 = packed struct {
magic: u32,
cputype: u32,
cpusubtype: u32,
filetype: u32,
ncmds: u32,
sizeofcmds: u32,
flags: u32,
reserved: u32,
};
const LoadCommand = packed struct {
cmd: u32,
cmdsize: u32,
};
const SymtabCommand = packed struct {
symoff: u32,
nsyms: u32,
stroff: u32,
strsize: u32,
};
const Nlist64 = packed struct {
n_strx: u32,
n_type: u8,
n_sect: u8,
n_desc: u16,
n_value: u64,
};
pub const Symbol = struct {
name: []const u8,
address: u64,
fn addressLessThan(lhs: &const Symbol, rhs: &const Symbol) bool {
return lhs.address < rhs.address;
}
};
pub const SymbolTable = struct {
allocator: &mem.Allocator,
symbols: []const Symbol,
strings: []const u8,
// Doubles as an eyecatcher to calculate the PIE slide, see loadSymbols().
// Ideally we'd use _mh_execute_header because it's always at 0x100000000
// in the image but as it's located in a different section than executable
// code, its displacement is different.
pub fn deinit(self: &SymbolTable) void {
self.allocator.free(self.symbols);
self.symbols = []const Symbol {};
self.allocator.free(self.strings);
self.strings = []const u8 {};
}
pub fn search(self: &const SymbolTable, address: usize) ?&const Symbol {
var min: usize = 0;
var max: usize = self.symbols.len - 1; // Exclude sentinel.
while (min < max) {
const mid = min + (max - min) / 2;
const curr = &self.symbols[mid];
const next = &self.symbols[mid + 1];
if (address >= next.address) {
min = mid + 1;
} else if (address < curr.address) {
max = mid;
} else {
return curr;
}
}
return null;
}
};
pub fn loadSymbols(allocator: &mem.Allocator, in: &io.FileInStream) !SymbolTable {
var file = in.file;
try file.seekTo(0);
var hdr: MachHeader64 = undefined;
try readOneNoEof(in, MachHeader64, &hdr);
if (hdr.magic != MH_MAGIC_64) return error.MissingDebugInfo;
const is_pie = MH_PIE == (hdr.flags & MH_PIE);
var pos: usize = @sizeOf(@typeOf(hdr));
var ncmd: u32 = hdr.ncmds;
while (ncmd != 0) : (ncmd -= 1) {
try file.seekTo(pos);
var lc: LoadCommand = undefined;
try readOneNoEof(in, LoadCommand, &lc);
if (lc.cmd == LC_SYMTAB) break;
pos += lc.cmdsize;
} else {
return error.MissingDebugInfo;
}
var cmd: SymtabCommand = undefined;
try readOneNoEof(in, SymtabCommand, &cmd);
try file.seekTo(cmd.symoff);
var syms = try allocator.alloc(Nlist64, cmd.nsyms);
defer allocator.free(syms);
try readNoEof(in, Nlist64, syms);
try file.seekTo(cmd.stroff);
var strings = try allocator.alloc(u8, cmd.strsize);
errdefer allocator.free(strings);
try in.stream.readNoEof(strings);
var nsyms: usize = 0;
for (syms) |sym| if (isSymbol(sym)) nsyms += 1;
if (nsyms == 0) return error.MissingDebugInfo;
var symbols = try allocator.alloc(Symbol, nsyms + 1); // Room for sentinel.
errdefer allocator.free(symbols);
var pie_slide: usize = 0;
var nsym: usize = 0;
for (syms) |sym| {
if (!isSymbol(sym)) continue;
const start = sym.n_strx;
const end = ??mem.indexOfScalarPos(u8, strings, start, 0);
const name = strings[start..end];
const address = sym.n_value;
symbols[nsym] = Symbol { .name = name, .address = address };
nsym += 1;
if (is_pie and mem.eql(u8, name, "_SymbolTable_deinit")) {
pie_slide = @ptrToInt(SymbolTable.deinit) - address;
}
}
// Effectively a no-op, lld emits symbols in ascending order.
std.sort.insertionSort(Symbol, symbols[0..nsyms], Symbol.addressLessThan);
// Insert the sentinel. Since we don't know where the last function ends,
// we arbitrarily limit it to the start address + 4 KB.
const top = symbols[nsyms - 1].address + 4096;
symbols[nsyms] = Symbol { .name = "", .address = top };
if (pie_slide != 0) {
for (symbols) |*symbol| symbol.address += pie_slide;
}
return SymbolTable {
.allocator = allocator,
.symbols = symbols,
.strings = strings,
};
}
fn readNoEof(in: &io.FileInStream, comptime T: type, result: []T) !void {
return in.stream.readNoEof(([]u8)(result));
}
fn readOneNoEof(in: &io.FileInStream, comptime T: type, result: &T) !void {
return readNoEof(in, T, result[0..1]);
}
fn isSymbol(sym: &const Nlist64) bool {
return sym.n_value != 0 and sym.n_desc == 0;
}

View File

@ -32,9 +32,6 @@ pub const ChildProcess = struct {
pub argv: []const []const u8,
/// Possibly called from a signal handler. Must set this before calling `spawn`.
pub onTerm: ?fn(&ChildProcess)void,
/// Leave as null to use the current env map using the supplied allocator.
pub env_map: ?&const BufMap,
@ -102,7 +99,6 @@ pub const ChildProcess = struct {
.err_pipe = undefined,
.llnode = undefined,
.term = null,
.onTerm = null,
.env_map = null,
.cwd = null,
.uid = if (is_windows) {} else null,
@ -124,7 +120,6 @@ pub const ChildProcess = struct {
self.gid = user_info.gid;
}
/// onTerm can be called before `spawn` returns.
/// On success must call `kill` or `wait`.
pub fn spawn(self: &ChildProcess) !void {
if (is_windows) {
@ -165,9 +160,6 @@ pub const ChildProcess = struct {
}
pub fn killPosix(self: &ChildProcess) !Term {
block_SIGCHLD();
defer restore_SIGCHLD();
if (self.term) |term| {
self.cleanupStreams();
return term;
@ -246,9 +238,6 @@ pub const ChildProcess = struct {
}
fn waitPosix(self: &ChildProcess) !Term {
block_SIGCHLD();
defer restore_SIGCHLD();
if (self.term) |term| {
self.cleanupStreams();
return term;
@ -298,10 +287,6 @@ pub const ChildProcess = struct {
fn handleWaitResult(self: &ChildProcess, status: i32) void {
self.term = self.cleanupAfterWait(status);
if (self.onTerm) |onTerm| {
onTerm(self);
}
}
fn cleanupStreams(self: &ChildProcess) void {
@ -347,9 +332,6 @@ pub const ChildProcess = struct {
}
fn spawnPosix(self: &ChildProcess) !void {
// TODO atomically set a flag saying that we already did this
install_SIGCHLD_handler();
const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try makePipe() else undefined;
errdefer if (self.stdin_behavior == StdIo.Pipe) { destroyPipe(stdin_pipe); };
@ -387,11 +369,9 @@ pub const ChildProcess = struct {
const err_pipe = try makePipe();
errdefer destroyPipe(err_pipe);
block_SIGCHLD();
const pid_result = posix.fork();
const pid_err = posix.getErrno(pid_result);
if (pid_err > 0) {
restore_SIGCHLD();
return switch (pid_err) {
posix.EAGAIN, posix.ENOMEM, posix.ENOSYS => error.SystemResources,
else => os.unexpectedErrorPosix(pid_err),
@ -399,7 +379,6 @@ pub const ChildProcess = struct {
}
if (pid_result == 0) {
// we are the child
restore_SIGCHLD();
setUpChildIo(self.stdin_behavior, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch
|err| forkChildErrReport(err_pipe[1], err);
@ -451,8 +430,6 @@ pub const ChildProcess = struct {
// TODO make this atomic so it works even with threads
children_nodes.prepend(&self.llnode);
restore_SIGCHLD();
if (self.stdin_behavior == StdIo.Pipe) { os.close(stdin_pipe[0]); }
if (self.stdout_behavior == StdIo.Pipe) { os.close(stdout_pipe[1]); }
if (self.stderr_behavior == StdIo.Pipe) { os.close(stderr_pipe[1]); }
@ -824,30 +801,3 @@ fn handleTerm(pid: i32, status: i32) void {
}
}
}
const sigchld_set = x: {
var signal_set = posix.empty_sigset;
posix.sigaddset(&signal_set, posix.SIGCHLD);
break :x signal_set;
};
fn block_SIGCHLD() void {
const err = posix.getErrno(posix.sigprocmask(posix.SIG_BLOCK, &sigchld_set, null));
assert(err == 0);
}
fn restore_SIGCHLD() void {
const err = posix.getErrno(posix.sigprocmask(posix.SIG_UNBLOCK, &sigchld_set, null));
assert(err == 0);
}
const sigchld_action = posix.Sigaction {
.handler = sigchld_handler,
.mask = posix.empty_sigset,
.flags = posix.SA_RESTART | posix.SA_NOCLDSTOP,
};
fn install_SIGCHLD_handler() void {
const err = posix.getErrno(posix.sigaction(posix.SIGCHLD, &sigchld_action, null));
assert(err == 0);
}

View File

@ -1,4 +1,5 @@
const std = @import("./index.zig");
const debug = std.debug;
/// Given the first byte of a UTF-8 codepoint,
/// returns a number 1-4 indicating the total length of the codepoint in bytes.
@ -25,8 +26,8 @@ pub fn utf8Decode(bytes: []const u8) !u32 {
};
}
pub fn utf8Decode2(bytes: []const u8) !u32 {
std.debug.assert(bytes.len == 2);
std.debug.assert(bytes[0] & 0b11100000 == 0b11000000);
debug.assert(bytes.len == 2);
debug.assert(bytes[0] & 0b11100000 == 0b11000000);
var value: u32 = bytes[0] & 0b00011111;
if (bytes[1] & 0b11000000 != 0b10000000) return error.Utf8ExpectedContinuation;
@ -38,8 +39,8 @@ pub fn utf8Decode2(bytes: []const u8) !u32 {
return value;
}
pub fn utf8Decode3(bytes: []const u8) !u32 {
std.debug.assert(bytes.len == 3);
std.debug.assert(bytes[0] & 0b11110000 == 0b11100000);
debug.assert(bytes.len == 3);
debug.assert(bytes[0] & 0b11110000 == 0b11100000);
var value: u32 = bytes[0] & 0b00001111;
if (bytes[1] & 0b11000000 != 0b10000000) return error.Utf8ExpectedContinuation;
@ -56,8 +57,8 @@ pub fn utf8Decode3(bytes: []const u8) !u32 {
return value;
}
pub fn utf8Decode4(bytes: []const u8) !u32 {
std.debug.assert(bytes.len == 4);
std.debug.assert(bytes[0] & 0b11111000 == 0b11110000);
debug.assert(bytes.len == 4);
debug.assert(bytes[0] & 0b11111000 == 0b11110000);
var value: u32 = bytes[0] & 0b00000111;
if (bytes[1] & 0b11000000 != 0b10000000) return error.Utf8ExpectedContinuation;
@ -78,6 +79,136 @@ pub fn utf8Decode4(bytes: []const u8) !u32 {
return value;
}
pub fn utf8ValidateSlice(s: []const u8) bool {
var i: usize = 0;
while (i < s.len) {
if (utf8ByteSequenceLength(s[i])) |cp_len| {
if (i + cp_len > s.len) {
return false;
}
if (utf8Decode(s[i..i+cp_len])) |_| {} else |_| { return false; }
i += cp_len;
} else |err| {
return false;
}
}
return true;
}
const Utf8View = struct {
bytes: []const u8,
pub fn init(s: []const u8) !Utf8View {
if (!utf8ValidateSlice(s)) {
return error.InvalidUtf8;
}
return initUnchecked(s);
}
pub fn initUnchecked(s: []const u8) Utf8View {
return Utf8View {
.bytes = s,
};
}
pub fn initComptime(comptime s: []const u8) Utf8View {
if (comptime init(s)) |r| {
return r;
} else |err| switch (err) {
error.InvalidUtf8 => {
@compileError("invalid utf8");
unreachable;
}
}
}
pub fn Iterator(s: &const Utf8View) Utf8Iterator {
return Utf8Iterator {
.bytes = s.bytes,
.i = 0,
};
}
};
const Utf8Iterator = struct {
bytes: []const u8,
i: usize,
pub fn nextCodepointSlice(it: &Utf8Iterator) ?[]const u8 {
if (it.i >= it.bytes.len) {
return null;
}
const cp_len = utf8ByteSequenceLength(it.bytes[it.i]) catch unreachable;
it.i += cp_len;
return it.bytes[it.i-cp_len..it.i];
}
pub fn nextCodepoint(it: &Utf8Iterator) ?u32 {
const slice = it.nextCodepointSlice() ?? return null;
const r = switch (slice.len) {
1 => u32(slice[0]),
2 => utf8Decode2(slice),
3 => utf8Decode3(slice),
4 => utf8Decode4(slice),
else => unreachable,
};
return r catch unreachable;
}
};
test "utf8 iterator on ascii" {
const s = Utf8View.initComptime("abc");
var it1 = s.Iterator();
debug.assert(std.mem.eql(u8, "a", ??it1.nextCodepointSlice()));
debug.assert(std.mem.eql(u8, "b", ??it1.nextCodepointSlice()));
debug.assert(std.mem.eql(u8, "c", ??it1.nextCodepointSlice()));
debug.assert(it1.nextCodepointSlice() == null);
var it2 = s.Iterator();
debug.assert(??it2.nextCodepoint() == 'a');
debug.assert(??it2.nextCodepoint() == 'b');
debug.assert(??it2.nextCodepoint() == 'c');
debug.assert(it2.nextCodepoint() == null);
}
test "utf8 view bad" {
// Compile-time error.
// const s3 = Utf8View.initComptime("\xfe\xf2");
const s = Utf8View.init("hel\xadlo");
if (s) |_| { unreachable; } else |err| { debug.assert(err == error.InvalidUtf8); }
}
test "utf8 view ok" {
const s = Utf8View.initComptime("東京市");
var it1 = s.Iterator();
debug.assert(std.mem.eql(u8, "", ??it1.nextCodepointSlice()));
debug.assert(std.mem.eql(u8, "", ??it1.nextCodepointSlice()));
debug.assert(std.mem.eql(u8, "", ??it1.nextCodepointSlice()));
debug.assert(it1.nextCodepointSlice() == null);
var it2 = s.Iterator();
debug.assert(??it2.nextCodepoint() == 0x6771);
debug.assert(??it2.nextCodepoint() == 0x4eac);
debug.assert(??it2.nextCodepoint() == 0x5e02);
debug.assert(it2.nextCodepoint() == null);
}
test "bad utf8 slice" {
debug.assert(utf8ValidateSlice("abc"));
debug.assert(!utf8ValidateSlice("abc\xc0"));
debug.assert(!utf8ValidateSlice("abc\xc0abc"));
debug.assert(utf8ValidateSlice("abc\xdf\xbf"));
}
test "valid utf8" {
testValid("\x00", 0x0);
testValid("\x20", 0x20);
@ -145,17 +276,17 @@ fn testError(bytes: []const u8, expected_err: error) void {
if (testDecode(bytes)) |_| {
unreachable;
} else |err| {
std.debug.assert(err == expected_err);
debug.assert(err == expected_err);
}
}
fn testValid(bytes: []const u8, expected_codepoint: u32) void {
std.debug.assert((testDecode(bytes) catch unreachable) == expected_codepoint);
debug.assert((testDecode(bytes) catch unreachable) == expected_codepoint);
}
fn testDecode(bytes: []const u8) !u32 {
const length = try utf8ByteSequenceLength(bytes[0]);
if (bytes.len < length) return error.UnexpectedEof;
std.debug.assert(bytes.len == length);
debug.assert(bytes.len == length);
return utf8Decode(bytes);
}

View File

@ -32,6 +32,108 @@ fn funcWithConstPtrPtr(x: &const &i32) void {
**x += 1;
}
test "implicitly cast a container to a const pointer of it" {
const z = Struct(void) { .x = void{} };
assert(0 == @sizeOf(@typeOf(z)));
assert(void{} == Struct(void).pointer(z).x);
assert(void{} == Struct(void).pointer(&z).x);
assert(void{} == Struct(void).maybePointer(z).x);
assert(void{} == Struct(void).maybePointer(&z).x);
assert(void{} == Struct(void).maybePointer(null).x);
const s = Struct(u8) { .x = 42 };
assert(0 != @sizeOf(@typeOf(s)));
assert(42 == Struct(u8).pointer(s).x);
assert(42 == Struct(u8).pointer(&s).x);
assert(42 == Struct(u8).maybePointer(s).x);
assert(42 == Struct(u8).maybePointer(&s).x);
assert(0 == Struct(u8).maybePointer(null).x);
const u = Union { .x = 42 };
assert(42 == Union.pointer(u).x);
assert(42 == Union.pointer(&u).x);
assert(42 == Union.maybePointer(u).x);
assert(42 == Union.maybePointer(&u).x);
assert(0 == Union.maybePointer(null).x);
const e = Enum.Some;
assert(Enum.Some == Enum.pointer(e));
assert(Enum.Some == Enum.pointer(&e));
assert(Enum.Some == Enum.maybePointer(e));
assert(Enum.Some == Enum.maybePointer(&e));
assert(Enum.None == Enum.maybePointer(null));
}
fn Struct(comptime T: type) type {
return struct {
const Self = this;
x: T,
fn pointer(self: &const Self) Self {
return *self;
}
fn maybePointer(self: ?&const Self) Self {
const none = Self { .x = if (T == void) void{} else 0 };
return *(self ?? &none);
}
};
}
const Union = union {
x: u8,
fn pointer(self: &const Union) Union {
return *self;
}
fn maybePointer(self: ?&const Union) Union {
const none = Union { .x = 0 };
return *(self ?? &none);
}
};
const Enum = enum {
None,
Some,
fn pointer(self: &const Enum) Enum {
return *self;
}
fn maybePointer(self: ?&const Enum) Enum {
return *(self ?? &Enum.None);
}
};
test "implicitly cast indirect pointer to maybe-indirect pointer" {
const S = struct {
const Self = this;
x: u8,
fn constConst(p: &const &const Self) u8 {
return (*p).x;
}
fn maybeConstConst(p: ?&const &const Self) u8 {
return (*??p).x;
}
fn constConstConst(p: &const &const &const Self) u8 {
return (**p).x;
}
fn maybeConstConstConst(p: ?&const &const &const Self) u8 {
return (**??p).x;
}
};
const s = S { .x = 42 };
const p = &s;
const q = &p;
const r = &q;
assert(42 == S.constConst(p));
assert(42 == S.constConst(q));
assert(42 == S.maybeConstConst(p));
assert(42 == S.maybeConstConst(q));
assert(42 == S.constConstConst(q));
assert(42 == S.constConstConst(r));
assert(42 == S.maybeConstConstConst(q));
assert(42 == S.maybeConstConstConst(r));
}
test "explicit cast from integer to error type" {
testCastIntToErr(error.ItBroke);
comptime testCastIntToErr(error.ItBroke);

View File

@ -499,12 +499,29 @@ test "@canImplicitCast" {
}
test "@typeName" {
const Struct = struct {
};
const Union = union {
unused: u8,
};
const Enum = enum {
Unused,
};
comptime {
assert(mem.eql(u8, @typeName(i64), "i64"));
assert(mem.eql(u8, @typeName(&usize), "&usize"));
// https://github.com/zig-lang/zig/issues/675
assert(mem.eql(u8, @typeName(TypeFromFn(u8)), "TypeFromFn(u8)"));
assert(mem.eql(u8, @typeName(Struct), "Struct"));
assert(mem.eql(u8, @typeName(Union), "Union"));
assert(mem.eql(u8, @typeName(Enum), "Enum"));
}
}
fn TypeFromFn(comptime T: type) type {
return struct {};
}
test "volatile load and store" {
var number: i32 = 1234;
const ptr = (&volatile i32)(&number);

View File

@ -3090,4 +3090,16 @@ pub fn addCases(cases: &tests.CompileErrorContext) void {
,
".tmp_source.zig:11:20: error: runtime cast to union 'Value' which has non-void fields",
".tmp_source.zig:3:5: note: field 'A' has type 'i32'");
cases.add("self-referencing function pointer field",
\\const S = struct {
\\ f: fn(_: S) void,
\\};
\\fn f(_: S) void {
\\}
\\export fn entry() void {
\\ var _ = S { .f = f };
\\}
,
".tmp_source.zig:4:9: error: type 'S' is not copyable; cannot pass by value");
}