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
25 using android::StringPiece;
26
27 namespace aapt {
28
29 class DiffContext : public IAaptContext {
30 public:
DiffContext()31 DiffContext() : name_mangler_({}), symbol_table_(&name_mangler_) {
32 }
33
GetPackageType()34 PackageType GetPackageType() override {
35 // Doesn't matter.
36 return PackageType::kApp;
37 }
38
GetCompilationPackage()39 const std::string& GetCompilationPackage() override {
40 return empty_;
41 }
42
GetPackageId()43 uint8_t GetPackageId() override {
44 return 0x0;
45 }
46
GetDiagnostics()47 IDiagnostics* GetDiagnostics() override {
48 return &diagnostics_;
49 }
50
GetNameMangler()51 NameMangler* GetNameMangler() override {
52 return &name_mangler_;
53 }
54
GetExternalSymbols()55 SymbolTable* GetExternalSymbols() override {
56 return &symbol_table_;
57 }
58
IsVerbose()59 bool IsVerbose() override {
60 return false;
61 }
62
GetMinSdkVersion()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
EmitDiffLine(const Source & source,const StringPiece & message)74 static void EmitDiffLine(const Source& source, const StringPiece& message) {
75 std::cerr << source << ": " << message << "\n";
76 }
77
IsSymbolVisibilityDifferent(const Symbol & symbol_a,const Symbol & symbol_b)78 static bool IsSymbolVisibilityDifferent(const Symbol& symbol_a, const Symbol& symbol_b) {
79 return symbol_a.state != symbol_b.state;
80 }
81
82 template <typename Id>
IsIdDiff(const Symbol & symbol_a,const Maybe<Id> & id_a,const Symbol & symbol_b,const Maybe<Id> & id_b)83 static bool IsIdDiff(const Symbol& symbol_a, const Maybe<Id>& id_a, const Symbol& symbol_b,
84 const Maybe<Id>& id_b) {
85 if (symbol_a.state == SymbolState::kPublic || symbol_b.state == SymbolState::kPublic) {
86 return id_a != id_b;
87 }
88 return false;
89 }
90
EmitResourceConfigValueDiff(IAaptContext * context,LoadedApk * apk_a,ResourceTablePackage * pkg_a,ResourceTableType * type_a,ResourceEntry * entry_a,ResourceConfigValue * config_value_a,LoadedApk * apk_b,ResourceTablePackage * pkg_b,ResourceTableType * type_b,ResourceEntry * entry_b,ResourceConfigValue * config_value_b)91 static 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
EmitResourceEntryDiff(IAaptContext * context,LoadedApk * apk_a,ResourceTablePackage * pkg_a,ResourceTableType * type_a,ResourceEntry * entry_a,LoadedApk * apk_b,ResourceTablePackage * pkg_b,ResourceTableType * type_b,ResourceEntry * entry_b)112 static 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
EmitResourceTypeDiff(IAaptContext * context,LoadedApk * apk_a,ResourceTablePackage * pkg_a,ResourceTableType * type_a,LoadedApk * apk_b,ResourceTablePackage * pkg_b,ResourceTableType * type_b)147 static 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->symbol_status, entry_b->symbol_status)) {
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->symbol_status.state == SymbolState::kPublic) {
165 str_stream << "PUBLIC";
166 } else {
167 str_stream << "PRIVATE";
168 }
169 str_stream << " vs ";
170 if (entry_a->symbol_status.state == SymbolState::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->symbol_status, entry_a->id, entry_b->symbol_status,
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
EmitResourcePackageDiff(IAaptContext * context,LoadedApk * apk_a,ResourceTablePackage * pkg_a,LoadedApk * apk_b,ResourceTablePackage * pkg_b)216 static 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 (IsSymbolVisibilityDifferent(type_a->symbol_status, type_b->symbol_status)) {
229 std::stringstream str_stream;
230 str_stream << pkg_a->name << ":" << type_a->type << " has different visibility (";
231 if (type_b->symbol_status.state == SymbolState::kPublic) {
232 str_stream << "PUBLIC";
233 } else {
234 str_stream << "PRIVATE";
235 }
236 str_stream << " vs ";
237 if (type_a->symbol_status.state == SymbolState::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->symbol_status, type_a->id, type_b->symbol_status, type_b->id)) {
246 std::stringstream str_stream;
247 str_stream << pkg_a->name << ":" << type_a->type << " has different public ID (";
248 if (type_b->id) {
249 str_stream << "0x" << std::hex << type_b->id.value();
250 } else {
251 str_stream << "none";
252 }
253 str_stream << " vs ";
254 if (type_a->id) {
255 str_stream << "0x " << std::hex << type_a->id.value();
256 } else {
257 str_stream << "none";
258 }
259 str_stream << ")";
260 EmitDiffLine(apk_b->GetSource(), str_stream.str());
261 diff = true;
262 }
263 diff |= EmitResourceTypeDiff(context, apk_a, pkg_a, type_a.get(), apk_b, pkg_b, type_b);
264 }
265 }
266
267 // Check for any newly added types.
268 for (std::unique_ptr<ResourceTableType>& type_b : pkg_b->types) {
269 ResourceTableType* type_a = pkg_a->FindType(type_b->type);
270 if (!type_a) {
271 std::stringstream str_stream;
272 str_stream << "new type " << pkg_b->name << ":" << type_b->type;
273 EmitDiffLine(apk_b->GetSource(), str_stream.str());
274 diff = true;
275 }
276 }
277 return diff;
278 }
279
EmitResourceTableDiff(IAaptContext * context,LoadedApk * apk_a,LoadedApk * apk_b)280 static bool EmitResourceTableDiff(IAaptContext* context, LoadedApk* apk_a, LoadedApk* apk_b) {
281 ResourceTable* table_a = apk_a->GetResourceTable();
282 ResourceTable* table_b = apk_b->GetResourceTable();
283
284 bool diff = false;
285 for (std::unique_ptr<ResourceTablePackage>& pkg_a : table_a->packages) {
286 ResourceTablePackage* pkg_b = table_b->FindPackage(pkg_a->name);
287 if (!pkg_b) {
288 std::stringstream str_stream;
289 str_stream << "missing package " << pkg_a->name;
290 EmitDiffLine(apk_b->GetSource(), str_stream.str());
291 diff = true;
292 } else {
293 if (pkg_a->id != pkg_b->id) {
294 std::stringstream str_stream;
295 str_stream << "package '" << pkg_a->name << "' has different id (";
296 if (pkg_b->id) {
297 str_stream << "0x" << std::hex << pkg_b->id.value();
298 } else {
299 str_stream << "none";
300 }
301 str_stream << " vs ";
302 if (pkg_a->id) {
303 str_stream << "0x" << std::hex << pkg_a->id.value();
304 } else {
305 str_stream << "none";
306 }
307 str_stream << ")";
308 EmitDiffLine(apk_b->GetSource(), str_stream.str());
309 diff = true;
310 }
311 diff |= EmitResourcePackageDiff(context, apk_a, pkg_a.get(), apk_b, pkg_b);
312 }
313 }
314
315 // Check for any newly added packages.
316 for (std::unique_ptr<ResourceTablePackage>& pkg_b : table_b->packages) {
317 ResourceTablePackage* pkg_a = table_a->FindPackage(pkg_b->name);
318 if (!pkg_a) {
319 std::stringstream str_stream;
320 str_stream << "new package " << pkg_b->name;
321 EmitDiffLine(apk_b->GetSource(), str_stream.str());
322 diff = true;
323 }
324 }
325 return diff;
326 }
327
328 class ZeroingReferenceVisitor : public ValueVisitor {
329 public:
330 using ValueVisitor::Visit;
331
Visit(Reference * ref)332 void Visit(Reference* ref) override {
333 if (ref->name && ref->id) {
334 if (ref->id.value().package_id() == kAppPackageId) {
335 ref->id = {};
336 }
337 }
338 }
339 };
340
ZeroOutAppReferences(ResourceTable * table)341 static void ZeroOutAppReferences(ResourceTable* table) {
342 ZeroingReferenceVisitor visitor;
343 VisitAllValuesInTable(table, &visitor);
344 }
345
Diff(const std::vector<StringPiece> & args)346 int Diff(const std::vector<StringPiece>& args) {
347 DiffContext context;
348
349 Flags flags;
350 if (!flags.Parse("aapt2 diff", args, &std::cerr)) {
351 return 1;
352 }
353
354 if (flags.GetArgs().size() != 2u) {
355 std::cerr << "must have two apks as arguments.\n\n";
356 flags.Usage("aapt2 diff", &std::cerr);
357 return 1;
358 }
359
360 std::unique_ptr<LoadedApk> apk_a = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[0]);
361 std::unique_ptr<LoadedApk> apk_b = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[1]);
362 if (!apk_a || !apk_b) {
363 return 1;
364 }
365
366 // Zero out Application IDs in references.
367 ZeroOutAppReferences(apk_a->GetResourceTable());
368 ZeroOutAppReferences(apk_b->GetResourceTable());
369
370 if (EmitResourceTableDiff(&context, apk_a.get(), apk_b.get())) {
371 // We emitted a diff, so return 1 (failure).
372 return 1;
373 }
374 return 0;
375 }
376
377 } // namespace aapt
378