Home | History | Annotate | Download | only in diff
      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:
     30     const std::u16string& getCompilationPackage() override {
     31         return mEmpty;
     32     }
     33 
     34     uint8_t getPackageId() override {
     35         return 0x0;
     36     }
     37 
     38     IDiagnostics* getDiagnostics() override {
     39         return &mDiagnostics;
     40     }
     41 
     42     NameMangler* getNameMangler() override {
     43         return &mNameMangler;
     44     }
     45 
     46     SymbolTable* getExternalSymbols() override {
     47         return &mSymbolTable;
     48     }
     49 
     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:
     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 
     68     io::IFileCollection* getFileCollection() {
     69         return mApk.get();
     70     }
     71 
     72     ResourceTable* getResourceTable() {
     73         return mTable.get();
     74     }
     75 
     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 
     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 
    118 static void emitDiffLine(const Source& source, const StringPiece& message) {
    119     std::cerr << source << ": " << message << "\n";
    120 }
    121 
    122 static bool isSymbolVisibilityDifferent(const Symbol& symbolA, const Symbol& symbolB) {
    123     return symbolA.state != symbolB.state;
    124 }
    125 
    126 template <typename Id>
    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 
    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 
    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 
    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 
    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 
    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 
    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