• 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_ =
36       master_table_->CreatePackage(context_->GetCompilationPackage(), context_->GetPackageId());
37   CHECK(master_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   // We allow adding new resources if this is not an overlay, or if the options allow overlays
42   // to add new resources.
43   return MergeImpl(src, table, overlay, options_.auto_add_overlay || !overlay /*allow_new*/);
44 }
45 
46 // This will merge packages with the same package name (or no package name).
MergeImpl(const Source & src,ResourceTable * table,bool overlay,bool allow_new)47 bool TableMerger::MergeImpl(const Source& src, ResourceTable* table, bool overlay, bool allow_new) {
48   bool error = false;
49   for (auto& package : table->packages) {
50     // Only merge an empty package or the package we're building.
51     // Other packages may exist, which likely contain attribute definitions.
52     // This is because at compile time it is unknown if the attributes are
53     // simply uses of the attribute or definitions.
54     if (package->name.empty() || context_->GetCompilationPackage() == package->name) {
55       // Merge here. Once the entries are merged and mangled, any references to them are still
56       // valid. This is because un-mangled references are mangled, then looked up at resolution
57       // time. Also, when linking, we convert references with no package name to use the compilation
58       // package name.
59       error |= !DoMerge(src, table, package.get(), false /*mangle*/, overlay, allow_new);
60     }
61   }
62   return !error;
63 }
64 
65 // This will merge and mangle resources from a static library. It is assumed that all FileReferences
66 // have correctly set their io::IFile*.
MergeAndMangle(const Source & src,const StringPiece & package_name,ResourceTable * table)67 bool TableMerger::MergeAndMangle(const Source& src, const StringPiece& package_name,
68                                  ResourceTable* table) {
69   bool error = false;
70   for (auto& package : table->packages) {
71     // Warn of packages with an unrelated ID.
72     if (package_name != package->name) {
73       context_->GetDiagnostics()->Warn(DiagMessage(src) << "ignoring package " << package->name);
74       continue;
75     }
76 
77     bool mangle = package_name != context_->GetCompilationPackage();
78     merged_packages_.insert(package->name);
79     error |= !DoMerge(src, table, package.get(), mangle, false /*overlay*/, true /*allow_new*/);
80   }
81   return !error;
82 }
83 
MergeType(IAaptContext * context,const Source & src,ResourceTableType * dst_type,ResourceTableType * src_type)84 static bool MergeType(IAaptContext* context, const Source& src, ResourceTableType* dst_type,
85                       ResourceTableType* src_type) {
86   if (src_type->visibility_level > dst_type->visibility_level) {
87     // The incoming type's visibility is stronger, so we should override the visibility.
88     if (src_type->visibility_level == Visibility::Level::kPublic) {
89       // Only copy the ID if the source is public, or else the ID is meaningless.
90       dst_type->id = src_type->id;
91     }
92     dst_type->visibility_level = src_type->visibility_level;
93   } else if (dst_type->visibility_level == Visibility::Level::kPublic &&
94              src_type->visibility_level == Visibility::Level::kPublic && dst_type->id &&
95              src_type->id && dst_type->id.value() != src_type->id.value()) {
96     // Both types are public and have different IDs.
97     context->GetDiagnostics()->Error(DiagMessage(src) << "cannot merge type '" << src_type->type
98                                                       << "': conflicting public IDs");
99     return false;
100   }
101   return true;
102 }
103 
MergeEntry(IAaptContext * context,const Source & src,bool overlay,ResourceEntry * dst_entry,ResourceEntry * src_entry)104 static bool MergeEntry(IAaptContext* context, const Source& src, bool overlay,
105                        ResourceEntry* dst_entry, ResourceEntry* src_entry) {
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) {
128     if (dst_entry->overlayable && !overlay) {
129       context->GetDiagnostics()->Error(DiagMessage(src_entry->overlayable.value().source)
130                                        << "duplicate overlayable declaration for resource '"
131                                        << src_entry->name << "'");
132       context->GetDiagnostics()->Error(DiagMessage(dst_entry->overlayable.value().source)
133                                        << "previous declaration here");
134       return false;
135     }
136     dst_entry->overlayable = std::move(src_entry->overlayable);
137   }
138   return true;
139 }
140 
141 // Modified CollisionResolver which will merge Styleables and Styles. Used with overlays.
142 //
143 // Styleables are not actual resources, but they are treated as such during the compilation phase.
144 //
145 // Styleables and Styles don't simply overlay each other, their definitions merge and accumulate.
146 // If both values are Styleables/Styles, we just merge them into the existing value.
ResolveMergeCollision(Value * existing,Value * incoming,StringPool * pool)147 static ResourceTable::CollisionResult ResolveMergeCollision(Value* existing, Value* incoming,
148                                                             StringPool* pool) {
149   if (Styleable* existing_styleable = ValueCast<Styleable>(existing)) {
150     if (Styleable* incoming_styleable = ValueCast<Styleable>(incoming)) {
151       // Styleables get merged.
152       existing_styleable->MergeWith(incoming_styleable);
153       return ResourceTable::CollisionResult::kKeepOriginal;
154     }
155   } else if (Style* existing_style = ValueCast<Style>(existing)) {
156     if (Style* incoming_style = ValueCast<Style>(incoming)) {
157       // Styles get merged.
158       existing_style->MergeWith(incoming_style, pool);
159       return ResourceTable::CollisionResult::kKeepOriginal;
160     }
161   }
162   // Delegate to the default handler.
163   return ResourceTable::ResolveValueCollision(existing, incoming);
164 }
165 
MergeConfigValue(IAaptContext * context,const ResourceNameRef & res_name,bool overlay,ResourceConfigValue * dst_config_value,ResourceConfigValue * src_config_value,StringPool * pool)166 static ResourceTable::CollisionResult MergeConfigValue(IAaptContext* context,
167                                                        const ResourceNameRef& res_name,
168                                                        bool overlay,
169                                                        ResourceConfigValue* dst_config_value,
170                                                        ResourceConfigValue* src_config_value,
171                                                        StringPool* pool) {
172   using CollisionResult = ResourceTable::CollisionResult;
173 
174   Value* dst_value = dst_config_value->value.get();
175   Value* src_value = src_config_value->value.get();
176 
177   CollisionResult collision_result;
178   if (overlay) {
179     collision_result = ResolveMergeCollision(dst_value, src_value, pool);
180   } else {
181     collision_result = ResourceTable::ResolveValueCollision(dst_value, src_value);
182   }
183 
184   if (collision_result == CollisionResult::kConflict) {
185     if (overlay) {
186       return CollisionResult::kTakeNew;
187     }
188 
189     // Error!
190     context->GetDiagnostics()->Error(DiagMessage(src_value->GetSource())
191                                      << "resource '" << res_name << "' has a conflicting value for "
192                                      << "configuration (" << src_config_value->config << ")");
193     context->GetDiagnostics()->Note(DiagMessage(dst_value->GetSource())
194                                     << "originally defined here");
195     return CollisionResult::kConflict;
196   }
197   return collision_result;
198 }
199 
DoMerge(const Source & src,ResourceTable * src_table,ResourceTablePackage * src_package,bool mangle_package,bool overlay,bool allow_new_resources)200 bool TableMerger::DoMerge(const Source& src, ResourceTable* src_table,
201                           ResourceTablePackage* src_package, bool mangle_package, bool overlay,
202                           bool allow_new_resources) {
203   bool error = false;
204 
205   for (auto& src_type : src_package->types) {
206     ResourceTableType* dst_type = master_package_->FindOrCreateType(src_type->type);
207     if (!MergeType(context_, src, dst_type, src_type.get())) {
208       error = true;
209       continue;
210     }
211 
212     for (auto& src_entry : src_type->entries) {
213       std::string entry_name = src_entry->name;
214       if (mangle_package) {
215         entry_name = NameMangler::MangleEntry(src_package->name, src_entry->name);
216       }
217 
218       ResourceEntry* dst_entry;
219       if (allow_new_resources || src_entry->allow_new) {
220         dst_entry = dst_type->FindOrCreateEntry(entry_name);
221       } else {
222         dst_entry = dst_type->FindEntry(entry_name);
223       }
224 
225       const ResourceNameRef res_name(src_package->name, src_type->type, src_entry->name);
226 
227       if (!dst_entry) {
228         context_->GetDiagnostics()->Error(DiagMessage(src)
229                                           << "resource " << res_name
230                                           << " does not override an existing resource");
231         context_->GetDiagnostics()->Note(DiagMessage(src) << "define an <add-resource> tag or use "
232                                                           << "--auto-add-overlay");
233         error = true;
234         continue;
235       }
236 
237       if (!MergeEntry(context_, src, overlay, dst_entry, src_entry.get())) {
238         error = true;
239         continue;
240       }
241 
242       for (auto& src_config_value : src_entry->values) {
243         using CollisionResult = ResourceTable::CollisionResult;
244 
245         ResourceConfigValue* dst_config_value = dst_entry->FindValue(
246             src_config_value->config, src_config_value->product);
247         if (dst_config_value) {
248           CollisionResult collision_result =
249               MergeConfigValue(context_, res_name, overlay, dst_config_value,
250                                src_config_value.get(), &master_table_->string_pool);
251           if (collision_result == CollisionResult::kConflict) {
252             error = true;
253             continue;
254           } else if (collision_result == CollisionResult::kKeepOriginal) {
255             continue;
256           }
257         } else {
258           dst_config_value =
259               dst_entry->FindOrCreateValue(src_config_value->config, src_config_value->product);
260         }
261 
262         // Continue if we're taking the new resource.
263 
264         if (FileReference* f = ValueCast<FileReference>(src_config_value->value.get())) {
265           std::unique_ptr<FileReference> new_file_ref;
266           if (mangle_package) {
267             new_file_ref = CloneAndMangleFile(src_package->name, *f);
268           } else {
269             new_file_ref = std::unique_ptr<FileReference>(f->Clone(&master_table_->string_pool));
270           }
271           dst_config_value->value = std::move(new_file_ref);
272 
273         } else {
274           dst_config_value->value = std::unique_ptr<Value>(
275               src_config_value->value->Clone(&master_table_->string_pool));
276         }
277       }
278     }
279   }
280   return !error;
281 }
282 
CloneAndMangleFile(const std::string & package,const FileReference & file_ref)283 std::unique_ptr<FileReference> TableMerger::CloneAndMangleFile(
284     const std::string& package, const FileReference& file_ref) {
285   StringPiece prefix, entry, suffix;
286   if (util::ExtractResFilePathParts(*file_ref.path, &prefix, &entry, &suffix)) {
287     std::string mangled_entry = NameMangler::MangleEntry(package, entry.to_string());
288     std::string newPath = prefix.to_string() + mangled_entry + suffix.to_string();
289     std::unique_ptr<FileReference> new_file_ref =
290         util::make_unique<FileReference>(master_table_->string_pool.MakeRef(newPath));
291     new_file_ref->SetComment(file_ref.GetComment());
292     new_file_ref->SetSource(file_ref.GetSource());
293     new_file_ref->type = file_ref.type;
294     new_file_ref->file = file_ref.file;
295     return new_file_ref;
296   }
297   return std::unique_ptr<FileReference>(file_ref.Clone(&master_table_->string_pool));
298 }
299 
MergeFile(const ResourceFile & file_desc,bool overlay,io::IFile * file)300 bool TableMerger::MergeFile(const ResourceFile& file_desc, bool overlay, io::IFile* file) {
301   ResourceTable table;
302   std::string path = ResourceUtils::BuildResourceFileName(file_desc);
303   std::unique_ptr<FileReference> file_ref =
304       util::make_unique<FileReference>(table.string_pool.MakeRef(path));
305   file_ref->SetSource(file_desc.source);
306   file_ref->type = file_desc.type;
307   file_ref->file = file;
308 
309   ResourceTablePackage* pkg = table.CreatePackage(file_desc.name.package, 0x0);
310   pkg->FindOrCreateType(file_desc.name.type)
311       ->FindOrCreateEntry(file_desc.name.entry)
312       ->FindOrCreateValue(file_desc.config, {})
313       ->value = std::move(file_ref);
314 
315   return DoMerge(file->GetSource(), &table, pkg, false /*mangle*/, overlay /*overlay*/,
316                  true /*allow_new*/);
317 }
318 
319 }  // namespace aapt
320