1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "android-base/macros.h"
18
19#include "Flags.h"
20#include "LoadedApk.h"
21#include "ValueVisitor.h"
22#include "process/IResourceTableConsumer.h"
23#include "process/SymbolTable.h"
24
25using android::StringPiece;
26
27namespace aapt {
28
29class DiffContext : public IAaptContext {
30 public:
31  DiffContext() : name_mangler_({}), symbol_table_(&name_mangler_) {
32  }
33
34  PackageType GetPackageType() override {
35    // Doesn't matter.
36    return PackageType::kApp;
37  }
38
39  const std::string& GetCompilationPackage() override {
40    return empty_;
41  }
42
43  uint8_t GetPackageId() override {
44    return 0x0;
45  }
46
47  IDiagnostics* GetDiagnostics() override {
48    return &diagnostics_;
49  }
50
51  NameMangler* GetNameMangler() override {
52    return &name_mangler_;
53  }
54
55  SymbolTable* GetExternalSymbols() override {
56    return &symbol_table_;
57  }
58
59  bool IsVerbose() override {
60    return false;
61  }
62
63  int GetMinSdkVersion() override {
64    return 0;
65  }
66
67 private:
68  std::string empty_;
69  StdErrDiagnostics diagnostics_;
70  NameMangler name_mangler_;
71  SymbolTable symbol_table_;
72};
73
74static void EmitDiffLine(const Source& source, const StringPiece& message) {
75  std::cerr << source << ": " << message << "\n";
76}
77
78static bool IsSymbolVisibilityDifferent(const Symbol& symbol_a, const Symbol& symbol_b) {
79  return symbol_a.state != symbol_b.state;
80}
81
82template <typename Id>
83static bool IsIdDiff(const Symbol& symbol_a, const Maybe<Id>& id_a, const Symbol& symbol_b,
84                     const Maybe<Id>& id_b) {
85  if (symbol_a.state == SymbolState::kPublic || symbol_b.state == SymbolState::kPublic) {
86    return id_a != id_b;
87  }
88  return false;
89}
90
91static bool EmitResourceConfigValueDiff(IAaptContext* context, LoadedApk* apk_a,
92                                        ResourceTablePackage* pkg_a, ResourceTableType* type_a,
93                                        ResourceEntry* entry_a, ResourceConfigValue* config_value_a,
94                                        LoadedApk* apk_b, ResourceTablePackage* pkg_b,
95                                        ResourceTableType* type_b, ResourceEntry* entry_b,
96                                        ResourceConfigValue* config_value_b) {
97  Value* value_a = config_value_a->value.get();
98  Value* value_b = config_value_b->value.get();
99  if (!value_a->Equals(value_b)) {
100    std::stringstream str_stream;
101    str_stream << "value " << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
102               << " config=" << config_value_a->config << " does not match:\n";
103    value_a->Print(&str_stream);
104    str_stream << "\n vs \n";
105    value_b->Print(&str_stream);
106    EmitDiffLine(apk_b->GetSource(), str_stream.str());
107    return true;
108  }
109  return false;
110}
111
112static bool EmitResourceEntryDiff(IAaptContext* context, LoadedApk* apk_a,
113                                  ResourceTablePackage* pkg_a, ResourceTableType* type_a,
114                                  ResourceEntry* entry_a, LoadedApk* apk_b,
115                                  ResourceTablePackage* pkg_b, ResourceTableType* type_b,
116                                  ResourceEntry* entry_b) {
117  bool diff = false;
118  for (std::unique_ptr<ResourceConfigValue>& config_value_a : entry_a->values) {
119    ResourceConfigValue* config_value_b = entry_b->FindValue(config_value_a->config);
120    if (!config_value_b) {
121      std::stringstream str_stream;
122      str_stream << "missing " << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
123                 << " config=" << config_value_a->config;
124      EmitDiffLine(apk_b->GetSource(), str_stream.str());
125      diff = true;
126    } else {
127      diff |=
128          EmitResourceConfigValueDiff(context, apk_a, pkg_a, type_a, entry_a, config_value_a.get(),
129                                      apk_b, pkg_b, type_b, entry_b, config_value_b);
130    }
131  }
132
133  // Check for any newly added config values.
134  for (std::unique_ptr<ResourceConfigValue>& config_value_b : entry_b->values) {
135    ResourceConfigValue* config_value_a = entry_a->FindValue(config_value_b->config);
136    if (!config_value_a) {
137      std::stringstream str_stream;
138      str_stream << "new config " << pkg_b->name << ":" << type_b->type << "/" << entry_b->name
139                 << " config=" << config_value_b->config;
140      EmitDiffLine(apk_b->GetSource(), str_stream.str());
141      diff = true;
142    }
143  }
144  return false;
145}
146
147static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a,
148                                 ResourceTablePackage* pkg_a, ResourceTableType* type_a,
149                                 LoadedApk* apk_b, ResourceTablePackage* pkg_b,
150                                 ResourceTableType* type_b) {
151  bool diff = false;
152  for (std::unique_ptr<ResourceEntry>& entry_a : type_a->entries) {
153    ResourceEntry* entry_b = type_b->FindEntry(entry_a->name);
154    if (!entry_b) {
155      std::stringstream str_stream;
156      str_stream << "missing " << pkg_a->name << ":" << type_a->type << "/" << entry_a->name;
157      EmitDiffLine(apk_b->GetSource(), str_stream.str());
158      diff = true;
159    } else {
160      if (IsSymbolVisibilityDifferent(entry_a->symbol_status, entry_b->symbol_status)) {
161        std::stringstream str_stream;
162        str_stream << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
163                   << " has different visibility (";
164        if (entry_b->symbol_status.state == SymbolState::kPublic) {
165          str_stream << "PUBLIC";
166        } else {
167          str_stream << "PRIVATE";
168        }
169        str_stream << " vs ";
170        if (entry_a->symbol_status.state == SymbolState::kPublic) {
171          str_stream << "PUBLIC";
172        } else {
173          str_stream << "PRIVATE";
174        }
175        str_stream << ")";
176        EmitDiffLine(apk_b->GetSource(), str_stream.str());
177        diff = true;
178      } else if (IsIdDiff(entry_a->symbol_status, entry_a->id, entry_b->symbol_status,
179                          entry_b->id)) {
180        std::stringstream str_stream;
181        str_stream << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
182                   << " has different public ID (";
183        if (entry_b->id) {
184          str_stream << "0x" << std::hex << entry_b->id.value();
185        } else {
186          str_stream << "none";
187        }
188        str_stream << " vs ";
189        if (entry_a->id) {
190          str_stream << "0x " << std::hex << entry_a->id.value();
191        } else {
192          str_stream << "none";
193        }
194        str_stream << ")";
195        EmitDiffLine(apk_b->GetSource(), str_stream.str());
196        diff = true;
197      }
198      diff |= EmitResourceEntryDiff(context, apk_a, pkg_a, type_a, entry_a.get(), apk_b, pkg_b,
199                                    type_b, entry_b);
200    }
201  }
202
203  // Check for any newly added entries.
204  for (std::unique_ptr<ResourceEntry>& entry_b : type_b->entries) {
205    ResourceEntry* entry_a = type_a->FindEntry(entry_b->name);
206    if (!entry_a) {
207      std::stringstream str_stream;
208      str_stream << "new entry " << pkg_b->name << ":" << type_b->type << "/" << entry_b->name;
209      EmitDiffLine(apk_b->GetSource(), str_stream.str());
210      diff = true;
211    }
212  }
213  return diff;
214}
215
216static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a,
217                                    ResourceTablePackage* pkg_a, LoadedApk* apk_b,
218                                    ResourceTablePackage* pkg_b) {
219  bool diff = false;
220  for (std::unique_ptr<ResourceTableType>& type_a : pkg_a->types) {
221    ResourceTableType* type_b = pkg_b->FindType(type_a->type);
222    if (!type_b) {
223      std::stringstream str_stream;
224      str_stream << "missing " << pkg_a->name << ":" << type_a->type;
225      EmitDiffLine(apk_a->GetSource(), str_stream.str());
226      diff = true;
227    } else {
228      if (IsSymbolVisibilityDifferent(type_a->symbol_status, type_b->symbol_status)) {
229        std::stringstream str_stream;
230        str_stream << pkg_a->name << ":" << type_a->type << " has different visibility (";
231        if (type_b->symbol_status.state == SymbolState::kPublic) {
232          str_stream << "PUBLIC";
233        } else {
234          str_stream << "PRIVATE";
235        }
236        str_stream << " vs ";
237        if (type_a->symbol_status.state == SymbolState::kPublic) {
238          str_stream << "PUBLIC";
239        } else {
240          str_stream << "PRIVATE";
241        }
242        str_stream << ")";
243        EmitDiffLine(apk_b->GetSource(), str_stream.str());
244        diff = true;
245      } else if (IsIdDiff(type_a->symbol_status, type_a->id, type_b->symbol_status, type_b->id)) {
246        std::stringstream str_stream;
247        str_stream << pkg_a->name << ":" << type_a->type << " has different public ID (";
248        if (type_b->id) {
249          str_stream << "0x" << std::hex << type_b->id.value();
250        } else {
251          str_stream << "none";
252        }
253        str_stream << " vs ";
254        if (type_a->id) {
255          str_stream << "0x " << std::hex << type_a->id.value();
256        } else {
257          str_stream << "none";
258        }
259        str_stream << ")";
260        EmitDiffLine(apk_b->GetSource(), str_stream.str());
261        diff = true;
262      }
263      diff |= EmitResourceTypeDiff(context, apk_a, pkg_a, type_a.get(), apk_b, pkg_b, type_b);
264    }
265  }
266
267  // Check for any newly added types.
268  for (std::unique_ptr<ResourceTableType>& type_b : pkg_b->types) {
269    ResourceTableType* type_a = pkg_a->FindType(type_b->type);
270    if (!type_a) {
271      std::stringstream str_stream;
272      str_stream << "new type " << pkg_b->name << ":" << type_b->type;
273      EmitDiffLine(apk_b->GetSource(), str_stream.str());
274      diff = true;
275    }
276  }
277  return diff;
278}
279
280static bool EmitResourceTableDiff(IAaptContext* context, LoadedApk* apk_a, LoadedApk* apk_b) {
281  ResourceTable* table_a = apk_a->GetResourceTable();
282  ResourceTable* table_b = apk_b->GetResourceTable();
283
284  bool diff = false;
285  for (std::unique_ptr<ResourceTablePackage>& pkg_a : table_a->packages) {
286    ResourceTablePackage* pkg_b = table_b->FindPackage(pkg_a->name);
287    if (!pkg_b) {
288      std::stringstream str_stream;
289      str_stream << "missing package " << pkg_a->name;
290      EmitDiffLine(apk_b->GetSource(), str_stream.str());
291      diff = true;
292    } else {
293      if (pkg_a->id != pkg_b->id) {
294        std::stringstream str_stream;
295        str_stream << "package '" << pkg_a->name << "' has different id (";
296        if (pkg_b->id) {
297          str_stream << "0x" << std::hex << pkg_b->id.value();
298        } else {
299          str_stream << "none";
300        }
301        str_stream << " vs ";
302        if (pkg_a->id) {
303          str_stream << "0x" << std::hex << pkg_a->id.value();
304        } else {
305          str_stream << "none";
306        }
307        str_stream << ")";
308        EmitDiffLine(apk_b->GetSource(), str_stream.str());
309        diff = true;
310      }
311      diff |= EmitResourcePackageDiff(context, apk_a, pkg_a.get(), apk_b, pkg_b);
312    }
313  }
314
315  // Check for any newly added packages.
316  for (std::unique_ptr<ResourceTablePackage>& pkg_b : table_b->packages) {
317    ResourceTablePackage* pkg_a = table_a->FindPackage(pkg_b->name);
318    if (!pkg_a) {
319      std::stringstream str_stream;
320      str_stream << "new package " << pkg_b->name;
321      EmitDiffLine(apk_b->GetSource(), str_stream.str());
322      diff = true;
323    }
324  }
325  return diff;
326}
327
328class ZeroingReferenceVisitor : public ValueVisitor {
329 public:
330  using ValueVisitor::Visit;
331
332  void Visit(Reference* ref) override {
333    if (ref->name && ref->id) {
334      if (ref->id.value().package_id() == kAppPackageId) {
335        ref->id = {};
336      }
337    }
338  }
339};
340
341static void ZeroOutAppReferences(ResourceTable* table) {
342  ZeroingReferenceVisitor visitor;
343  VisitAllValuesInTable(table, &visitor);
344}
345
346int Diff(const std::vector<StringPiece>& args) {
347  DiffContext context;
348
349  Flags flags;
350  if (!flags.Parse("aapt2 diff", args, &std::cerr)) {
351    return 1;
352  }
353
354  if (flags.GetArgs().size() != 2u) {
355    std::cerr << "must have two apks as arguments.\n\n";
356    flags.Usage("aapt2 diff", &std::cerr);
357    return 1;
358  }
359
360  std::unique_ptr<LoadedApk> apk_a = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[0]);
361  std::unique_ptr<LoadedApk> apk_b = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[1]);
362  if (!apk_a || !apk_b) {
363    return 1;
364  }
365
366  // Zero out Application IDs in references.
367  ZeroOutAppReferences(apk_a->GetResourceTable());
368  ZeroOutAppReferences(apk_b->GetResourceTable());
369
370  if (EmitResourceTableDiff(&context, apk_a.get(), apk_b.get())) {
371    // We emitted a diff, so return 1 (failure).
372    return 1;
373  }
374  return 0;
375}
376
377}  // namespace aapt
378