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