• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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 "link/TableMerger.h"
18 
19 #include "android-base/logging.h"
20 
21 #include "ResourceTable.h"
22 #include "ResourceUtils.h"
23 #include "ResourceValues.h"
24 #include "ValueVisitor.h"
25 #include "util/Util.h"
26 
27 using android::StringPiece;
28 
29 namespace aapt {
30 
TableMerger(IAaptContext * context,ResourceTable * out_table,const TableMergerOptions & options)31 TableMerger::TableMerger(IAaptContext* context, ResourceTable* out_table,
32                          const TableMergerOptions& options)
33     : context_(context), master_table_(out_table), options_(options) {
34   // Create the desired package that all tables will be merged into.
35   master_package_ = master_table_->CreatePackage(
36       context_->GetCompilationPackage(), context_->GetPackageId());
37   CHECK(master_package_ != nullptr) << "package name or ID already taken";
38 }
39 
Merge(const Source & src,ResourceTable * table,io::IFileCollection * collection)40 bool TableMerger::Merge(const Source& src, ResourceTable* table,
41                         io::IFileCollection* collection) {
42   return MergeImpl(src, table, collection, false /* overlay */, true /* allow new */);
43 }
44 
MergeOverlay(const Source & src,ResourceTable * table,io::IFileCollection * collection)45 bool TableMerger::MergeOverlay(const Source& src, ResourceTable* table,
46                                io::IFileCollection* collection) {
47   return MergeImpl(src, table, collection, true /* overlay */, options_.auto_add_overlay);
48 }
49 
50 /**
51  * This will merge packages with the same package name (or no package name).
52  */
MergeImpl(const Source & src,ResourceTable * table,io::IFileCollection * collection,bool overlay,bool allow_new)53 bool TableMerger::MergeImpl(const Source& src, ResourceTable* table,
54                             io::IFileCollection* collection, bool overlay,
55                             bool allow_new) {
56   bool error = false;
57   for (auto& package : table->packages) {
58     // Only merge an empty package or the package we're building.
59     // Other packages may exist, which likely contain attribute definitions.
60     // This is because at compile time it is unknown if the attributes are
61     // simply uses of the attribute or definitions.
62     if (package->name.empty() || context_->GetCompilationPackage() == package->name) {
63       FileMergeCallback callback;
64       if (collection) {
65         callback = [&](const ResourceNameRef& name,
66                        const ConfigDescription& config, FileReference* new_file,
67                        FileReference* old_file) -> bool {
68           // The old file's path points inside the APK, so we can use it as is.
69           io::IFile* f = collection->FindFile(*old_file->path);
70           if (!f) {
71             context_->GetDiagnostics()->Error(DiagMessage(src)
72                                               << "file '" << *old_file->path << "' not found");
73             return false;
74           }
75 
76           new_file->file = f;
77           return true;
78         };
79       }
80 
81       // Merge here. Once the entries are merged and mangled, any references to
82       // them are still valid. This is because un-mangled references are
83       // mangled, then looked up at resolution time.
84       // Also, when linking, we convert references with no package name to use
85       // the compilation package name.
86       error |= !DoMerge(src, table, package.get(), false /* mangle */, overlay,
87                         allow_new, callback);
88     }
89   }
90   return !error;
91 }
92 
93 /**
94  * This will merge and mangle resources from a static library.
95  */
MergeAndMangle(const Source & src,const StringPiece & package_name,ResourceTable * table,io::IFileCollection * collection)96 bool TableMerger::MergeAndMangle(const Source& src,
97                                  const StringPiece& package_name,
98                                  ResourceTable* table,
99                                  io::IFileCollection* collection) {
100   bool error = false;
101   for (auto& package : table->packages) {
102     // Warn of packages with an unrelated ID.
103     if (package_name != package->name) {
104       context_->GetDiagnostics()->Warn(DiagMessage(src) << "ignoring package "
105                                                         << package->name);
106       continue;
107     }
108 
109     bool mangle = package_name != context_->GetCompilationPackage();
110     merged_packages_.insert(package->name);
111 
112     auto callback = [&](
113         const ResourceNameRef& name, const ConfigDescription& config,
114         FileReference* new_file, FileReference* old_file) -> bool {
115       // The old file's path points inside the APK, so we can use it as is.
116       io::IFile* f = collection->FindFile(*old_file->path);
117       if (!f) {
118         context_->GetDiagnostics()->Error(
119             DiagMessage(src) << "file '" << *old_file->path << "' not found");
120         return false;
121       }
122 
123       new_file->file = f;
124       return true;
125     };
126 
127     error |= !DoMerge(src, table, package.get(), mangle, false /* overlay */,
128                       true /* allow new */, callback);
129   }
130   return !error;
131 }
132 
MergeType(IAaptContext * context,const Source & src,ResourceTableType * dst_type,ResourceTableType * src_type)133 static bool MergeType(IAaptContext* context, const Source& src,
134                       ResourceTableType* dst_type,
135                       ResourceTableType* src_type) {
136   if (dst_type->symbol_status.state < src_type->symbol_status.state) {
137     // The incoming type's visibility is stronger, so we should override
138     // the visibility.
139     if (src_type->symbol_status.state == SymbolState::kPublic) {
140       // Only copy the ID if the source is public, or else the ID is
141       // meaningless.
142       dst_type->id = src_type->id;
143     }
144     dst_type->symbol_status = std::move(src_type->symbol_status);
145   } else if (dst_type->symbol_status.state == SymbolState::kPublic &&
146              src_type->symbol_status.state == SymbolState::kPublic &&
147              dst_type->id && src_type->id &&
148              dst_type->id.value() != src_type->id.value()) {
149     // Both types are public and have different IDs.
150     context->GetDiagnostics()->Error(DiagMessage(src)
151                                      << "cannot merge type '" << src_type->type
152                                      << "': conflicting public IDs");
153     return false;
154   }
155   return true;
156 }
157 
MergeEntry(IAaptContext * context,const Source & src,ResourceEntry * dst_entry,ResourceEntry * src_entry)158 static bool MergeEntry(IAaptContext* context, const Source& src,
159                        ResourceEntry* dst_entry, ResourceEntry* src_entry) {
160   if (dst_entry->symbol_status.state < src_entry->symbol_status.state) {
161     // The incoming type's visibility is stronger, so we should override
162     // the visibility.
163     if (src_entry->symbol_status.state == SymbolState::kPublic) {
164       // Only copy the ID if the source is public, or else the ID is
165       // meaningless.
166       dst_entry->id = src_entry->id;
167     }
168     dst_entry->symbol_status = std::move(src_entry->symbol_status);
169   } else if (src_entry->symbol_status.state == SymbolState::kPublic &&
170              dst_entry->symbol_status.state == SymbolState::kPublic &&
171              dst_entry->id && src_entry->id &&
172              dst_entry->id.value() != src_entry->id.value()) {
173     // Both entries are public and have different IDs.
174     context->GetDiagnostics()->Error(
175         DiagMessage(src) << "cannot merge entry '" << src_entry->name
176                          << "': conflicting public IDs");
177     return false;
178   }
179   return true;
180 }
181 
182 // Modified CollisionResolver which will merge Styleables and Styles. Used with overlays.
183 //
184 // Styleables are not actual resources, but they are treated as such during the
185 // compilation phase.
186 //
187 // Styleables and Styles don't simply overlay each other, their definitions merge
188 // and accumulate. If both values are Styleables/Styles, we just merge them into the
189 // existing value.
ResolveMergeCollision(Value * existing,Value * incoming,StringPool * pool)190 static ResourceTable::CollisionResult ResolveMergeCollision(Value* existing, Value* incoming,
191                                                             StringPool* pool) {
192   if (Styleable* existing_styleable = ValueCast<Styleable>(existing)) {
193     if (Styleable* incoming_styleable = ValueCast<Styleable>(incoming)) {
194       // Styleables get merged.
195       existing_styleable->MergeWith(incoming_styleable);
196       return ResourceTable::CollisionResult::kKeepOriginal;
197     }
198   } else if (Style* existing_style = ValueCast<Style>(existing)) {
199     if (Style* incoming_style = ValueCast<Style>(incoming)) {
200       // Styles get merged.
201       existing_style->MergeWith(incoming_style, pool);
202       return ResourceTable::CollisionResult::kKeepOriginal;
203     }
204   }
205   // Delegate to the default handler.
206   return ResourceTable::ResolveValueCollision(existing, incoming);
207 }
208 
MergeConfigValue(IAaptContext * context,const ResourceNameRef & res_name,const bool overlay,ResourceConfigValue * dst_config_value,ResourceConfigValue * src_config_value,StringPool * pool)209 static ResourceTable::CollisionResult MergeConfigValue(IAaptContext* context,
210                                                        const ResourceNameRef& res_name,
211                                                        const bool overlay,
212                                                        ResourceConfigValue* dst_config_value,
213                                                        ResourceConfigValue* src_config_value,
214                                                        StringPool* pool) {
215   using CollisionResult = ResourceTable::CollisionResult;
216 
217   Value* dst_value = dst_config_value->value.get();
218   Value* src_value = src_config_value->value.get();
219 
220   CollisionResult collision_result;
221   if (overlay) {
222     collision_result = ResolveMergeCollision(dst_value, src_value, pool);
223   } else {
224     collision_result = ResourceTable::ResolveValueCollision(dst_value, src_value);
225   }
226 
227   if (collision_result == CollisionResult::kConflict) {
228     if (overlay) {
229       return CollisionResult::kTakeNew;
230     }
231 
232     // Error!
233     context->GetDiagnostics()->Error(DiagMessage(src_value->GetSource())
234                                      << "resource '" << res_name << "' has a conflicting value for "
235                                      << "configuration (" << src_config_value->config << ")");
236     context->GetDiagnostics()->Note(DiagMessage(dst_value->GetSource())
237                                     << "originally defined here");
238     return CollisionResult::kConflict;
239   }
240   return collision_result;
241 }
242 
DoMerge(const Source & src,ResourceTable * src_table,ResourceTablePackage * src_package,const bool mangle_package,const bool overlay,const bool allow_new_resources,const FileMergeCallback & callback)243 bool TableMerger::DoMerge(const Source& src, ResourceTable* src_table,
244                           ResourceTablePackage* src_package,
245                           const bool mangle_package, const bool overlay,
246                           const bool allow_new_resources,
247                           const FileMergeCallback& callback) {
248   bool error = false;
249 
250   for (auto& src_type : src_package->types) {
251     ResourceTableType* dst_type = master_package_->FindOrCreateType(src_type->type);
252     if (!MergeType(context_, src, dst_type, src_type.get())) {
253       error = true;
254       continue;
255     }
256 
257     for (auto& src_entry : src_type->entries) {
258       std::string entry_name = src_entry->name;
259       if (mangle_package) {
260         entry_name = NameMangler::MangleEntry(src_package->name, src_entry->name);
261       }
262 
263       ResourceEntry* dst_entry;
264       if (allow_new_resources || src_entry->symbol_status.allow_new) {
265         dst_entry = dst_type->FindOrCreateEntry(entry_name);
266       } else {
267         dst_entry = dst_type->FindEntry(entry_name);
268       }
269 
270       const ResourceNameRef res_name(src_package->name, src_type->type, src_entry->name);
271 
272       if (!dst_entry) {
273         context_->GetDiagnostics()->Error(DiagMessage(src)
274                                           << "resource " << res_name
275                                           << " does not override an existing resource");
276         context_->GetDiagnostics()->Note(DiagMessage(src) << "define an <add-resource> tag or use "
277                                                           << "--auto-add-overlay");
278         error = true;
279         continue;
280       }
281 
282       if (!MergeEntry(context_, src, dst_entry, src_entry.get())) {
283         error = true;
284         continue;
285       }
286 
287       for (auto& src_config_value : src_entry->values) {
288         using CollisionResult = ResourceTable::CollisionResult;
289 
290         ResourceConfigValue* dst_config_value = dst_entry->FindValue(
291             src_config_value->config, src_config_value->product);
292         if (dst_config_value) {
293           CollisionResult collision_result =
294               MergeConfigValue(context_, res_name, overlay, dst_config_value,
295                                src_config_value.get(), &master_table_->string_pool);
296           if (collision_result == CollisionResult::kConflict) {
297             error = true;
298             continue;
299           } else if (collision_result == CollisionResult::kKeepOriginal) {
300             continue;
301           }
302         } else {
303           dst_config_value =
304               dst_entry->FindOrCreateValue(src_config_value->config, src_config_value->product);
305         }
306 
307         // Continue if we're taking the new resource.
308 
309         if (FileReference* f = ValueCast<FileReference>(src_config_value->value.get())) {
310           std::unique_ptr<FileReference> new_file_ref;
311           if (mangle_package) {
312             new_file_ref = CloneAndMangleFile(src_package->name, *f);
313           } else {
314             new_file_ref = std::unique_ptr<FileReference>(f->Clone(&master_table_->string_pool));
315           }
316 
317           if (callback) {
318             if (!callback(res_name, src_config_value->config, new_file_ref.get(), f)) {
319               error = true;
320               continue;
321             }
322           }
323           dst_config_value->value = std::move(new_file_ref);
324 
325         } else {
326           dst_config_value->value = std::unique_ptr<Value>(
327               src_config_value->value->Clone(&master_table_->string_pool));
328         }
329       }
330     }
331   }
332   return !error;
333 }
334 
CloneAndMangleFile(const std::string & package,const FileReference & file_ref)335 std::unique_ptr<FileReference> TableMerger::CloneAndMangleFile(
336     const std::string& package, const FileReference& file_ref) {
337   StringPiece prefix, entry, suffix;
338   if (util::ExtractResFilePathParts(*file_ref.path, &prefix, &entry, &suffix)) {
339     std::string mangled_entry = NameMangler::MangleEntry(package, entry.to_string());
340     std::string newPath = prefix.to_string() + mangled_entry + suffix.to_string();
341     std::unique_ptr<FileReference> new_file_ref =
342         util::make_unique<FileReference>(master_table_->string_pool.MakeRef(newPath));
343     new_file_ref->SetComment(file_ref.GetComment());
344     new_file_ref->SetSource(file_ref.GetSource());
345     return new_file_ref;
346   }
347   return std::unique_ptr<FileReference>(file_ref.Clone(&master_table_->string_pool));
348 }
349 
MergeFileImpl(const ResourceFile & file_desc,io::IFile * file,bool overlay)350 bool TableMerger::MergeFileImpl(const ResourceFile& file_desc, io::IFile* file, bool overlay) {
351   ResourceTable table;
352   std::string path = ResourceUtils::BuildResourceFileName(file_desc);
353   std::unique_ptr<FileReference> file_ref =
354       util::make_unique<FileReference>(table.string_pool.MakeRef(path));
355   file_ref->SetSource(file_desc.source);
356   file_ref->file = file;
357 
358   ResourceTablePackage* pkg = table.CreatePackage(file_desc.name.package, 0x0);
359   pkg->FindOrCreateType(file_desc.name.type)
360       ->FindOrCreateEntry(file_desc.name.entry)
361       ->FindOrCreateValue(file_desc.config, {})
362       ->value = std::move(file_ref);
363 
364   return DoMerge(file->GetSource(), &table, pkg, false /* mangle */,
365                  overlay /* overlay */, true /* allow_new */, {});
366 }
367 
MergeFile(const ResourceFile & file_desc,io::IFile * file)368 bool TableMerger::MergeFile(const ResourceFile& file_desc, io::IFile* file) {
369   return MergeFileImpl(file_desc, file, false /* overlay */);
370 }
371 
MergeFileOverlay(const ResourceFile & file_desc,io::IFile * file)372 bool TableMerger::MergeFileOverlay(const ResourceFile& file_desc,
373                                    io::IFile* file) {
374   return MergeFileImpl(file_desc, file, true /* overlay */);
375 }
376 
377 }  // namespace aapt
378