Skip to content

Commit

Permalink
disallow single-item pointer indexing
Browse files Browse the repository at this point in the history
add pointer arithmetic for unknown length pointer
  • Loading branch information
andrewrk committed Jun 4, 2018
1 parent 4c27312 commit 96164ce
Show file tree
Hide file tree
Showing 35 changed files with 584 additions and 443 deletions.
48 changes: 28 additions & 20 deletions doc/langref.html.in
Expand Up @@ -458,7 +458,7 @@ test "string literals" {

// A C string literal is a null terminated pointer.
const null_terminated_bytes = c"hello";
assert(@typeOf(null_terminated_bytes) == *const u8);
assert(@typeOf(null_terminated_bytes) == [*]const u8);
assert(null_terminated_bytes[5] == 0);
}
{#code_end#}
Expand Down Expand Up @@ -547,7 +547,7 @@ const c_string_literal =
;
{#code_end#}
<p>
In this example the variable <code>c_string_literal</code> has type <code>*const char</code> and
In this example the variable <code>c_string_literal</code> has type <code>[*]const char</code> and
has a terminating null byte.
</p>
{#see_also|@embedFile#}
Expand Down Expand Up @@ -1288,7 +1288,7 @@ const assert = @import("std").debug.assert;
const mem = @import("std").mem;

// array literal
const message = []u8{'h', 'e', 'l', 'l', 'o'};
const message = []u8{ 'h', 'e', 'l', 'l', 'o' };

// get the size of an array
comptime {
Expand Down Expand Up @@ -1324,11 +1324,11 @@ test "modify an array" {

// array concatenation works if the values are known
// at compile time
const part_one = []i32{1, 2, 3, 4};
const part_two = []i32{5, 6, 7, 8};
const part_one = []i32{ 1, 2, 3, 4 };
const part_two = []i32{ 5, 6, 7, 8 };
const all_of_it = part_one ++ part_two;
comptime {
assert(mem.eql(i32, all_of_it, []i32{1,2,3,4,5,6,7,8}));
assert(mem.eql(i32, all_of_it, []i32{ 1, 2, 3, 4, 5, 6, 7, 8 }));
}

// remember that string literals are arrays
Expand Down Expand Up @@ -1357,7 +1357,7 @@ comptime {
var fancy_array = init: {
var initial_value: [10]Point = undefined;
for (initial_value) |*pt, i| {
pt.* = Point {
pt.* = Point{
.x = i32(i),
.y = i32(i) * 2,
};
Expand All @@ -1377,7 +1377,7 @@ test "compile-time array initalization" {
// call a function to initialize an array
var more_points = []Point{makePoint(3)} ** 10;
fn makePoint(x: i32) Point {
return Point {
return Point{
.x = x,
.y = x * 2,
};
Expand Down Expand Up @@ -1414,25 +1414,24 @@ test "address of syntax" {
}

test "pointer array access" {
// Pointers do not support pointer arithmetic. If you
// need such a thing, use array index syntax:
// Taking an address of an individual element gives a
// pointer to a single item. This kind of pointer
// does not support pointer arithmetic.

var array = []u8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
const ptr = &array[1];
const ptr = &array[2];
assert(@typeOf(ptr) == *u8);

assert(array[2] == 3);
ptr[1] += 1;
ptr.* += 1;
assert(array[2] == 4);
}

test "pointer slicing" {
// In Zig, we prefer using slices over null-terminated pointers.
// You can turn a pointer into a slice using slice syntax:
// You can turn an array into a slice using slice syntax:
var array = []u8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
const ptr = &array[1];
const slice = ptr[1..3];

assert(slice.ptr == &ptr[1]);
const slice = array[2..4];
assert(slice.len == 2);

// Slices have bounds checking and are therefore protected
Expand Down Expand Up @@ -1622,18 +1621,27 @@ fn foo(bytes: []u8) u32 {
const assert = @import("std").debug.assert;

test "basic slices" {
var array = []i32{1, 2, 3, 4};
var array = []i32{ 1, 2, 3, 4 };
// A slice is a pointer and a length. The difference between an array and
// a slice is that the array's length is part of the type and known at
// compile-time, whereas the slice's length is known at runtime.
// Both can be accessed with the `len` field.
const slice = array[0..array.len];
assert(slice.ptr == &array[0]);
assert(&slice[0] == &array[0]);
assert(slice.len == array.len);

// Using the address-of operator on a slice gives a pointer to a single
// item, while using the `ptr` field gives an unknown length pointer.
assert(@typeOf(slice.ptr) == [*]i32);
assert(@typeOf(&slice[0]) == *i32);
assert(@ptrToInt(slice.ptr) == @ptrToInt(&slice[0]));

// Slices have array bounds checking. If you try to access something out
// of bounds, you'll get a safety check failure:
slice[10] += 1;

// Note that `slice.ptr` does not invoke safety checking, while `&slice[0]`
// asserts that the slice has len >= 1.
}
{#code_end#}
<p>This is one reason we prefer slices to pointers.</p>
Expand Down Expand Up @@ -5937,7 +5945,7 @@ pub const __zig_test_fn_slice = {}; // overwritten later
{#header_open|C String Literals#}
{#code_begin|exe#}
{#link_libc#}
extern fn puts(*const u8) void;
extern fn puts([*]const u8) void;

pub fn main() void {
puts(c"this has a null terminator");
Expand Down
9 changes: 9 additions & 0 deletions src/all_types.hpp
Expand Up @@ -974,8 +974,14 @@ struct FnTypeId {
uint32_t fn_type_id_hash(FnTypeId*);
bool fn_type_id_eql(FnTypeId *a, FnTypeId *b);

enum PtrLen {
PtrLenUnknown,
PtrLenSingle,
};

struct TypeTableEntryPointer {
TypeTableEntry *child_type;
PtrLen ptr_len;
bool is_const;
bool is_volatile;
uint32_t alignment;
Expand Down Expand Up @@ -1397,6 +1403,7 @@ struct TypeId {
union {
struct {
TypeTableEntry *child_type;
PtrLen ptr_len;
bool is_const;
bool is_volatile;
uint32_t alignment;
Expand Down Expand Up @@ -2268,6 +2275,7 @@ struct IrInstructionElemPtr {

IrInstruction *array_ptr;
IrInstruction *elem_index;
PtrLen ptr_len;
bool is_const;
bool safety_check_on;
};
Expand Down Expand Up @@ -2419,6 +2427,7 @@ struct IrInstructionPtrType {
IrInstruction *child_type;
uint32_t bit_offset_start;
uint32_t bit_offset_end;
PtrLen ptr_len;
bool is_const;
bool is_volatile;
};
Expand Down
53 changes: 36 additions & 17 deletions src/analyze.cpp
Expand Up @@ -381,21 +381,22 @@ TypeTableEntry *get_promise_type(CodeGen *g, TypeTableEntry *result_type) {
}

TypeTableEntry *get_pointer_to_type_extra(CodeGen *g, TypeTableEntry *child_type, bool is_const,
bool is_volatile, uint32_t byte_alignment, uint32_t bit_offset, uint32_t unaligned_bit_count)
bool is_volatile, PtrLen ptr_len, uint32_t byte_alignment, uint32_t bit_offset, uint32_t unaligned_bit_count)
{
assert(!type_is_invalid(child_type));

TypeId type_id = {};
TypeTableEntry **parent_pointer = nullptr;
uint32_t abi_alignment = get_abi_alignment(g, child_type);
if (unaligned_bit_count != 0 || is_volatile || byte_alignment != abi_alignment) {
if (unaligned_bit_count != 0 || is_volatile || byte_alignment != abi_alignment || ptr_len != PtrLenSingle) {
type_id.id = TypeTableEntryIdPointer;
type_id.data.pointer.child_type = child_type;
type_id.data.pointer.is_const = is_const;
type_id.data.pointer.is_volatile = is_volatile;
type_id.data.pointer.alignment = byte_alignment;
type_id.data.pointer.bit_offset = bit_offset;
type_id.data.pointer.unaligned_bit_count = unaligned_bit_count;
type_id.data.pointer.ptr_len = ptr_len;

auto existing_entry = g->type_table.maybe_get(type_id);
if (existing_entry)
Expand All @@ -414,16 +415,17 @@ TypeTableEntry *get_pointer_to_type_extra(CodeGen *g, TypeTableEntry *child_type
TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdPointer);
entry->is_copyable = true;

const char *star_str = ptr_len == PtrLenSingle ? "*" : "[*]";
const char *const_str = is_const ? "const " : "";
const char *volatile_str = is_volatile ? "volatile " : "";
buf_resize(&entry->name, 0);
if (unaligned_bit_count == 0 && byte_alignment == abi_alignment) {
buf_appendf(&entry->name, "*%s%s%s", const_str, volatile_str, buf_ptr(&child_type->name));
buf_appendf(&entry->name, "%s%s%s%s", star_str, const_str, volatile_str, buf_ptr(&child_type->name));
} else if (unaligned_bit_count == 0) {
buf_appendf(&entry->name, "*align(%" PRIu32 ") %s%s%s", byte_alignment,
buf_appendf(&entry->name, "%salign(%" PRIu32 ") %s%s%s", star_str, byte_alignment,
const_str, volatile_str, buf_ptr(&child_type->name));
} else {
buf_appendf(&entry->name, "*align(%" PRIu32 ":%" PRIu32 ":%" PRIu32 ") %s%s%s", byte_alignment,
buf_appendf(&entry->name, "%salign(%" PRIu32 ":%" PRIu32 ":%" PRIu32 ") %s%s%s", star_str, byte_alignment,
bit_offset, bit_offset + unaligned_bit_count, const_str, volatile_str, buf_ptr(&child_type->name));
}

Expand All @@ -433,7 +435,9 @@ TypeTableEntry *get_pointer_to_type_extra(CodeGen *g, TypeTableEntry *child_type

if (!entry->zero_bits) {
assert(byte_alignment > 0);
if (is_const || is_volatile || unaligned_bit_count != 0 || byte_alignment != abi_alignment) {
if (is_const || is_volatile || unaligned_bit_count != 0 || byte_alignment != abi_alignment ||
ptr_len != PtrLenSingle)
{
TypeTableEntry *peer_type = get_pointer_to_type(g, child_type, false);
entry->type_ref = peer_type->type_ref;
entry->di_type = peer_type->di_type;
Expand All @@ -451,6 +455,7 @@ TypeTableEntry *get_pointer_to_type_extra(CodeGen *g, TypeTableEntry *child_type
entry->di_type = g->builtin_types.entry_void->di_type;
}

entry->data.pointer.ptr_len = ptr_len;
entry->data.pointer.child_type = child_type;
entry->data.pointer.is_const = is_const;
entry->data.pointer.is_volatile = is_volatile;
Expand All @@ -467,7 +472,8 @@ TypeTableEntry *get_pointer_to_type_extra(CodeGen *g, TypeTableEntry *child_type
}

TypeTableEntry *get_pointer_to_type(CodeGen *g, TypeTableEntry *child_type, bool is_const) {
return get_pointer_to_type_extra(g, child_type, is_const, false, get_abi_alignment(g, child_type), 0, 0);
return get_pointer_to_type_extra(g, child_type, is_const, false, PtrLenSingle,
get_abi_alignment(g, child_type), 0, 0);
}

TypeTableEntry *get_promise_frame_type(CodeGen *g, TypeTableEntry *return_type) {
Expand Down Expand Up @@ -757,6 +763,7 @@ static void slice_type_common_init(CodeGen *g, TypeTableEntry *pointer_type, Typ

TypeTableEntry *get_slice_type(CodeGen *g, TypeTableEntry *ptr_type) {
assert(ptr_type->id == TypeTableEntryIdPointer);
assert(ptr_type->data.pointer.ptr_len == PtrLenUnknown);

TypeTableEntry **parent_pointer = &ptr_type->data.pointer.slice_parent;
if (*parent_pointer) {
Expand All @@ -768,14 +775,16 @@ TypeTableEntry *get_slice_type(CodeGen *g, TypeTableEntry *ptr_type) {

// replace the & with [] to go from a ptr type name to a slice type name
buf_resize(&entry->name, 0);
buf_appendf(&entry->name, "[]%s", buf_ptr(&ptr_type->name) + 1);
size_t name_offset = (ptr_type->data.pointer.ptr_len == PtrLenSingle) ? 1 : 3;
buf_appendf(&entry->name, "[]%s", buf_ptr(&ptr_type->name) + name_offset);

TypeTableEntry *child_type = ptr_type->data.pointer.child_type;
uint32_t abi_alignment;
uint32_t abi_alignment = get_abi_alignment(g, child_type);
if (ptr_type->data.pointer.is_const || ptr_type->data.pointer.is_volatile ||
ptr_type->data.pointer.alignment != (abi_alignment = get_abi_alignment(g, child_type)))
ptr_type->data.pointer.alignment != abi_alignment)
{
TypeTableEntry *peer_ptr_type = get_pointer_to_type(g, child_type, false);
TypeTableEntry *peer_ptr_type = get_pointer_to_type_extra(g, child_type, false, false,
PtrLenUnknown, abi_alignment, 0, 0);
TypeTableEntry *peer_slice_type = get_slice_type(g, peer_ptr_type);

slice_type_common_init(g, ptr_type, entry);
Expand All @@ -799,9 +808,11 @@ TypeTableEntry *get_slice_type(CodeGen *g, TypeTableEntry *ptr_type) {
if (child_ptr_type->data.pointer.is_const || child_ptr_type->data.pointer.is_volatile ||
child_ptr_type->data.pointer.alignment != get_abi_alignment(g, grand_child_type))
{
TypeTableEntry *bland_child_ptr_type = get_pointer_to_type(g, grand_child_type, false);
TypeTableEntry *bland_child_ptr_type = get_pointer_to_type_extra(g, grand_child_type, false, false,
PtrLenUnknown, get_abi_alignment(g, grand_child_type), 0, 0);
TypeTableEntry *bland_child_slice = get_slice_type(g, bland_child_ptr_type);
TypeTableEntry *peer_ptr_type = get_pointer_to_type(g, bland_child_slice, false);
TypeTableEntry *peer_ptr_type = get_pointer_to_type_extra(g, bland_child_slice, false, false,
PtrLenUnknown, get_abi_alignment(g, bland_child_slice), 0, 0);
TypeTableEntry *peer_slice_type = get_slice_type(g, peer_ptr_type);

entry->type_ref = peer_slice_type->type_ref;
Expand Down Expand Up @@ -1284,7 +1295,8 @@ static bool analyze_const_align(CodeGen *g, Scope *scope, AstNode *node, uint32_
}

static bool analyze_const_string(CodeGen *g, Scope *scope, AstNode *node, Buf **out_buffer) {
TypeTableEntry *ptr_type = get_pointer_to_type(g, g->builtin_types.entry_u8, true);
TypeTableEntry *ptr_type = get_pointer_to_type_extra(g, g->builtin_types.entry_u8, true, false,
PtrLenUnknown, get_abi_alignment(g, g->builtin_types.entry_u8), 0, 0);
TypeTableEntry *str_type = get_slice_type(g, ptr_type);
IrInstruction *instr = analyze_const_value(g, scope, node, str_type, nullptr);
if (type_is_invalid(instr->value.type))
Expand Down Expand Up @@ -2954,7 +2966,8 @@ static void typecheck_panic_fn(CodeGen *g, FnTableEntry *panic_fn) {
if (fn_type_id->param_count != 2) {
return wrong_panic_prototype(g, proto_node, fn_type);
}
TypeTableEntry *const_u8_ptr = get_pointer_to_type(g, g->builtin_types.entry_u8, true);
TypeTableEntry *const_u8_ptr = get_pointer_to_type_extra(g, g->builtin_types.entry_u8, true, false,
PtrLenUnknown, get_abi_alignment(g, g->builtin_types.entry_u8), 0, 0);
TypeTableEntry *const_u8_slice = get_slice_type(g, const_u8_ptr);
if (fn_type_id->param_info[0].type != const_u8_slice) {
return wrong_panic_prototype(g, proto_node, fn_type);
Expand Down Expand Up @@ -4994,7 +5007,9 @@ void init_const_c_str_lit(CodeGen *g, ConstExprValue *const_val, Buf *str) {

// then make the pointer point to it
const_val->special = ConstValSpecialStatic;
const_val->type = get_pointer_to_type(g, g->builtin_types.entry_u8, true);
// TODO make this `[*]null u8` instead of `[*]u8`
const_val->type = get_pointer_to_type_extra(g, g->builtin_types.entry_u8, true, false,
PtrLenUnknown, get_abi_alignment(g, g->builtin_types.entry_u8), 0, 0);
const_val->data.x_ptr.special = ConstPtrSpecialBaseArray;
const_val->data.x_ptr.data.base_array.array_val = array_val;
const_val->data.x_ptr.data.base_array.elem_index = 0;
Expand Down Expand Up @@ -5135,7 +5150,9 @@ void init_const_slice(CodeGen *g, ConstExprValue *const_val, ConstExprValue *arr
{
assert(array_val->type->id == TypeTableEntryIdArray);

TypeTableEntry *ptr_type = get_pointer_to_type(g, array_val->type->data.array.child_type, is_const);
TypeTableEntry *ptr_type = get_pointer_to_type_extra(g, array_val->type->data.array.child_type,
is_const, false, PtrLenUnknown, get_abi_alignment(g, array_val->type->data.array.child_type),
0, 0);

const_val->special = ConstValSpecialStatic;
const_val->type = get_slice_type(g, ptr_type);
Expand Down Expand Up @@ -5759,6 +5776,7 @@ uint32_t type_id_hash(TypeId x) {
return hash_ptr(x.data.error_union.err_set_type) ^ hash_ptr(x.data.error_union.payload_type);
case TypeTableEntryIdPointer:
return hash_ptr(x.data.pointer.child_type) +
((x.data.pointer.ptr_len == PtrLenSingle) ? (uint32_t)1120226602 : (uint32_t)3200913342) +
(x.data.pointer.is_const ? (uint32_t)2749109194 : (uint32_t)4047371087) +
(x.data.pointer.is_volatile ? (uint32_t)536730450 : (uint32_t)1685612214) +
(((uint32_t)x.data.pointer.alignment) ^ (uint32_t)0x777fbe0e) +
Expand Down Expand Up @@ -5807,6 +5825,7 @@ bool type_id_eql(TypeId a, TypeId b) {

case TypeTableEntryIdPointer:
return a.data.pointer.child_type == b.data.pointer.child_type &&
a.data.pointer.ptr_len == b.data.pointer.ptr_len &&
a.data.pointer.is_const == b.data.pointer.is_const &&
a.data.pointer.is_volatile == b.data.pointer.is_volatile &&
a.data.pointer.alignment == b.data.pointer.alignment &&
Expand Down
2 changes: 1 addition & 1 deletion src/analyze.hpp
Expand Up @@ -16,7 +16,7 @@ ErrorMsg *add_error_note(CodeGen *g, ErrorMsg *parent_msg, AstNode *node, Buf *m
TypeTableEntry *new_type_table_entry(TypeTableEntryId id);
TypeTableEntry *get_pointer_to_type(CodeGen *g, TypeTableEntry *child_type, bool is_const);
TypeTableEntry *get_pointer_to_type_extra(CodeGen *g, TypeTableEntry *child_type, bool is_const,
bool is_volatile, uint32_t byte_alignment, uint32_t bit_offset, uint32_t unaligned_bit_count);
bool is_volatile, PtrLen ptr_len, uint32_t byte_alignment, uint32_t bit_offset, uint32_t unaligned_bit_count);
uint64_t type_size(CodeGen *g, TypeTableEntry *type_entry);
uint64_t type_size_bits(CodeGen *g, TypeTableEntry *type_entry);
TypeTableEntry **get_int_type_ptr(CodeGen *g, bool is_signed, uint32_t size_in_bits);
Expand Down
8 changes: 7 additions & 1 deletion src/ast_render.cpp
Expand Up @@ -625,7 +625,13 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) {
case NodeTypePointerType:
{
if (!grouped) fprintf(ar->f, "(");
fprintf(ar->f, "*");
const char *star = "[*]";
if (node->data.pointer_type.star_token != nullptr &&
(node->data.pointer_type.star_token->id == TokenIdStar || node->data.pointer_type.star_token->id == TokenIdStarStar))
{
star = "*";
}
fprintf(ar->f, "%s", star);
if (node->data.pointer_type.align_expr != nullptr) {
fprintf(ar->f, "align(");
render_node_grouped(ar, node->data.pointer_type.align_expr);
Expand Down

0 comments on commit 96164ce

Please sign in to comment.