1 // Copyright 2019 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 "tools/binary_size/libsupersize/caspian/model.h"
6
7 #include <algorithm>
8 #include <iostream>
9 #include <list>
10 #include <tuple>
11 #include <unordered_map>
12
13 #include "tools/binary_size/libsupersize/caspian/file_format.h"
14 #include "tools/binary_size/libsupersize/caspian/function_signature.h"
15
16 namespace caspian {
17
18 BaseSymbol::~BaseSymbol() = default;
19
20 Symbol::Symbol() = default;
21 Symbol::~Symbol() = default;
22 Symbol::Symbol(const Symbol& other) = default;
23
DeriveNames() const24 void Symbol::DeriveNames() const {
25 if (name_.data() != nullptr) {
26 return;
27 }
28 if (IsPak()) {
29 // full_name: "about_ui_resources.grdp: IDR_ABOUT_UI_CREDITS_HTML".
30 size_t space_idx = full_name_.rfind(' ');
31 template_name_ = full_name_.substr(space_idx + 1);
32 name_ = template_name_;
33 } else if ((!full_name_.empty() && full_name_[0] == '*') || IsOverhead() ||
34 IsOther()) {
35 template_name_ = full_name_;
36 name_ = full_name_;
37 } else if (IsDex()) {
38 std::tuple<std::string_view, std::string_view, std::string_view>
39 parsed_names = ParseJava(full_name_, &size_info_->owned_strings);
40 template_name_ = std::get<1>(parsed_names);
41 name_ = std::get<2>(parsed_names);
42 } else if (IsStringLiteral()) {
43 template_name_ = full_name_;
44 name_ = full_name_;
45 } else if (IsNative()) {
46 std::tuple<std::string_view, std::string_view, std::string_view>
47 parsed_names = ParseCpp(full_name_, &size_info_->owned_strings);
48 template_name_ = std::get<1>(parsed_names);
49 name_ = std::get<2>(parsed_names);
50 } else {
51 template_name_ = full_name_;
52 name_ = full_name_;
53 }
54 }
55
Address() const56 int32_t Symbol::Address() const {
57 return address_;
58 }
Size() const59 int32_t Symbol::Size() const {
60 return size_;
61 }
Flags() const62 int32_t Symbol::Flags() const {
63 return flags_;
64 }
Padding() const65 int32_t Symbol::Padding() const {
66 return padding_;
67 }
68
FullName() const69 std::string_view Symbol::FullName() const {
70 return full_name_;
71 }
72 // Derived from |full_name|. Generated lazily and cached.
TemplateName() const73 std::string_view Symbol::TemplateName() const {
74 DeriveNames();
75 return template_name_;
76 }
Name() const77 std::string_view Symbol::Name() const {
78 DeriveNames();
79 return name_;
80 }
Aliases() const81 const std::vector<Symbol*>* Symbol::Aliases() const {
82 return aliases_;
83 }
Section() const84 SectionId Symbol::Section() const {
85 return section_id_;
86 }
87
ObjectPath() const88 const char* Symbol::ObjectPath() const {
89 return object_path_;
90 }
SourcePath() const91 const char* Symbol::SourcePath() const {
92 return source_path_;
93 }
SectionName() const94 const char* Symbol::SectionName() const {
95 return section_name_;
96 }
Component() const97 const char* Symbol::Component() const {
98 return component_;
99 }
100
Pss() const101 float Symbol::Pss() const {
102 return static_cast<float>(Size()) / NumAliases();
103 }
104
PssWithoutPadding() const105 float Symbol::PssWithoutPadding() const {
106 return Pss() - PaddingPss();
107 }
108
PaddingPss() const109 float Symbol::PaddingPss() const {
110 return static_cast<float>(Padding()) / NumAliases();
111 }
112
GetDiffStatus() const113 DiffStatus Symbol::GetDiffStatus() const {
114 return DiffStatus::kUnchanged;
115 }
116
117 // delta symbol
118
DeltaSymbol(const Symbol * before,const Symbol * after)119 DeltaSymbol::DeltaSymbol(const Symbol* before, const Symbol* after)
120 : before_(before), after_(after) {
121 if (!before_ && !after_) {
122 exit(1);
123 }
124 }
125
126 DeltaSymbol::~DeltaSymbol() = default;
127
Address() const128 int32_t DeltaSymbol::Address() const {
129 if (after_) {
130 return after_->Address();
131 }
132 return 0;
133 }
134
Size() const135 int32_t DeltaSymbol::Size() const {
136 if (!after_) {
137 return -before_->Size();
138 }
139 if (!before_) {
140 return after_->Size();
141 }
142 // Padding tracked in aggregate, except for padding-only symbols.
143 if (before_->SizeWithoutPadding() == 0) {
144 return after_->Padding() - before_->Padding();
145 }
146 return after_->SizeWithoutPadding() - before_->SizeWithoutPadding();
147 }
148
Flags() const149 int32_t DeltaSymbol::Flags() const {
150 int32_t before_flags = before_ ? before_->Flags() : 0;
151 int32_t after_flags = after_ ? after_->Flags() : 0;
152 return before_flags ^ after_flags;
153 }
154
Padding() const155 int32_t DeltaSymbol::Padding() const {
156 if (!after_) {
157 return -before_->Padding();
158 }
159 if (!before_) {
160 return after_->Padding();
161 }
162 // Padding tracked in aggregate, except for padding-only symbols.
163 if (before_->SizeWithoutPadding() == 0) {
164 return after_->Padding() - before_->Padding();
165 }
166 return 0;
167 }
168
FullName() const169 std::string_view DeltaSymbol::FullName() const {
170 return (after_ ? after_ : before_)->FullName();
171 }
172
173 // Derived from |full_name|. Generated lazily and cached.
TemplateName() const174 std::string_view DeltaSymbol::TemplateName() const {
175 return (after_ ? after_ : before_)->TemplateName();
176 }
177
Name() const178 std::string_view DeltaSymbol::Name() const {
179 return (after_ ? after_ : before_)->Name();
180 }
181
Aliases() const182 const std::vector<Symbol*>* DeltaSymbol::Aliases() const {
183 return nullptr;
184 }
185
Section() const186 SectionId DeltaSymbol::Section() const {
187 return (after_ ? after_ : before_)->Section();
188 }
189
ObjectPath() const190 const char* DeltaSymbol::ObjectPath() const {
191 return (after_ ? after_ : before_)->ObjectPath();
192 }
193
SourcePath() const194 const char* DeltaSymbol::SourcePath() const {
195 return (after_ ? after_ : before_)->SourcePath();
196 }
197
SectionName() const198 const char* DeltaSymbol::SectionName() const {
199 return (after_ ? after_ : before_)->SectionName();
200 }
201
Component() const202 const char* DeltaSymbol::Component() const {
203 return (after_ ? after_ : before_)->Component();
204 }
205
Pss() const206 float DeltaSymbol::Pss() const {
207 if (!after_) {
208 return -before_->Pss();
209 }
210 if (!before_) {
211 return after_->Pss();
212 }
213 // Padding tracked in aggregate, except for padding-only symbols.
214 if (before_->SizeWithoutPadding() == 0) {
215 return after_->Pss() - before_->Pss();
216 }
217 return after_->PssWithoutPadding() - before_->PssWithoutPadding();
218 }
219
PssWithoutPadding() const220 float DeltaSymbol::PssWithoutPadding() const {
221 return Pss() - PaddingPss();
222 }
223
PaddingPss() const224 float DeltaSymbol::PaddingPss() const {
225 if (!after_) {
226 return -before_->PaddingPss();
227 }
228 if (!before_) {
229 return after_->PaddingPss();
230 }
231 // Padding tracked in aggregate, except for padding-only symbols.
232 if (before_->SizeWithoutPadding() == 0) {
233 return after_->PaddingPss() - before_->PaddingPss();
234 }
235 return 0;
236 }
237
GetDiffStatus() const238 DiffStatus DeltaSymbol::GetDiffStatus() const {
239 if (!before_) {
240 return DiffStatus::kAdded;
241 }
242 if (!after_) {
243 return DiffStatus::kRemoved;
244 }
245 if (Size() || Pss() != 0) {
246 return DiffStatus::kChanged;
247 }
248 return DiffStatus::kUnchanged;
249 }
250
251 TreeNode::TreeNode() = default;
~TreeNode()252 TreeNode::~TreeNode() {
253 // TODO(jaspercb): Could use custom allocator to delete all nodes in one go.
254 for (TreeNode* child : children) {
255 delete child;
256 }
257 }
258
operator <<(std::ostream & os,const Symbol & sym)259 std::ostream& operator<<(std::ostream& os, const Symbol& sym) {
260 return os << "Symbol(full_name=" << sym.FullName()
261 << ", section=" << static_cast<char>(sym.section_id_)
262 << ", section=" << sym.section_name_ << ", address=" << sym.address_
263 << ", size=" << sym.size_ << ", flags=" << sym.flags_
264 << ", padding=" << sym.padding_ << ")";
265 }
266
267 BaseSizeInfo::BaseSizeInfo() = default;
268 BaseSizeInfo::BaseSizeInfo(const BaseSizeInfo&) = default;
269 BaseSizeInfo::~BaseSizeInfo() = default;
270
ShortSectionName(const char * section_name)271 SectionId BaseSizeInfo::ShortSectionName(const char* section_name) {
272 static std::map<const char*, SectionId> short_section_name_cache;
273 SectionId& ret = short_section_name_cache[section_name];
274 if (ret == SectionId::kNone) {
275 if (!strcmp(section_name, ".text")) {
276 ret = SectionId::kText;
277 } else if (!strcmp(section_name, ".dex")) {
278 ret = SectionId::kDex;
279 } else if (!strcmp(section_name, ".dex.method")) {
280 ret = SectionId::kDexMethod;
281 } else if (!strcmp(section_name, ".other")) {
282 ret = SectionId::kOther;
283 } else if (!strcmp(section_name, ".rodata")) {
284 ret = SectionId::kRoData;
285 } else if (!strcmp(section_name, ".data")) {
286 ret = SectionId::kData;
287 } else if (!strcmp(section_name, ".data.rel.ro")) {
288 ret = SectionId::kDataRelRo;
289 } else if (!strcmp(section_name, ".bss")) {
290 ret = SectionId::kBss;
291 } else if (!strcmp(section_name, ".bss.rel.ro")) {
292 ret = SectionId::kBss;
293 } else if (!strcmp(section_name, ".pak.nontranslated")) {
294 ret = SectionId::kPakNontranslated;
295 } else if (!strcmp(section_name, ".pak.translations")) {
296 ret = SectionId::kPakTranslations;
297 } else {
298 std::cerr << "Attributing unrecognized section name to .other: "
299 << section_name << std::endl;
300 ret = SectionId::kOther;
301 }
302 }
303 return ret;
304 }
305
306 SizeInfo::SizeInfo() = default;
307 SizeInfo::~SizeInfo() = default;
308
IsSparse() const309 bool SizeInfo::IsSparse() const {
310 return is_sparse;
311 }
312
DeltaSizeInfo(const SizeInfo * before,const SizeInfo * after)313 DeltaSizeInfo::DeltaSizeInfo(const SizeInfo* before, const SizeInfo* after)
314 : before(before), after(after) {}
315
316 DeltaSizeInfo::~DeltaSizeInfo() = default;
317 DeltaSizeInfo::DeltaSizeInfo(const DeltaSizeInfo&) = default;
318 DeltaSizeInfo& DeltaSizeInfo::operator=(const DeltaSizeInfo&) = default;
319
IsSparse() const320 bool DeltaSizeInfo::IsSparse() const {
321 return before->IsSparse() && after->IsSparse();
322 }
323
WriteIntoJson(int depth,std::function<bool (const TreeNode * const & l,const TreeNode * const & r)> compare_func,bool is_sparse,bool method_count_mode,Json::Value * out)324 void TreeNode::WriteIntoJson(
325 int depth,
326 std::function<bool(const TreeNode* const& l, const TreeNode* const& r)>
327 compare_func,
328 bool is_sparse,
329 bool method_count_mode,
330 Json::Value* out) {
331 if (symbol) {
332 (*out)["helpme"] = std::string(symbol->Name());
333 (*out)["idPath"] = std::string(symbol->TemplateName());
334 (*out)["fullName"] = std::string(symbol->FullName());
335 if (symbol->NumAliases() > 1) {
336 (*out)["numAliases"] = symbol->NumAliases();
337 }
338 if (symbol->SourcePath()) {
339 (*out)["srcPath"] = symbol->SourcePath();
340 }
341 if (symbol->Component()) {
342 (*out)["component"] = symbol->Component();
343 }
344 } else {
345 (*out)["idPath"] = id_path.ToString();
346
347 if (!is_sparse && !children.empty()) {
348 // Add tag to containers in which all child symbols were added/removed.
349 DiffStatus diff_status = node_stats.GetGlobalDiffStatus();
350 if (diff_status != DiffStatus::kUnchanged) {
351 (*out)["diffStatus"] = static_cast<uint8_t>(diff_status);
352 }
353 }
354 }
355 (*out)["shortNameIndex"] = short_name_index;
356 std::string type;
357 if (container_type != ContainerType::kSymbol) {
358 type += static_cast<char>(container_type);
359 }
360 SectionId biggest_section = node_stats.ComputeBiggestSection();
361 type += static_cast<char>(biggest_section);
362 (*out)["type"] = type;
363
364 (*out)["size"] = size;
365 (*out)["flags"] = flags;
366 node_stats.WriteIntoJson(method_count_mode, &(*out)["childStats"]);
367
368 const size_t kMaxChildNodesToExpand = 1000;
369 if (children.size() > kMaxChildNodesToExpand) {
370 // When the tree is very flat, don't expand child nodes to avoid cost of
371 // sending thousands of children and grandchildren to renderer.
372 depth = 0;
373 }
374
375 if (depth < 0 && children.size() > 1) {
376 (*out)["children"] = Json::Value(); // null
377 } else {
378 (*out)["children"] = Json::Value(Json::arrayValue);
379 // Reorder children for output.
380 // TODO: Support additional compare functions.
381 std::sort(children.begin(), children.end(), compare_func);
382 for (unsigned int i = 0; i < children.size(); i++) {
383 children[i]->WriteIntoJson(depth - 1, compare_func, is_sparse,
384 method_count_mode, &(*out)["children"][i]);
385 }
386 }
387 }
388
389 NodeStats::NodeStats() = default;
390 NodeStats::~NodeStats() = default;
391
NodeStats(const BaseSymbol & symbol)392 NodeStats::NodeStats(const BaseSymbol& symbol) {
393 const SectionId section = symbol.Section();
394 Stat& section_stats = child_stats[section];
395 section_stats = {1, 0, 0, 0, symbol.Pss()};
396 switch (symbol.GetDiffStatus()) {
397 case DiffStatus::kUnchanged:
398 break;
399 case DiffStatus::kAdded:
400 section_stats.added = 1;
401 break;
402 case DiffStatus::kRemoved:
403 section_stats.removed = 1;
404 break;
405 case DiffStatus::kChanged:
406 section_stats.changed = 1;
407 break;
408 }
409 }
410
WriteIntoJson(bool method_count_mode,Json::Value * out) const411 void NodeStats::WriteIntoJson(bool method_count_mode, Json::Value* out) const {
412 (*out) = Json::Value(Json::objectValue);
413 for (const auto kv : child_stats) {
414 const std::string sectionId = std::string(1, static_cast<char>(kv.first));
415 const Stat stats = kv.second;
416 (*out)[sectionId] = Json::Value(Json::objectValue);
417 (*out)[sectionId]["size"] = stats.size;
418 (*out)[sectionId]["added"] = stats.added;
419 (*out)[sectionId]["removed"] = stats.removed;
420 (*out)[sectionId]["changed"] = stats.changed;
421 // Count is used to store value for "method count" mode.
422 // Why? Because that's how it was implemented in the .ndjson worker.
423 int count = stats.count;
424 bool is_diff = stats.added > 0 || stats.removed > 0 || stats.changed > 0;
425 if (method_count_mode && is_diff) {
426 count = stats.added - stats.removed;
427 }
428 (*out)[sectionId]["count"] = count;
429 }
430 }
431
operator +=(const NodeStats & other)432 NodeStats& NodeStats::operator+=(const NodeStats& other) {
433 for (const auto& it : other.child_stats) {
434 child_stats[it.first] += it.second;
435 }
436 return *this;
437 }
438
ComputeBiggestSection() const439 SectionId NodeStats::ComputeBiggestSection() const {
440 SectionId ret = SectionId::kNone;
441 float max = 0.0f;
442 for (auto& pair : child_stats) {
443 if (abs(pair.second.size) > max) {
444 ret = pair.first;
445 max = abs(pair.second.size);
446 }
447 }
448 return ret;
449 }
450
SumCount() const451 int32_t NodeStats::SumCount() const {
452 int32_t count = 0;
453 for (auto& pair : child_stats) {
454 count += pair.second.count;
455 }
456 return count;
457 }
458
SumAdded() const459 int32_t NodeStats::SumAdded() const {
460 int32_t count = 0;
461 for (auto& pair : child_stats) {
462 count += pair.second.added;
463 }
464 return count;
465 }
466
SumRemoved() const467 int32_t NodeStats::SumRemoved() const {
468 int32_t count = 0;
469 for (auto& pair : child_stats) {
470 count += pair.second.removed;
471 }
472 return count;
473 }
474
GetGlobalDiffStatus() const475 DiffStatus NodeStats::GetGlobalDiffStatus() const {
476 int32_t count = SumCount();
477 if (SumAdded() == count) {
478 return DiffStatus::kAdded;
479 } else if (SumRemoved() == count) {
480 return DiffStatus::kRemoved;
481 }
482 return DiffStatus::kUnchanged;
483 }
484 } // namespace caspian
485