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 "ResourceTable.h"
18 #include "ResourceUtils.h"
19 #include "ResourceValues.h"
20 #include "ValueVisitor.h"
21 #include "link/TableMerger.h"
22 #include "util/Util.h"
23
24 #include <cassert>
25
26 namespace aapt {
27
TableMerger(IAaptContext * context,ResourceTable * outTable,const TableMergerOptions & options)28 TableMerger::TableMerger(IAaptContext* context, ResourceTable* outTable,
29 const TableMergerOptions& options) :
30 mContext(context), mMasterTable(outTable), mOptions(options) {
31 // Create the desired package that all tables will be merged into.
32 mMasterPackage = mMasterTable->createPackage(
33 mContext->getCompilationPackage(), mContext->getPackageId());
34 assert(mMasterPackage && "package name or ID already taken");
35 }
36
merge(const Source & src,ResourceTable * table,io::IFileCollection * collection)37 bool TableMerger::merge(const Source& src, ResourceTable* table,
38 io::IFileCollection* collection) {
39 return mergeImpl(src, table, collection, false /* overlay */, true /* allow new */);
40 }
41
mergeOverlay(const Source & src,ResourceTable * table,io::IFileCollection * collection)42 bool TableMerger::mergeOverlay(const Source& src, ResourceTable* table,
43 io::IFileCollection* collection) {
44 return mergeImpl(src, table, collection, true /* overlay */, mOptions.autoAddOverlay);
45 }
46
47 /**
48 * This will merge packages with the same package name (or no package name).
49 */
mergeImpl(const Source & src,ResourceTable * table,io::IFileCollection * collection,bool overlay,bool allowNew)50 bool TableMerger::mergeImpl(const Source& src, ResourceTable* table,
51 io::IFileCollection* collection,
52 bool overlay, bool allowNew) {
53 const uint8_t desiredPackageId = mContext->getPackageId();
54
55 bool error = false;
56 for (auto& package : table->packages) {
57 // Warn of packages with an unrelated ID.
58 if (package->id && package->id.value() != 0x0 && package->id.value() != desiredPackageId) {
59 mContext->getDiagnostics()->warn(DiagMessage(src)
60 << "ignoring package " << package->name);
61 continue;
62 }
63
64 if (package->name.empty() || mContext->getCompilationPackage() == package->name) {
65 FileMergeCallback callback;
66 if (collection) {
67 callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
68 FileReference* newFile, FileReference* oldFile) -> bool {
69 // The old file's path points inside the APK, so we can use it as is.
70 io::IFile* f = collection->findFile(util::utf16ToUtf8(*oldFile->path));
71 if (!f) {
72 mContext->getDiagnostics()->error(DiagMessage(src) << "file '"
73 << *oldFile->path
74 << "' not found");
75 return false;
76 }
77
78 newFile->file = f;
79 return true;
80 };
81 }
82
83 // Merge here. Once the entries are merged and mangled, any references to
84 // them are still valid. This is because un-mangled references are
85 // mangled, then looked up at resolution time.
86 // Also, when linking, we convert references with no package name to use
87 // the compilation package name.
88 error |= !doMerge(src, table, package.get(),
89 false /* mangle */, overlay, allowNew, callback);
90 }
91 }
92 return !error;
93 }
94
95 /**
96 * This will merge and mangle resources from a static library.
97 */
mergeAndMangle(const Source & src,const StringPiece16 & packageName,ResourceTable * table,io::IFileCollection * collection)98 bool TableMerger::mergeAndMangle(const Source& src, const StringPiece16& packageName,
99 ResourceTable* table, io::IFileCollection* collection) {
100 bool error = false;
101 for (auto& package : table->packages) {
102 // Warn of packages with an unrelated ID.
103 if (packageName != package->name) {
104 mContext->getDiagnostics()->warn(DiagMessage(src)
105 << "ignoring package " << package->name);
106 continue;
107 }
108
109 bool mangle = packageName != mContext->getCompilationPackage();
110 mMergedPackages.insert(package->name);
111
112 auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
113 FileReference* newFile, FileReference* oldFile) -> bool {
114 // The old file's path points inside the APK, so we can use it as is.
115 io::IFile* f = collection->findFile(util::utf16ToUtf8(*oldFile->path));
116 if (!f) {
117 mContext->getDiagnostics()->error(DiagMessage(src) << "file '" << *oldFile->path
118 << "' not found");
119 return false;
120 }
121
122 newFile->file = f;
123 return true;
124 };
125
126 error |= !doMerge(src, table, package.get(),
127 mangle, false /* overlay */, true /* allow new */, callback);
128 }
129 return !error;
130 }
131
doMerge(const Source & src,ResourceTable * srcTable,ResourceTablePackage * srcPackage,const bool manglePackage,const bool overlay,const bool allowNewResources,FileMergeCallback callback)132 bool TableMerger::doMerge(const Source& src,
133 ResourceTable* srcTable,
134 ResourceTablePackage* srcPackage,
135 const bool manglePackage,
136 const bool overlay,
137 const bool allowNewResources,
138 FileMergeCallback callback) {
139 bool error = false;
140
141 for (auto& srcType : srcPackage->types) {
142 ResourceTableType* dstType = mMasterPackage->findOrCreateType(srcType->type);
143 if (srcType->symbolStatus.state == SymbolState::kPublic) {
144 if (dstType->symbolStatus.state == SymbolState::kPublic && dstType->id && srcType->id
145 && dstType->id.value() == srcType->id.value()) {
146 // Both types are public and have different IDs.
147 mContext->getDiagnostics()->error(DiagMessage(src)
148 << "can not merge type '"
149 << srcType->type
150 << "': conflicting public IDs");
151 error = true;
152 continue;
153 }
154
155 dstType->symbolStatus = std::move(srcType->symbolStatus);
156 dstType->id = srcType->id;
157 }
158
159 for (auto& srcEntry : srcType->entries) {
160 ResourceEntry* dstEntry;
161 if (manglePackage) {
162 std::u16string mangledName = NameMangler::mangleEntry(srcPackage->name,
163 srcEntry->name);
164 if (allowNewResources) {
165 dstEntry = dstType->findOrCreateEntry(mangledName);
166 } else {
167 dstEntry = dstType->findEntry(mangledName);
168 }
169 } else {
170 if (allowNewResources) {
171 dstEntry = dstType->findOrCreateEntry(srcEntry->name);
172 } else {
173 dstEntry = dstType->findEntry(srcEntry->name);
174 }
175 }
176
177 if (!dstEntry) {
178 mContext->getDiagnostics()->error(DiagMessage(src)
179 << "resource "
180 << ResourceNameRef(srcPackage->name,
181 srcType->type,
182 srcEntry->name)
183 << " does not override an existing resource");
184 mContext->getDiagnostics()->note(DiagMessage(src)
185 << "define an <add-resource> tag or use "
186 "--auto-add-overlay");
187 error = true;
188 continue;
189 }
190
191 if (srcEntry->symbolStatus.state != SymbolState::kUndefined) {
192 if (srcEntry->symbolStatus.state == SymbolState::kPublic) {
193 if (dstEntry->symbolStatus.state == SymbolState::kPublic &&
194 dstEntry->id && srcEntry->id &&
195 dstEntry->id.value() != srcEntry->id.value()) {
196 // Both entries are public and have different IDs.
197 mContext->getDiagnostics()->error(DiagMessage(src)
198 << "can not merge entry '"
199 << srcEntry->name
200 << "': conflicting public IDs");
201 error = true;
202 continue;
203 }
204
205 if (srcEntry->id) {
206 dstEntry->id = srcEntry->id;
207 }
208 }
209
210 if (dstEntry->symbolStatus.state != SymbolState::kPublic &&
211 dstEntry->symbolStatus.state != srcEntry->symbolStatus.state) {
212 dstEntry->symbolStatus = std::move(srcEntry->symbolStatus);
213 }
214 }
215
216 ResourceNameRef resName(mMasterPackage->name, dstType->type, dstEntry->name);
217
218 for (auto& srcValue : srcEntry->values) {
219 ResourceConfigValue* dstValue = dstEntry->findValue(srcValue->config,
220 srcValue->product);
221 if (dstValue) {
222 const int collisionResult = ResourceTable::resolveValueCollision(
223 dstValue->value.get(), srcValue->value.get());
224 if (collisionResult == 0 && !overlay) {
225 // Error!
226 ResourceNameRef resourceName(srcPackage->name,
227 srcType->type,
228 srcEntry->name);
229
230 mContext->getDiagnostics()->error(DiagMessage(srcValue->value->getSource())
231 << "resource '" << resourceName
232 << "' has a conflicting value for "
233 << "configuration ("
234 << srcValue->config << ")");
235 mContext->getDiagnostics()->note(DiagMessage(dstValue->value->getSource())
236 << "originally defined here");
237 error = true;
238 continue;
239 } else if (collisionResult < 0) {
240 // Keep our existing value.
241 continue;
242 }
243
244 }
245
246 if (!dstValue) {
247 // Force create the entry if we didn't have it.
248 dstValue = dstEntry->findOrCreateValue(srcValue->config, srcValue->product);
249 }
250
251 if (FileReference* f = valueCast<FileReference>(srcValue->value.get())) {
252 std::unique_ptr<FileReference> newFileRef;
253 if (manglePackage) {
254 newFileRef = cloneAndMangleFile(srcPackage->name, *f);
255 } else {
256 newFileRef = std::unique_ptr<FileReference>(f->clone(
257 &mMasterTable->stringPool));
258 }
259
260 if (callback) {
261 if (!callback(resName, srcValue->config, newFileRef.get(), f)) {
262 error = true;
263 continue;
264 }
265 }
266 dstValue->value = std::move(newFileRef);
267
268 } else {
269 dstValue->value = std::unique_ptr<Value>(srcValue->value->clone(
270 &mMasterTable->stringPool));
271 }
272 }
273 }
274 }
275 return !error;
276 }
277
cloneAndMangleFile(const std::u16string & package,const FileReference & fileRef)278 std::unique_ptr<FileReference> TableMerger::cloneAndMangleFile(const std::u16string& package,
279 const FileReference& fileRef) {
280
281 StringPiece16 prefix, entry, suffix;
282 if (util::extractResFilePathParts(*fileRef.path, &prefix, &entry, &suffix)) {
283 std::u16string mangledEntry = NameMangler::mangleEntry(package, entry.toString());
284 std::u16string newPath = prefix.toString() + mangledEntry + suffix.toString();
285 std::unique_ptr<FileReference> newFileRef = util::make_unique<FileReference>(
286 mMasterTable->stringPool.makeRef(newPath));
287 newFileRef->setComment(fileRef.getComment());
288 newFileRef->setSource(fileRef.getSource());
289 return newFileRef;
290 }
291 return std::unique_ptr<FileReference>(fileRef.clone(&mMasterTable->stringPool));
292 }
293
mergeFileImpl(const ResourceFile & fileDesc,io::IFile * file,bool overlay)294 bool TableMerger::mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, bool overlay) {
295 ResourceTable table;
296 std::u16string path = util::utf8ToUtf16(ResourceUtils::buildResourceFileName(fileDesc,
297 nullptr));
298 std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
299 table.stringPool.makeRef(path));
300 fileRef->setSource(fileDesc.source);
301 fileRef->file = file;
302
303 ResourceTablePackage* pkg = table.createPackage(fileDesc.name.package, 0x0);
304 pkg->findOrCreateType(fileDesc.name.type)
305 ->findOrCreateEntry(fileDesc.name.entry)
306 ->findOrCreateValue(fileDesc.config, {})
307 ->value = std::move(fileRef);
308
309 return doMerge(file->getSource(), &table, pkg,
310 false /* mangle */, overlay /* overlay */, true /* allow new */, {});
311 }
312
mergeFile(const ResourceFile & fileDesc,io::IFile * file)313 bool TableMerger::mergeFile(const ResourceFile& fileDesc, io::IFile* file) {
314 return mergeFileImpl(fileDesc, file, false /* overlay */);
315 }
316
mergeFileOverlay(const ResourceFile & fileDesc,io::IFile * file)317 bool TableMerger::mergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file) {
318 return mergeFileImpl(fileDesc, file, true /* overlay */);
319 }
320
321 } // namespace aapt
322