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