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 Visibility& vis_a, const Visibility& vis_b) {
79  return vis_a.level != vis_b.level;
80}
81
82template <typename Id>
83static bool IsIdDiff(const Visibility::Level& level_a, const Maybe<Id>& id_a,
84                     const Visibility::Level& level_b, const Maybe<Id>& id_b) {
85  if (level_a == Visibility::Level::kPublic || level_b == Visibility::Level::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->visibility, entry_b->visibility)) {
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->visibility.level == Visibility::Level::kPublic) {
165          str_stream << "PUBLIC";
166        } else {
167          str_stream << "PRIVATE";
168        }
169        str_stream << " vs ";
170        if (entry_a->visibility.level == Visibility::Level::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->visibility.level, entry_a->id, entry_b->visibility.level,
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 (type_a->visibility_level != type_b->visibility_level) {
229        std::stringstream str_stream;
230        str_stream << pkg_a->name << ":" << type_a->type << " has different visibility (";
231        if (type_b->visibility_level == Visibility::Level::kPublic) {
232          str_stream << "PUBLIC";
233        } else {
234          str_stream << "PRIVATE";
235        }
236        str_stream << " vs ";
237        if (type_a->visibility_level == Visibility::Level::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->visibility_level, type_a->id, type_b->visibility_level,
246                          type_b->id)) {
247        std::stringstream str_stream;
248        str_stream << pkg_a->name << ":" << type_a->type << " has different public ID (";
249        if (type_b->id) {
250          str_stream << "0x" << std::hex << type_b->id.value();
251        } else {
252          str_stream << "none";
253        }
254        str_stream << " vs ";
255        if (type_a->id) {
256          str_stream << "0x " << std::hex << type_a->id.value();
257        } else {
258          str_stream << "none";
259        }
260        str_stream << ")";
261        EmitDiffLine(apk_b->GetSource(), str_stream.str());
262        diff = true;
263      }
264      diff |= EmitResourceTypeDiff(context, apk_a, pkg_a, type_a.get(), apk_b, pkg_b, type_b);
265    }
266  }
267
268  // Check for any newly added types.
269  for (std::unique_ptr<ResourceTableType>& type_b : pkg_b->types) {
270    ResourceTableType* type_a = pkg_a->FindType(type_b->type);
271    if (!type_a) {
272      std::stringstream str_stream;
273      str_stream << "new type " << pkg_b->name << ":" << type_b->type;
274      EmitDiffLine(apk_b->GetSource(), str_stream.str());
275      diff = true;
276    }
277  }
278  return diff;
279}
280
281static bool EmitResourceTableDiff(IAaptContext* context, LoadedApk* apk_a, LoadedApk* apk_b) {
282  ResourceTable* table_a = apk_a->GetResourceTable();
283  ResourceTable* table_b = apk_b->GetResourceTable();
284
285  bool diff = false;
286  for (std::unique_ptr<ResourceTablePackage>& pkg_a : table_a->packages) {
287    ResourceTablePackage* pkg_b = table_b->FindPackage(pkg_a->name);
288    if (!pkg_b) {
289      std::stringstream str_stream;
290      str_stream << "missing package " << pkg_a->name;
291      EmitDiffLine(apk_b->GetSource(), str_stream.str());
292      diff = true;
293    } else {
294      if (pkg_a->id != pkg_b->id) {
295        std::stringstream str_stream;
296        str_stream << "package '" << pkg_a->name << "' has different id (";
297        if (pkg_b->id) {
298          str_stream << "0x" << std::hex << pkg_b->id.value();
299        } else {
300          str_stream << "none";
301        }
302        str_stream << " vs ";
303        if (pkg_a->id) {
304          str_stream << "0x" << std::hex << pkg_a->id.value();
305        } else {
306          str_stream << "none";
307        }
308        str_stream << ")";
309        EmitDiffLine(apk_b->GetSource(), str_stream.str());
310        diff = true;
311      }
312      diff |= EmitResourcePackageDiff(context, apk_a, pkg_a.get(), apk_b, pkg_b);
313    }
314  }
315
316  // Check for any newly added packages.
317  for (std::unique_ptr<ResourceTablePackage>& pkg_b : table_b->packages) {
318    ResourceTablePackage* pkg_a = table_a->FindPackage(pkg_b->name);
319    if (!pkg_a) {
320      std::stringstream str_stream;
321      str_stream << "new package " << pkg_b->name;
322      EmitDiffLine(apk_b->GetSource(), str_stream.str());
323      diff = true;
324    }
325  }
326  return diff;
327}
328
329class ZeroingReferenceVisitor : public DescendingValueVisitor {
330 public:
331  using DescendingValueVisitor::Visit;
332
333  void Visit(Reference* ref) override {
334    if (ref->name && ref->id) {
335      if (ref->id.value().package_id() == kAppPackageId) {
336        ref->id = {};
337      }
338    }
339  }
340};
341
342static void ZeroOutAppReferences(ResourceTable* table) {
343  ZeroingReferenceVisitor visitor;
344  VisitAllValuesInTable(table, &visitor);
345}
346
347int Diff(const std::vector<StringPiece>& args) {
348  DiffContext context;
349
350  Flags flags;
351  if (!flags.Parse("aapt2 diff", args, &std::cerr)) {
352    return 1;
353  }
354
355  if (flags.GetArgs().size() != 2u) {
356    std::cerr << "must have two apks as arguments.\n\n";
357    flags.Usage("aapt2 diff", &std::cerr);
358    return 1;
359  }
360
361  IDiagnostics* diag = context.GetDiagnostics();
362  std::unique_ptr<LoadedApk> apk_a = LoadedApk::LoadApkFromPath(flags.GetArgs()[0], diag);
363  std::unique_ptr<LoadedApk> apk_b = LoadedApk::LoadApkFromPath(flags.GetArgs()[1], diag);
364  if (!apk_a || !apk_b) {
365    return 1;
366  }
367
368  // Zero out Application IDs in references.
369  ZeroOutAppReferences(apk_a->GetResourceTable());
370  ZeroOutAppReferences(apk_b->GetResourceTable());
371
372  if (EmitResourceTableDiff(&context, apk_a.get(), apk_b.get())) {
373    // We emitted a diff, so return 1 (failure).
374    return 1;
375  }
376  return 0;
377}
378
379}  // namespace aapt
380