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
25 using ::android::StringPiece;
26
27 namespace aapt {
28
29 class DiffContext : public IAaptContext {
30 public:
DiffContext()31 DiffContext() : name_mangler_({}), symbol_table_(&name_mangler_) {
32 }
33
GetPackageType()34 PackageType GetPackageType() override {
35 // Doesn't matter.
36 return PackageType::kApp;
37 }
38
GetCompilationPackage()39 const std::string& GetCompilationPackage() override {
40 return empty_;
41 }
42
GetPackageId()43 uint8_t GetPackageId() override {
44 return 0x0;
45 }
46
GetDiagnostics()47 IDiagnostics* GetDiagnostics() override {
48 return &diagnostics_;
49 }
50
GetNameMangler()51 NameMangler* GetNameMangler() override {
52 return &name_mangler_;
53 }
54
GetExternalSymbols()55 SymbolTable* GetExternalSymbols() override {
56 return &symbol_table_;
57 }
58
IsVerbose()59 bool IsVerbose() override {
60 return false;
61 }
62
GetMinSdkVersion()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
EmitDiffLine(const Source & source,const StringPiece & message)74 static void EmitDiffLine(const Source& source, const StringPiece& message) {
75 std::cerr << source << ": " << message << "\n";
76 }
77
IsSymbolVisibilityDifferent(const Visibility & vis_a,const Visibility & vis_b)78 static bool IsSymbolVisibilityDifferent(const Visibility& vis_a, const Visibility& vis_b) {
79 return vis_a.level != vis_b.level;
80 }
81
82 template <typename Id>
IsIdDiff(const Visibility::Level & level_a,const Maybe<Id> & id_a,const Visibility::Level & level_b,const Maybe<Id> & id_b)83 static 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
EmitResourceConfigValueDiff(IAaptContext * context,LoadedApk * apk_a,ResourceTablePackage * pkg_a,ResourceTableType * type_a,ResourceEntry * entry_a,ResourceConfigValue * config_value_a,LoadedApk * apk_b,ResourceTablePackage * pkg_b,ResourceTableType * type_b,ResourceEntry * entry_b,ResourceConfigValue * config_value_b)91 static 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
EmitResourceEntryDiff(IAaptContext * context,LoadedApk * apk_a,ResourceTablePackage * pkg_a,ResourceTableType * type_a,ResourceEntry * entry_a,LoadedApk * apk_b,ResourceTablePackage * pkg_b,ResourceTableType * type_b,ResourceEntry * entry_b)112 static 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
EmitResourceTypeDiff(IAaptContext * context,LoadedApk * apk_a,ResourceTablePackage * pkg_a,ResourceTableType * type_a,LoadedApk * apk_b,ResourceTablePackage * pkg_b,ResourceTableType * type_b)147 static 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
EmitResourcePackageDiff(IAaptContext * context,LoadedApk * apk_a,ResourceTablePackage * pkg_a,LoadedApk * apk_b,ResourceTablePackage * pkg_b)216 static 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
EmitResourceTableDiff(IAaptContext * context,LoadedApk * apk_a,LoadedApk * apk_b)281 static 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
329 class ZeroingReferenceVisitor : public DescendingValueVisitor {
330 public:
331 using DescendingValueVisitor::Visit;
332
Visit(Reference * ref)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
ZeroOutAppReferences(ResourceTable * table)342 static void ZeroOutAppReferences(ResourceTable* table) {
343 ZeroingReferenceVisitor visitor;
344 VisitAllValuesInTable(table, &visitor);
345 }
346
Diff(const std::vector<StringPiece> & args)347 int 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