xref: /aosp_15_r20/frameworks/base/tools/aapt2/cmd/Diff.cpp (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
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 "Diff.h"
18 
19 #include "Diagnostics.h"
20 #include "LoadedApk.h"
21 #include "ValueVisitor.h"
22 #include "android-base/macros.h"
23 #include "process/IResourceTableConsumer.h"
24 #include "process/SymbolTable.h"
25 
26 using ::android::StringPiece;
27 
28 namespace aapt {
29 
30 class DiffContext : public IAaptContext {
31  public:
DiffContext()32   DiffContext() : name_mangler_({}), symbol_table_(&name_mangler_) {
33   }
34 
GetPackageType()35   PackageType GetPackageType() override {
36     // Doesn't matter.
37     return PackageType::kApp;
38   }
39 
GetCompilationPackage()40   const std::string& GetCompilationPackage() override {
41     return empty_;
42   }
43 
GetPackageId()44   uint8_t GetPackageId() override {
45     return 0x0;
46   }
47 
GetDiagnostics()48   android::IDiagnostics* GetDiagnostics() override {
49     return &diagnostics_;
50   }
51 
GetNameMangler()52   NameMangler* GetNameMangler() override {
53     return &name_mangler_;
54   }
55 
GetExternalSymbols()56   SymbolTable* GetExternalSymbols() override {
57     return &symbol_table_;
58   }
59 
IsVerbose()60   bool IsVerbose() override {
61     return false;
62   }
63 
GetMinSdkVersion()64   int GetMinSdkVersion() override {
65     return 0;
66   }
67 
GetSplitNameDependencies()68   const std::set<std::string>& GetSplitNameDependencies() override {
69     UNIMPLEMENTED(FATAL) << "Split Name Dependencies should not be necessary";
70     static std::set<std::string> empty;
71     return empty;
72   }
73 
74  private:
75   std::string empty_;
76   StdErrDiagnostics diagnostics_;
77   NameMangler name_mangler_;
78   SymbolTable symbol_table_;
79 };
80 
EmitDiffLine(const android::Source & source,StringPiece message)81 static void EmitDiffLine(const android::Source& source, StringPiece message) {
82   std::cerr << source << ": " << message << "\n";
83 }
84 
IsSymbolVisibilityDifferent(const Visibility & vis_a,const Visibility & vis_b)85 static bool IsSymbolVisibilityDifferent(const Visibility& vis_a, const Visibility& vis_b) {
86   return vis_a.level != vis_b.level || vis_a.staged_api != vis_b.staged_api;
87 }
88 
89 template <typename Id>
IsIdDiff(const Visibility::Level & level_a,const std::optional<Id> & id_a,const Visibility::Level & level_b,const std::optional<Id> & id_b)90 static bool IsIdDiff(const Visibility::Level& level_a, const std::optional<Id>& id_a,
91                      const Visibility::Level& level_b, const std::optional<Id>& id_b) {
92   if (level_a == Visibility::Level::kPublic || level_b == Visibility::Level::kPublic) {
93     return id_a != id_b;
94   }
95   return false;
96 }
97 
EmitResourceConfigValueDiff(IAaptContext * context,LoadedApk * apk_a,const ResourceTablePackageView & pkg_a,const ResourceTableTypeView & type_a,const ResourceTableEntryView & entry_a,const ResourceConfigValue * config_value_a,LoadedApk * apk_b,const ResourceTablePackageView & pkg_b,const ResourceTableTypeView & type_b,const ResourceTableEntryView & entry_b,const ResourceConfigValue * config_value_b)98 static bool EmitResourceConfigValueDiff(
99     IAaptContext* context, LoadedApk* apk_a, const ResourceTablePackageView& pkg_a,
100     const ResourceTableTypeView& type_a, const ResourceTableEntryView& entry_a,
101     const ResourceConfigValue* config_value_a, LoadedApk* apk_b,
102     const ResourceTablePackageView& pkg_b, const ResourceTableTypeView& type_b,
103     const ResourceTableEntryView& entry_b, const ResourceConfigValue* config_value_b) {
104   Value* value_a = config_value_a->value.get();
105   Value* value_b = config_value_b->value.get();
106   if (!value_a->Equals(value_b)) {
107     std::stringstream str_stream;
108     str_stream << "value " << pkg_a.name << ":" << type_a.named_type << "/" << entry_a.name
109                << " config='" << config_value_a->config << "' does not match:\n";
110     value_a->Print(&str_stream);
111     str_stream << "\n vs \n";
112     value_b->Print(&str_stream);
113     EmitDiffLine(apk_b->GetSource(), str_stream.str());
114     return true;
115   }
116   return false;
117 }
118 
EmitResourceEntryDiff(IAaptContext * context,LoadedApk * apk_a,const ResourceTablePackageView & pkg_a,const ResourceTableTypeView & type_a,const ResourceTableEntryView & entry_a,LoadedApk * apk_b,const ResourceTablePackageView & pkg_b,const ResourceTableTypeView & type_b,const ResourceTableEntryView & entry_b)119 static bool EmitResourceEntryDiff(IAaptContext* context, LoadedApk* apk_a,
120                                   const ResourceTablePackageView& pkg_a,
121                                   const ResourceTableTypeView& type_a,
122                                   const ResourceTableEntryView& entry_a, LoadedApk* apk_b,
123                                   const ResourceTablePackageView& pkg_b,
124                                   const ResourceTableTypeView& type_b,
125                                   const ResourceTableEntryView& entry_b) {
126   bool diff = false;
127   for (const ResourceConfigValue* config_value_a : entry_a.values) {
128     auto config_value_b = entry_b.FindValue(config_value_a->config);
129     if (!config_value_b) {
130       std::stringstream str_stream;
131       str_stream << "missing " << pkg_a.name << ":" << type_a.named_type << "/" << entry_a.name
132                  << " config=" << config_value_a->config;
133       EmitDiffLine(apk_b->GetSource(), str_stream.str());
134       diff = true;
135     } else {
136       diff |= EmitResourceConfigValueDiff(context, apk_a, pkg_a, type_a, entry_a, config_value_a,
137                                           apk_b, pkg_b, type_b, entry_b, config_value_b);
138     }
139   }
140 
141   for (const ResourceConfigValue* config_value_a : entry_a.flag_disabled_values) {
142     auto config_value_b = entry_b.FindFlagDisabledValue(config_value_a->value->GetFlag().value(),
143                                                         config_value_a->config);
144     if (!config_value_b) {
145       std::stringstream str_stream;
146       str_stream << "missing disabled value " << pkg_a.name << ":" << type_a.named_type << "/"
147                  << entry_a.name << " config=" << config_value_a->config
148                  << " flag=" << config_value_a->value->GetFlag()->ToString();
149       EmitDiffLine(apk_b->GetSource(), str_stream.str());
150       diff = true;
151     } else {
152       diff |= EmitResourceConfigValueDiff(context, apk_a, pkg_a, type_a, entry_a, config_value_a,
153                                           apk_b, pkg_b, type_b, entry_b, config_value_b);
154     }
155   }
156 
157   // Check for any newly added config values.
158   for (const ResourceConfigValue* config_value_b : entry_b.values) {
159     auto config_value_a = entry_a.FindValue(config_value_b->config);
160     if (!config_value_a) {
161       std::stringstream str_stream;
162       str_stream << "new config " << pkg_b.name << ":" << type_b.named_type << "/" << entry_b.name
163                  << " config=" << config_value_b->config;
164       EmitDiffLine(apk_b->GetSource(), str_stream.str());
165       diff = true;
166     }
167   }
168   for (const ResourceConfigValue* config_value_b : entry_b.flag_disabled_values) {
169     auto config_value_a = entry_a.FindFlagDisabledValue(config_value_b->value->GetFlag().value(),
170                                                         config_value_b->config);
171     if (!config_value_a) {
172       std::stringstream str_stream;
173       str_stream << "new disabled config " << pkg_b.name << ":" << type_b.named_type << "/"
174                  << entry_b.name << " config=" << config_value_b->config
175                  << " flag=" << config_value_b->value->GetFlag()->ToString();
176       EmitDiffLine(apk_b->GetSource(), str_stream.str());
177       diff = true;
178     }
179   }
180   return diff;
181 }
182 
EmitResourceTypeDiff(IAaptContext * context,LoadedApk * apk_a,const ResourceTablePackageView & pkg_a,const ResourceTableTypeView & type_a,LoadedApk * apk_b,const ResourceTablePackageView & pkg_b,const ResourceTableTypeView & type_b)183 static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a,
184                                  const ResourceTablePackageView& pkg_a,
185                                  const ResourceTableTypeView& type_a, LoadedApk* apk_b,
186                                  const ResourceTablePackageView& pkg_b,
187                                  const ResourceTableTypeView& type_b) {
188   bool diff = false;
189   auto entry_a_iter = type_a.entries.begin();
190   auto entry_b_iter = type_b.entries.begin();
191   while (entry_a_iter != type_a.entries.end() || entry_b_iter != type_b.entries.end()) {
192     if (entry_b_iter == type_b.entries.end()) {
193       // Type A contains a type that type B does not have.
194       std::stringstream str_stream;
195       str_stream << "missing " << pkg_a.name << ":" << type_a.named_type << "/"
196                  << entry_a_iter->name;
197       EmitDiffLine(apk_a->GetSource(), str_stream.str());
198       diff = true;
199     } else if (entry_a_iter == type_a.entries.end()) {
200       // Type B contains a type that type A does not have.
201       std::stringstream str_stream;
202       str_stream << "new entry " << pkg_b.name << ":" << type_b.named_type << "/"
203                  << entry_b_iter->name;
204       EmitDiffLine(apk_b->GetSource(), str_stream.str());
205       diff = true;
206     } else {
207       const auto& entry_a = *entry_a_iter;
208       const auto& entry_b = *entry_b_iter;
209       if (IsSymbolVisibilityDifferent(entry_a.visibility, entry_b.visibility)) {
210         std::stringstream str_stream;
211         str_stream << pkg_a.name << ":" << type_a.named_type << "/" << entry_a.name
212                    << " has different visibility (";
213         if (entry_b.visibility.staged_api) {
214           str_stream << "STAGED ";
215         }
216         if (entry_b.visibility.level == Visibility::Level::kPublic) {
217           str_stream << "PUBLIC";
218         } else {
219           str_stream << "PRIVATE";
220         }
221         str_stream << " vs ";
222         if (entry_a.visibility.staged_api) {
223           str_stream << "STAGED ";
224         }
225         if (entry_a.visibility.level == Visibility::Level::kPublic) {
226           str_stream << "PUBLIC";
227         } else {
228           str_stream << "PRIVATE";
229         }
230         str_stream << ")";
231         EmitDiffLine(apk_b->GetSource(), str_stream.str());
232         diff = true;
233       } else if (IsIdDiff(entry_a.visibility.level, entry_a.id, entry_b.visibility.level,
234                           entry_b.id)) {
235         std::stringstream str_stream;
236         str_stream << pkg_a.name << ":" << type_a.named_type << "/" << entry_a.name
237                    << " has different public ID (";
238         if (entry_b.id) {
239           str_stream << "0x" << std::hex << entry_b.id.value();
240         } else {
241           str_stream << "none";
242         }
243         str_stream << " vs ";
244         if (entry_a.id) {
245           str_stream << "0x " << std::hex << entry_a.id.value();
246         } else {
247           str_stream << "none";
248         }
249         str_stream << ")";
250         EmitDiffLine(apk_b->GetSource(), str_stream.str());
251         diff = true;
252       }
253       diff |= EmitResourceEntryDiff(context, apk_a, pkg_a, type_a, entry_a, apk_b, pkg_b, type_b,
254                                     entry_b);
255     }
256     if (entry_a_iter != type_a.entries.end()) {
257       ++entry_a_iter;
258     }
259     if (entry_b_iter != type_b.entries.end()) {
260       ++entry_b_iter;
261     }
262   }
263   return diff;
264 }
265 
EmitResourcePackageDiff(IAaptContext * context,LoadedApk * apk_a,const ResourceTablePackageView & pkg_a,LoadedApk * apk_b,const ResourceTablePackageView & pkg_b)266 static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a,
267                                     const ResourceTablePackageView& pkg_a, LoadedApk* apk_b,
268                                     const ResourceTablePackageView& pkg_b) {
269   bool diff = false;
270   auto type_a_iter = pkg_a.types.begin();
271   auto type_b_iter = pkg_b.types.begin();
272   while (type_a_iter != pkg_a.types.end() || type_b_iter != pkg_b.types.end()) {
273     if (type_b_iter == pkg_b.types.end()) {
274       // Type A contains a type that type B does not have.
275       std::stringstream str_stream;
276       str_stream << "missing " << pkg_a.name << ":" << type_a_iter->named_type;
277       EmitDiffLine(apk_a->GetSource(), str_stream.str());
278       diff = true;
279     } else if (type_a_iter == pkg_a.types.end()) {
280       // Type B contains a type that type A does not have.
281       std::stringstream str_stream;
282       str_stream << "new type " << pkg_b.name << ":" << type_b_iter->named_type;
283       EmitDiffLine(apk_b->GetSource(), str_stream.str());
284       diff = true;
285     } else {
286       const auto& type_a = *type_a_iter;
287       const auto& type_b = *type_b_iter;
288       if (type_a.visibility_level != type_b.visibility_level) {
289         std::stringstream str_stream;
290         str_stream << pkg_a.name << ":" << type_a.named_type << " has different visibility (";
291         if (type_b.visibility_level == Visibility::Level::kPublic) {
292           str_stream << "PUBLIC";
293         } else {
294           str_stream << "PRIVATE";
295         }
296         str_stream << " vs ";
297         if (type_a.visibility_level == Visibility::Level::kPublic) {
298           str_stream << "PUBLIC";
299         } else {
300           str_stream << "PRIVATE";
301         }
302         str_stream << ")";
303         EmitDiffLine(apk_b->GetSource(), str_stream.str());
304         diff = true;
305       } else if (IsIdDiff(type_a.visibility_level, type_a.id, type_b.visibility_level, type_b.id)) {
306         std::stringstream str_stream;
307         str_stream << pkg_a.name << ":" << type_a.named_type << " has different public ID (";
308         if (type_b.id) {
309           str_stream << "0x" << std::hex << type_b.id.value();
310         } else {
311           str_stream << "none";
312         }
313         str_stream << " vs ";
314         if (type_a.id) {
315           str_stream << "0x " << std::hex << type_a.id.value();
316         } else {
317           str_stream << "none";
318         }
319         str_stream << ")";
320         EmitDiffLine(apk_b->GetSource(), str_stream.str());
321         diff = true;
322       }
323       diff |= EmitResourceTypeDiff(context, apk_a, pkg_a, type_a, apk_b, pkg_b, type_b);
324     }
325     if (type_a_iter != pkg_a.types.end()) {
326       ++type_a_iter;
327     }
328     if (type_b_iter != pkg_b.types.end()) {
329       ++type_b_iter;
330     }
331   }
332   return diff;
333 }
334 
EmitResourceTableDiff(IAaptContext * context,LoadedApk * apk_a,LoadedApk * apk_b)335 static bool EmitResourceTableDiff(IAaptContext* context, LoadedApk* apk_a, LoadedApk* apk_b) {
336   const auto table_a = apk_a->GetResourceTable()->GetPartitionedView();
337   const auto table_b = apk_b->GetResourceTable()->GetPartitionedView();
338 
339   bool diff = false;
340   auto package_a_iter = table_a.packages.begin();
341   auto package_b_iter = table_b.packages.begin();
342   while (package_a_iter != table_a.packages.end() || package_b_iter != table_b.packages.end()) {
343     if (package_b_iter == table_b.packages.end()) {
344       // Table A contains a package that table B does not have.
345       std::stringstream str_stream;
346       str_stream << "missing package " << package_a_iter->name;
347       EmitDiffLine(apk_a->GetSource(), str_stream.str());
348       diff = true;
349     } else if (package_a_iter == table_a.packages.end()) {
350       // Table B contains a package that table A does not have.
351       std::stringstream str_stream;
352       str_stream << "new package " << package_b_iter->name;
353       EmitDiffLine(apk_b->GetSource(), str_stream.str());
354       diff = true;
355     } else {
356       const auto& package_a = *package_a_iter;
357       const auto& package_b = *package_b_iter;
358       if (package_a.id != package_b.id) {
359         std::stringstream str_stream;
360         str_stream << "package '" << package_a.name << "' has different id (";
361         if (package_b.id) {
362           str_stream << "0x" << std::hex << package_b.id.value();
363         } else {
364           str_stream << "none";
365         }
366         str_stream << " vs ";
367         if (package_a.id) {
368           str_stream << "0x" << std::hex << package_b.id.value();
369         } else {
370           str_stream << "none";
371         }
372         str_stream << ")";
373         EmitDiffLine(apk_b->GetSource(), str_stream.str());
374         diff = true;
375       }
376       diff |= EmitResourcePackageDiff(context, apk_a, package_a, apk_b, package_b);
377     }
378     if (package_a_iter != table_a.packages.end()) {
379       ++package_a_iter;
380     }
381     if (package_b_iter != table_b.packages.end()) {
382       ++package_b_iter;
383     }
384   }
385 
386   return diff;
387 }
388 
389 class ZeroingReferenceVisitor : public DescendingValueVisitor {
390  public:
391   using DescendingValueVisitor::Visit;
392 
Visit(Reference * ref)393   void Visit(Reference* ref) override {
394     if (ref->name && ref->id) {
395       if (ref->id.value().package_id() == kAppPackageId) {
396         ref->id = {};
397       }
398     }
399   }
400 };
401 
ZeroOutAppReferences(ResourceTable * table)402 static void ZeroOutAppReferences(ResourceTable* table) {
403   ZeroingReferenceVisitor visitor;
404   VisitAllValuesInTable(table, &visitor);
405 }
406 
Action(const std::vector<std::string> & args)407 int DiffCommand::Action(const std::vector<std::string>& args) {
408   DiffContext context;
409 
410   if (args.size() != 2u) {
411     std::cerr << "must have two apks as arguments.\n\n";
412     Usage(&std::cerr);
413     return 1;
414   }
415 
416   android::IDiagnostics* diag = context.GetDiagnostics();
417   std::unique_ptr<LoadedApk> apk_a = LoadedApk::LoadApkFromPath(args[0], diag);
418   std::unique_ptr<LoadedApk> apk_b = LoadedApk::LoadApkFromPath(args[1], diag);
419   if (!apk_a || !apk_b) {
420     return 1;
421   }
422 
423   // Zero out Application IDs in references.
424   ZeroOutAppReferences(apk_a->GetResourceTable());
425   ZeroOutAppReferences(apk_b->GetResourceTable());
426 
427   if (EmitResourceTableDiff(&context, apk_a.get(), apk_b.get())) {
428     // We emitted a diff, so return 1 (failure).
429     return 1;
430   }
431   return 0;
432 }
433 
434 }  // namespace aapt
435