1 // Copyright 2024 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <vector>
6
7 #include "base/command_line.h"
8 #include "skia/ext/switches.h"
9 #include "third_party/abseil-cpp/absl/types/variant.h"
10 #include "third_party/fuzztest/src/fuzztest/fuzztest.h"
11 #include "third_party/skia/include/core/SkColor.h"
12 #include "ui/gfx/canvas.h"
13 #include "ui/gfx/geometry/size.h"
14 #include "ui/gfx/paint_vector_icon.h"
15 #include "ui/gfx/vector_icon_types.h"
16 #include "ui/gfx/vector_icon_utils.h"
17
18 namespace {
19
20 using fuzztest::Arbitrary;
21 using fuzztest::Domain;
22 using fuzztest::ElementOf;
23 using fuzztest::Finite;
24 using fuzztest::FlatMap;
25 using fuzztest::InRange;
26 using fuzztest::Just;
27 using fuzztest::Map;
28 using fuzztest::NonEmpty;
29 using fuzztest::StructOf;
30 using fuzztest::VariantOf;
31 using fuzztest::VectorOf;
32
33 // Fuzztest does not yet support enums out of the box, but thankfully the
34 // `gfx::CommandType` enum is defined through a pair of macros that work very
35 // well for us. `DECLARE_VECTOR_COMMAND(x)` is supposed to be overridden like
36 // this before `DECLARE_VECTOR_COMMANDS` can be used. Dependency injection!
37 #define DECLARE_VECTOR_COMMAND(x) gfx::CommandType::x,
38
39 // That allows us to define a domain that contains only valid vector commands.
AnyCommandType()40 auto AnyCommandType() {
41 return ElementOf({DECLARE_VECTOR_COMMANDS});
42 }
43
44 // Each command type has a specific number of args it expects, otherwise the
45 // command validation code CHECK-fails. We thus make sure to generate only
46 // valid sequences of `gfx::PathElement`s by packaging commands with the right
47 // number of arguments.
48 struct Command {
49 gfx::CommandType type;
50 std::vector<SkScalar> args;
51 };
52
53 // Returns the domain of all possible arguments for the given command.
54 //
55 // We need this to account for the fact that not all arguments are valid for
56 // all commands, and passing invalid arguments can trigger shallow CHECK
57 // failures that prevent deeper fuzzing.
AnyArgsForCommandType(gfx::CommandType type)58 Domain<std::vector<SkScalar>> AnyArgsForCommandType(gfx::CommandType type) {
59 int args_count = gfx::GetCommandArgumentCount(type);
60 switch (type) {
61 case gfx::PATH_COLOR_ARGB:
62 return VectorOf(InRange(SkScalar(0.0), SkScalar(255.0)))
63 .WithSize(args_count);
64 case gfx::CANVAS_DIMENSIONS:
65 return VectorOf(InRange(SkScalar(1.0), SkScalar(1024.0)))
66 .WithSize(args_count);
67 default:
68 return VectorOf(Finite<SkScalar>()).WithSize(args_count);
69 }
70 }
71
AnyCommandWithType(gfx::CommandType type)72 Domain<Command> AnyCommandWithType(gfx::CommandType type) {
73 return StructOf<Command>(Just(type), AnyArgsForCommandType(type));
74 }
75
76 // Returns the domain of all possible commands.
AnyCommand()77 auto AnyCommand() {
78 return FlatMap(AnyCommandWithType, AnyCommandType());
79 }
80
81 // Flattens the given `commands` into a sequence of path elements that can be
82 // passed to `PaintVectorIcon()`.
ConvertCommands(const std::vector<Command> & commands)83 std::vector<gfx::PathElement> ConvertCommands(
84 const std::vector<Command>& commands) {
85 std::vector<gfx::PathElement> path;
86 for (const auto& command : commands) {
87 path.emplace_back(command.type);
88 for (SkScalar arg : command.args) {
89 path.emplace_back(arg);
90 }
91 }
92 return path;
93 }
94
95 class PaintVectorIconFuzzTest {
96 public:
PaintVectorIconFuzzTest()97 PaintVectorIconFuzzTest() {
98 // `Init()` ignores its arguments on Windows, so init with nothing and add
99 // switches later.
100 CHECK(base::CommandLine::Init(0, nullptr));
101
102 // Set command-line arguments correctly to avoid check failures down the
103 // line.
104 base::CommandLine& command_line = *base::CommandLine::ForCurrentProcess();
105 command_line.AppendSwitchASCII(switches::kTextContrast, "1.0");
106 command_line.AppendSwitchASCII(switches::kTextGamma, "1.0");
107 }
108
PaintVectorIcon(std::vector<Command> commands)109 void PaintVectorIcon(std::vector<Command> commands) {
110 std::vector<gfx::PathElement> path = ConvertCommands(commands);
111
112 // An icon can contain multiple representations. We do not fuzz the code
113 // that chooses which representation to draw based on canvas size and scale,
114 // and instead use a single representation.
115 gfx::VectorIconRep rep(path.data(), path.size());
116 gfx::VectorIcon icon(&rep, /*reps_size=*/1u, "icon");
117
118 constexpr float kImageScale = 1.f;
119 constexpr bool kIsOpaque = true;
120 gfx::Canvas canvas(gfx::Size(1024, 1024), kImageScale, kIsOpaque);
121
122 // The length of a single edge of the square icon, in device-independent
123 // pixels.
124 constexpr int kDipSize = 1024;
125 constexpr SkColor kBlack = SkColorSetARGB(255, 0, 0, 0);
126 gfx::PaintVectorIcon(&canvas, icon, kDipSize, kBlack);
127 }
128 };
129
130 FUZZ_TEST_F(PaintVectorIconFuzzTest, PaintVectorIcon)
131 .WithDomains(NonEmpty(VectorOf(AnyCommand())));
132
133 } // namespace
134