Skip to content

Commit fc4ad14

Browse files
committed
reintroduce fuzzing
1 parent 248895a commit fc4ad14

7 files changed

Lines changed: 228 additions & 230 deletions

File tree

build.zig

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -97,22 +97,6 @@ pub fn build(b: *std.Build) !void {
9797
"error: git tag missing, cannot make release builds",
9898
).step);
9999
}
100-
101-
setupGeneratorStep(b, target);
102-
}
103-
104-
fn setupGeneratorStep(b: *std.Build, target: std.Build.ResolvedTarget) void {
105-
const gen = b.step("generator", "Build generator executable for reproing fuzz cases");
106-
const supergen = b.addExecutable(.{
107-
.name = "generator",
108-
.root_module = b.createModule(.{
109-
.root_source_file = b.path("src/generator.zig"),
110-
.target = target,
111-
.optimize = .ReleaseSafe,
112-
}),
113-
});
114-
115-
gen.dependOn(&b.addInstallArtifact(supergen, .{}).step);
116100
}
117101

118102
fn setupCheckStep(

src/cli/logging.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ pub fn setup(io: Io, gpa: Allocator, environ: *std.process.Environ.Map) void {
4949
defer std.debug.unlockStderr();
5050

5151
setupInternal(io, gpa, environ) catch {
52-
log_writer = Io.File.stderr().writerStreaming(&.{});
52+
log_writer = Io.File.stderr().writerStreaming(io, &.{});
5353
};
5454
}
5555

src/fuzz.zig

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
const builtin = @import("builtin");
2+
const std = @import("std");
3+
const Io = std.Io;
4+
const Allocator = std.mem.Allocator;
5+
const Writer = std.Io.Writer;
6+
const Reader = std.Io.Reader;
7+
const Element = @import("html/Element.zig");
8+
const Attribute = @import("html/Attribute.zig");
9+
const Ast = @import("html/Ast.zig");
10+
11+
const Op = enum(u8) {
12+
add_newline,
13+
up,
14+
push_doctype,
15+
push_comment,
16+
push_text,
17+
push_elem,
18+
push_self_closing_elem,
19+
add_or_push_erroneous_end_tag,
20+
21+
fn apply(
22+
op: Op,
23+
gpa: Allocator,
24+
arena: Allocator,
25+
stack: *std.ArrayList([]const u8),
26+
smith: *std.testing.Smith,
27+
w: *Writer,
28+
) !void {
29+
const tags = Element.elements.keys();
30+
31+
switch (op) {
32+
.add_newline => try w.writeAll("\n"),
33+
.up => {
34+
if (stack.pop()) |tag| {
35+
try w.print("</{s}>", .{tag});
36+
} else return error.Done;
37+
},
38+
.push_doctype => try w.writeAll("<!DOCTYPE html>"),
39+
.push_comment => try w.writeAll("<!-- comment -->"),
40+
.push_text => {
41+
switch (smith.value(u2)) {
42+
0 => try w.writeAll("lorem ipsum"),
43+
1 => try w.writeAll(" lorem ipsum"),
44+
2 => try w.writeAll("lorem ipsum "),
45+
3 => try w.writeAll(" lorem ipsum "),
46+
}
47+
},
48+
.push_elem, .push_self_closing_elem => {
49+
// const rng = input.takeByte() catch return error.Done;
50+
const final_space = smith.value(bool);
51+
const attrs_count = smith.valueRangeAtMost(u8, 0, 64);
52+
53+
const name = if (smith.boolWeighted(1, 10)) blk: {
54+
const tag_idx = smith.valueRangeLessThan(u32, 0, @intCast(tags.len));
55+
const name = tags[tag_idx];
56+
const kind = Element.elements.values()[tag_idx];
57+
const attrs = Attribute.element_attrs.get(kind);
58+
try w.print("<{s}", .{name});
59+
60+
for (0..attrs_count) |_| {
61+
const has_value = smith.value(bool);
62+
if (smith.boolWeighted(1, 5)) {
63+
if (attrs.list.len > 0 and smith.value(bool)) {
64+
// element attr
65+
const idx = smith.valueRangeLessThan(u32, 0, @intCast(attrs.list.len));
66+
const named_model = attrs.list[idx];
67+
try renderAttr(gpa, named_model, has_value, smith, w);
68+
} else {
69+
// global attr
70+
const idx = smith.valueRangeLessThan(u32, 0, @intCast(Attribute.global.list.len));
71+
const named_model = Attribute.global.list[idx];
72+
try renderAttr(gpa, named_model, has_value, smith, w);
73+
}
74+
} else {
75+
var buf: [64]u8 = undefined;
76+
const attr_name = buf[0..smith.slice(&buf)];
77+
try renderAttr(gpa, .{
78+
.name = attr_name,
79+
.model = .{ .rule = .any, .desc = "" },
80+
}, has_value, smith, w);
81+
}
82+
}
83+
break :blk name;
84+
} else blk: {
85+
const buf = try arena.alloc(u8, 64);
86+
const name = buf[0..smith.slice(buf)];
87+
try w.print("<{s}", .{name});
88+
// TODO: attributes?
89+
break :blk name;
90+
};
91+
92+
if (final_space) try w.writeAll(" ");
93+
if (op == .push_self_closing_elem) {
94+
try w.writeAll("/>");
95+
} else {
96+
try stack.append(gpa, name);
97+
try w.writeAll(">");
98+
}
99+
},
100+
.add_or_push_erroneous_end_tag => {
101+
if (smith.value(bool)) {
102+
_ = stack.pop() orelse return error.Done;
103+
}
104+
105+
if (smith.value(bool)) {
106+
const t = tags[smith.valueRangeLessThan(u32, 0, @intCast(tags.len))];
107+
try w.print("</{s}>", .{t});
108+
return;
109+
} else {
110+
var buf: [64]u8 = undefined;
111+
const name = buf[0..smith.slice(&buf)];
112+
try w.print("</{s}>", .{name});
113+
return;
114+
}
115+
},
116+
}
117+
}
118+
};
119+
120+
fn renderAttr(
121+
gpa: Allocator,
122+
named_model: Attribute.Named,
123+
has_value: bool,
124+
smith: *std.testing.Smith,
125+
w: *Writer,
126+
) !void {
127+
_ = gpa;
128+
try w.print(" {s}", .{named_model.name});
129+
if (!has_value) return;
130+
131+
switch (smith.value(u2)) {
132+
1 => return, // no value
133+
2 => try w.writeAll("=''"),
134+
3 => try w.writeAll("='not_empty'"),
135+
0 => switch (named_model.model.rule) { // apply rule semantically
136+
else => {
137+
var buf: [64]u8 = undefined;
138+
const value = buf[0..smith.slice(&buf)];
139+
try w.print("=\"{s}\"", .{value});
140+
},
141+
142+
.list => |list_rule| {
143+
const case = list_rule.completions[
144+
smith.valueRangeLessThan(
145+
u32,
146+
0,
147+
@intCast(list_rule.completions.len),
148+
)
149+
];
150+
try w.print("=\"{s}\"", .{case.label});
151+
},
152+
.cors => {
153+
const case: []const []const u8 = &.{
154+
"anonymous", "use-credentials", "arst",
155+
};
156+
157+
try w.print("=\"{s}\"", .{case[
158+
smith.valueRangeLessThan(
159+
u32,
160+
0,
161+
@intCast(case.len),
162+
)
163+
]});
164+
},
165+
.non_neg_int => {
166+
const case: []const []const u8 = &.{
167+
"0", "1", "2", "-1", "100", "-99", "101", "-101", "-102",
168+
};
169+
170+
try w.print("=\"{s}\"", .{case[
171+
smith.valueRangeLessThan(
172+
u32,
173+
0,
174+
@intCast(case.len),
175+
)
176+
]});
177+
},
178+
},
179+
}
180+
}
181+
182+
test "fuzz" {
183+
const log = try Io.Dir.cwd().createFile(std.testing.io, "fuzz.log", .{ .truncate = false });
184+
var file_writer = log.writerStreaming(std.testing.io, &.{});
185+
186+
try std.testing.fuzz(&file_writer.interface, struct {
187+
fn fuzz(l: *Io.Writer, smith: *std.testing.Smith) !void {
188+
var stack: std.ArrayList([]const u8) = .empty;
189+
defer stack.deinit(std.testing.allocator);
190+
191+
var case: Io.Writer.Allocating = .init(std.testing.allocator);
192+
defer case.deinit();
193+
194+
var arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
195+
defer arena.deinit();
196+
197+
var newlines: usize = 0;
198+
while (!smith.eosWeightedSimple(7, 1)) {
199+
const op = smith.value(Op);
200+
if (op == .add_newline) newlines += 1;
201+
if (newlines > 5) return error.Skip;
202+
203+
op.apply(std.testing.allocator, arena.allocator(), &stack, smith, &case.writer) catch |err| switch (err) {
204+
error.Done => break,
205+
else => unreachable,
206+
};
207+
}
208+
209+
try l.print("{s}\n---\n\n", .{case.written()});
210+
211+
const ast: Ast = try .init(std.testing.allocator, case.written(), .html, false);
212+
defer ast.deinit(std.testing.allocator);
213+
214+
if (!ast.has_syntax_errors) {
215+
var fmt_out: Io.Writer.Allocating = .init(std.testing.allocator);
216+
defer fmt_out.deinit();
217+
218+
try ast.render(case.written(), &fmt_out.writer);
219+
}
220+
}
221+
}.fuzz, .{});
222+
}

src/generator.zig

Lines changed: 0 additions & 25 deletions
This file was deleted.

0 commit comments

Comments
 (0)