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