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