1 // Copyright 2018 The Chromium Authors. All rights reserved.
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 "components/zucchini/disassembler_ztf.h"
6
7 #include <stddef.h>
8 #include <stdint.h>
9
10 #include <algorithm>
11 #include <map>
12 #include <set>
13 #include <utility>
14 #include <vector>
15
16 #include "base/cxx17_backports.h"
17 #include "base/strings/string_piece.h"
18 #include "components/zucchini/buffer_view.h"
19 #include "components/zucchini/element_detection.h"
20 #include "testing/gtest/include/gtest/gtest.h"
21
22 namespace zucchini {
23
24 namespace {
25
26 constexpr char kNormalText[] = R"(ZTxt
27 Hello World!
28 This is an example of an absolute reference <<1,1>>
29 And {-01,+05} is an example of a relative ref
30 txTZ
31 TRAILING DATA)";
32 // -1 to exclude null byte.
33 constexpr size_t kNormalTextExtraBytes = base::size("TRAILING DATA") - 1;
34
35 constexpr char kOutOfBoundsText[] = R"(ZTxt<1,1>
36 Hello World!
37 This is an example of an OOB absolute reference <890,605>
38 And {-050,+100} is an example of an OOB relative ref.
39 but [+00,+10] is valid at least. As is (1,5).
40 <1, 6> and { ,1} aren't nor is {4,5]
41 {7,6}<1,1><2,3>{+00,+00}{004,100}[+00,+60][+000,-100]<-000,-035>(-00,-00)txTZ
42 )";
43
44 // Converts a raw string into data.
StrToData(base::StringPiece s)45 std::vector<uint8_t> StrToData(base::StringPiece s) {
46 return std::vector<uint8_t>(s.begin(), s.end());
47 }
48
49 // Compare if |a.location < b.location| as references have unique locations.
50 struct ReferenceCompare {
operator ()zucchini::__anon5f0ed1f70111::ReferenceCompare51 bool operator()(const Reference& a, const Reference& b) const {
52 return a.location < b.location;
53 }
54 };
55
56 using ReferenceKey =
57 std::pair<DisassemblerZtf::ReferencePool, DisassemblerZtf::ReferenceType>;
58 using ReferenceSets =
59 std::map<ReferenceKey, std::set<Reference, ReferenceCompare>>;
60
61 // Write references in |refs_to_write| to |image|. Also validate the
62 // disassembler parses |image| such that it is of |expected_size|.
WriteReferences(MutableBufferView image,size_t expected_size,const ReferenceSets & refs_to_write)63 void WriteReferences(MutableBufferView image,
64 size_t expected_size,
65 const ReferenceSets& refs_to_write) {
66 EXPECT_TRUE(DisassemblerZtf::QuickDetect(image));
67 std::unique_ptr<DisassemblerZtf> dis =
68 Disassembler::Make<DisassemblerZtf>(image);
69 EXPECT_TRUE(dis);
70 EXPECT_EQ(expected_size, dis->size());
71 image.shrink(dis->size());
72 auto reference_groups = dis->MakeReferenceGroups();
73 for (const auto& group : reference_groups) {
74 auto writer = group.GetWriter(image, dis.get());
75 ReferenceKey key = {
76 static_cast<DisassemblerZtf::ReferencePool>(group.pool_tag().value()),
77 static_cast<DisassemblerZtf::ReferenceType>(group.type_tag().value())};
78 if (!refs_to_write.count(key))
79 continue;
80 for (const auto& ref : refs_to_write.at(key))
81 writer->PutNext(ref);
82 }
83 }
84
85 // Read references in |refs_to_read| from |image|. Once found
86 // the elements are removed from |refs_to_read|. Also validate the
87 // disassembler parses |image| such that it is of |expected_size|.
ReadReferences(ConstBufferView image,size_t expected_size,ReferenceSets * refs_to_read)88 void ReadReferences(ConstBufferView image,
89 size_t expected_size,
90 ReferenceSets* refs_to_read) {
91 EXPECT_TRUE(DisassemblerZtf::QuickDetect(image));
92 std::unique_ptr<DisassemblerZtf> dis =
93 Disassembler::Make<DisassemblerZtf>(image);
94 EXPECT_TRUE(dis);
95 EXPECT_EQ(expected_size, dis->size());
96 auto reference_groups = dis->MakeReferenceGroups();
97 for (const auto& group : reference_groups) {
98 auto reader = group.GetReader(dis.get());
99 ReferenceKey key = {
100 static_cast<DisassemblerZtf::ReferencePool>(group.pool_tag().value()),
101 static_cast<DisassemblerZtf::ReferenceType>(group.type_tag().value())};
102 if (!refs_to_read->count(key)) {
103 // No elements of this pool/type pair are expected so assert that none are
104 // found.
105 auto ref = reader->GetNext();
106 EXPECT_FALSE(ref.has_value());
107 continue;
108 }
109 // For each reference remove it from the set if it exists, error if
110 // unexpected references are found.
111 for (auto ref = reader->GetNext(); ref.has_value();
112 ref = reader->GetNext()) {
113 EXPECT_EQ(1UL, refs_to_read->at(key).erase(ref.value()));
114 }
115 EXPECT_EQ(0U, refs_to_read->at(key).size());
116 }
117 }
118
TestTranslation(const ZtfTranslator & translator,offset_t expected_location,ztf::LineCol lc)119 void TestTranslation(const ZtfTranslator& translator,
120 offset_t expected_location,
121 ztf::LineCol lc) {
122 // Check the lc is translated to the expected location.
123 EXPECT_EQ(expected_location, translator.LineColToOffset(lc));
124 auto new_lc = translator.OffsetToLineCol(expected_location);
125 if (expected_location == kInvalidOffset) {
126 EXPECT_FALSE(translator.IsValid(lc));
127 EXPECT_FALSE(new_lc.has_value());
128 } else {
129 EXPECT_TRUE(translator.IsValid(lc));
130 // Check that the reverse is true. |ztf::LineCol{0, 0}| is a sentinel and
131 // should never be valid.
132 EXPECT_EQ(lc.line, new_lc->line);
133 EXPECT_EQ(lc.col, new_lc->col);
134 }
135 }
136
137 template <typename T>
CountDistinct(const std::vector<T> & v)138 size_t CountDistinct(const std::vector<T>& v) {
139 return std::set<T>(v.begin(), v.end()).size();
140 }
141
142 } // namespace
143
TEST(ZtfTranslatorTest,Translate)144 TEST(ZtfTranslatorTest, Translate) {
145 ztf::dim_t kMaxVal = INT16_MAX;
146 ztf::dim_t kMinVal = INT16_MIN;
147
148 const std::vector<uint8_t> text(StrToData(kOutOfBoundsText));
149 ConstBufferView image(text.data(), text.size());
150 ZtfTranslator translator;
151 EXPECT_TRUE(translator.Init(image));
152
153 // Absolute Translations:
154
155 // Check a bunch of invalid locations.
156 TestTranslation(translator, kInvalidOffset, ztf::LineCol{50, 60});
157 TestTranslation(translator, kInvalidOffset, ztf::LineCol{0, 0});
158 TestTranslation(translator, kInvalidOffset, ztf::LineCol{1, 0});
159 TestTranslation(translator, kInvalidOffset, ztf::LineCol{0, 1});
160 TestTranslation(translator, kInvalidOffset, ztf::LineCol{0, 1});
161 TestTranslation(translator, kInvalidOffset, ztf::LineCol{1, -1});
162 TestTranslation(translator, kInvalidOffset, ztf::LineCol{-1, 1});
163 TestTranslation(translator, kInvalidOffset, ztf::LineCol{-1, -1});
164 TestTranslation(translator, kInvalidOffset, ztf::LineCol{1, kMaxVal});
165 TestTranslation(translator, kInvalidOffset, ztf::LineCol{kMaxVal, 1});
166 TestTranslation(translator, kInvalidOffset, ztf::LineCol{1, kMinVal});
167 TestTranslation(translator, kInvalidOffset, ztf::LineCol{kMinVal, 1});
168
169 // Check the start of the file.
170 TestTranslation(translator, 0, ztf::LineCol{1, 1});
171 TestTranslation(translator, 1, ztf::LineCol{1, 2});
172
173 // Check the boundary around a newline.
174 TestTranslation(translator, 9, ztf::LineCol{1, 10});
175 TestTranslation(translator, kInvalidOffset, ztf::LineCol{1, 11});
176 TestTranslation(translator, 10, ztf::LineCol{2, 1});
177 TestTranslation(translator, kInvalidOffset, ztf::LineCol{2, 0});
178
179 // Check the end of the file.
180 TestTranslation(translator, kInvalidOffset, ztf::LineCol{8, 1});
181 TestTranslation(translator, kInvalidOffset, ztf::LineCol{7, 79});
182 // Need to subtract to account for the newline.
183 TestTranslation(translator, text.size() - 1, ztf::LineCol{7, 78});
184 TestTranslation(translator, text.size() - 2, ztf::LineCol{7, 77});
185
186 // Delta Validity
187 // - Reminder! 0 -> 1:1
188
189 // Common possible edge cases.
190 EXPECT_TRUE(translator.IsValid(0, ztf::DeltaLineCol{0, 0}));
191 EXPECT_TRUE(translator.IsValid(0, ztf::DeltaLineCol{0, 1}));
192 EXPECT_TRUE(translator.IsValid(0, ztf::DeltaLineCol{1, 0}));
193 EXPECT_FALSE(translator.IsValid(0, ztf::DeltaLineCol{-1, -1}));
194 EXPECT_FALSE(translator.IsValid(0, ztf::DeltaLineCol{-1, 0}));
195 EXPECT_FALSE(translator.IsValid(0, ztf::DeltaLineCol{0, -1}));
196 EXPECT_FALSE(translator.IsValid(0, ztf::DeltaLineCol{0, -1}));
197 EXPECT_FALSE(translator.IsValid(0, ztf::DeltaLineCol{0, kMaxVal}));
198 EXPECT_FALSE(translator.IsValid(0, ztf::DeltaLineCol{kMaxVal, 0}));
199 EXPECT_FALSE(translator.IsValid(0, ztf::DeltaLineCol{0, kMinVal}));
200 EXPECT_FALSE(translator.IsValid(0, ztf::DeltaLineCol{kMinVal, 0}));
201 EXPECT_FALSE(translator.IsValid(233, ztf::DeltaLineCol{0, kMaxVal}));
202 EXPECT_FALSE(translator.IsValid(233, ztf::DeltaLineCol{kMaxVal, 0}));
203 EXPECT_FALSE(translator.IsValid(233, ztf::DeltaLineCol{kMaxVal, kMaxVal}));
204
205 // Newline area.
206 EXPECT_TRUE(translator.IsValid(0, ztf::DeltaLineCol{0, 9}));
207 EXPECT_FALSE(translator.IsValid(0, ztf::DeltaLineCol{0, 10}));
208 EXPECT_FALSE(translator.IsValid(9, ztf::DeltaLineCol{0, 1}));
209 EXPECT_FALSE(translator.IsValid(9, ztf::DeltaLineCol{-1, 0}));
210 EXPECT_FALSE(translator.IsValid(9, ztf::DeltaLineCol{1, -10}));
211 EXPECT_TRUE(translator.IsValid(9, ztf::DeltaLineCol{1, -9}));
212
213 // End of file.
214 EXPECT_FALSE(translator.IsValid(0, ztf::DeltaLineCol{7, 78}));
215 EXPECT_FALSE(translator.IsValid(0, ztf::DeltaLineCol{7, 77}));
216 EXPECT_FALSE(translator.IsValid(0, ztf::DeltaLineCol{6, 78}));
217 EXPECT_TRUE(translator.IsValid(0, ztf::DeltaLineCol{6, 77}));
218 EXPECT_FALSE(translator.IsValid(text.size() - 1, ztf::DeltaLineCol{0, 1}));
219 EXPECT_FALSE(translator.IsValid(text.size() - 1, ztf::DeltaLineCol{1, 0}));
220 EXPECT_TRUE(translator.IsValid(text.size() - 2, ztf::DeltaLineCol{0, 1}));
221 EXPECT_FALSE(translator.IsValid(text.size() - 2, ztf::DeltaLineCol{1, 0}));
222 }
223
224 // Ensures that ReferenceGroups from DisassemblerZtf::MakeReferenceGroups()
225 // cover each non-sentinel element in ReferenceType in order, exactly once. Also
226 // ensures that the ReferenceType elements are grouped by ReferencePool, and
227 // listed in increasing order.
TEST(DisassemblerZtfTest,ReferenceGroups)228 TEST(DisassemblerZtfTest, ReferenceGroups) {
229 std::vector<uint32_t> pool_list;
230 std::vector<uint32_t> type_list;
231 DisassemblerZtf dis;
232 for (ReferenceGroup group : dis.MakeReferenceGroups()) {
233 pool_list.push_back(static_cast<uint32_t>(group.pool_tag().value()));
234 type_list.push_back(static_cast<uint32_t>(group.type_tag().value()));
235 }
236
237 // Check ReferenceByte coverage.
238 constexpr size_t kNumTypes = DisassemblerZtf::kNumTypes;
239 EXPECT_EQ(kNumTypes, type_list.size());
240 EXPECT_EQ(kNumTypes, CountDistinct(type_list));
241 EXPECT_TRUE(std::is_sorted(type_list.begin(), type_list.end()));
242
243 // Check that ReferenceType elements are grouped by ReferencePool. Note that
244 // repeats can occur, and pools can be skipped.
245 EXPECT_TRUE(std::is_sorted(pool_list.begin(), pool_list.end()));
246 }
247
TEST(DisassemblerZtfTest,BadMagic)248 TEST(DisassemblerZtfTest, BadMagic) {
249 // Test a case where there is no header so a disassembler cannot be created.
250 {
251 const std::vector<uint8_t> text(StrToData("foobarbaz bazbarfoo"));
252 ConstBufferView image(text.data(), text.size());
253 EXPECT_FALSE(DisassemblerZtf::QuickDetect(image));
254 EXPECT_FALSE(Disassembler::Make<DisassemblerZtf>(image));
255 }
256 // Test a case where there is no footer so a disassembler cannot be created.
257 {
258 const std::vector<uint8_t> text(StrToData("ZTxtfoobarbaz bazbarfootxTZ"));
259 ConstBufferView image(text.data(), text.size());
260 EXPECT_TRUE(DisassemblerZtf::QuickDetect(image));
261 EXPECT_FALSE(Disassembler::Make<DisassemblerZtf>(image));
262 }
263 // Test when the header is too short
264 {
265 const std::vector<uint8_t> text(StrToData("ZTxtxTZ\n"));
266 ConstBufferView image(text.data(), text.size());
267 EXPECT_FALSE(DisassemblerZtf::QuickDetect(image));
268 EXPECT_FALSE(Disassembler::Make<DisassemblerZtf>(image));
269 }
270 }
271
TEST(DisassemblerZtfTest,ZtfSizeBound)272 TEST(DisassemblerZtfTest, ZtfSizeBound) {
273 {
274 std::vector<uint8_t> text(StrToData("ZTxt"));
275 std::fill_n(std::back_inserter(text), ztf::kMaxDimValue - 2, '\n');
276 text.insert(text.end(), {'t', 'x', 'T', 'Z', '\n'});
277 ConstBufferView image(text.data(), text.size());
278 EXPECT_TRUE(DisassemblerZtf::QuickDetect(image));
279 EXPECT_TRUE(Disassembler::Make<DisassemblerZtf>(image));
280 }
281 {
282 std::vector<uint8_t> text(StrToData("ZTxt"));
283 std::fill_n(std::back_inserter(text), ztf::kMaxDimValue - 1, '\n');
284 text.insert(text.end(), {'t', 'x', 'T', 'Z', '\n'});
285 ConstBufferView image(text.data(), text.size());
286 EXPECT_TRUE(DisassemblerZtf::QuickDetect(image));
287 EXPECT_FALSE(Disassembler::Make<DisassemblerZtf>(image));
288 }
289 }
290
291 // Try reading from a well formed source.
TEST(DisassemblerZtfTest,NormalRead)292 TEST(DisassemblerZtfTest, NormalRead) {
293 const std::vector<uint8_t> text(StrToData(kNormalText));
294 ConstBufferView image(text.data(), text.size());
295 ReferenceSets expected_map = {
296 {{DisassemblerZtf::kAngles, DisassemblerZtf::kAnglesAbs1},
297 {Reference({63, 0})}},
298 {{DisassemblerZtf::kBraces, DisassemblerZtf::kBracesRel2},
299 {Reference({74, 27})}},
300 };
301 ReadReferences(image, text.size() - kNormalTextExtraBytes, &expected_map);
302 }
303
304 // Try writing to a well formed source and ensure that what is read back
305 // reflects what was written.
TEST(DisassemblerZtfTest,NormalWrite)306 TEST(DisassemblerZtfTest, NormalWrite) {
307 std::vector<uint8_t> mutable_text(StrToData(kNormalText));
308 MutableBufferView image(mutable_text.data(), mutable_text.size());
309 ReferenceSets change_map = {
310 {{DisassemblerZtf::kParentheses, DisassemblerZtf::kParenthesesAbs1},
311 {Reference({63, 71})}},
312 {{DisassemblerZtf::kBrackets, DisassemblerZtf::kBracketsRel3},
313 {Reference({74, 4})}},
314 };
315 WriteReferences(image, mutable_text.size() - kNormalTextExtraBytes,
316 change_map);
317
318 // As a sanity check see if a disassembler can identify the same references.
319 ConstBufferView const_image(image);
320 ReadReferences(const_image, mutable_text.size() - kNormalTextExtraBytes,
321 &change_map);
322 }
323
324 // Try reading from a source rife with errors.
TEST(DisassemblerZtfTest,ReadOutOfBoundsRefs)325 TEST(DisassemblerZtfTest, ReadOutOfBoundsRefs) {
326 const std::vector<uint8_t> text(StrToData(kOutOfBoundsText));
327 ConstBufferView image(text.data(), text.size());
328 ReferenceSets expected_map = {
329 {{DisassemblerZtf::kAngles, DisassemblerZtf::kAnglesAbs1},
330 {Reference({4, 0}), Reference({223, 0}), Reference({228, 12})}},
331 {{DisassemblerZtf::kBrackets, DisassemblerZtf::kBracketsRel2},
332 {Reference({139, 149})}},
333 {{DisassemblerZtf::kBraces, DisassemblerZtf::kBracesAbs1},
334 {Reference({218, 223})}},
335 {{DisassemblerZtf::kBraces, DisassemblerZtf::kBracesRel2},
336 {Reference({233, 233})}},
337 {{DisassemblerZtf::kParentheses, DisassemblerZtf::kParenthesesAbs1},
338 {Reference({174, 4})}},
339 };
340 ReadReferences(image, text.size(), &expected_map);
341 }
342
343 // Try writing to a source rife with errors (malformed references or ones that
344 // reference non-existent locations. Some of the values written are also bad. To
345 // validate check if the expected set of references are read back.
TEST(DisassemblerZtfTest,WriteOutOfBoundsRefs)346 TEST(DisassemblerZtfTest, WriteOutOfBoundsRefs) {
347 // Replace |old_val| (provided for checking) with |new_val| in |set|.
348 auto update_set = [](Reference old_ref, Reference new_ref,
349 std::set<Reference, ReferenceCompare>* set) {
350 auto it = set->find(old_ref);
351 EXPECT_NE(it, set->cend());
352 EXPECT_EQ(*it, old_ref);
353 set->erase(it);
354 set->insert(new_ref);
355 };
356
357 // Replace |old_val| (provided for checking) with |new_val| in the set which
358 // is the value corresponding to |key| in |map|.
359 auto update_map =
360 [update_set](
361 ReferenceKey key, Reference old_ref, Reference new_ref,
362 std::map<ReferenceKey, std::set<Reference, ReferenceCompare>>* map) {
363 auto it = map->find(key);
364 EXPECT_NE(it, map->cend());
365 update_set(old_ref, new_ref, &(it->second));
366 };
367
368 std::vector<uint8_t> mutable_text(StrToData(kOutOfBoundsText));
369 MutableBufferView image(mutable_text.data(), mutable_text.size());
370 ReferenceSets change_map = {
371 {{DisassemblerZtf::kAngles, DisassemblerZtf::kAnglesAbs1},
372 {Reference({223, 15}), Reference({228, 13})}},
373 {{DisassemblerZtf::kAngles, DisassemblerZtf::kAnglesAbs3},
374 {Reference({4, 50})}}, // This should fail to write.
375 {{DisassemblerZtf::kBrackets, DisassemblerZtf::kBracketsRel2},
376 {Reference({139, static_cast<offset_t>(
377 mutable_text.size())})}}, // This should fail.
378 {{DisassemblerZtf::kParentheses, DisassemblerZtf::kParenthesesAbs1},
379 {Reference({174, 21})}}, // This should fail.
380 {{DisassemblerZtf::kBraces, DisassemblerZtf::kBracesAbs1},
381 {Reference({218, 219})}},
382 {{DisassemblerZtf::kBraces, DisassemblerZtf::kBracesRel2},
383 {Reference({233, 174})}},
384 };
385 WriteReferences(image, mutable_text.size(), change_map);
386
387 // As a sanity check see if a disassembler can identify the same references
388 // (excluding the invalid ones).
389 change_map.erase(change_map.find(
390 {DisassemblerZtf::kAngles, DisassemblerZtf::kAnglesAbs3}));
391 change_map.at({DisassemblerZtf::kAngles, DisassemblerZtf::kAnglesAbs1})
392 .emplace(Reference{4, 0});
393 update_map({DisassemblerZtf::kBrackets, DisassemblerZtf::kBracketsRel2},
394 Reference({139, static_cast<offset_t>(mutable_text.size())}),
395 Reference({139, 149}), &change_map);
396 update_map({DisassemblerZtf::kParentheses, DisassemblerZtf::kParenthesesAbs1},
397 Reference({174, 21}), Reference({174, 4}), &change_map);
398 ConstBufferView const_image(image);
399 ReadReferences(const_image, mutable_text.size(), &change_map);
400 }
401
402 } // namespace zucchini
403