blob: 9b1f0572123dc1b16fbcb51592994343bd8a7a16 [file] [log] [blame]
Adam Lesinski458b8772016-04-25 14:20:21 -07001/*
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 "Flags.h"
18#include "ResourceTable.h"
Adam Lesinski5e8fa3a2016-06-27 16:21:42 -070019#include "ValueVisitor.h"
Adam Lesinski458b8772016-04-25 14:20:21 -070020#include "io/ZipArchive.h"
21#include "process/IResourceTableConsumer.h"
22#include "process/SymbolTable.h"
23#include "unflatten/BinaryResourceParser.h"
24
25#include <android-base/macros.h>
26
27namespace aapt {
28
29class DiffContext : public IAaptContext {
30public:
Adam Lesinskid0f116b2016-07-08 15:00:32 -070031 const std::string& getCompilationPackage() override {
Adam Lesinski458b8772016-04-25 14:20:21 -070032 return mEmpty;
33 }
34
35 uint8_t getPackageId() override {
36 return 0x0;
37 }
38
39 IDiagnostics* getDiagnostics() override {
40 return &mDiagnostics;
41 }
42
43 NameMangler* getNameMangler() override {
44 return &mNameMangler;
45 }
46
47 SymbolTable* getExternalSymbols() override {
48 return &mSymbolTable;
49 }
50
51 bool verbose() override {
52 return false;
53 }
54
Adam Lesinskifb6312f2016-06-28 14:40:32 -070055 int getMinSdkVersion() override {
56 return 0;
57 }
58
Adam Lesinski458b8772016-04-25 14:20:21 -070059private:
Adam Lesinskid0f116b2016-07-08 15:00:32 -070060 std::string mEmpty;
Adam Lesinski458b8772016-04-25 14:20:21 -070061 StdErrDiagnostics mDiagnostics;
62 NameMangler mNameMangler = NameMangler(NameManglerPolicy{});
63 SymbolTable mSymbolTable;
64};
65
66class LoadedApk {
67public:
68 LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk,
69 std::unique_ptr<ResourceTable> table) :
70 mSource(source), mApk(std::move(apk)), mTable(std::move(table)) {
71 }
72
73 io::IFileCollection* getFileCollection() {
74 return mApk.get();
75 }
76
77 ResourceTable* getResourceTable() {
78 return mTable.get();
79 }
80
81 const Source& getSource() {
82 return mSource;
83 }
84
85private:
86 Source mSource;
87 std::unique_ptr<io::IFileCollection> mApk;
88 std::unique_ptr<ResourceTable> mTable;
89
90 DISALLOW_COPY_AND_ASSIGN(LoadedApk);
91};
92
93static std::unique_ptr<LoadedApk> loadApkFromPath(IAaptContext* context, const StringPiece& path) {
94 Source source(path);
95 std::string error;
96 std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::create(path, &error);
97 if (!apk) {
98 context->getDiagnostics()->error(DiagMessage(source) << error);
99 return {};
100 }
101
102 io::IFile* file = apk->findFile("resources.arsc");
103 if (!file) {
104 context->getDiagnostics()->error(DiagMessage(source) << "no resources.arsc found");
105 return {};
106 }
107
108 std::unique_ptr<io::IData> data = file->openAsData();
109 if (!data) {
110 context->getDiagnostics()->error(DiagMessage(source) << "could not open resources.arsc");
111 return {};
112 }
113
114 std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
115 BinaryResourceParser parser(context, table.get(), source, data->data(), data->size());
116 if (!parser.parse()) {
117 return {};
118 }
119
120 return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table));
121}
122
123static void emitDiffLine(const Source& source, const StringPiece& message) {
124 std::cerr << source << ": " << message << "\n";
125}
126
127static bool isSymbolVisibilityDifferent(const Symbol& symbolA, const Symbol& symbolB) {
128 return symbolA.state != symbolB.state;
129}
130
131template <typename Id>
132static bool isIdDiff(const Symbol& symbolA, const Maybe<Id>& idA,
133 const Symbol& symbolB, const Maybe<Id>& idB) {
134 if (symbolA.state == SymbolState::kPublic || symbolB.state == SymbolState::kPublic) {
135 return idA != idB;
136 }
137 return false;
138}
139
140static bool emitResourceConfigValueDiff(IAaptContext* context,
141 LoadedApk* apkA,
142 ResourceTablePackage* pkgA,
143 ResourceTableType* typeA,
144 ResourceEntry* entryA,
145 ResourceConfigValue* configValueA,
146 LoadedApk* apkB,
147 ResourceTablePackage* pkgB,
148 ResourceTableType* typeB,
149 ResourceEntry* entryB,
150 ResourceConfigValue* configValueB) {
151 Value* valueA = configValueA->value.get();
152 Value* valueB = configValueB->value.get();
153 if (!valueA->equals(valueB)) {
154 std::stringstream strStream;
155 strStream << "value " << pkgA->name << ":" << typeA->type << "/" << entryA->name
156 << " config=" << configValueA->config << " does not match:\n";
157 valueA->print(&strStream);
158 strStream << "\n vs \n";
159 valueB->print(&strStream);
160 emitDiffLine(apkB->getSource(), strStream.str());
161 return true;
162 }
163 return false;
164}
165
166static bool emitResourceEntryDiff(IAaptContext* context,
167 LoadedApk* apkA,
168 ResourceTablePackage* pkgA,
169 ResourceTableType* typeA,
170 ResourceEntry* entryA,
171 LoadedApk* apkB,
172 ResourceTablePackage* pkgB,
173 ResourceTableType* typeB,
174 ResourceEntry* entryB) {
175 bool diff = false;
176 for (std::unique_ptr<ResourceConfigValue>& configValueA : entryA->values) {
177 ResourceConfigValue* configValueB = entryB->findValue(configValueA->config);
178 if (!configValueB) {
179 std::stringstream strStream;
180 strStream << "missing " << pkgA->name << ":" << typeA->type << "/" << entryA->name
181 << " config=" << configValueA->config;
182 emitDiffLine(apkB->getSource(), strStream.str());
183 diff = true;
184 } else {
185 diff |= emitResourceConfigValueDiff(context, apkA, pkgA, typeA, entryA,
186 configValueA.get(), apkB, pkgB, typeB, entryB,
187 configValueB);
188 }
189 }
190
191 // Check for any newly added config values.
192 for (std::unique_ptr<ResourceConfigValue>& configValueB : entryB->values) {
193 ResourceConfigValue* configValueA = entryA->findValue(configValueB->config);
194 if (!configValueA) {
195 std::stringstream strStream;
196 strStream << "new config " << pkgB->name << ":" << typeB->type << "/" << entryB->name
197 << " config=" << configValueB->config;
198 emitDiffLine(apkB->getSource(), strStream.str());
199 diff = true;
200 }
201 }
202 return false;
203}
204
205static bool emitResourceTypeDiff(IAaptContext* context,
206 LoadedApk* apkA,
207 ResourceTablePackage* pkgA,
208 ResourceTableType* typeA,
209 LoadedApk* apkB,
210 ResourceTablePackage* pkgB,
211 ResourceTableType* typeB) {
212 bool diff = false;
213 for (std::unique_ptr<ResourceEntry>& entryA : typeA->entries) {
214 ResourceEntry* entryB = typeB->findEntry(entryA->name);
215 if (!entryB) {
216 std::stringstream strStream;
217 strStream << "missing " << pkgA->name << ":" << typeA->type << "/" << entryA->name;
218 emitDiffLine(apkB->getSource(), strStream.str());
219 diff = true;
220 } else {
221 if (isSymbolVisibilityDifferent(entryA->symbolStatus, entryB->symbolStatus)) {
222 std::stringstream strStream;
223 strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name
224 << " has different visibility (";
225 if (entryB->symbolStatus.state == SymbolState::kPublic) {
226 strStream << "PUBLIC";
227 } else {
228 strStream << "PRIVATE";
229 }
230 strStream << " vs ";
231 if (entryA->symbolStatus.state == SymbolState::kPublic) {
232 strStream << "PUBLIC";
233 } else {
234 strStream << "PRIVATE";
235 }
236 strStream << ")";
237 emitDiffLine(apkB->getSource(), strStream.str());
238 diff = true;
239 } else if (isIdDiff(entryA->symbolStatus, entryA->id,
240 entryB->symbolStatus, entryB->id)) {
241 std::stringstream strStream;
242 strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name
243 << " has different public ID (";
244 if (entryB->id) {
245 strStream << "0x" << std::hex << entryB->id.value();
246 } else {
247 strStream << "none";
248 }
249 strStream << " vs ";
250 if (entryA->id) {
251 strStream << "0x " << std::hex << entryA->id.value();
252 } else {
253 strStream << "none";
254 }
255 strStream << ")";
256 emitDiffLine(apkB->getSource(), strStream.str());
257 diff = true;
258 }
259 diff |= emitResourceEntryDiff(context, apkA, pkgA, typeA, entryA.get(),
260 apkB, pkgB, typeB, entryB);
261 }
262 }
263
264 // Check for any newly added entries.
265 for (std::unique_ptr<ResourceEntry>& entryB : typeB->entries) {
266 ResourceEntry* entryA = typeA->findEntry(entryB->name);
267 if (!entryA) {
268 std::stringstream strStream;
269 strStream << "new entry " << pkgB->name << ":" << typeB->type << "/" << entryB->name;
270 emitDiffLine(apkB->getSource(), strStream.str());
271 diff = true;
272 }
273 }
274 return diff;
275}
276
277static bool emitResourcePackageDiff(IAaptContext* context, LoadedApk* apkA,
278 ResourceTablePackage* pkgA,
279 LoadedApk* apkB, ResourceTablePackage* pkgB) {
280 bool diff = false;
281 for (std::unique_ptr<ResourceTableType>& typeA : pkgA->types) {
282 ResourceTableType* typeB = pkgB->findType(typeA->type);
283 if (!typeB) {
284 std::stringstream strStream;
285 strStream << "missing " << pkgA->name << ":" << typeA->type;
286 emitDiffLine(apkA->getSource(), strStream.str());
287 diff = true;
288 } else {
289 if (isSymbolVisibilityDifferent(typeA->symbolStatus, typeB->symbolStatus)) {
290 std::stringstream strStream;
291 strStream << pkgA->name << ":" << typeA->type << " has different visibility (";
292 if (typeB->symbolStatus.state == SymbolState::kPublic) {
293 strStream << "PUBLIC";
294 } else {
295 strStream << "PRIVATE";
296 }
297 strStream << " vs ";
298 if (typeA->symbolStatus.state == SymbolState::kPublic) {
299 strStream << "PUBLIC";
300 } else {
301 strStream << "PRIVATE";
302 }
303 strStream << ")";
304 emitDiffLine(apkB->getSource(), strStream.str());
305 diff = true;
306 } else if (isIdDiff(typeA->symbolStatus, typeA->id, typeB->symbolStatus, typeB->id)) {
307 std::stringstream strStream;
308 strStream << pkgA->name << ":" << typeA->type << " has different public ID (";
309 if (typeB->id) {
310 strStream << "0x" << std::hex << typeB->id.value();
311 } else {
312 strStream << "none";
313 }
314 strStream << " vs ";
315 if (typeA->id) {
316 strStream << "0x " << std::hex << typeA->id.value();
317 } else {
318 strStream << "none";
319 }
320 strStream << ")";
321 emitDiffLine(apkB->getSource(), strStream.str());
322 diff = true;
323 }
324 diff |= emitResourceTypeDiff(context, apkA, pkgA, typeA.get(), apkB, pkgB, typeB);
325 }
326 }
327
328 // Check for any newly added types.
329 for (std::unique_ptr<ResourceTableType>& typeB : pkgB->types) {
330 ResourceTableType* typeA = pkgA->findType(typeB->type);
331 if (!typeA) {
332 std::stringstream strStream;
333 strStream << "new type " << pkgB->name << ":" << typeB->type;
334 emitDiffLine(apkB->getSource(), strStream.str());
335 diff = true;
336 }
337 }
338 return diff;
339}
340
341static bool emitResourceTableDiff(IAaptContext* context, LoadedApk* apkA, LoadedApk* apkB) {
342 ResourceTable* tableA = apkA->getResourceTable();
343 ResourceTable* tableB = apkB->getResourceTable();
344
345 bool diff = false;
346 for (std::unique_ptr<ResourceTablePackage>& pkgA : tableA->packages) {
347 ResourceTablePackage* pkgB = tableB->findPackage(pkgA->name);
348 if (!pkgB) {
349 std::stringstream strStream;
350 strStream << "missing package " << pkgA->name;
351 emitDiffLine(apkB->getSource(), strStream.str());
352 diff = true;
353 } else {
354 if (pkgA->id != pkgB->id) {
355 std::stringstream strStream;
356 strStream << "package '" << pkgA->name << "' has different id (";
357 if (pkgB->id) {
358 strStream << "0x" << std::hex << pkgB->id.value();
359 } else {
360 strStream << "none";
361 }
362 strStream << " vs ";
363 if (pkgA->id) {
364 strStream << "0x" << std::hex << pkgA->id.value();
365 } else {
366 strStream << "none";
367 }
368 strStream << ")";
369 emitDiffLine(apkB->getSource(), strStream.str());
370 diff = true;
371 }
372 diff |= emitResourcePackageDiff(context, apkA, pkgA.get(), apkB, pkgB);
373 }
374 }
375
376 // Check for any newly added packages.
377 for (std::unique_ptr<ResourceTablePackage>& pkgB : tableB->packages) {
378 ResourceTablePackage* pkgA = tableA->findPackage(pkgB->name);
379 if (!pkgA) {
380 std::stringstream strStream;
381 strStream << "new package " << pkgB->name;
382 emitDiffLine(apkB->getSource(), strStream.str());
383 diff = true;
384 }
385 }
386 return diff;
387}
388
Adam Lesinski5e8fa3a2016-06-27 16:21:42 -0700389class ZeroingReferenceVisitor : public ValueVisitor {
390public:
391 using ValueVisitor::visit;
392
393 void visit(Reference* ref) override {
394 if (ref->name && ref->id) {
395 if (ref->id.value().packageId() == 0x7f) {
396 ref->id = {};
397 }
398 }
399 }
400};
401
402static void zeroOutAppReferences(ResourceTable* table) {
403 ZeroingReferenceVisitor visitor;
404 visitAllValuesInTable(table, &visitor);
405}
406
Adam Lesinski458b8772016-04-25 14:20:21 -0700407int diff(const std::vector<StringPiece>& args) {
408 DiffContext context;
409
410 Flags flags;
411 if (!flags.parse("aapt2 diff", args, &std::cerr)) {
412 return 1;
413 }
414
415 if (flags.getArgs().size() != 2u) {
416 std::cerr << "must have two apks as arguments.\n\n";
417 flags.usage("aapt2 diff", &std::cerr);
418 return 1;
419 }
420
421 std::unique_ptr<LoadedApk> apkA = loadApkFromPath(&context, flags.getArgs()[0]);
422 std::unique_ptr<LoadedApk> apkB = loadApkFromPath(&context, flags.getArgs()[1]);
423 if (!apkA || !apkB) {
424 return 1;
425 }
426
Adam Lesinski5e8fa3a2016-06-27 16:21:42 -0700427 // Zero out Application IDs in references.
428 zeroOutAppReferences(apkA->getResourceTable());
429 zeroOutAppReferences(apkB->getResourceTable());
430
Adam Lesinski458b8772016-04-25 14:20:21 -0700431 if (emitResourceTableDiff(&context, apkA.get(), apkB.get())) {
432 // We emitted a diff, so return 1 (failure).
433 return 1;
434 }
435 return 0;
436}
437
438} // namespace aapt