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