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