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