xref: /aosp_15_r20/frameworks/base/tools/aapt2/link/TableMerger.cpp (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
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       ResourceTable::ResolveFlagCollision(dst_value->GetFlagStatus(), src_value->GetFlagStatus());
212   if (collision_result == CollisionResult::kConflict) {
213     if (overlay) {
214       collision_result =
215           ResolveMergeCollision(override_styles_instead_of_overlaying, dst_value, src_value, pool);
216     } else {
217       collision_result = ResourceTable::ResolveValueCollision(dst_value, src_value);
218     }
219   }
220 
221   if (collision_result == CollisionResult::kConflict) {
222     if (overlay) {
223       return CollisionResult::kTakeNew;
224     }
225 
226     // Error!
227     context->GetDiagnostics()->Error(android::DiagMessage(src_value->GetSource())
228                                      << "resource '" << res_name << "' has a conflicting value for "
229                                      << "configuration (" << src_config_value->config << ")");
230     context->GetDiagnostics()->Note(android::DiagMessage(dst_value->GetSource())
231                                     << "originally defined here");
232     return CollisionResult::kConflict;
233   }
234   return collision_result;
235 }
236 
DoMerge(const android::Source & src,ResourceTablePackage * src_package,bool mangle_package,bool overlay,bool allow_new_resources)237 bool TableMerger::DoMerge(const android::Source& src, ResourceTablePackage* src_package,
238                           bool mangle_package, bool overlay, bool allow_new_resources) {
239   bool error = false;
240 
241   for (auto& src_type : src_package->types) {
242     ResourceTableType* dst_type = main_package_->FindOrCreateType(src_type->named_type);
243     if (!MergeType(context_, src, dst_type, src_type.get())) {
244       error = true;
245       continue;
246     }
247 
248     for (auto& src_entry : src_type->entries) {
249       std::string entry_name = src_entry->name;
250       if (mangle_package) {
251         entry_name = NameMangler::MangleEntry(src_package->name, src_entry->name);
252       }
253 
254       ResourceEntry* dst_entry;
255       if (allow_new_resources || src_entry->allow_new) {
256         dst_entry = dst_type->FindOrCreateEntry(entry_name);
257       } else {
258         dst_entry = dst_type->FindEntry(entry_name);
259       }
260 
261       const ResourceNameRef res_name(src_package->name, src_type->named_type, src_entry->name);
262 
263       if (!dst_entry) {
264         context_->GetDiagnostics()->Error(android::DiagMessage(src)
265                                           << "resource " << res_name
266                                           << " does not override an existing resource");
267         context_->GetDiagnostics()->Note(android::DiagMessage(src)
268                                          << "define an <add-resource> tag or use "
269                                          << "--auto-add-overlay");
270         error = true;
271         continue;
272       }
273 
274       if (!MergeEntry(context_, src, dst_entry, src_entry.get(), options_.strict_visibility)) {
275         error = true;
276         continue;
277       }
278 
279       for (auto& src_config_value : src_entry->values) {
280         using CollisionResult = ResourceTable::CollisionResult;
281 
282         ResourceConfigValue* dst_config_value = dst_entry->FindValue(
283             src_config_value->config, src_config_value->product);
284         if (dst_config_value) {
285           CollisionResult collision_result = MergeConfigValue(
286               context_, res_name, overlay, options_.override_styles_instead_of_overlaying,
287               dst_config_value, src_config_value.get(), &main_table_->string_pool);
288           if (collision_result == CollisionResult::kConflict) {
289             error = true;
290             continue;
291           } else if (collision_result == CollisionResult::kKeepOriginal) {
292             continue;
293           }
294         } else {
295           dst_config_value =
296               dst_entry->FindOrCreateValue(src_config_value->config, src_config_value->product);
297         }
298 
299         // Continue if we're taking the new resource.
300         CloningValueTransformer cloner(&main_table_->string_pool);
301         if (FileReference* f = ValueCast<FileReference>(src_config_value->value.get())) {
302           std::unique_ptr<FileReference> new_file_ref;
303           if (mangle_package) {
304             new_file_ref = CloneAndMangleFile(src_package->name, *f);
305           } else {
306             new_file_ref = std::unique_ptr<FileReference>(f->Transform(cloner));
307           }
308           dst_config_value->value = std::move(new_file_ref);
309 
310         } else {
311           auto original_comment = (dst_config_value->value)
312               ? dst_config_value->value->GetComment() : std::optional<std::string>();
313 
314           dst_config_value->value = src_config_value->value->Transform(cloner);
315 
316           // Keep the comment from the original resource and ignore all comments from overlaying
317           // resources
318           if (overlay && original_comment) {
319             dst_config_value->value->SetComment(original_comment.value());
320           }
321         }
322       }
323 
324       // disabled values
325       for (auto& src_config_value : src_entry->flag_disabled_values) {
326         auto dst_config_value = dst_entry->FindOrCreateFlagDisabledValue(
327             src_config_value->value->GetFlag().value(), src_config_value->config,
328             src_config_value->product);
329         if (!dst_config_value->value) {
330           // Resource does not exist, add it now.
331           // Must clone the value since it might be in the values vector as well
332           CloningValueTransformer cloner(&main_table_->string_pool);
333           dst_config_value->value = src_config_value->value->Transform(cloner);
334         } else {
335           error = true;
336           context_->GetDiagnostics()->Error(
337               android::DiagMessage(src_config_value->value->GetSource())
338               << "duplicate value for resource '" << src_entry->name << "' " << "with config '"
339               << src_config_value->config << "' and flag '"
340               << (src_config_value->value->GetFlag()->negated ? "!" : "")
341               << src_config_value->value->GetFlag()->name << "'");
342           context_->GetDiagnostics()->Note(
343               android::DiagMessage(dst_config_value->value->GetSource())
344               << "resource previously defined here");
345         }
346       }
347     }
348   }
349   return !error;
350 }
351 
CloneAndMangleFile(const std::string & package,const FileReference & file_ref)352 std::unique_ptr<FileReference> TableMerger::CloneAndMangleFile(
353     const std::string& package, const FileReference& file_ref) {
354   StringPiece prefix, entry, suffix;
355   if (util::ExtractResFilePathParts(*file_ref.path, &prefix, &entry, &suffix)) {
356     std::string mangled_entry = NameMangler::MangleEntry(package, entry);
357     std::string newPath = (std::string(prefix) += mangled_entry) += suffix;
358     std::unique_ptr<FileReference> new_file_ref =
359         util::make_unique<FileReference>(main_table_->string_pool.MakeRef(newPath));
360     new_file_ref->SetComment(file_ref.GetComment());
361     new_file_ref->SetSource(file_ref.GetSource());
362     new_file_ref->type = file_ref.type;
363     new_file_ref->file = file_ref.file;
364     return new_file_ref;
365   }
366 
367   CloningValueTransformer cloner(&main_table_->string_pool);
368   return std::unique_ptr<FileReference>(file_ref.Transform(cloner));
369 }
370 
MergeFile(const ResourceFile & file_desc,bool overlay,io::IFile * file)371 bool TableMerger::MergeFile(const ResourceFile& file_desc, bool overlay, io::IFile* file) {
372   ResourceTable table;
373   std::string path = ResourceUtils::BuildResourceFileName(file_desc);
374   std::unique_ptr<FileReference> file_ref =
375       util::make_unique<FileReference>(table.string_pool.MakeRef(path));
376   file_ref->SetSource(file_desc.source);
377   file_ref->type = file_desc.type;
378   file_ref->file = file;
379   file_ref->SetFlagStatus(file_desc.flag_status);
380   file_ref->SetFlag(file_desc.flag);
381 
382   ResourceTablePackage* pkg = table.FindOrCreatePackage(file_desc.name.package);
383   pkg->FindOrCreateType(file_desc.name.type)
384       ->FindOrCreateEntry(file_desc.name.entry)
385       ->FindOrCreateValue(file_desc.config, {})
386       ->value = std::move(file_ref);
387 
388   return DoMerge(file->GetSource(), pkg, false /*mangle*/, overlay /*overlay*/, true /*allow_new*/);
389 }
390 
391 }  // namespace aapt
392