Commit 1aa978f32e

Andrew Kelley <andrew@ziglang.org>
2019-11-14 09:26:33
implement null terminated pointers
1 parent e3404e3
lib/std/builtin.zig
@@ -144,6 +144,7 @@ pub const TypeInfo = union(enum) {
         alignment: comptime_int,
         child: type,
         is_allowzero: bool,
+        is_null_terminated: bool,
 
         /// This data structure is used by the Zig language code generation and
         /// therefore must be kept in sync with the compiler implementation.
lib/std/meta.zig
@@ -558,3 +558,4 @@ pub fn refAllDecls(comptime T: type) void {
     if (!builtin.is_test) return;
     _ = declarations(T);
 }
+
src/all_types.hpp
@@ -55,6 +55,7 @@ enum PtrLen {
     PtrLenUnknown,
     PtrLenSingle,
     PtrLenC,
+    PtrLenNull,
 };
 
 // This one corresponds to the builtin.zig enum.
@@ -825,6 +826,7 @@ struct AstNodePointerType {
     Token *allow_zero_token;
     bool is_const;
     bool is_volatile;
+    bool is_null_terminated;
 };
 
 struct AstNodeInferredArrayType {
@@ -838,6 +840,7 @@ struct AstNodeArrayType {
     Token *allow_zero_token;
     bool is_const;
     bool is_volatile;
+    bool is_null_terminated;
 };
 
 struct AstNodeUsingNamespace {
src/analyze.cpp
@@ -460,6 +460,8 @@ static const char *ptr_len_to_star_str(PtrLen ptr_len) {
             return "[*]";
         case PtrLenC:
             return "[*c]";
+        case PtrLenNull:
+            return "[*]null ";
     }
     zig_unreachable();
 }
@@ -7032,7 +7034,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 ZigTypeIdPointer:
             return hash_ptr(x.data.pointer.child_type) +
-                ((x.data.pointer.ptr_len == PtrLenSingle) ? (uint32_t)1120226602 : (uint32_t)3200913342) +
+                (uint32_t)x.data.pointer.ptr_len * 1120226602u +
                 (x.data.pointer.is_const ? (uint32_t)2749109194 : (uint32_t)4047371087) +
                 (x.data.pointer.is_volatile ? (uint32_t)536730450 : (uint32_t)1685612214) +
                 (x.data.pointer.allow_zero ? (uint32_t)3324284834 : (uint32_t)3584904923) +
src/dump_analysis.cpp
@@ -992,6 +992,10 @@ static void anal_dump_type(AnalDumpCtx *ctx, ZigType *ty) {
                     jw_object_field(jw, "len");
                     jw_int(jw, 3);
                     break;
+                case PtrLenNull:
+                    jw_object_field(jw, "len");
+                    jw_int(jw, 4);
+                    break;
             }
             anal_dump_pointer_attrs(ctx, ty);
             break;
src/ir.cpp
@@ -6043,13 +6043,25 @@ static PtrLen star_token_to_ptr_len(TokenId token_id) {
 
 static IrInstruction *ir_gen_pointer_type(IrBuilder *irb, Scope *scope, AstNode *node) {
     assert(node->type == NodeTypePointerType);
+
     PtrLen ptr_len = star_token_to_ptr_len(node->data.pointer_type.star_token->id);
+    if (node->data.pointer_type.is_null_terminated) {
+        if (ptr_len == PtrLenUnknown) {
+            ptr_len = PtrLenNull;
+        } else {
+            exec_add_error_node(irb->codegen, irb->exec, node,
+                    buf_sprintf("null-terminated pointer must be specified with [*] token"));
+            return irb->codegen->invalid_instruction;
+        }
+    }
+
     bool is_const = node->data.pointer_type.is_const;
     bool is_volatile = node->data.pointer_type.is_volatile;
     bool is_allow_zero = node->data.pointer_type.allow_zero_token != nullptr;
     AstNode *expr_node = node->data.pointer_type.op_expr;
     AstNode *align_expr = node->data.pointer_type.align_expr;
 
+
     IrInstruction *align_value;
     if (align_expr != nullptr) {
         align_value = ir_gen_node(irb, align_expr, scope);
@@ -9793,6 +9805,7 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, ZigType *wanted
     // alignment can be decreased
     // bit offset attributes must match exactly
     // PtrLenSingle/PtrLenUnknown must match exactly, but PtrLenC matches either one
+    // PtrLenNull can coerce into PtrLenUnknown
     ZigType *wanted_ptr_type = get_src_ptr_type(wanted_type);
     ZigType *actual_ptr_type = get_src_ptr_type(actual_type);
     bool wanted_allows_zero = ptr_allows_addr_zero(wanted_type);
@@ -9843,7 +9856,10 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, ZigType *wanted
             return result;
         }
         bool ptr_lens_equal = actual_ptr_type->data.pointer.ptr_len == wanted_ptr_type->data.pointer.ptr_len;
-        if ((ptr_lens_equal || wanted_is_c_ptr || actual_is_c_ptr) &&
+        bool ok_null_term_ptrs =
+            actual_ptr_type->data.pointer.ptr_len == PtrLenNull ||
+            wanted_ptr_type->data.pointer.ptr_len == PtrLenUnknown;
+        if ((ptr_lens_equal || wanted_is_c_ptr || actual_is_c_ptr || ok_null_term_ptrs) &&
             type_has_bits(wanted_type) == type_has_bits(actual_type) &&
             (!actual_ptr_type->data.pointer.is_const || wanted_ptr_type->data.pointer.is_const) &&
             (!actual_ptr_type->data.pointer.is_volatile || wanted_ptr_type->data.pointer.is_volatile) &&
@@ -14532,6 +14548,7 @@ static bool is_pointer_arithmetic_allowed(ZigType *lhs_type, IrBinOp op) {
         case PtrLenSingle:
             return false;
         case PtrLenUnknown:
+        case PtrLenNull:
         case PtrLenC:
             break;
     }
@@ -21166,6 +21183,7 @@ static BuiltinPtrSize ptr_len_to_size_enum_index(PtrLen ptr_len) {
         case PtrLenSingle:
             return BuiltinPtrSizeOne;
         case PtrLenUnknown:
+        case PtrLenNull:
             return BuiltinPtrSizeMany;
         case PtrLenC:
             return BuiltinPtrSizeC;
@@ -21210,7 +21228,7 @@ static ConstExprValue *create_ptr_like_type_info(IrAnalyze *ira, ZigType *ptr_ty
     result->special = ConstValSpecialStatic;
     result->type = type_info_pointer_type;
 
-    ConstExprValue **fields = alloc_const_vals_ptrs(6);
+    ConstExprValue **fields = alloc_const_vals_ptrs(7);
     result->data.x_struct.fields = fields;
 
     // size: Size
@@ -21246,6 +21264,11 @@ static ConstExprValue *create_ptr_like_type_info(IrAnalyze *ira, ZigType *ptr_ty
     fields[5]->special = ConstValSpecialStatic;
     fields[5]->type = ira->codegen->builtin_types.entry_bool;
     fields[5]->data.x_bool = attrs_type->data.pointer.allow_zero;
+    // is_null_terminated: bool
+    ensure_field_index(result->type, "is_null_terminated", 6);
+    fields[6]->special = ConstValSpecialStatic;
+    fields[6]->type = ira->codegen->builtin_types.entry_bool;
+    fields[6]->data.x_bool = attrs_type->data.pointer.ptr_len == PtrLenNull;
 
     return result;
 };
src/parser.cpp
@@ -2618,6 +2618,11 @@ static AstNode *ast_parse_prefix_type_op(ParseContext *pc) {
     if (array != nullptr) {
         assert(array->type == NodeTypeArrayType);
         while (true) {
+            if (eat_token_if(pc, TokenIdKeywordNull) != nullptr) {
+                array->data.array_type.is_null_terminated = true;
+                continue;
+            }
+
             Token *allowzero_token = eat_token_if(pc, TokenIdKeywordAllowZero);
             if (allowzero_token != nullptr) {
                 array->data.array_type.allow_zero_token = allowzero_token;
@@ -2653,6 +2658,11 @@ static AstNode *ast_parse_prefix_type_op(ParseContext *pc) {
         if (child == nullptr)
             child = ptr;
         while (true) {
+            if (eat_token_if(pc, TokenIdKeywordNull) != nullptr) {
+                child->data.pointer_type.is_null_terminated = true;
+                continue;
+            }
+
             Token *allowzero_token = eat_token_if(pc, TokenIdKeywordAllowZero);
             if (allowzero_token != nullptr) {
                 child->data.pointer_type.allow_zero_token = allowzero_token;
src/translate_c.cpp
@@ -291,6 +291,7 @@ static TokenId ptr_len_to_token_id(PtrLen ptr_len) {
         case PtrLenSingle:
             return TokenIdStar;
         case PtrLenUnknown:
+        case PtrLenNull:
             return TokenIdBracketStarBracket;
         case PtrLenC:
             return TokenIdBracketStarCBracket;
@@ -302,6 +303,7 @@ static AstNode *trans_create_node_ptr_type(Context *c, bool is_const, bool is_vo
     AstNode *node = trans_create_node(c, NodeTypePointerType);
     node->data.pointer_type.star_token = allocate<ZigToken>(1);
     node->data.pointer_type.star_token->id = ptr_len_to_token_id(ptr_len);
+    node->data.pointer_type.is_null_terminated = (ptr_len == PtrLenNull);
     node->data.pointer_type.is_const = is_const;
     node->data.pointer_type.is_volatile = is_volatile;
     node->data.pointer_type.op_expr = child_node;
test/stage1/behavior/pointers.zig
@@ -200,3 +200,17 @@ test "assign null directly to C pointer and test null equality" {
     }
     comptime expect((y1 orelse &othery) == y1);
 }
+
+test "null terminated pointer" {
+    const S = struct {
+        fn doTheTest() void {
+            var array_with_zero = [_]u8{'h', 'e', 'l', 'l', 'o', 0};
+            var zero_ptr: [*]null const u8 = @ptrCast([*]null const u8, &array_with_zero);
+            var no_zero_ptr: [*]const u8 = zero_ptr;
+            expect(std.mem.eql(u8, std.mem.toSliceConst(u8, no_zero_ptr), "hello"));
+        }
+    };
+    S.doTheTest();
+    // TODO test fails at comptime
+    //comptime S.doTheTest();
+}
test/compile_errors.zig
@@ -68,6 +68,18 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
         "tmp.zig:9:27: error: @atomicRmw on enum only works with .Xchg",
     );
 
+    cases.add(
+        "disallow coercion from non-null-terminated pointer to null-terminated pointer",
+        \\extern fn puts(s: [*]null const u8) c_int;
+        \\pub fn main() void {
+        \\    const no_zero_array = [_]u8{'h', 'e', 'l', 'l', 'o'};
+        \\    const no_zero_ptr: [*]const u8 = &no_zero_array;
+        \\    _ = puts(no_zero_ptr);
+        \\}
+    ,
+        "tmp.zig:5:14: error: expected type '[*]null const u8', found '[*]const u8'",
+    );
+
     cases.add(
         "atomic orderings of atomicStore Acquire or AcqRel",
         \\export fn entry() void {