authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-07-31 11:51:19-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-07-31 16:57:42-07:00
log377e8579f9539c56fc5988c9a452a01438af89f3
tree542f9ac9cf63e7b083e9a606e6c8a77eade2e6ce
parentc08effc20abe2595ba5c83e25b78c274ec9c58ec

std.zig.tokenizer: simplify

I pointed a fuzzer at the tokenizer and it crashed immediately. Upon inspection, I was dissatisfied with the implementation. This commit removes several mechanisms: * Removes the "invalid byte" compile error note. * Dramatically simplifies tokenizer recovery by making recovery always occur at newlines, and never otherwise. * Removes UTF-8 validation. * Moves some character validation logic to `std.zig.parseCharLiteral`. Removing UTF-8 validation is a regression of #663, however, the existing implementation was already buggy. When adding this functionality back, it must be fuzz-tested while checking the property that it matches an independent Unicode validation implementation on the same file. While we're at it, fuzzing should check the other properties of that proposal, such as no ASCII control characters existing inside the source code. Other changes included in this commit: * Deprecate `std.unicode.utf8Decode` and its WTF-8 counterpart. This function has an awkward API that is too easy to misuse. * Make `utf8Decode2` and friends use arrays as parameters, eliminating a runtime assertion in favor of using the type system. After this commit, the crash found by fuzzing, which was "\x07\xd5\x80\xc3=o\xda|a\xfc{\x9a\xec\x91\xdf\x0f\\\x1a^\xbe;\x8c\xbf\xee\xea" no longer causes a crash. However, I did not feel the need to add this test case because the simplified logic eradicates most crashes of this nature.

12 files changed, 234 insertions(+), 392 deletions(-)

lib/std/unicode.zig+14-19
......@@ -95,16 +95,13 @@ pub inline fn utf8EncodeComptime(comptime c: u21) [
9595
9696const Utf8DecodeError = Utf8Decode2Error || Utf8Decode3Error || Utf8Decode4Error;
9797
98/// Decodes the UTF-8 codepoint encoded in the given slice of bytes.
99/// bytes.len must be equal to utf8ByteSequenceLength(bytes[0]) catch unreachable.
100/// If you already know the length at comptime, you can call one of
101/// utf8Decode2,utf8Decode3,utf8Decode4 directly instead of this function.
98/// Deprecated. This function has an awkward API that is too easy to use incorrectly.
10299pub fn utf8Decode(bytes: []const u8) Utf8DecodeError!u21 {
103100 return switch (bytes.len) {
104 1 => @as(u21, bytes[0]),
105 2 => utf8Decode2(bytes),
106 3 => utf8Decode3(bytes),
107 4 => utf8Decode4(bytes),
101 1 => bytes[0],
102 2 => utf8Decode2(bytes[0..2].*),
103 3 => utf8Decode3(bytes[0..3].*),
104 4 => utf8Decode4(bytes[0..4].*),
108105 else => unreachable,
109106 };
110107}
......@@ -113,8 +110,7 @@ const Utf8Decode2Error = error{
113110 Utf8ExpectedContinuation,
114111 Utf8OverlongEncoding,
115112};
116pub fn utf8Decode2(bytes: []const u8) Utf8Decode2Error!u21 {
117 assert(bytes.len == 2);
113pub fn utf8Decode2(bytes: [2]u8) Utf8Decode2Error!u21 {
118114 assert(bytes[0] & 0b11100000 == 0b11000000);
119115 var value: u21 = bytes[0] & 0b00011111;
120116
......@@ -130,7 +126,7 @@ pub fn utf8Decode2(bytes: []const u8) Utf8Decode2Error!u21 {
130126const Utf8Decode3Error = Utf8Decode3AllowSurrogateHalfError || error{
131127 Utf8EncodesSurrogateHalf,
132128};
133pub fn utf8Decode3(bytes: []const u8) Utf8Decode3Error!u21 {
129pub fn utf8Decode3(bytes: [3]u8) Utf8Decode3Error!u21 {
134130 const value = try utf8Decode3AllowSurrogateHalf(bytes);
135131
136132 if (0xd800 <= value and value <= 0xdfff) return error.Utf8EncodesSurrogateHalf;
......@@ -142,8 +138,7 @@ const Utf8Decode3AllowSurrogateHalfError = error{
142138 Utf8ExpectedContinuation,
143139 Utf8OverlongEncoding,
144140};
145pub fn utf8Decode3AllowSurrogateHalf(bytes: []const u8) Utf8Decode3AllowSurrogateHalfError!u21 {
146 assert(bytes.len == 3);
141pub fn utf8Decode3AllowSurrogateHalf(bytes: [3]u8) Utf8Decode3AllowSurrogateHalfError!u21 {
147142 assert(bytes[0] & 0b11110000 == 0b11100000);
148143 var value: u21 = bytes[0] & 0b00001111;
149144
......@@ -165,8 +160,7 @@ const Utf8Decode4Error = error{
165160 Utf8OverlongEncoding,
166161 Utf8CodepointTooLarge,
167162};
168pub fn utf8Decode4(bytes: []const u8) Utf8Decode4Error!u21 {
169 assert(bytes.len == 4);
163pub fn utf8Decode4(bytes: [4]u8) Utf8Decode4Error!u21 {
170164 assert(bytes[0] & 0b11111000 == 0b11110000);
171165 var value: u21 = bytes[0] & 0b00000111;
172166
......@@ -1637,12 +1631,13 @@ pub fn wtf8Encode(c: u21, out: []u8) error{CodepointTooLarge}!u3 {
16371631
16381632const Wtf8DecodeError = Utf8Decode2Error || Utf8Decode3AllowSurrogateHalfError || Utf8Decode4Error;
16391633
1634/// Deprecated. This function has an awkward API that is too easy to use incorrectly.
16401635pub fn wtf8Decode(bytes: []const u8) Wtf8DecodeError!u21 {
16411636 return switch (bytes.len) {
1642 1 => @as(u21, bytes[0]),
1643 2 => utf8Decode2(bytes),
1644 3 => utf8Decode3AllowSurrogateHalf(bytes),
1645 4 => utf8Decode4(bytes),
1637 1 => bytes[0],
1638 2 => utf8Decode2(bytes[0..2].*),
1639 3 => utf8Decode3AllowSurrogateHalf(bytes[0..3].*),
1640 4 => utf8Decode4(bytes[0..4].*),
16461641 else => unreachable,
16471642 };
16481643}
lib/std/zig/Ast.zig+1-1
......@@ -69,7 +69,7 @@ pub fn parse(gpa: Allocator, source: [:0]const u8, mode: Mode) Allocator.Error!A
6969 const token = tokenizer.next();
7070 try tokens.append(gpa, .{
7171 .tag = token.tag,
72 .start = @as(u32, @intCast(token.loc.start)),
72 .start = @intCast(token.loc.start),
7373 });
7474 if (token.tag == .eof) break;
7575 }
lib/std/zig/AstGen.zig+3-12
......@@ -11351,6 +11351,9 @@ fn failWithStrLitError(astgen: *AstGen, err: std.zig.string_literal.Error, token
1135111351 .{raw_string[bad_index]},
1135211352 );
1135311353 },
11354 .empty_char_literal => {
11355 return astgen.failOff(token, offset, "empty character literal", .{});
11356 },
1135411357 }
1135511358}
1135611359
......@@ -13820,21 +13823,9 @@ fn lowerAstErrors(astgen: *AstGen) !void {
1382013823 var msg: std.ArrayListUnmanaged(u8) = .{};
1382113824 defer msg.deinit(gpa);
1382213825
13823 const token_starts = tree.tokens.items(.start);
13824 const token_tags = tree.tokens.items(.tag);
13825
1382613826 var notes: std.ArrayListUnmanaged(u32) = .{};
1382713827 defer notes.deinit(gpa);
1382813828
13829 const tok = parse_err.token + @intFromBool(parse_err.token_is_prev);
13830 if (token_tags[tok] == .invalid) {
13831 const bad_off: u32 = @intCast(tree.tokenSlice(tok).len);
13832 const byte_abs = token_starts[tok] + bad_off;
13833 try notes.append(gpa, try astgen.errNoteTokOff(tok, bad_off, "invalid byte: '{'}'", .{
13834 std.zig.fmtEscapes(tree.source[byte_abs..][0..1]),
13835 }));
13836 }
13837
1383813829 for (tree.errors[1..]) |note| {
1383913830 if (!note.is_note) break;
1384013831
lib/std/zig/parser_test.zig-1
......@@ -6061,7 +6061,6 @@ test "recovery: invalid container members" {
60616061 , &[_]Error{
60626062 .expected_expr,
60636063 .expected_comma_after_field,
6064 .expected_type_expr,
60656064 .expected_semi_after_stmt,
60666065 });
60676066}
lib/std/zig/string_literal.zig+19-5
......@@ -1,6 +1,5 @@
11const std = @import("../std.zig");
22const assert = std.debug.assert;
3const utf8Decode = std.unicode.utf8Decode;
43const utf8Encode = std.unicode.utf8Encode;
54
65pub const ParseError = error{
......@@ -37,12 +36,16 @@ pub const Error = union(enum) {
3736 expected_single_quote: usize,
3837 /// The character at this index cannot be represented without an escape sequence.
3938 invalid_character: usize,
39 /// `''`. Not returned for string literals.
40 empty_char_literal,
4041};
4142
42/// Only validates escape sequence characters.
43/// Slice must be valid utf8 starting and ending with "'" and exactly one codepoint in between.
43/// Asserts the slice starts and ends with single-quotes.
44/// Returns an error if there is not exactly one UTF-8 codepoint in between.
4445pub fn parseCharLiteral(slice: []const u8) ParsedCharLiteral {
45 assert(slice.len >= 3 and slice[0] == '\'' and slice[slice.len - 1] == '\'');
46 if (slice.len < 3) return .{ .failure = .empty_char_literal };
47 assert(slice[0] == '\'');
48 assert(slice[slice.len - 1] == '\'');
4649
4750 switch (slice[1]) {
4851 '\\' => {
......@@ -55,7 +58,18 @@ pub fn parseCharLiteral(slice: []const u8) ParsedCharLiteral {
5558 },
5659 0 => return .{ .failure = .{ .invalid_character = 1 } },
5760 else => {
58 const codepoint = utf8Decode(slice[1 .. slice.len - 1]) catch unreachable;
61 const inner = slice[1 .. slice.len - 1];
62 const n = std.unicode.utf8ByteSequenceLength(inner[0]) catch return .{
63 .failure = .{ .invalid_unicode_codepoint = 1 },
64 };
65 if (inner.len > n) return .{ .failure = .{ .expected_single_quote = 1 + n } };
66 const codepoint = switch (n) {
67 1 => inner[0],
68 2 => std.unicode.utf8Decode2(inner[0..2].*),
69 3 => std.unicode.utf8Decode3(inner[0..3].*),
70 4 => std.unicode.utf8Decode4(inner[0..4].*),
71 else => unreachable,
72 } catch return .{ .failure = .{ .invalid_unicode_codepoint = 1 } };
5973 return .{ .success = codepoint };
6074 },
6175 }
lib/std/zig/tokenizer.zig+176-336
......@@ -320,7 +320,7 @@ pub const Token = struct {
320320
321321 pub fn symbol(tag: Tag) []const u8 {
322322 return tag.lexeme() orelse switch (tag) {
323 .invalid => "invalid bytes",
323 .invalid => "invalid token",
324324 .identifier => "an identifier",
325325 .string_literal, .multiline_string_literal_line => "a string literal",
326326 .char_literal => "a character literal",
......@@ -338,22 +338,22 @@ pub const Tokenizer = struct {
338338 buffer: [:0]const u8,
339339 index: usize,
340340
341 /// For debugging purposes
341 /// For debugging purposes.
342342 pub fn dump(self: *Tokenizer, token: *const Token) void {
343343 std.debug.print("{s} \"{s}\"\n", .{ @tagName(token.tag), self.buffer[token.loc.start..token.loc.end] });
344344 }
345345
346346 pub fn init(buffer: [:0]const u8) Tokenizer {
347 // Skip the UTF-8 BOM if present
348 const src_start: usize = if (std.mem.startsWith(u8, buffer, "\xEF\xBB\xBF")) 3 else 0;
349 return Tokenizer{
347 // Skip the UTF-8 BOM if present.
348 return .{
350349 .buffer = buffer,
351 .index = src_start,
350 .index = if (std.mem.startsWith(u8, buffer, "\xEF\xBB\xBF")) 3 else 0,
352351 };
353352 }
354353
355354 const State = enum {
356355 start,
356 expect_newline,
357357 identifier,
358358 builtin,
359359 string_literal,
......@@ -361,10 +361,6 @@ pub const Tokenizer = struct {
361361 multiline_string_literal_line,
362362 char_literal,
363363 char_literal_backslash,
364 char_literal_hex_escape,
365 char_literal_unicode_escape_saw_u,
366 char_literal_unicode_escape,
367 char_literal_end,
368364 backslash,
369365 equal,
370366 bang,
......@@ -400,32 +396,38 @@ pub const Tokenizer = struct {
400396 period_2,
401397 period_asterisk,
402398 saw_at_sign,
399 invalid,
403400 };
404401
402 /// After this returns invalid, it will reset on the next newline, returning tokens starting from there.
403 /// An eof token will always be returned at the end.
405404 pub fn next(self: *Tokenizer) Token {
406405 var state: State = .start;
407 var result = Token{
408 .tag = .eof,
406 var result: Token = .{
407 .tag = undefined,
409408 .loc = .{
410409 .start = self.index,
411410 .end = undefined,
412411 },
413412 };
414 var seen_escape_digits: usize = undefined;
415413 while (true) : (self.index += 1) {
416414 const c = self.buffer[self.index];
417415 switch (state) {
418416 .start => switch (c) {
419417 0 => {
420 if (self.index != self.buffer.len) {
421 result.tag = .invalid;
422 result.loc.end = self.index;
423 self.index += 1;
424 return result;
425 }
426 break;
427 },
428 ' ', '\n', '\t', '\r' => {
418 if (self.index == self.buffer.len) return .{
419 .tag = .eof,
420 .loc = .{
421 .start = self.index,
422 .end = self.index,
423 },
424 };
425 state = .invalid;
426 },
427 '\r' => {
428 state = .expect_newline;
429 },
430 ' ', '\n', '\t' => {
429431 result.loc.start = self.index + 1;
430432 },
431433 '"' => {
......@@ -434,6 +436,7 @@ pub const Tokenizer = struct {
434436 },
435437 '\'' => {
436438 state = .char_literal;
439 result.tag = .char_literal;
437440 },
438441 'a'...'z', 'A'...'Z', '_' => {
439442 state = .identifier;
......@@ -545,14 +548,37 @@ pub const Tokenizer = struct {
545548 result.tag = .number_literal;
546549 },
547550 else => {
551 state = .invalid;
552 },
553 },
554
555 .expect_newline => switch (c) {
556 '\n' => {
557 result.loc.start = self.index + 1;
558 state = .start;
559 },
560 else => {
561 state = .invalid;
562 },
563 },
564
565 .invalid => switch (c) {
566 0 => if (self.index == self.buffer.len) {
567 result.tag = .invalid;
568 break;
569 },
570 '\n' => {
548571 result.tag = .invalid;
549 result.loc.end = self.index;
550 self.index += std.unicode.utf8ByteSequenceLength(c) catch 1;
551 return result;
572 break;
552573 },
574 else => continue,
553575 },
554576
555577 .saw_at_sign => switch (c) {
578 0, '\n' => {
579 result.tag = .invalid;
580 break;
581 },
556582 '"' => {
557583 result.tag = .identifier;
558584 state = .string_literal;
......@@ -562,8 +588,7 @@ pub const Tokenizer = struct {
562588 result.tag = .builtin;
563589 },
564590 else => {
565 result.tag = .invalid;
566 break;
591 state = .invalid;
567592 },
568593 },
569594
......@@ -698,7 +723,7 @@ pub const Tokenizer = struct {
698723 },
699724
700725 .identifier => switch (c) {
701 'a'...'z', 'A'...'Z', '_', '0'...'9' => {},
726 'a'...'z', 'A'...'Z', '_', '0'...'9' => continue,
702727 else => {
703728 if (Token.getKeyword(self.buffer[result.loc.start..self.index])) |tag| {
704729 result.tag = tag;
......@@ -707,26 +732,37 @@ pub const Tokenizer = struct {
707732 },
708733 },
709734 .builtin => switch (c) {
710 'a'...'z', 'A'...'Z', '_', '0'...'9' => {},
735 'a'...'z', 'A'...'Z', '_', '0'...'9' => continue,
711736 else => break,
712737 },
713738 .backslash => switch (c) {
739 0 => {
740 result.tag = .invalid;
741 break;
742 },
714743 '\\' => {
715744 state = .multiline_string_literal_line;
716745 },
717 else => {
746 '\n' => {
718747 result.tag = .invalid;
719748 break;
720749 },
750 else => {
751 state = .invalid;
752 },
721753 },
722754 .string_literal => switch (c) {
723 0, '\n' => {
724 result.tag = .invalid;
725 result.loc.end = self.index;
755 0 => {
726756 if (self.index != self.buffer.len) {
727 self.index += 1;
757 state = .invalid;
758 continue;
728759 }
729 return result;
760 result.tag = .invalid;
761 break;
762 },
763 '\n' => {
764 result.tag = .invalid;
765 break;
730766 },
731767 '\\' => {
732768 state = .string_literal_backslash;
......@@ -735,150 +771,74 @@ pub const Tokenizer = struct {
735771 self.index += 1;
736772 break;
737773 },
738 else => {
739 if (self.invalidCharacterLength()) |len| {
740 result.tag = .invalid;
741 result.loc.end = self.index;
742 self.index += len;
743 return result;
744 }
745
746 self.index += (std.unicode.utf8ByteSequenceLength(c) catch unreachable) - 1;
774 0x01...0x09, 0x0b...0x1f, 0x7f => {
775 state = .invalid;
747776 },
777 else => continue,
748778 },
749779
750780 .string_literal_backslash => switch (c) {
751781 0, '\n' => {
752782 result.tag = .invalid;
753 result.loc.end = self.index;
754 if (self.index != self.buffer.len) {
755 self.index += 1;
756 }
757 return result;
783 break;
758784 },
759785 else => {
760786 state = .string_literal;
761
762 if (self.invalidCharacterLength()) |len| {
763 result.tag = .invalid;
764 result.loc.end = self.index;
765 self.index += len;
766 return result;
767 }
768
769 self.index += (std.unicode.utf8ByteSequenceLength(c) catch unreachable) - 1;
770787 },
771788 },
772789
773790 .char_literal => switch (c) {
774 0, '\n', '\'' => {
775 result.tag = .invalid;
776 result.loc.end = self.index;
791 0 => {
777792 if (self.index != self.buffer.len) {
778 self.index += 1;
793 state = .invalid;
794 continue;
779795 }
780 return result;
796 result.tag = .invalid;
797 break;
798 },
799 '\n' => {
800 result.tag = .invalid;
801 break;
781802 },
782803 '\\' => {
783804 state = .char_literal_backslash;
784805 },
785 else => {
786 state = .char_literal_end;
787
788 if (self.invalidCharacterLength()) |len| {
789 result.tag = .invalid;
790 result.loc.end = self.index;
791 self.index += len;
792 return result;
793 }
794
795 self.index += (std.unicode.utf8ByteSequenceLength(c) catch unreachable) - 1;
806 '\'' => {
807 self.index += 1;
808 break;
796809 },
810 0x01...0x09, 0x0b...0x1f, 0x7f => {
811 state = .invalid;
812 },
813 else => continue,
797814 },
798815
799816 .char_literal_backslash => switch (c) {
800 0, '\n' => {
801 result.tag = .invalid;
802 result.loc.end = self.index;
817 0 => {
803818 if (self.index != self.buffer.len) {
804 self.index += 1;
805 }
806 return result;
807 },
808 'x' => {
809 state = .char_literal_hex_escape;
810 seen_escape_digits = 0;
811 },
812 'u' => {
813 state = .char_literal_unicode_escape_saw_u;
814 },
815 else => {
816 state = .char_literal_end;
817
818 if (self.invalidCharacterLength()) |len| {
819 result.tag = .invalid;
820 result.loc.end = self.index;
821 self.index += len;
822 return result;
823 }
824
825 self.index += (std.unicode.utf8ByteSequenceLength(c) catch unreachable) - 1;
826 },
827 },
828
829 .char_literal_hex_escape => switch (c) {
830 '0'...'9', 'a'...'f', 'A'...'F' => {
831 seen_escape_digits += 1;
832 if (seen_escape_digits == 2) {
833 state = .char_literal_end;
819 state = .invalid;
820 continue;
834821 }
835 },
836 else => {
837822 result.tag = .invalid;
838823 break;
839824 },
840 },
841
842 .char_literal_unicode_escape_saw_u => switch (c) {
843 '{' => {
844 state = .char_literal_unicode_escape;
845 },
846 else => {
847 result.tag = .invalid;
848 break;
849 },
850 },
851
852 .char_literal_unicode_escape => switch (c) {
853 '0'...'9', 'a'...'f', 'A'...'F' => {},
854 '}' => {
855 state = .char_literal_end; // too many/few digits handled later
856 },
857 else => {
825 '\n' => {
858826 result.tag = .invalid;
859827 break;
860828 },
861 },
862
863 .char_literal_end => switch (c) {
864 '\'' => {
865 result.tag = .char_literal;
866 self.index += 1;
867 break;
829 0x01...0x09, 0x0b...0x1f, 0x7f => {
830 state = .invalid;
868831 },
869832 else => {
870 result.tag = .invalid;
871 break;
833 state = .char_literal;
872834 },
873835 },
874836
875837 .multiline_string_literal_line => switch (c) {
876838 0 => {
877839 if (self.index != self.buffer.len) {
878 result.tag = .invalid;
879 result.loc.end = self.index;
880 self.index += 1;
881 return result;
840 state = .invalid;
841 continue;
882842 }
883843 break;
884844 },
......@@ -886,17 +846,10 @@ pub const Tokenizer = struct {
886846 self.index += 1;
887847 break;
888848 },
889 '\t' => {},
890 else => {
891 if (self.invalidCharacterLength()) |len| {
892 result.tag = .invalid;
893 result.loc.end = self.index;
894 self.index += len;
895 return result;
896 }
897
898 self.index += (std.unicode.utf8ByteSequenceLength(c) catch unreachable) - 1;
849 0x01...0x08, 0x0b...0x1f, 0x7f => {
850 state = .invalid;
899851 },
852 else => continue,
900853 },
901854
902855 .bang => switch (c) {
......@@ -1113,12 +1066,16 @@ pub const Tokenizer = struct {
11131066 .line_comment_start => switch (c) {
11141067 0 => {
11151068 if (self.index != self.buffer.len) {
1116 result.tag = .invalid;
1117 result.loc.end = self.index;
1118 self.index += 1;
1119 return result;
1069 state = .invalid;
1070 continue;
11201071 }
1121 break;
1072 return .{
1073 .tag = .eof,
1074 .loc = .{
1075 .start = self.index,
1076 .end = self.index,
1077 },
1078 };
11221079 },
11231080 '/' => {
11241081 state = .doc_comment_start;
......@@ -1127,105 +1084,74 @@ pub const Tokenizer = struct {
11271084 result.tag = .container_doc_comment;
11281085 state = .doc_comment;
11291086 },
1087 '\r' => {
1088 state = .expect_newline;
1089 },
11301090 '\n' => {
11311091 state = .start;
11321092 result.loc.start = self.index + 1;
11331093 },
1134 '\t' => {
1135 state = .line_comment;
1094 0x01...0x08, 0x0b...0x0c, 0x0e...0x1f, 0x7f => {
1095 state = .invalid;
11361096 },
11371097 else => {
11381098 state = .line_comment;
1139
1140 if (self.invalidCharacterLength()) |len| {
1141 result.tag = .invalid;
1142 result.loc.end = self.index;
1143 self.index += len;
1144 return result;
1145 }
1146
1147 self.index += (std.unicode.utf8ByteSequenceLength(c) catch unreachable) - 1;
11481099 },
11491100 },
11501101 .doc_comment_start => switch (c) {
1151 '/' => {
1152 state = .line_comment;
1153 },
1154 0 => {
1155 if (self.index != self.buffer.len) {
1156 result.tag = .invalid;
1157 result.loc.end = self.index;
1158 self.index += 1;
1159 return result;
1160 }
1102 0, '\n', '\r' => {
11611103 result.tag = .doc_comment;
11621104 break;
11631105 },
1164 '\n' => {
1165 result.tag = .doc_comment;
1166 break;
1106 '/' => {
1107 state = .line_comment;
11671108 },
1168 '\t' => {
1169 state = .doc_comment;
1170 result.tag = .doc_comment;
1109 0x01...0x08, 0x0b...0x0c, 0x0e...0x1f, 0x7f => {
1110 state = .invalid;
11711111 },
11721112 else => {
11731113 state = .doc_comment;
11741114 result.tag = .doc_comment;
1175
1176 if (self.invalidCharacterLength()) |len| {
1177 result.tag = .invalid;
1178 result.loc.end = self.index;
1179 self.index += len;
1180 return result;
1181 }
1182
1183 self.index += (std.unicode.utf8ByteSequenceLength(c) catch unreachable) - 1;
11841115 },
11851116 },
11861117 .line_comment => switch (c) {
11871118 0 => {
11881119 if (self.index != self.buffer.len) {
1189 result.tag = .invalid;
1190 result.loc.end = self.index;
1191 self.index += 1;
1192 return result;
1120 state = .invalid;
1121 continue;
11931122 }
1194 break;
1123 return .{
1124 .tag = .eof,
1125 .loc = .{
1126 .start = self.index,
1127 .end = self.index,
1128 },
1129 };
1130 },
1131 '\r' => {
1132 state = .expect_newline;
11951133 },
11961134 '\n' => {
11971135 state = .start;
11981136 result.loc.start = self.index + 1;
11991137 },
1200 '\t' => {},
1201 else => {
1202 if (self.invalidCharacterLength()) |len| {
1203 result.tag = .invalid;
1204 result.loc.end = self.index;
1205 self.index += len;
1206 return result;
1207 }
1208
1209 self.index += (std.unicode.utf8ByteSequenceLength(c) catch unreachable) - 1;
1138 0x01...0x08, 0x0b...0x0c, 0x0e...0x1f, 0x7f => {
1139 state = .invalid;
12101140 },
1141 else => continue,
12111142 },
12121143 .doc_comment => switch (c) {
1213 0, '\n' => break,
1214 '\t' => {},
1215 else => {
1216 if (self.invalidCharacterLength()) |len| {
1217 result.tag = .invalid;
1218 result.loc.end = self.index;
1219 self.index += len;
1220 return result;
1221 }
1222
1223 self.index += (std.unicode.utf8ByteSequenceLength(c) catch unreachable) - 1;
1144 0, '\n', '\r' => {
1145 break;
12241146 },
1147 0x01...0x08, 0x0b...0x0c, 0x0e...0x1f, 0x7f => {
1148 state = .invalid;
1149 },
1150 else => continue,
12251151 },
12261152 .int => switch (c) {
12271153 '.' => state = .int_period,
1228 '_', 'a'...'d', 'f'...'o', 'q'...'z', 'A'...'D', 'F'...'O', 'Q'...'Z', '0'...'9' => {},
1154 '_', 'a'...'d', 'f'...'o', 'q'...'z', 'A'...'D', 'F'...'O', 'Q'...'Z', '0'...'9' => continue,
12291155 'e', 'E', 'p', 'P' => state = .int_exponent,
12301156 else => break,
12311157 },
......@@ -1249,7 +1175,7 @@ pub const Tokenizer = struct {
12491175 },
12501176 },
12511177 .float => switch (c) {
1252 '_', 'a'...'d', 'f'...'o', 'q'...'z', 'A'...'D', 'F'...'O', 'Q'...'Z', '0'...'9' => {},
1178 '_', 'a'...'d', 'f'...'o', 'q'...'z', 'A'...'D', 'F'...'O', 'Q'...'Z', '0'...'9' => continue,
12531179 'e', 'E', 'p', 'P' => state = .float_exponent,
12541180 else => break,
12551181 },
......@@ -1263,57 +1189,9 @@ pub const Tokenizer = struct {
12631189 }
12641190 }
12651191
1266 if (result.tag == .eof) {
1267 result.loc.start = self.index;
1268 }
1269
12701192 result.loc.end = self.index;
12711193 return result;
12721194 }
1273
1274 fn invalidCharacterLength(self: *Tokenizer) ?u3 {
1275 const c0 = self.buffer[self.index];
1276 if (std.ascii.isAscii(c0)) {
1277 if (c0 == '\r') {
1278 if (self.index + 1 < self.buffer.len and self.buffer[self.index + 1] == '\n') {
1279 // Carriage returns are *only* allowed just before a linefeed as part of a CRLF pair, otherwise
1280 // they constitute an illegal byte!
1281 return null;
1282 } else {
1283 return 1;
1284 }
1285 } else if (std.ascii.isControl(c0)) {
1286 // ascii control codes are never allowed
1287 // (note that \n was checked before we got here)
1288 return 1;
1289 }
1290 // looks fine to me.
1291 return null;
1292 } else {
1293 // check utf8-encoded character.
1294 const length = std.unicode.utf8ByteSequenceLength(c0) catch return 1;
1295 if (self.index + length > self.buffer.len) {
1296 return @as(u3, @intCast(self.buffer.len - self.index));
1297 }
1298 const bytes = self.buffer[self.index .. self.index + length];
1299 switch (length) {
1300 2 => {
1301 const value = std.unicode.utf8Decode2(bytes) catch return length;
1302 if (value == 0x85) return length; // U+0085 (NEL)
1303 },
1304 3 => {
1305 const value = std.unicode.utf8Decode3(bytes) catch return length;
1306 if (value == 0x2028) return length; // U+2028 (LS)
1307 if (value == 0x2029) return length; // U+2029 (PS)
1308 },
1309 4 => {
1310 _ = std.unicode.utf8Decode4(bytes) catch return length;
1311 },
1312 else => unreachable,
1313 }
1314 return null;
1315 }
1316 }
13171195};
13181196
13191197test "keywords" {
......@@ -1355,7 +1233,7 @@ test "code point literal with hex escape" {
13551233 , &.{.char_literal});
13561234 try testTokenize(
13571235 \\'\x1'
1358 , &.{ .invalid, .invalid });
1236 , &.{.char_literal});
13591237}
13601238
13611239test "newline in char literal" {
......@@ -1396,40 +1274,30 @@ test "code point literal with unicode escapes" {
13961274 // Invalid unicode escapes
13971275 try testTokenize(
13981276 \\'\u'
1399 , &.{ .invalid, .invalid });
1277 , &.{.char_literal});
14001278 try testTokenize(
14011279 \\'\u{{'
1402 , &.{ .invalid, .l_brace, .invalid });
1280 , &.{.char_literal});
14031281 try testTokenize(
14041282 \\'\u{}'
14051283 , &.{.char_literal});
14061284 try testTokenize(
14071285 \\'\u{s}'
1408 , &.{
1409 .invalid,
1410 .identifier,
1411 .r_brace,
1412 .invalid,
1413 });
1286 , &.{.char_literal});
14141287 try testTokenize(
14151288 \\'\u{2z}'
1416 , &.{
1417 .invalid,
1418 .identifier,
1419 .r_brace,
1420 .invalid,
1421 });
1289 , &.{.char_literal});
14221290 try testTokenize(
14231291 \\'\u{4a'
1424 , &.{ .invalid, .invalid }); // 4a is valid
1292 , &.{.char_literal});
14251293
14261294 // Test old-style unicode literals
14271295 try testTokenize(
14281296 \\'\u0333'
1429 , &.{ .invalid, .number_literal, .invalid });
1297 , &.{.char_literal});
14301298 try testTokenize(
14311299 \\'\U0333'
1432 , &.{ .invalid, .number_literal, .invalid });
1300 , &.{.char_literal});
14331301}
14341302
14351303test "code point literal with unicode code point" {
......@@ -1465,24 +1333,15 @@ test "invalid token characters" {
14651333 try testTokenize("`", &.{.invalid});
14661334 try testTokenize("'c", &.{.invalid});
14671335 try testTokenize("'", &.{.invalid});
1468 try testTokenize("''", &.{.invalid});
1336 try testTokenize("''", &.{.char_literal});
14691337 try testTokenize("'\n'", &.{ .invalid, .invalid });
14701338}
14711339
14721340test "invalid literal/comment characters" {
1473 try testTokenize("\"\x00\"", &.{
1474 .invalid,
1475 .invalid, // Incomplete string literal starting after invalid
1476 });
1477 try testTokenize("//\x00", &.{
1478 .invalid,
1479 });
1480 try testTokenize("//\x1f", &.{
1481 .invalid,
1482 });
1483 try testTokenize("//\x7f", &.{
1484 .invalid,
1485 });
1341 try testTokenize("\"\x00\"", &.{.invalid});
1342 try testTokenize("//\x00", &.{.invalid});
1343 try testTokenize("//\x1f", &.{.invalid});
1344 try testTokenize("//\x7f", &.{.invalid});
14861345}
14871346
14881347test "utf8" {
......@@ -1491,46 +1350,24 @@ test "utf8" {
14911350}
14921351
14931352test "invalid utf8" {
1494 try testTokenize("//\x80", &.{
1495 .invalid,
1496 });
1497 try testTokenize("//\xbf", &.{
1498 .invalid,
1499 });
1500 try testTokenize("//\xf8", &.{
1501 .invalid,
1502 });
1503 try testTokenize("//\xff", &.{
1504 .invalid,
1505 });
1506 try testTokenize("//\xc2\xc0", &.{
1507 .invalid,
1508 });
1509 try testTokenize("//\xe0", &.{
1510 .invalid,
1511 });
1512 try testTokenize("//\xf0", &.{
1513 .invalid,
1514 });
1515 try testTokenize("//\xf0\x90\x80\xc0", &.{
1516 .invalid,
1517 });
1353 try testTokenize("//\x80", &.{});
1354 try testTokenize("//\xbf", &.{});
1355 try testTokenize("//\xf8", &.{});
1356 try testTokenize("//\xff", &.{});
1357 try testTokenize("//\xc2\xc0", &.{});
1358 try testTokenize("//\xe0", &.{});
1359 try testTokenize("//\xf0", &.{});
1360 try testTokenize("//\xf0\x90\x80\xc0", &.{});
15181361}
15191362
15201363test "illegal unicode codepoints" {
15211364 // unicode newline characters.U+0085, U+2028, U+2029
15221365 try testTokenize("//\xc2\x84", &.{});
1523 try testTokenize("//\xc2\x85", &.{
1524 .invalid,
1525 });
1366 try testTokenize("//\xc2\x85", &.{});
15261367 try testTokenize("//\xc2\x86", &.{});
15271368 try testTokenize("//\xe2\x80\xa7", &.{});
1528 try testTokenize("//\xe2\x80\xa8", &.{
1529 .invalid,
1530 });
1531 try testTokenize("//\xe2\x80\xa9", &.{
1532 .invalid,
1533 });
1369 try testTokenize("//\xe2\x80\xa8", &.{});
1370 try testTokenize("//\xe2\x80\xa9", &.{});
15341371 try testTokenize("//\xe2\x80\xaa", &.{});
15351372}
15361373
......@@ -1892,8 +1729,8 @@ test "multi line string literal with only 1 backslash" {
18921729}
18931730
18941731test "invalid builtin identifiers" {
1895 try testTokenize("@()", &.{ .invalid, .l_paren, .r_paren });
1896 try testTokenize("@0()", &.{ .invalid, .number_literal, .l_paren, .r_paren });
1732 try testTokenize("@()", &.{.invalid});
1733 try testTokenize("@0()", &.{.invalid});
18971734}
18981735
18991736test "invalid token with unfinished escape right before eof" {
......@@ -1921,12 +1758,12 @@ test "saturating operators" {
19211758}
19221759
19231760test "null byte before eof" {
1924 try testTokenize("123 \x00 456", &.{ .number_literal, .invalid, .number_literal });
1761 try testTokenize("123 \x00 456", &.{ .number_literal, .invalid });
19251762 try testTokenize("//\x00", &.{.invalid});
19261763 try testTokenize("\\\\\x00", &.{.invalid});
19271764 try testTokenize("\x00", &.{.invalid});
19281765 try testTokenize("// NUL\x00\n", &.{.invalid});
1929 try testTokenize("///\x00\n", &.{.invalid});
1766 try testTokenize("///\x00\n", &.{ .doc_comment, .invalid });
19301767 try testTokenize("/// NUL\x00\n", &.{ .doc_comment, .invalid });
19311768}
19321769
......@@ -1936,6 +1773,9 @@ fn testTokenize(source: [:0]const u8, expected_token_tags: []const Token.Tag) !v
19361773 const token = tokenizer.next();
19371774 try std.testing.expectEqual(expected_token_tag, token.tag);
19381775 }
1776 // Last token should always be eof, even when the last token was invalid,
1777 // in which case the tokenizer is in an invalid state, which can only be
1778 // recovered by opinionated means outside the scope of this implementation.
19391779 const last_token = tokenizer.next();
19401780 try std.testing.expectEqual(Token.Tag.eof, last_token.tag);
19411781 try std.testing.expectEqual(source.len, last_token.loc.start);
src/Package/Manifest.zig+3
......@@ -549,6 +549,9 @@ const Parse = struct {
549549 .{raw_string[bad_index]},
550550 );
551551 },
552 .empty_char_literal => {
553 try p.appendErrorOff(token, offset, "empty character literal", .{});
554 },
552555 }
553556 }
554557
test/cases/compile_errors/empty_char_lit.zig created+9
......@@ -0,0 +1,9 @@
1export fn entry() u8 {
2 return '';
3}
4
5// error
6// backend=stage2
7// target=native
8//
9// :2:12: error: empty character literal
test/cases/compile_errors/invalid_legacy_unicode_escape.zig+1-2
......@@ -6,5 +6,4 @@ export fn entry() void {
66// backend=stage2
77// target=native
88//
9// :2:15: error: expected expression, found 'invalid bytes'
10// :2:18: note: invalid byte: '1'
9// :2:17: error: invalid escape character: 'U'
test/cases/compile_errors/invalid_unicode_escape.zig+1-2
......@@ -6,6 +6,5 @@ export fn entry() void {
66// backend=stage2
77// target=native
88//
9// :2:15: error: expected expression, found 'invalid bytes'
10// :2:21: note: invalid byte: 'z'
9// :2:21: error: expected hex digit or '}', found 'z'
1110
test/cases/compile_errors/normal_string_with_newline.zig+1-2
......@@ -5,5 +5,4 @@ b";
55// backend=stage2
66// target=native
77//
8// :1:13: error: expected expression, found 'invalid bytes'
9// :1:15: note: invalid byte: '\n'
8// :1:13: error: expected expression, found 'invalid token'
test/compile_errors.zig+6-12
......@@ -42,8 +42,7 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void {
4242 const case = ctx.obj("isolated carriage return in multiline string literal", b.graph.host);
4343
4444 case.addError("const foo = \\\\\test\r\r rogue carriage return\n;", &[_][]const u8{
45 ":1:13: error: expected expression, found 'invalid bytes'",
46 ":1:19: note: invalid byte: '\\r'",
45 ":1:13: error: expected expression, found 'invalid token'",
4746 });
4847 }
4948
......@@ -179,8 +178,7 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void {
179178 \\ return true;
180179 \\}
181180 , &[_][]const u8{
182 ":1:1: error: expected type expression, found 'invalid bytes'",
183 ":1:1: note: invalid byte: '\\xff'",
181 ":1:1: error: expected type expression, found 'invalid token'",
184182 });
185183 }
186184
......@@ -222,8 +220,7 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void {
222220 const case = ctx.obj("invalid byte in string", b.graph.host);
223221
224222 case.addError("_ = \"\x01Q\";", &[_][]const u8{
225 ":1:5: error: expected expression, found 'invalid bytes'",
226 ":1:6: note: invalid byte: '\\x01'",
223 ":1:5: error: expected expression, found 'invalid token'",
227224 });
228225 }
229226
......@@ -231,8 +228,7 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void {
231228 const case = ctx.obj("invalid byte in comment", b.graph.host);
232229
233230 case.addError("//\x01Q", &[_][]const u8{
234 ":1:1: error: expected type expression, found 'invalid bytes'",
235 ":1:3: note: invalid byte: '\\x01'",
231 ":1:1: error: expected type expression, found 'invalid token'",
236232 });
237233 }
238234
......@@ -240,8 +236,7 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void {
240236 const case = ctx.obj("control character in character literal", b.graph.host);
241237
242238 case.addError("const c = '\x01';", &[_][]const u8{
243 ":1:11: error: expected expression, found 'invalid bytes'",
244 ":1:12: note: invalid byte: '\\x01'",
239 ":1:11: error: expected expression, found 'invalid token'",
245240 });
246241 }
247242
......@@ -249,8 +244,7 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void {
249244 const case = ctx.obj("invalid byte at start of token", b.graph.host);
250245
251246 case.addError("x = \x00Q", &[_][]const u8{
252 ":1:5: error: expected expression, found 'invalid bytes'",
253 ":1:5: note: invalid byte: '\\x00'",
247 ":1:5: error: expected expression, found 'invalid token'",
254248 });
255249 }
256250}