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